[tor-commits] [arm/master] Supporting color names in Panel's addstr()

atagar at torproject.org atagar at torproject.org
Mon Sep 1 00:11:29 UTC 2014


commit 7634f6fbe1e3c76d84fdd6929c30f21cb7a09fe1
Author: Damian Johnson <atagar at torproject.org>
Date:   Sun Aug 31 14:45:15 2014 -0700

    Supporting color names in Panel's addstr()
    
    Python's curses module is a very thin wrapper around the C curses library and
    as such it's clunky. One bit it's especially sucky is how it does colors.
    
    We smoothed over this quite a bit by adding a get_color() helper that would
    convert a color name (red, blue, etc) into the registered color attribute code
    for that. On reflection though why not simply have our addstr() method do that
    conversion for us?
    
    This pretty much drops the need for us to call get_color() all throughout our
    codebase.
---
 arm/config_panel.py                 |   24 +++++++++++------------
 arm/connections/circ_entry.py       |    4 ++--
 arm/connections/conn_entry.py       |    6 +++---
 arm/connections/conn_panel.py       |    4 ++--
 arm/connections/count_popup.py      |    6 +++---
 arm/connections/descriptor_popup.py |   19 +++++++++---------
 arm/graphing/bandwidth_stats.py     |   10 +++++-----
 arm/graphing/graph_panel.py         |   12 ++++++------
 arm/header_panel.py                 |   29 ++++++++++++++--------------
 arm/log_panel.py                    |   32 +++++++++++++++----------------
 arm/menu/menu.py                    |    6 +++---
 arm/torrc_panel.py                  |   36 +++++++++++++++++------------------
 arm/util/panel.py                   |   12 ++++++++++--
 13 files changed, 104 insertions(+), 96 deletions(-)

diff --git a/arm/config_panel.py b/arm/config_panel.py
index dabe078..061da61 100644
--- a/arm/config_panel.py
+++ b/arm/config_panel.py
@@ -504,10 +504,10 @@ class ConfigPanel(panel.Panel):
 
           if " " in line:
             option, arg = line.split(" ", 1)
-            popup.addstr(i + 1, 1, option, curses.A_BOLD | ui_tools.get_color("green"))
-            popup.addstr(i + 1, len(option) + 2, arg, curses.A_BOLD | ui_tools.get_color("cyan"))
+            popup.addstr(i + 1, 1, option, curses.A_BOLD, 'green')
+            popup.addstr(i + 1, len(option) + 2, arg, curses.A_BOLD, 'cyan')
           else:
-            popup.addstr(i + 1, 1, line, curses.A_BOLD | ui_tools.get_color("green"))
+            popup.addstr(i + 1, 1, line, curses.A_BOLD, 'green')
 
         # draws selection options (drawn right to left)
 
@@ -525,7 +525,7 @@ class ConfigPanel(panel.Panel):
 
           selection_format = curses.A_STANDOUT if i == selection else curses.A_NORMAL
           popup.addstr(height - 2, draw_x, "[")
-          popup.addstr(height - 2, draw_x + 1, option_label, selection_format | curses.A_BOLD)
+          popup.addstr(height - 2, draw_x + 1, option_label, selection_format, curses.A_BOLD)
           popup.addstr(height - 2, draw_x + len(option_label) + 1, "]")
 
           draw_x -= 1  # space gap between the options
@@ -635,16 +635,16 @@ class ConfigPanel(panel.Panel):
       entry = self._get_config_options()[line_number]
       draw_line = line_number + detail_panel_height + 1 - scroll_location
 
-      line_format = curses.A_NORMAL if entry.get(Field.IS_DEFAULT) else curses.A_BOLD
+      line_format = [curses.A_NORMAL if entry.get(Field.IS_DEFAULT) else curses.A_BOLD]
 
       if entry.get(Field.CATEGORY):
-        line_format |= ui_tools.get_color(CATEGORY_COLOR[entry.get(Field.CATEGORY)])
+        line_format += [CATEGORY_COLOR[entry.get(Field.CATEGORY)]]
 
       if entry == cursor_selection:
-        line_format |= curses.A_STANDOUT
+        line_format += [curses.A_STANDOUT]
 
       line_text = entry.get_label(option_width, value_width, description_width)
-      self.addstr(draw_line, scroll_offset, line_text, line_format)
+      self.addstr(draw_line, scroll_offset, line_text, *line_format)
 
       if draw_line >= height:
         break
@@ -667,13 +667,13 @@ class ConfigPanel(panel.Panel):
     if is_scrollbar_visible:
       self.addch(detail_panel_height, 1, curses.ACS_TTEE)
 
-    selection_format = curses.A_BOLD | ui_tools.get_color(CATEGORY_COLOR[selection.get(Field.CATEGORY)])
+    selection_format = (curses.A_BOLD, CATEGORY_COLOR[selection.get(Field.CATEGORY)])
 
     # first entry:
     # <option> (<category> Option)
 
     option_label = " (%s Option)" % selection.get(Field.CATEGORY)
-    self.addstr(1, 2, selection.get(Field.OPTION) + option_label, selection_format)
+    self.addstr(1, 2, selection.get(Field.OPTION) + option_label, *selection_format)
 
     # second entry:
     # Value: <value> ([default|custom], <type>, usage: <argument usage>)
@@ -688,7 +688,7 @@ class ConfigPanel(panel.Panel):
       value_label_width = width - 12 - len(value_attr_label)
       value_label = ui_tools.crop_str(selection.get(Field.VALUE), value_label_width)
 
-      self.addstr(2, 2, "Value: %s (%s)" % (value_label, value_attr_label), selection_format)
+      self.addstr(2, 2, "Value: %s (%s)" % (value_label, value_attr_label), *selection_format)
 
     # remainder is filled with the man page description
 
@@ -723,4 +723,4 @@ class ConfigPanel(panel.Panel):
 
         msg = ui_tools.crop_str(line_content, width - 3, 4, 4)
 
-      self.addstr(3 + i, 2, msg, selection_format)
+      self.addstr(3 + i, 2, msg, *selection_format)
diff --git a/arm/connections/circ_entry.py b/arm/connections/circ_entry.py
index 0041a97..4e7b2d6 100644
--- a/arm/connections/circ_entry.py
+++ b/arm/connections/circ_entry.py
@@ -121,7 +121,7 @@ class CircHeaderLine(conn_entry.ConnectionLine):
 
   def get_details(self, width):
     if not self.is_built:
-      detail_format = curses.A_BOLD | ui_tools.get_color(conn_entry.CATEGORY_COLOR[self.get_type()])
+      detail_format = (curses.A_BOLD, conn_entry.CATEGORY_COLOR[self.get_type()])
       return [("Building Circuit...", detail_format)]
     else:
       return conn_entry.ConnectionLine.get_details(self, width)
@@ -170,7 +170,7 @@ class CircLine(conn_entry.ConnectionLine):
     return entries.ConnectionPanelLine.get_listing_entry(self, width, current_time, listing_type)
 
   def _get_listing_entry(self, width, current_time, listing_type):
-    line_format = ui_tools.get_color(conn_entry.CATEGORY_COLOR[self.get_type()])
+    line_format = conn_entry.CATEGORY_COLOR[self.get_type()]
 
     # The required widths are the sum of the following:
     # initial space (1 character)
diff --git a/arm/connections/conn_entry.py b/arm/connections/conn_entry.py
index f3ec86c..cad2dbb 100644
--- a/arm/connections/conn_entry.py
+++ b/arm/connections/conn_entry.py
@@ -374,14 +374,14 @@ class ConnectionLine(entries.ConnectionPanelLine):
     # category - "<type>"
     # postType - ")   "
 
-    line_format = ui_tools.get_color(CATEGORY_COLOR[entry_type])
+    line_format = CATEGORY_COLOR[entry_type]
     time_width = 6 if CONFIG["features.connection.markInitialConnections"] else 5
 
     draw_entry = [(" ", line_format),
                   (self._get_listing_content(width - (12 + time_width) - 1, listing_type), line_format),
                   (" " * time_width, line_format),
                   (" (", line_format),
-                  (entry_type.upper(), line_format | curses.A_BOLD),
+                  (entry_type.upper(), line_format, curses.A_BOLD),
                   (")" + " " * (9 - len(entry_type)), line_format)]
 
     return draw_entry
@@ -395,7 +395,7 @@ class ConnectionLine(entries.ConnectionPanelLine):
       width - available space to display in
     """
 
-    detail_format = curses.A_BOLD | ui_tools.get_color(CATEGORY_COLOR[self.get_type()])
+    detail_format = (curses.A_BOLD, CATEGORY_COLOR[self.get_type()])
     return [(line, detail_format) for line in self._get_detail_content(width)]
 
   def reset_display(self):
diff --git a/arm/connections/conn_panel.py b/arm/connections/conn_panel.py
index 491c810..8168572 100644
--- a/arm/connections/conn_panel.py
+++ b/arm/connections/conn_panel.py
@@ -432,7 +432,7 @@ class ConnectionPanel(panel.Panel, threading.Thread):
       draw_entries = cursor_selection.get_details(width)
 
       for i in range(min(len(draw_entries), DETAILS_HEIGHT)):
-        self.addstr(1 + i, 2, draw_entries[i][0], draw_entries[i][1])
+        self.addstr(1 + i, 2, draw_entries[i][0], *draw_entries[i][1])
 
     # title label with connection counts
 
@@ -476,7 +476,7 @@ class ConnectionPanel(panel.Panel, threading.Thread):
 
       for msg, attr in draw_entry:
         attr |= extra_format
-        self.addstr(draw_line, x_offset, msg, attr)
+        self.addstr(draw_line, x_offset, msg, *attr)
         x_offset += len(msg)
 
       if draw_line >= height:
diff --git a/arm/connections/count_popup.py b/arm/connections/count_popup.py
index b764c05..50fb7d8 100644
--- a/arm/connections/count_popup.py
+++ b/arm/connections/count_popup.py
@@ -55,7 +55,7 @@ def showCountDialog(count_type, counts):
     popup.addstr(0, 0, title, curses.A_STANDOUT)
 
     if is_no_stats:
-      popup.addstr(1, 2, no_stats_msg, curses.A_BOLD | ui_tools.get_color("cyan"))
+      popup.addstr(1, 2, no_stats_msg, curses.A_BOLD, 'cyan')
     else:
       sorted_counts = sorted(counts.iteritems(), key=operator.itemgetter(1))
       sorted_counts.reverse()
@@ -89,7 +89,7 @@ def showCountDialog(count_type, counts):
             k = key_format % (k, usage[:EXIT_USAGE_WIDTH - 3])
 
         label = label_format % (k, v, v * 100 / value_total)
-        popup.addstr(i + 1, 2, label, curses.A_BOLD | ui_tools.get_color("green"))
+        popup.addstr(i + 1, 2, label, curses.A_BOLD, 'green')
 
         # All labels have the same size since they're based on the max widths.
         # If this changes then this'll need to be the max label width.
@@ -101,7 +101,7 @@ def showCountDialog(count_type, counts):
         fill_width = v * (width - 4 - label_width) / value_total
 
         for j in range(fill_width):
-          popup.addstr(i + 1, 3 + label_width + j, " ", curses.A_STANDOUT | ui_tools.get_color("red"))
+          popup.addstr(i + 1, 3 + label_width + j, " ", curses.A_STANDOUT, 'red')
 
       popup.addstr(height - 2, 2, "Press any key...")
 
diff --git a/arm/connections/descriptor_popup.py b/arm/connections/descriptor_popup.py
index 9404224..30adb6c 100644
--- a/arm/connections/descriptor_popup.py
+++ b/arm/connections/descriptor_popup.py
@@ -194,38 +194,37 @@ def draw(popup, fingerprint, display_text, display_color, scroll, show_line_numb
 
     if show_line_number:
       line_number_label = ("%%%ii" % line_number_width) % (i + 1)
-      line_number_format = curses.A_BOLD | ui_tools.get_color(LINE_NUM_COLOR)
 
-      popup.addstr(draw_line, x_offset, line_number_label, line_number_format)
+      popup.addstr(draw_line, x_offset, line_number_label, curses.A_BOLD, LINE_NUM_COLOR)
       x_offset += line_number_width + 1
 
     # Most consensus and descriptor lines are keyword/value pairs. Both are
     # shown with the same color, but the keyword is bolded.
 
     keyword, value = line_text, ""
-    draw_format = ui_tools.get_color(display_color)
+    draw_format = display_color
 
     if line_text.startswith(HEADER_PREFIX[0]) or line_text.startswith(HEADER_PREFIX[1]):
       keyword, value = line_text, ""
-      draw_format = ui_tools.get_color(HEADER_COLOR)
+      draw_format = HEADER_COLOR
     elif line_text == UNRESOLVED_MSG or line_text == ERROR_MSG:
       keyword, value = line_text, ""
     elif line_text in SIG_START_KEYS:
       keyword, value = line_text, ""
       is_encryption_block = True
-      draw_format = ui_tools.get_color(SIG_COLOR)
+      draw_format = SIG_COLOR
     elif line_text in SIG_END_KEYS:
       keyword, value = line_text, ""
       is_encryption_block = False
-      draw_format = ui_tools.get_color(SIG_COLOR)
+      draw_format = SIG_COLOR
     elif is_encryption_block:
       keyword, value = "", line_text
-      draw_format = ui_tools.get_color(SIG_COLOR)
+      draw_format = SIG_COLOR
     elif " " in line_text:
       div_index = line_text.find(" ")
       keyword, value = line_text[:div_index], line_text[div_index:]
 
-    display_queue = [(keyword, draw_format | curses.A_BOLD), (value, draw_format)]
+    display_queue = [(keyword, (draw_format, curses.A_BOLD)), (value, (draw_format,))]
     cursor_location = x_offset
 
     while display_queue:
@@ -251,14 +250,14 @@ def draw(popup, fingerprint, display_text, display_color, scroll, show_line_numb
           else:
             remainder = ""
 
-        popup.addstr(draw_line, cursor_location, msg, msg_format)
+        popup.addstr(draw_line, cursor_location, msg, *msg_format)
         cursor_location = x_offset
 
         if remainder:
           display_queue.insert(0, (remainder.strip(), msg_format))
           draw_line += 1
       else:
-        popup.addstr(draw_line, cursor_location, msg, msg_format)
+        popup.addstr(draw_line, cursor_location, msg, *msg_format)
         cursor_location += len(msg)
 
       if draw_line > page_height:
diff --git a/arm/graphing/bandwidth_stats.py b/arm/graphing/bandwidth_stats.py
index 6396873..634a0f5 100644
--- a/arm/graphing/bandwidth_stats.py
+++ b/arm/graphing/bandwidth_stats.py
@@ -307,8 +307,8 @@ class BandwidthStats(graph_panel.GraphStats):
       primary_footer = "%s, %s" % (self._get_avg_label(True), self._get_total_label(True))
       secondary_footer = "%s, %s" % (self._get_avg_label(False), self._get_total_label(False))
 
-      panel.addstr(labeling_line, 1, primary_footer, ui_tools.get_color(self.get_color(True)))
-      panel.addstr(labeling_line, graph_column + 6, secondary_footer, ui_tools.get_color(self.get_color(False)))
+      panel.addstr(labeling_line, 1, primary_footer, self.get_color(True))
+      panel.addstr(labeling_line, graph_column + 6, secondary_footer, self.get_color(False))
 
     # provides accounting stats if enabled
 
@@ -327,7 +327,7 @@ class BandwidthStats(graph_panel.GraphStats):
           status, hibernate_color = "unknown", "red"
 
         panel.addstr(labeling_line + 2, 0, "Accounting (", curses.A_BOLD)
-        panel.addstr(labeling_line + 2, 12, status, curses.A_BOLD | ui_tools.get_color(hibernate_color))
+        panel.addstr(labeling_line + 2, 12, status, curses.A_BOLD, hibernate_color)
         panel.addstr(labeling_line + 2, 12 + len(status), ")", curses.A_BOLD)
 
         reset_time = self.accounting_info["reset_time"]
@@ -340,12 +340,12 @@ class BandwidthStats(graph_panel.GraphStats):
         used, total = self.accounting_info["read"], self.accounting_info["read_limit"]
 
         if used and total:
-          panel.addstr(labeling_line + 3, 2, "%s / %s" % (used, total), ui_tools.get_color(self.get_color(True)))
+          panel.addstr(labeling_line + 3, 2, "%s / %s" % (used, total), self.get_color(True))
 
         used, total = self.accounting_info["written"], self.accounting_info["writtenLimit"]
 
         if used and total:
-          panel.addstr(labeling_line + 3, 37, "%s / %s" % (used, total), ui_tools.get_color(self.get_color(False)))
+          panel.addstr(labeling_line + 3, 37, "%s / %s" % (used, total), self.get_color(False))
       else:
         panel.addstr(labeling_line + 2, 0, "Accounting:", curses.A_BOLD)
         panel.addstr(labeling_line + 2, 12, "Connection Closed...")
diff --git a/arm/graphing/graph_panel.py b/arm/graphing/graph_panel.py
index 41bd352..b54b8b8 100644
--- a/arm/graphing/graph_panel.py
+++ b/arm/graphing/graph_panel.py
@@ -435,8 +435,8 @@ class GraphPanel(panel.Panel):
       param = self.get_attr("stats")[self.current_display]
       graph_column = min((width - 10) / 2, param.max_column)
 
-      primary_color = ui_tools.get_color(param.get_color(True))
-      secondary_color = ui_tools.get_color(param.get_color(False))
+      primary_color = param.get_color(True)
+      secondary_color = param.get_color(False)
 
       if self.is_title_visible():
         self.addstr(0, 0, param.get_title(width), curses.A_STANDOUT)
@@ -446,10 +446,10 @@ class GraphPanel(panel.Panel):
       left, right = param.get_header_label(width / 2, True), param.get_header_label(width / 2, False)
 
       if left:
-        self.addstr(1, 0, left, curses.A_BOLD | primary_color)
+        self.addstr(1, 0, left, curses.A_BOLD, primary_color)
 
       if right:
-        self.addstr(1, graph_column + 5, right, curses.A_BOLD | secondary_color)
+        self.addstr(1, graph_column + 5, right, curses.A_BOLD, secondary_color)
 
       # determines max/min value on the graph
 
@@ -518,13 +518,13 @@ class GraphPanel(panel.Panel):
         column_height = min(self.graph_height, self.graph_height * column_count / (max(1, primary_max_bound) - primary_min_bound))
 
         for row in range(column_height):
-          self.addstr(self.graph_height + 1 - row, col + 5, " ", curses.A_STANDOUT | primary_color)
+          self.addstr(self.graph_height + 1 - row, col + 5, " ", curses.A_STANDOUT, primary_color)
 
         column_count = int(param.secondary_counts[self.update_interval][col + 1]) - secondary_min_bound
         column_height = min(self.graph_height, self.graph_height * column_count / (max(1, secondary_max_bound) - secondary_min_bound))
 
         for row in range(column_height):
-          self.addstr(self.graph_height + 1 - row, col + graph_column + 10, " ", curses.A_STANDOUT | secondary_color)
+          self.addstr(self.graph_height + 1 - row, col + graph_column + 10, " ", curses.A_STANDOUT, secondary_color)
 
       # bottom labeling of x-axis
 
diff --git a/arm/header_panel.py b/arm/header_panel.py
index ff862a8..d8dc8ad 100644
--- a/arm/header_panel.py
+++ b/arm/header_panel.py
@@ -18,11 +18,12 @@ import arm.controller
 import arm.popups
 import arm.starter
 import arm.util.tracker
+import arm.util.ui_tools
 
 from stem.control import Listener, State
 from stem.util import conf, log
 
-from util import panel, ui_tools, tor_controller
+from util import panel, tor_controller
 
 MIN_DUAL_COL_WIDTH = 141  # minimum width where we'll show two columns
 SHOW_FD_THRESHOLD = 60  # show file descriptor usage if usage is over this percentage
@@ -201,7 +202,7 @@ class HeaderPanel(panel.Panel, threading.Thread):
         version_color = CONFIG['attr.version_status_colors'].get(vals.version_status, 'white')
 
         x = self.addstr(y, x, ' (')
-        x = self.addstr(y, x, vals.version_status, ui_tools.get_color(version_color))
+        x = self.addstr(y, x, vals.version_status, version_color)
         self.addstr(y, x, ')')
 
   def _draw_ports_section(self, x, y, width, vals):
@@ -212,7 +213,7 @@ class HeaderPanel(panel.Panel, threading.Thread):
     """
 
     if not vals.is_relay:
-      x = self.addstr(y, x, 'Relaying Disabled', ui_tools.get_color('cyan'))
+      x = self.addstr(y, x, 'Relaying Disabled', 'cyan')
     else:
       x = self.addstr(y, x, vals.format('{nickname} - {or_address}:{or_port}'))
 
@@ -226,7 +227,7 @@ class HeaderPanel(panel.Panel, threading.Thread):
         auth_color = 'red' if vals.auth_type == 'open' else 'green'
 
         x = self.addstr(y, x, ', Control Port (')
-        x = self.addstr(y, x, vals.auth_type, ui_tools.get_color(auth_color))
+        x = self.addstr(y, x, vals.auth_type, auth_color)
         self.addstr(y, x, vals.format('): {control_port}'))
       else:
         self.addstr(y, x, ', Control Port: %s' % vals.control_port)
@@ -238,7 +239,7 @@ class HeaderPanel(panel.Panel, threading.Thread):
       Tor Disconnected (15:21 07/13/2014, press r to reconnect)
     """
 
-    x = self.addstr(y, x, 'Tor Disconnected', curses.A_BOLD | ui_tools.get_color('red'))
+    x = self.addstr(y, x, 'Tor Disconnected', curses.A_BOLD, 'red')
     self.addstr(y, x, ' (%s, press r to reconnect)' % vals.last_heartbeat)
 
   def _draw_resource_usage(self, x, y, width, vals):
@@ -291,17 +292,17 @@ class HeaderPanel(panel.Panel, threading.Thread):
 
       if fd_percent >= SHOW_FD_THRESHOLD:
         if fd_percent >= 95:
-          percentage_format = curses.A_BOLD | ui_tools.get_color('red')
+          percentage_format = (curses.A_BOLD, 'red')
         elif fd_percent >= 90:
-          percentage_format = ui_tools.get_color('red')
+          percentage_format = ('red',)
         elif fd_percent >= 60:
-          percentage_format = ui_tools.get_color('yellow')
+          percentage_format = ('yellow',)
         else:
-          percentage_format = curses.A_NORMAL
+          percentage_format = ()
 
         x = self.addstr(y, x, ', file descriptors' if space_left >= 37 else ', file desc')
         x = self.addstr(y, x, vals.format(': {fd_used} / {fd_limit} ('))
-        x = self.addstr(y, x, '%i%%' % fd_percent, percentage_format)
+        x = self.addstr(y, x, '%i%%' % fd_percent, *percentage_format)
         self.addstr(y, x, ')')
 
   def _draw_flags(self, x, y, width, vals):
@@ -316,12 +317,12 @@ class HeaderPanel(panel.Panel, threading.Thread):
     if len(vals.flags) > 0:
       for i, flag in enumerate(vals.flags):
         flag_color = CONFIG['attr.flag_colors'].get(flag, 'white')
-        x = self.addstr(y, x, flag, curses.A_BOLD | ui_tools.get_color(flag_color))
+        x = self.addstr(y, x, flag, curses.A_BOLD, flag_color)
 
         if i < len(vals.flags) - 1:
           x = self.addstr(y, x, ', ')
     else:
-      self.addstr(y, x, 'none', curses.A_BOLD | ui_tools.get_color('cyan'))
+      self.addstr(y, x, 'none', curses.A_BOLD, 'cyan')
 
   def _draw_exit_policy(self, x, y, width, vals):
     """
@@ -342,7 +343,7 @@ class HeaderPanel(panel.Panel, threading.Thread):
 
     for i, rule in enumerate(rules):
       policy_color = 'green' if rule.is_accept else 'red'
-      x = self.addstr(y, x, str(rule), curses.A_BOLD | ui_tools.get_color(policy_color))
+      x = self.addstr(y, x, str(rule), curses.A_BOLD, policy_color)
 
       if i < len(rules) - 1:
         x = self.addstr(y, x, ', ')
@@ -509,7 +510,7 @@ class Sampling(object):
     formatted_msg = msg.format(**self.__dict__)
 
     if crop_width:
-      formatted_msg = ui_tools.crop_str(formatted_msg, crop_width)
+      formatted_msg = arm.util.ui_tools.crop_str(formatted_msg, crop_width)
 
     return formatted_msg
 
diff --git a/arm/log_panel.py b/arm/log_panel.py
index e984938..e39c70f 100644
--- a/arm/log_panel.py
+++ b/arm/log_panel.py
@@ -967,7 +967,7 @@ class LogPanel(panel.Panel, threading.Thread, logging.Handler):
 
     line_count = 1 - self.scroll
     seen_first_date_divider = False
-    divider_attr, duplicate_attr = curses.A_BOLD | ui_tools.get_color("yellow"), curses.A_BOLD | ui_tools.get_color("green")
+    divider_attr, duplicate_attr = (curses.A_BOLD, 'yellow'), (curses.A_BOLD, 'green')
 
     is_dates_shown = self.regex_filter is None and CONFIG["features.log.showDateDividers"]
     event_log = get_daybreaks(current_log, self.is_paused()) if is_dates_shown else list(current_log)
@@ -999,9 +999,9 @@ class LogPanel(panel.Panel, threading.Thread, logging.Handler):
 
         if seen_first_date_divider:
           if line_count >= 1 and line_count < height and show_daybreaks:
-            self.addch(line_count, divider_indent, curses.ACS_LLCORNER, divider_attr)
-            self.hline(line_count, divider_indent + 1, width - divider_indent - 2, divider_attr)
-            self.addch(line_count, width - 1, curses.ACS_LRCORNER, divider_attr)
+            self.addch(line_count, divider_indent, curses.ACS_LLCORNER, *divider_attr)
+            self.hline(line_count, divider_indent + 1, width - divider_indent - 2, *divider_attr)
+            self.addch(line_count, width - 1, curses.ACS_LRCORNER, *divider_attr)
 
           line_count += 1
 
@@ -1009,13 +1009,13 @@ class LogPanel(panel.Panel, threading.Thread, logging.Handler):
 
         if line_count >= 1 and line_count < height and show_daybreaks:
           time_label = time.strftime(" %B %d, %Y ", time.localtime(entry.timestamp))
-          self.addch(line_count, divider_indent, curses.ACS_ULCORNER, divider_attr)
-          self.addch(line_count, divider_indent + 1, curses.ACS_HLINE, divider_attr)
-          self.addstr(line_count, divider_indent + 2, time_label, curses.A_BOLD | divider_attr)
+          self.addch(line_count, divider_indent, curses.ACS_ULCORNER, *divider_attr)
+          self.addch(line_count, divider_indent + 1, curses.ACS_HLINE, *divider_attr)
+          self.addstr(line_count, divider_indent + 2, time_label, curses.A_BOLD, *divider_attr)
 
           line_length = width - divider_indent - len(time_label) - 3
-          self.hline(line_count, divider_indent + len(time_label) + 2, line_length, divider_attr)
-          self.addch(line_count, divider_indent + len(time_label) + 2 + line_length, curses.ACS_URCORNER, divider_attr)
+          self.hline(line_count, divider_indent + len(time_label) + 2, line_length, *divider_attr)
+          self.addch(line_count, divider_indent + len(time_label) + 2 + line_length, curses.ACS_URCORNER, *divider_attr)
 
         seen_first_date_divider = True
         line_count += 1
@@ -1029,7 +1029,7 @@ class LogPanel(panel.Panel, threading.Thread, logging.Handler):
 
         for i in range(len(msg_comp)):
           font = curses.A_BOLD if "ERR" in entry.type else curses.A_NORMAL  # emphasizes ERR messages
-          display_queue.append((msg_comp[i].strip(), font | ui_tools.get_color(entry.color), i != len(msg_comp) - 1))
+          display_queue.append((msg_comp[i].strip(), (font, entry.color), i != len(msg_comp) - 1))
 
         if duplicate_count:
           plural_label = "s" if duplicate_count > 1 else ""
@@ -1060,10 +1060,10 @@ class LogPanel(panel.Panel, threading.Thread, logging.Handler):
 
           if draw_line < height and draw_line >= 1:
             if seen_first_date_divider and width - divider_indent >= 3 and show_daybreaks:
-              self.addch(draw_line, divider_indent, curses.ACS_VLINE, divider_attr)
-              self.addch(draw_line, width - 1, curses.ACS_VLINE, divider_attr)
+              self.addch(draw_line, divider_indent, curses.ACS_VLINE, *divider_attr)
+              self.addch(draw_line, width - 1, curses.ACS_VLINE, *divider_attr)
 
-            self.addstr(draw_line, cursor_location, msg, format)
+            self.addstr(draw_line, cursor_location, msg, *format)
 
           cursor_location += len(msg)
 
@@ -1077,9 +1077,9 @@ class LogPanel(panel.Panel, threading.Thread, logging.Handler):
 
       if not deduplicated_log and seen_first_date_divider:
         if line_count < height and show_daybreaks:
-          self.addch(line_count, divider_indent, curses.ACS_LLCORNER, divider_attr)
-          self.hline(line_count, divider_indent + 1, width - divider_indent - 2, divider_attr)
-          self.addch(line_count, width - 1, curses.ACS_LRCORNER, divider_attr)
+          self.addch(line_count, divider_indent, curses.ACS_LLCORNER, *divider_attr)
+          self.hline(line_count, divider_indent + 1, width - divider_indent - 2, *divider_attr)
+          self.addch(line_count, width - 1, curses.ACS_LRCORNER, *divider_attr)
 
         line_count += 1
 
diff --git a/arm/menu/menu.py b/arm/menu/menu.py
index be705e5..435725b 100644
--- a/arm/menu/menu.py
+++ b/arm/menu/menu.py
@@ -178,12 +178,12 @@ def _draw_submenu(cursor, level, top, left):
 
     for menu_item in submenu.get_children():
       if menu_item == selection:
-        draw_format = curses.A_BOLD | ui_tools.get_color("white")
+        draw_format = (curses.A_BOLD, 'white')
         selection_top = draw_top
       else:
-        draw_format = curses.A_NORMAL
+        draw_format = (curses.A_NORMAL,)
 
-      popup.addstr(draw_top, 0, label_format % menu_item.get_label(), draw_format)
+      popup.addstr(draw_top, 0, label_format % menu_item.get_label(), *draw_format)
       draw_top += 1
 
     popup.win.refresh()
diff --git a/arm/torrc_panel.py b/arm/torrc_panel.py
index e561478..a299ef9 100644
--- a/arm/torrc_panel.py
+++ b/arm/torrc_panel.py
@@ -239,10 +239,10 @@ class TorrcPanel(panel.Panel):
       # splits the line into its component (msg, format) tuples
 
       line_comp = {
-        "option": ["", curses.A_BOLD | ui_tools.get_color("green")],
-        "argument": ["", curses.A_BOLD | ui_tools.get_color("cyan")],
-        "correction": ["", curses.A_BOLD | ui_tools.get_color("cyan")],
-        "comment": ["", ui_tools.get_color("white")],
+        'option': ['', (curses.A_BOLD, 'green')],
+        'argument': ['', (curses.A_BOLD, 'cyan')],
+        'correction': ['', (curses.A_BOLD, 'cyan')],
+        'comment': ['', ('white',)],
       }
 
       # parses the comment
@@ -250,7 +250,7 @@ class TorrcPanel(panel.Panel):
       comment_index = line_text.find("#")
 
       if comment_index != -1:
-        line_comp["comment"][0] = line_text[comment_index:]
+        line_comp['comment'][0] = line_text[comment_index:]
         line_text = line_text[:comment_index]
 
       # splits the option and argument, preserving any whitespace around them
@@ -261,15 +261,15 @@ class TorrcPanel(panel.Panel):
       if is_multiline:
         # part of a multiline entry started on a previous line so everything
         # is part of the argument
-        line_comp["argument"][0] = line_text
+        line_comp['argument'][0] = line_text
       elif option_index == -1:
         # no argument provided
-        line_comp["option"][0] = line_text
+        line_comp['option'][0] = line_text
       else:
         option_text = stripped_line[:option_index]
         option_end = line_text.find(option_text) + len(option_text)
-        line_comp["option"][0] = line_text[:option_end]
-        line_comp["argument"][0] = line_text[option_end:]
+        line_comp['option'][0] = line_text[:option_end]
+        line_comp['argument'][0] = line_text[option_end:]
 
       # flags following lines as belonging to this multiline entry if it ends
       # with a slash
@@ -283,29 +283,29 @@ class TorrcPanel(panel.Panel):
         line_issue, line_issue_msg = corrections[line_number]
 
         if line_issue in (tor_config.ValidationError.DUPLICATE, tor_config.ValidationError.IS_DEFAULT):
-          line_comp["option"][1] = curses.A_BOLD | ui_tools.get_color("blue")
-          line_comp["argument"][1] = curses.A_BOLD | ui_tools.get_color("blue")
+          line_comp['option'][1] = (curses.A_BOLD, 'blue')
+          line_comp['argument'][1] = (curses.A_BOLD, 'blue')
         elif line_issue == tor_config.ValidationError.MISMATCH:
-          line_comp["argument"][1] = curses.A_BOLD | ui_tools.get_color("red")
-          line_comp["correction"][0] = " (%s)" % line_issue_msg
+          line_comp['argument'][1] = (curses.A_BOLD, 'red')
+          line_comp['correction'][0] = ' (%s)' % line_issue_msg
         else:
           # For some types of configs the correction field is simply used to
           # provide extra data (for instance, the type for tor state fields).
 
-          line_comp["correction"][0] = " (%s)" % line_issue_msg
-          line_comp["correction"][1] = curses.A_BOLD | ui_tools.get_color("magenta")
+          line_comp['correction'][0] = ' (%s)' % line_issue_msg
+          line_comp['correction'][1] = (curses.A_BOLD, 'magenta')
 
       # draws the line number
 
       if self.show_line_num and display_line < height and display_line >= 1:
         line_number_str = ("%%%ii" % (line_number_offset - 1)) % (line_number + 1)
-        self.addstr(display_line, scroll_offset, line_number_str, curses.A_BOLD | ui_tools.get_color("yellow"))
+        self.addstr(display_line, scroll_offset, line_number_str, curses.A_BOLD, 'yellow')
 
       # draws the rest of the components with line wrap
 
       cursor_location, line_offset = line_number_offset + scroll_offset, 0
       max_lines_per_entry = CONFIG["features.config.file.max_lines_per_entry"]
-      display_queue = [line_comp[entry] for entry in ("option", "argument", "correction", "comment")]
+      display_queue = [line_comp[entry] for entry in ('option', 'argument', 'correction', 'comment')]
 
       while display_queue:
         msg, format = display_queue.pop(0)
@@ -325,7 +325,7 @@ class TorrcPanel(panel.Panel):
         draw_line = display_line + line_offset
 
         if msg and draw_line < height and draw_line >= 1:
-          self.addstr(draw_line, cursor_location, msg, format)
+          self.addstr(draw_line, cursor_location, msg, *format)
 
         # If we're done, and have added content to this line, then start
         # further content on the next line.
diff --git a/arm/util/panel.py b/arm/util/panel.py
index 33cff32..d68268a 100644
--- a/arm/util/panel.py
+++ b/arm/util/panel.py
@@ -489,7 +489,7 @@ class Panel():
         # in edge cases drawing could cause a _curses.error
         pass
 
-  def addstr(self, y, x, msg, attr=curses.A_NORMAL):
+  def addstr(self, y, x, msg, *attributes):
     """
     Writes string to subwindow if able. This takes into account screen bounds
     to avoid making curses upset. This should only be called from the context
@@ -502,13 +502,21 @@ class Panel():
       attr - text attributes
     """
 
+    format_attr = curses.A_NORMAL
+
+    for attr in attributes:
+      if isinstance(attr, str):
+        format_attr |= ui_tools.get_color(attr)
+      else:
+        format_attr |= attr
+
     # subwindows need a single character buffer (either in the x or y
     # direction) from actual content to prevent crash when shrank
 
     if self.win and self.max_x > x and self.max_y > y:
       try:
         drawn_msg = msg[:self.max_x - x]
-        self.win.addstr(y, x, drawn_msg, attr)
+        self.win.addstr(y, x, drawn_msg, format_attr)
         return x + len(drawn_msg)
       except:
         # this might produce a _curses.error during edge cases, for instance





More information about the tor-commits mailing list