[tor-commits] [nyx/master] Replace curses text format attributes

atagar at torproject.org atagar at torproject.org
Fri Mar 11 17:02:58 UTC 2016


commit 3245a5383b813f281bb19ec768519dedf13447b0
Author: Damian Johnson <atagar at torproject.org>
Date:   Fri Mar 11 09:00:57 2016 -0800

    Replace curses text format attributes
    
    Bit by bit we're removing direct curses usage, and someday there won't be an
    'import curses' line left outside our new nyx.curses module.
    
    For now though settling for our text format attributes. We already had enums
    for the colors and now we set text attributes (bold, underline, etc) the same
    way. This is a pretty big overhaul so no surprise if there's a regression here
    or there but gotta bite the bullet at some point. :)
---
 nyx/controller.py           |  15 ++--
 nyx/curses.py               | 191 ++++++++++++++++++++++++++++++++++++++++++++
 nyx/menu/actions.py         |   9 ++-
 nyx/menu/menu.py            |  23 +++---
 nyx/panel/config.py         |  31 +++----
 nyx/panel/connection.py     |  37 ++++-----
 nyx/panel/graph.py          |  23 +++---
 nyx/panel/header.py         |  29 +++----
 nyx/panel/log.py            |  18 ++---
 nyx/panel/torrc.py          |  14 ++--
 nyx/popups.py               |  48 +++++------
 nyx/settings/attributes.cfg | 142 ++++++++++++++++----------------
 nyx/starter.py              |   2 +
 nyx/util/panel.py           |  13 +--
 nyx/util/tracker.py         |   2 +-
 nyx/util/ui_tools.py        | 173 +++------------------------------------
 16 files changed, 411 insertions(+), 359 deletions(-)

diff --git a/nyx/controller.py b/nyx/controller.py
index 318df4b..47fa61e 100644
--- a/nyx/controller.py
+++ b/nyx/controller.py
@@ -3,6 +3,8 @@ Main interface loop for nyx, periodically redrawing the screen and issuing
 user input to the proper panels.
 """
 
+from __future__ import absolute_import
+
 import time
 import curses
 import threading
@@ -22,6 +24,7 @@ import stem
 
 from stem.util import conf, log
 
+from nyx.curses import NORMAL, BOLD, HIGHLIGHT
 from nyx.util import panel, tor_controller, ui_tools
 
 
@@ -65,7 +68,7 @@ class LabelPanel(panel.Panel):
   def __init__(self, stdscr):
     panel.Panel.__init__(self, stdscr, 'msg', 0, height=1)
     self.msg_text = ''
-    self.msg_attr = curses.A_NORMAL
+    self.msg_attr = NORMAL
 
   def set_message(self, msg, attr = None):
     """
@@ -77,7 +80,7 @@ class LabelPanel(panel.Panel):
     """
 
     if attr is None:
-      attr = curses.A_NORMAL
+      attr = NORMAL
 
     self.msg_text = msg
     self.msg_attr = attr
@@ -338,10 +341,10 @@ class Controller:
       if attr is None:
         if not self._is_paused:
           msg = 'page %i / %i - m: menu, p: pause, h: page help, q: quit' % (self._page + 1, len(self._page_panels))
-          attr = curses.A_NORMAL
+          attr = NORMAL
         else:
           msg = 'Paused'
-          attr = curses.A_STANDOUT
+          attr = HIGHLIGHT
 
     control_panel = self.get_panel('msg')
     control_panel.set_message(msg, attr)
@@ -454,7 +457,7 @@ def start_nyx(stdscr):
 
       if CONFIG['features.confirmQuit']:
         msg = 'Are you sure (q again to confirm)?'
-        confirmation_key = nyx.popups.show_msg(msg, attr = curses.A_BOLD)
+        confirmation_key = nyx.popups.show_msg(msg, attr = BOLD)
         quit_confirmed = confirmation_key.match('q')
       else:
         quit_confirmed = True
@@ -465,7 +468,7 @@ def start_nyx(stdscr):
       # provides prompt to confirm that nyx should issue a sighup
 
       msg = "This will reset Tor's internal state. Are you sure (x again to confirm)?"
-      confirmation_key = nyx.popups.show_msg(msg, attr = curses.A_BOLD)
+      confirmation_key = nyx.popups.show_msg(msg, attr = BOLD)
 
       if confirmation_key in (ord('x'), ord('X')):
         try:
diff --git a/nyx/curses.py b/nyx/curses.py
new file mode 100644
index 0000000..1280fc8
--- /dev/null
+++ b/nyx/curses.py
@@ -0,0 +1,191 @@
+"""
+Toolkit for working with curses. Curses earns its name, and this abstracts away
+its usage providing us more easy to use high level functions. This abstraction
+may also allow us to use libraries like `PDCurses <http://pdcurses.sourceforge.net/>`_
+if we want Windows support in the future too.
+
+**Module Overview:**
+
+::
+
+  curses_attr - curses encoded text attribute
+
+  is_color_supported - checks if terminal supports color output
+  get_color_override - provides color we override requests with
+  set_color_override - sets color we override requests with
+
+.. data:: Color (enum)
+
+  Terminal colors.
+
+  =========== ===========
+  Color       Description
+  =========== ===========
+  **RED**     red color
+  **GREEN**   green color
+  **YELLOW**  yellow color
+  **BLUE**    blue color
+  **CYAN**    cyan color
+  **MAGENTA** magenta color
+  **BLACK**   black color
+  **WHITE**   white color
+  =========== ===========
+
+.. data:: Attr (enum)
+
+  Terminal text attributes.
+
+  =================== ===========
+  Attr                Description
+  =================== ===========
+  **NORMAL**          no text attributes
+  **BOLD**            heavy typeface
+  **UNDERLINE**       underlined text
+  **HIGHLIGHT**       inverted foreground and background
+  =================== ===========
+"""
+
+from __future__ import absolute_import
+
+import curses
+
+import stem.util.conf
+import stem.util.enum
+
+from nyx.util import msg, log
+
+# Text colors and attributes. These are *very* commonly used so including
+# shorter aliases (so they can be referenced as just GREEN or BOLD).
+
+Color = stem.util.enum.Enum('RED', 'GREEN', 'YELLOW', 'BLUE', 'CYAN', 'MAGENTA', 'BLACK', 'WHITE')
+RED, GREEN, YELLOW, BLUE, CYAN, MAGENTA, BLACK, WHITE = list(Color)
+
+Attr = stem.util.enum.Enum('NORMAL', 'BOLD', 'UNDERLINE', 'HIGHLIGHT')
+NORMAL, BOLD, UNDERLINE, HIGHLIGHT = list(Attr)
+
+CURSES_COLORS = {
+  Color.RED: curses.COLOR_RED,
+  Color.GREEN: curses.COLOR_GREEN,
+  Color.YELLOW: curses.COLOR_YELLOW,
+  Color.BLUE: curses.COLOR_BLUE,
+  Color.CYAN: curses.COLOR_CYAN,
+  Color.MAGENTA: curses.COLOR_MAGENTA,
+  Color.BLACK: curses.COLOR_BLACK,
+  Color.WHITE: curses.COLOR_WHITE,
+}
+
+CURSES_ATTRIBUTES = {
+  Attr.NORMAL: curses.A_NORMAL,
+  Attr.BOLD: curses.A_BOLD,
+  Attr.UNDERLINE: curses.A_UNDERLINE,
+  Attr.HIGHLIGHT: curses.A_STANDOUT,
+}
+
+DEFAULT_COLOR_ATTR = dict([(color, 0) for color in Color])
+COLOR_ATTR = None
+
+
+def conf_handler(key, value):
+  if key == 'features.colorOverride':
+    if value not in Color and value != 'None':
+      raise ValueError(msg('usage.unable_to_set_color_override', color = value))
+
+
+CONFIG = stem.util.conf.config_dict('nyx', {
+  'features.colorOverride': 'None',
+  'features.colorInterface': True,
+}, conf_handler)
+
+
+def curses_attr(*attributes):
+  """
+  Provides encoding for the given curses text attributes.
+
+  :param list attributes: curses text attributes and colors
+
+  :returns: **int** that can be used with curses
+  """
+
+  encoded = curses.A_NORMAL
+
+  for attr in attributes:
+    if attr in Color:
+      override = get_color_override()
+      encoded |= _color_attr()[override if override else attr]
+    elif attr in Attr:
+      encoded |= CURSES_ATTRIBUTES[attr]
+    else:
+      raise ValueError("'%s' isn't a valid curses text attribute" % attr)
+
+  return encoded
+
+
+def is_color_supported():
+  """
+  Checks if curses currently supports rendering colors.
+
+  :returns: **True** if colors can be rendered, **False** otherwise
+  """
+
+  return _color_attr() != DEFAULT_COLOR_ATTR
+
+
+def get_color_override():
+  """
+  Provides the override color used by the interface.
+
+  :returns: :data:`~nyx.curses.Color` for the color requrests will be
+    overwritten with, **None** if no override is set
+  """
+
+  color_override = CONFIG.get('features.colorOverride', 'None')
+  return None if color_override == 'None' else color_override
+
+
+def set_color_override(color = None):
+  """
+  Overwrites all requests for color with the given color instead.
+
+  :param nyx.curses.Color color: color to override all requests with, **None**
+    if color requests shouldn't be overwritten
+
+  :raises: **ValueError** if the color name is invalid
+  """
+
+  nyx_config = stem.util.conf.get_config('nyx')
+
+  if color is None:
+    nyx_config.set('features.colorOverride', 'None')
+  elif color in Color:
+    nyx_config.set('features.colorOverride', color)
+  else:
+    raise ValueError(msg('usage.unable_to_set_color_override', color = color))
+
+
+def _color_attr():
+  """
+  Initializes color mappings usable by curses. This can only be done after
+  calling curses.initscr().
+  """
+
+  global COLOR_ATTR
+
+  if COLOR_ATTR is None:
+    if not CONFIG['features.colorInterface']:
+      COLOR_ATTR = DEFAULT_COLOR_ATTR
+    elif curses.has_colors():
+      color_attr = dict(DEFAULT_COLOR_ATTR)
+
+      for color_pair, color_name in enumerate(CURSES_COLORS):
+        foreground_color = CURSES_COLORS[color_name]
+        background_color = -1  # allows for default (possibly transparent) background
+        curses.init_pair(color_pair + 1, foreground_color, background_color)
+        color_attr[color_name] = curses.color_pair(color_pair + 1)
+
+      log.info('setup.color_support_available')
+      COLOR_ATTR = color_attr
+    else:
+      log.info('setup.color_support_unavailable')
+      COLOR_ATTR = DEFAULT_COLOR_ATTR
+
+  return COLOR_ATTR
diff --git a/nyx/menu/actions.py b/nyx/menu/actions.py
index 222e3e7..892be90 100644
--- a/nyx/menu/actions.py
+++ b/nyx/menu/actions.py
@@ -5,12 +5,13 @@ Generates the menu for nyx, binding options with their related actions.
 import functools
 
 import nyx.controller
+import nyx.curses
 import nyx.panel.graph
 import nyx.popups
 import nyx.menu.item
 import nyx.util.tracker
 
-from nyx.util import tor_controller, ui_tools
+from nyx.util import tor_controller
 
 import stem
 import stem.util.connection
@@ -104,13 +105,13 @@ def make_view_menu():
 
       view_menu.add(nyx.menu.item.SelectionMenuItem(label, page_group, i))
 
-  if ui_tools.is_color_supported():
+  if nyx.curses.is_color_supported():
     color_menu = nyx.menu.item.Submenu('Color')
-    color_group = nyx.menu.item.SelectionGroup(ui_tools.set_color_override, ui_tools.get_color_override())
+    color_group = nyx.menu.item.SelectionGroup(nyx.curses.set_color_override, nyx.curses.get_color_override())
 
     color_menu.add(nyx.menu.item.SelectionMenuItem('All', color_group, None))
 
-    for color in ui_tools.COLOR_LIST:
+    for color in nyx.curses.Color:
       color_menu.add(nyx.menu.item.SelectionMenuItem(str_tools._to_camel_case(color), color_group, color))
 
     view_menu.add(color_menu)
diff --git a/nyx/menu/menu.py b/nyx/menu/menu.py
index ef36063..df7d499 100644
--- a/nyx/menu/menu.py
+++ b/nyx/menu/menu.py
@@ -2,14 +2,17 @@
 Display logic for presenting the menu.
 """
 
+from __future__ import absolute_import
+
 import curses
 
+import nyx.curses
 import nyx.popups
 import nyx.controller
 import nyx.menu.item
 import nyx.menu.actions
 
-from nyx.util import ui_tools
+from nyx.curses import RED, WHITE, NORMAL, BOLD, UNDERLINE, HIGHLIGHT
 
 
 class MenuCursor:
@@ -90,26 +93,26 @@ def show_menu():
         # sets the background color
 
         popup.win.clear()
-        popup.win.bkgd(' ', curses.A_STANDOUT | ui_tools.get_color("red"))
+        popup.win.bkgd(' ', nyx.curses.curses_attr(RED, HIGHLIGHT))
         selection_hierarchy = cursor.get_selection().get_hierarchy()
 
         # provide a message saying how to close the menu
 
-        control.set_msg('Press m or esc to close the menu.', curses.A_BOLD, True)
+        control.set_msg('Press m or esc to close the menu.', BOLD, True)
 
         # renders the menu bar, noting where the open submenu is positioned
 
         draw_left, selection_left = 0, 0
 
         for top_level_item in menu.get_children():
-          draw_format = curses.A_BOLD
-
           if top_level_item == selection_hierarchy[1]:
-            draw_format |= curses.A_UNDERLINE
+            attr = UNDERLINE
             selection_left = draw_left
+          else:
+            attr = NORMAL
 
           draw_label = ' %s ' % top_level_item.get_label()[1]
-          popup.addstr(0, draw_left, draw_label, draw_format)
+          popup.addstr(0, draw_left, draw_label, BOLD, attr)
           popup.addch(0, draw_left + len(draw_label), curses.ACS_VLINE)
 
           draw_left += len(draw_label) + 1
@@ -162,16 +165,16 @@ def _draw_submenu(cursor, level, top, left):
 
     # sets the background color
 
-    popup.win.bkgd(' ', curses.A_STANDOUT | ui_tools.get_color('red'))
+    popup.win.bkgd(' ', nyx.curses.curses_attr(RED, HIGHLIGHT))
 
     draw_top, selection_top = 0, 0
 
     for menu_item in submenu.get_children():
       if menu_item == selection:
-        draw_format = (curses.A_BOLD, 'white')
+        draw_format = (WHITE, BOLD)
         selection_top = draw_top
       else:
-        draw_format = (curses.A_NORMAL,)
+        draw_format = (NORMAL,)
 
       popup.addstr(draw_top, 0, label_format % menu_item.get_label(), *draw_format)
       draw_top += 1
diff --git a/nyx/panel/config.py b/nyx/panel/config.py
index 2352fe7..fd529ca 100644
--- a/nyx/panel/config.py
+++ b/nyx/panel/config.py
@@ -12,6 +12,7 @@ import nyx.popups
 import stem.control
 import stem.manual
 
+from nyx.curses import GREEN, CYAN, WHITE, NORMAL, BOLD, HIGHLIGHT
 from nyx.util import DATA_DIR, panel, tor_controller, ui_tools
 
 from stem.util import conf, enum, log, str_tools
@@ -168,7 +169,7 @@ class ConfigPanel(panel.Panel):
     Provides the dialog for sorting our configuration options.
     """
 
-    sort_colors = dict([(attr, CONFIG['attr.config.sort_color'].get(attr, 'white')) for attr in SortAttr])
+    sort_colors = dict([(attr, CONFIG['attr.config.sort_color'].get(attr, WHITE)) for attr in SortAttr])
     results = nyx.popups.show_sort_dialog('Config Option Ordering:', SortAttr, self._sort_order, sort_colors)
 
     if results:
@@ -196,18 +197,18 @@ class ConfigPanel(panel.Panel):
           line = str_tools.crop(full_line, width - 2)
           option, arg = line.split(' ', 1) if ' ' in line else (line, '')
 
-          popup.addstr(i + 1, 1, option, curses.A_BOLD, 'green')
-          popup.addstr(i + 1, len(option) + 2, arg, curses.A_BOLD, 'cyan')
+          popup.addstr(i + 1, 1, option, GREEN, BOLD)
+          popup.addstr(i + 1, len(option) + 2, arg, CYAN, BOLD)
 
         x = width - 16
 
         for i, option in enumerate(['Save', 'Cancel']):
           x = popup.addstr(height - 2, x, '[')
-          x = popup.addstr(height - 2, x, option, curses.A_BOLD, curses.A_STANDOUT if i == selection else curses.A_NORMAL)
+          x = popup.addstr(height - 2, x, option, BOLD, HIGHLIGHT if i == selection else NORMAL)
           x = popup.addstr(height - 2, x, '] ')
 
         popup.win.box()
-        popup.addstr(0, 0, 'Torrc to save:', curses.A_STANDOUT)
+        popup.addstr(0, 0, 'Torrc to save:', HIGHLIGHT)
         popup.win.refresh()
 
         key = nyx.controller.get_controller().key_input()
@@ -291,7 +292,7 @@ class ConfigPanel(panel.Panel):
 
     if self.is_title_visible():
       hidden_msg = "press 'a' to hide most options" if self._show_all else "press 'a' to show all options"
-      self.addstr(0, 0, 'Tor Configuration (%s):' % hidden_msg, curses.A_STANDOUT)
+      self.addstr(0, 0, 'Tor Configuration (%s):' % hidden_msg, HIGHLIGHT)
 
     scroll_offset = 1
 
@@ -314,15 +315,15 @@ class ConfigPanel(panel.Panel):
       value_width = VALUE_WIDTH
 
     for i, entry in enumerate(contents[scroll_location:]):
-      attr = nyx.util.ui_tools.get_color(CONFIG['attr.config.category_color'].get(entry.manual.category, 'white'))
-      attr |= curses.A_BOLD if entry.is_set() else curses.A_NORMAL
-      attr |= curses.A_STANDOUT if entry == selection else curses.A_NORMAL
+      attr = [CONFIG['attr.config.category_color'].get(entry.manual.category, WHITE)]
+      attr.append(BOLD if entry.is_set() else NORMAL)
+      attr.append(HIGHLIGHT if entry == selection else NORMAL)
 
       option_label = str_tools.crop(entry.name, NAME_WIDTH).ljust(NAME_WIDTH + 1)
       value_label = str_tools.crop(entry.value(), value_width).ljust(value_width + 1)
       summary_label = str_tools.crop(entry.manual.summary, description_width).ljust(description_width)
 
-      self.addstr(DETAILS_HEIGHT + i, scroll_offset, option_label + value_label + summary_label, attr)
+      self.addstr(DETAILS_HEIGHT + i, scroll_offset, option_label + value_label + summary_label, *attr)
 
       if DETAILS_HEIGHT + i >= height:
         break
@@ -337,11 +338,11 @@ class ConfigPanel(panel.Panel):
 
     description = 'Description: %s' % (selection.manual.description)
     attr = ', '.join(('custom' if selection.is_set() else 'default', selection.value_type, 'usage: %s' % selection.manual.usage))
-    selected_color = CONFIG['attr.config.category_color'].get(selection.manual.category, 'white')
+    selected_color = CONFIG['attr.config.category_color'].get(selection.manual.category, WHITE)
     ui_tools.draw_box(self, 0, 0, width, DETAILS_HEIGHT)
 
-    self.addstr(1, 2, '%s (%s Option)' % (selection.name, selection.manual.category), curses.A_BOLD, selected_color)
-    self.addstr(2, 2, 'Value: %s (%s)' % (selection.value(), str_tools.crop(attr, width - len(selection.value()) - 13)), curses.A_BOLD, selected_color)
+    self.addstr(1, 2, '%s (%s Option)' % (selection.name, selection.manual.category), selected_color, BOLD)
+    self.addstr(2, 2, 'Value: %s (%s)' % (selection.value(), str_tools.crop(attr, width - len(selection.value()) - 13)), selected_color, BOLD)
 
     for i in range(DETAILS_HEIGHT - 4):
       if not description:
@@ -352,6 +353,6 @@ class ConfigPanel(panel.Panel):
       if i < DETAILS_HEIGHT - 5:
         line, remainder = str_tools.crop(line, width - 3, 4, 4, str_tools.Ending.HYPHEN, True)
         description = '  ' + remainder.strip() + description
-        self.addstr(3 + i, 2, line, curses.A_BOLD, selected_color)
+        self.addstr(3 + i, 2, line, selected_color, BOLD)
       else:
-        self.addstr(3 + i, 2, str_tools.crop(line, width - 3, 4, 4), curses.A_BOLD, selected_color)
+        self.addstr(3 + i, 2, str_tools.crop(line, width - 3, 4, 4), selected_color, BOLD)
diff --git a/nyx/panel/connection.py b/nyx/panel/connection.py
index e28f531..2c8fce9 100644
--- a/nyx/panel/connection.py
+++ b/nyx/panel/connection.py
@@ -13,6 +13,7 @@ import nyx.popups
 import nyx.util.tracker
 import nyx.util.ui_tools
 
+from nyx.curses import WHITE, NORMAL, BOLD, HIGHLIGHT
 from nyx.util import panel, tor_controller, ui_tools
 
 from stem.control import Listener
@@ -305,7 +306,7 @@ class ConnectionPanel(panel.Panel, threading.Thread):
     Provides a dialog for sorting our connections.
     """
 
-    sort_colors = dict([(attr, CONFIG['attr.connection.sort_color'].get(attr, 'white')) for attr in SortAttr])
+    sort_colors = dict([(attr, CONFIG['attr.connection.sort_color'].get(attr, WHITE)) for attr in SortAttr])
     results = nyx.popups.show_sort_dialog('Connection Ordering:', SortAttr, self._sort_order, sort_colors)
 
     if results:
@@ -356,7 +357,7 @@ class ConnectionPanel(panel.Panel, threading.Thread):
         def is_close_key(key):
           return key.is_selection() or key.match('d') or key.match('left') or key.match('right')
 
-        color = CONFIG['attr.connection.category_color'].get(selection.entry.get_type(), 'white')
+        color = CONFIG['attr.connection.category_color'].get(selection.entry.get_type(), WHITE)
         key = nyx.popups.show_descriptor_popup(selection.fingerprint, color, self.max_x, is_close_key)
 
         if not key or key.is_selection() or key.match('d'):
@@ -482,20 +483,20 @@ class ConnectionPanel(panel.Panel, threading.Thread):
     """
 
     if showing_details:
-      self.addstr(0, 0, 'Connection Details:', curses.A_STANDOUT)
+      self.addstr(0, 0, 'Connection Details:', HIGHLIGHT)
     elif not entries:
-      self.addstr(0, 0, 'Connections:', curses.A_STANDOUT)
+      self.addstr(0, 0, 'Connections:', HIGHLIGHT)
     else:
       counts = collections.Counter([entry.get_type() for entry in entries])
       count_labels = ['%i %s' % (counts[category], category.lower()) for category in Category if counts[category]]
-      self.addstr(0, 0, 'Connections (%s):' % ', '.join(count_labels), curses.A_STANDOUT)
+      self.addstr(0, 0, 'Connections (%s):' % ', '.join(count_labels), HIGHLIGHT)
 
   def _draw_details(self, selected, width, is_scrollbar_visible):
     """
     Shows detailed information about the selected connection.
     """
 
-    attr = (CONFIG['attr.connection.category_color'].get(selected.entry.get_type(), 'white'), curses.A_BOLD)
+    attr = (CONFIG['attr.connection.category_color'].get(selected.entry.get_type(), WHITE), BOLD)
 
     if selected.line_type == LineType.CIRCUIT_HEADER and selected.circuit.status != 'BUILT':
       self.addstr(1, 2, 'Building Circuit...', *attr)
@@ -552,10 +553,10 @@ class ConnectionPanel(panel.Panel, threading.Thread):
       self.addch(DETAILS_HEIGHT + 1, 1, curses.ACS_TTEE)
 
   def _draw_line(self, x, y, line, is_selected, width, current_time):
-    attr = nyx.util.ui_tools.get_color(CONFIG['attr.connection.category_color'].get(line.entry.get_type(), 'white'))
-    attr |= curses.A_STANDOUT if is_selected else curses.A_NORMAL
+    attr = [CONFIG['attr.connection.category_color'].get(line.entry.get_type(), WHITE)]
+    attr.append(HIGHLIGHT if is_selected else NORMAL)
 
-    self.addstr(y, x, ' ' * (width - x), attr)
+    self.addstr(y, x, ' ' * (width - x), *attr)
 
     if line.line_type == LineType.CIRCUIT:
       if line.circuit.path[-1][0] == line.fingerprint:
@@ -594,9 +595,9 @@ class ConnectionPanel(panel.Panel, threading.Thread):
       dst, src = src, dst
 
     if line.line_type == LineType.CIRCUIT:
-      self.addstr(y, x, dst, attr)
+      self.addstr(y, x, dst, *attr)
     else:
-      self.addstr(y, x, '%-21s  -->  %-26s' % (src, dst), attr)
+      self.addstr(y, x, '%-21s  -->  %-26s' % (src, dst), *attr)
 
   def _draw_line_details(self, x, y, line, width, attr):
     if line.line_type == LineType.CIRCUIT_HEADER:
@@ -615,7 +616,7 @@ class ConnectionPanel(panel.Panel, threading.Thread):
 
     for entry in comp:
       if width >= x + len(entry):
-        x = self.addstr(y, x, entry, attr)
+        x = self.addstr(y, x, entry, *attr)
       else:
         return
 
@@ -631,13 +632,13 @@ class ConnectionPanel(panel.Panel, threading.Thread):
       else:
         placement_type = 'Middle'
 
-      self.addstr(y, x + 4, '%i / %s' % (circ_index + 1, placement_type), attr)
+      self.addstr(y, x + 4, '%i / %s' % (circ_index + 1, placement_type), *attr)
     else:
-      x = self.addstr(y, x, '+' if line.connection.is_legacy else ' ', attr)
-      x = self.addstr(y, x, '%5s' % str_tools.time_label(current_time - line.connection.start_time, 1), attr)
-      x = self.addstr(y, x, ' (', attr)
-      x = self.addstr(y, x, line.entry.get_type().upper(), attr | curses.A_BOLD)
-      x = self.addstr(y, x, ')', attr)
+      x = self.addstr(y, x, '+' if line.connection.is_legacy else ' ', *attr)
+      x = self.addstr(y, x, '%5s' % str_tools.time_label(current_time - line.connection.start_time, 1), *attr)
+      x = self.addstr(y, x, ' (', *attr)
+      x = self.addstr(y, x, line.entry.get_type().upper(), BOLD, *attr)
+      x = self.addstr(y, x, ')', *attr)
 
   def stop(self):
     """
diff --git a/nyx/panel/graph.py b/nyx/panel/graph.py
index 086d04d..c7b9a36 100644
--- a/nyx/panel/graph.py
+++ b/nyx/panel/graph.py
@@ -23,6 +23,7 @@ import nyx.util.tracker
 from stem.control import EventType, Listener
 from stem.util import conf, enum, log, str_tools, system
 from nyx.util import join, msg, panel, tor_controller
+from nyx.curses import RED, GREEN, CYAN, BOLD, HIGHLIGHT
 
 GraphStat = enum.Enum(('BANDWIDTH', 'bandwidth'), ('CONNECTIONS', 'connections'), ('SYSTEM_RESOURCES', 'resources'))
 Interval = enum.Enum(('EACH_SECOND', 'each second'), ('FIVE_SECONDS', '5 seconds'), ('THIRTY_SECONDS', '30 seconds'), ('MINUTELY', 'minutely'), ('FIFTEEN_MINUTE', '15 minute'), ('THIRTY_MINUTE', '30 minute'), ('HOURLY', 'hourly'), ('DAILY', 'daily'))
@@ -41,7 +42,7 @@ INTERVAL_SECONDS = {
   Interval.DAILY: 86400,
 }
 
-PRIMARY_COLOR, SECONDARY_COLOR = 'green', 'cyan'
+PRIMARY_COLOR, SECONDARY_COLOR = GREEN, CYAN
 
 ACCOUNTING_RATE = 5
 DEFAULT_CONTENT_HEIGHT = 4  # space needed for labeling above and below the graph
@@ -477,8 +478,8 @@ class GraphPanel(panel.Panel):
       try:
         while True:
           msg = 'press the down/up to resize the graph, and enter when done'
-          control.set_msg(msg, curses.A_BOLD, True)
-          curses.cbreak()
+          control.set_msg(msg, BOLD, True)
+          curses.cbreak()  # TODO: can we drop this?
           key = control.key_input()
 
           if key.match('down'):
@@ -557,7 +558,7 @@ class GraphPanel(panel.Panel):
     )
 
     if self.is_title_visible():
-      self.addstr(0, 0, attr.stat.title(width), curses.A_STANDOUT)
+      self.addstr(0, 0, attr.stat.title(width), HIGHLIGHT)
 
     self._draw_subgraph(attr, attr.stat.primary, 0, PRIMARY_COLOR)
     self._draw_subgraph(attr, attr.stat.secondary, attr.subgraph_width, SECONDARY_COLOR)
@@ -583,7 +584,7 @@ class GraphPanel(panel.Panel):
     subgraph_columns = max(subgraph_columns, attr.subgraph_width - max([len(label) for label in y_axis_labels.values()]) - 2)
     axis_offset = max([len(label) for label in y_axis_labels.values()])
 
-    self.addstr(1, x, data.header(attr.subgraph_width), curses.A_BOLD, color)
+    self.addstr(1, x, data.header(attr.subgraph_width), color, BOLD)
 
     for x_offset, label in x_axis_labels.items():
       self.addstr(attr.subgraph_height, x + x_offset + axis_offset, label, color)
@@ -596,7 +597,7 @@ class GraphPanel(panel.Panel):
       column_height = int(min(attr.subgraph_height - 2, (attr.subgraph_height - 2) * column_count / (max(1, max_bound) - min_bound)))
 
       for row in range(column_height):
-        self.addstr(attr.subgraph_height - 1 - row, x + col + axis_offset + 1, ' ', curses.A_STANDOUT, color)
+        self.addstr(attr.subgraph_height - 1 - row, x + col + axis_offset + 1, ' ', color, HIGHLIGHT)
 
   def _get_graph_bounds(self, attr, data, subgraph_columns):
     """
@@ -699,18 +700,18 @@ class GraphPanel(panel.Panel):
     y = DEFAULT_CONTENT_HEIGHT + attr.subgraph_height - 2
 
     if tor_controller().is_alive():
-      hibernate_color = CONFIG['attr.hibernate_color'].get(attr.accounting.status, 'red')
+      hibernate_color = CONFIG['attr.hibernate_color'].get(attr.accounting.status, RED)
 
-      x = self.addstr(y, 0, 'Accounting (', curses.A_BOLD)
-      x = self.addstr(y, x, attr.accounting.status, curses.A_BOLD, hibernate_color)
-      x = self.addstr(y, x, ')', curses.A_BOLD)
+      x = self.addstr(y, 0, 'Accounting (', BOLD)
+      x = self.addstr(y, x, attr.accounting.status, BOLD, hibernate_color)
+      x = self.addstr(y, x, ')', BOLD)
 
       self.addstr(y, 35, 'Time to reset: %s' % str_tools.short_time_label(attr.accounting.time_until_reset))
 
       self.addstr(y + 1, 2, '%s / %s' % (attr.accounting.read_bytes, attr.accounting.read_limit), PRIMARY_COLOR)
       self.addstr(y + 1, 37, '%s / %s' % (attr.accounting.written_bytes, attr.accounting.write_limit), SECONDARY_COLOR)
     else:
-      self.addstr(y, 0, 'Accounting:', curses.A_BOLD)
+      self.addstr(y, 0, 'Accounting:', BOLD)
       self.addstr(y, 12, 'Connection Closed...')
 
   def copy_attr(self, attr):
diff --git a/nyx/panel/header.py b/nyx/panel/header.py
index 0d6591a..5cff92f 100644
--- a/nyx/panel/header.py
+++ b/nyx/panel/header.py
@@ -7,7 +7,6 @@ available.
 import collections
 import os
 import time
-import curses
 import threading
 
 import stem
@@ -19,6 +18,8 @@ from stem.control import Listener, State
 from stem.util import conf, log, proc, str_tools, system
 from nyx.util import msg, tor_controller, panel, tracker
 
+from nyx.curses import RED, GREEN, YELLOW, CYAN, WHITE, BOLD
+
 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
 UPDATE_RATE = 5  # rate in seconds at which we refresh
@@ -177,7 +178,7 @@ class HeaderPanel(panel.Panel, threading.Thread):
       space_left -= x - 43 - initial_x
 
       if space_left >= 7 + len(vals.version_status):
-        version_color = CONFIG['attr.version_status_colors'].get(vals.version_status, 'white')
+        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, version_color)
@@ -191,7 +192,7 @@ class HeaderPanel(panel.Panel, threading.Thread):
     """
 
     if not vals.is_relay:
-      x = self.addstr(y, x, 'Relaying Disabled', 'cyan')
+      x = self.addstr(y, x, 'Relaying Disabled', CYAN)
     else:
       x = self.addstr(y, x, vals.format('{nickname} - {address}:{or_port}'))
 
@@ -200,7 +201,7 @@ class HeaderPanel(panel.Panel, threading.Thread):
 
     if vals.control_port:
       if width >= x + 19 + len(vals.control_port) + len(vals.auth_type):
-        auth_color = 'red' if vals.auth_type == 'open' else 'green'
+        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, auth_color)
@@ -217,7 +218,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, 'red')
+    x = self.addstr(y, x, 'Tor Disconnected', RED, BOLD)
     last_heartbeat = time.strftime('%H:%M %m/%d/%Y', time.localtime(vals.last_heartbeat))
     self.addstr(y, x, ' (%s, press r to reconnect)' % last_heartbeat)
 
@@ -271,11 +272,11 @@ class HeaderPanel(panel.Panel, threading.Thread):
 
       if fd_percent >= SHOW_FD_THRESHOLD:
         if fd_percent >= 95:
-          percentage_format = (curses.A_BOLD, 'red')
+          percentage_format = (RED, BOLD)
         elif fd_percent >= 90:
-          percentage_format = ('red',)
+          percentage_format = (RED,)
         elif fd_percent >= 60:
-          percentage_format = ('yellow',)
+          percentage_format = (YELLOW,)
         else:
           percentage_format = ()
 
@@ -295,13 +296,13 @@ class HeaderPanel(panel.Panel, threading.Thread):
 
     if vals.flags:
       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, flag_color)
+        flag_color = CONFIG['attr.flag_colors'].get(flag, WHITE)
+        x = self.addstr(y, x, flag, flag_color, BOLD)
 
         if i < len(vals.flags) - 1:
           x = self.addstr(y, x, ', ')
     else:
-      self.addstr(y, x, 'none', curses.A_BOLD, 'cyan')
+      self.addstr(y, x, 'none', CYAN, BOLD)
 
   def _draw_exit_policy(self, x, y, width, vals):
     """
@@ -318,8 +319,8 @@ class HeaderPanel(panel.Panel, threading.Thread):
     rules = list(vals.exit_policy.strip_private().strip_default())
 
     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, policy_color)
+      policy_color = GREEN if rule.is_accept else RED
+      x = self.addstr(y, x, str(rule), policy_color, BOLD)
 
       if i < len(rules) - 1:
         x = self.addstr(y, x, ', ')
@@ -328,7 +329,7 @@ class HeaderPanel(panel.Panel, threading.Thread):
       if rules:
         x = self.addstr(y, x, ', ')
 
-      self.addstr(y, x, '<default>', curses.A_BOLD, 'cyan')
+      self.addstr(y, x, '<default>', CYAN, BOLD)
 
   def _draw_newnym_option(self, x, y, width, vals):
     """
diff --git a/nyx/panel/log.py b/nyx/panel/log.py
index 708a679..e674ac1 100644
--- a/nyx/panel/log.py
+++ b/nyx/panel/log.py
@@ -6,7 +6,6 @@ regular expressions.
 
 import os
 import time
-import curses
 import threading
 
 import stem.response.events
@@ -15,6 +14,7 @@ import nyx.arguments
 import nyx.popups
 import nyx.util.log
 
+from nyx.curses import GREEN, YELLOW, WHITE, NORMAL, BOLD, HIGHLIGHT
 from nyx.util import join, panel, tor_controller, ui_tools
 from stem.util import conf, log
 
@@ -144,7 +144,7 @@ class LogPanel(panel.Panel, threading.Thread):
         # displays the available flags
 
         popup.win.box()
-        popup.addstr(0, 0, 'Event Types:', curses.A_STANDOUT)
+        popup.addstr(0, 0, 'Event Types:', HIGHLIGHT)
         event_lines = CONFIG['msg.misc.event_types'].split('\n')
 
         for i in range(len(event_lines)):
@@ -233,7 +233,7 @@ class LogPanel(panel.Panel, threading.Thread):
       self.redraw(True)
     elif key.match('c'):
       msg = 'This will clear the log. Are you sure (c again to confirm)?'
-      key_press = nyx.popups.show_msg(msg, attr = curses.A_BOLD)
+      key_press = nyx.popups.show_msg(msg, attr = BOLD)
 
       if key_press.match('c'):
         self.clear()
@@ -309,9 +309,9 @@ class LogPanel(panel.Panel, threading.Thread):
         for entry in day_to_entries[day]:
           y = self._draw_entry(x, y, width, entry, show_duplicates)
 
-        ui_tools.draw_box(self, original_y, x - 1, width - x + 1, y - original_y + 1, curses.A_BOLD, 'yellow')
+        ui_tools.draw_box(self, original_y, x - 1, width - x + 1, y - original_y + 1, YELLOW, BOLD)
         time_label = time.strftime(' %B %d, %Y ', time.localtime(day_to_entries[day][0].timestamp))
-        self.addstr(original_y, x + 1, time_label, curses.A_BOLD, curses.A_BOLD, 'yellow')
+        self.addstr(original_y, x + 1, time_label, YELLOW, BOLD)
 
         y += 1
 
@@ -360,7 +360,7 @@ class LogPanel(panel.Panel, threading.Thread):
     title_comp_str = join(title_comp, ', ', width - 10)
     title = 'Events (%s):' % title_comp_str if title_comp_str else 'Events:'
 
-    self.addstr(0, 0, title, curses.A_STANDOUT)
+    self.addstr(0, 0, title, HIGHLIGHT)
 
   def _draw_entry(self, x, y, width, entry, show_duplicates):
     """
@@ -368,8 +368,8 @@ class LogPanel(panel.Panel, threading.Thread):
     """
 
     min_x, msg = x + 2, entry.display_message
-    boldness = curses.A_BOLD if 'ERR' in entry.type else curses.A_NORMAL  # emphasize ERR messages
-    color = CONFIG['attr.log_color'].get(entry.type, 'white')
+    boldness = BOLD if 'ERR' in entry.type else NORMAL  # emphasize ERR messages
+    color = CONFIG['attr.log_color'].get(entry.type, WHITE)
 
     for line in msg.splitlines():
       x, y = self.addstr_wrap(y, x, line, width, min_x, boldness, color)
@@ -378,7 +378,7 @@ class LogPanel(panel.Panel, threading.Thread):
       duplicate_count = len(entry.duplicates) - 1
       plural = 's' if duplicate_count > 1 else ''
       duplicate_msg = ' [%i duplicate%s hidden]' % (duplicate_count, plural)
-      x, y = self.addstr_wrap(y, x, duplicate_msg, width, min_x, curses.A_BOLD, 'green')
+      x, y = self.addstr_wrap(y, x, duplicate_msg, width, min_x, GREEN, BOLD)
 
     return y + 1
 
diff --git a/nyx/panel/torrc.py b/nyx/panel/torrc.py
index f0f66b4..ae26c83 100644
--- a/nyx/panel/torrc.py
+++ b/nyx/panel/torrc.py
@@ -3,8 +3,8 @@ Panel displaying the torrc or nyxrc with the validation done against it.
 """
 
 import math
-import curses
 
+from nyx.curses import RED, GREEN, YELLOW, CYAN, WHITE, BOLD, HIGHLIGHT
 from nyx.util import expand_path, msg, panel, tor_controller, ui_tools
 
 from stem import ControllerError
@@ -102,7 +102,7 @@ class TorrcPanel(panel.Panel):
 
   def draw(self, width, height):
     if self._torrc_content is None:
-      self.addstr(1, 0, self._torrc_load_error, 'red', curses.A_BOLD)
+      self.addstr(1, 0, self._torrc_load_error, RED, BOLD)
       new_content_height = 1
     else:
       self._scroll = max(0, min(self._scroll, self._last_content_height - height + 1))
@@ -148,14 +148,14 @@ class TorrcPanel(panel.Panel):
         is_multiline = line.endswith('\\')  # next line's part of a multi-line entry
 
         if self._show_line_numbers:
-          self.addstr(y, scroll_offset, str(line_number + 1).rjust(line_number_offset - 1), curses.A_BOLD, 'yellow')
+          self.addstr(y, scroll_offset, str(line_number + 1).rjust(line_number_offset - 1), YELLOW, BOLD)
 
         x = line_number_offset + scroll_offset
         min_x = line_number_offset + scroll_offset
 
-        x, y = self.addstr_wrap(y, x, option, width, min_x, curses.A_BOLD, 'green')
-        x, y = self.addstr_wrap(y, x, argument, width, min_x, curses.A_BOLD, 'cyan')
-        x, y = self.addstr_wrap(y, x, comment, width, min_x, 'white')
+        x, y = self.addstr_wrap(y, x, option, width, min_x, GREEN, BOLD)
+        x, y = self.addstr_wrap(y, x, argument, width, min_x, CYAN, BOLD)
+        x, y = self.addstr_wrap(y, x, comment, width, min_x, WHITE)
 
         y += 1
 
@@ -164,7 +164,7 @@ class TorrcPanel(panel.Panel):
     if self.is_title_visible():
       self.addstr(0, 0, ' ' * width)  # clear line
       location = ' (%s)' % self._torrc_location if self._torrc_location else ''
-      self.addstr(0, 0, 'Tor Configuration File%s:' % location, curses.A_STANDOUT)
+      self.addstr(0, 0, 'Tor Configuration File%s:' % location, HIGHLIGHT)
 
     if self._last_content_height != new_content_height:
       self._last_content_height = new_content_height
diff --git a/nyx/popups.py b/nyx/popups.py
index a9d3d27..1a711c7 100644
--- a/nyx/popups.py
+++ b/nyx/popups.py
@@ -2,6 +2,8 @@
 Functions for displaying popups in the interface.
 """
 
+from __future__ import absolute_import
+
 import math
 import curses
 import operator
@@ -9,13 +11,14 @@ import operator
 import nyx.controller
 
 from nyx import __version__, __release_date__
+from nyx.curses import RED, GREEN, YELLOW, CYAN, WHITE, NORMAL, BOLD, HIGHLIGHT
 from nyx.util import tor_controller, panel, ui_tools
 
 NO_STATS_MSG = "Usage stats aren't available yet, press any key..."
 
 HEADERS = ['Consensus:', 'Microdescriptor:', 'Server Descriptor:']
-HEADER_COLOR = 'cyan'
-LINE_NUMBER_COLOR = 'yellow'
+HEADER_COLOR = CYAN
+LINE_NUMBER_COLOR = YELLOW
 
 BLOCK_START, BLOCK_END = '-----BEGIN ', '-----END '
 
@@ -95,7 +98,7 @@ def input_prompt(msg, initial_value = ''):
     return user_input
 
 
-def show_msg(msg, max_wait = -1, attr = curses.A_STANDOUT):
+def show_msg(msg, max_wait = -1, attr = HIGHLIGHT):
   """
   Displays a single line message on the control line for a set time. Pressing
   any key will end the message. This returns the key pressed.
@@ -146,7 +149,7 @@ def show_help_popup():
       # test doing afterward in case of overwriting
 
       popup.win.box()
-      popup.addstr(0, 0, 'Page %i Commands:' % (control.get_page() + 1), curses.A_STANDOUT)
+      popup.addstr(0, 0, 'Page %i Commands:' % (control.get_page() + 1), HIGHLIGHT)
 
       for i in range(len(help_options)):
         if i / 2 >= height - 2:
@@ -164,14 +167,14 @@ def show_help_popup():
         row = (i / 2) + 1
         col = 2 if i % 2 == 0 else 41
 
-        popup.addstr(row, col, key, curses.A_BOLD)
+        popup.addstr(row, col, key, BOLD)
         col += len(key)
         popup.addstr(row, col, description)
         col += len(description)
 
         if selection:
           popup.addstr(row, col, ' (')
-          popup.addstr(row, col + 2, selection, curses.A_BOLD)
+          popup.addstr(row, col + 2, selection, BOLD)
           popup.addstr(row, col + 2 + len(selection), ')')
 
       # tells user to press a key if the lower left is unoccupied
@@ -200,8 +203,8 @@ def show_about_popup():
       control = nyx.controller.get_controller()
 
       popup.win.box()
-      popup.addstr(0, 0, 'About:', curses.A_STANDOUT)
-      popup.addstr(1, 2, 'nyx, version %s (released %s)' % (__version__, __release_date__), curses.A_BOLD)
+      popup.addstr(0, 0, 'About:', HIGHLIGHT)
+      popup.addstr(1, 2, 'nyx, version %s (released %s)' % (__version__, __release_date__), BOLD)
       popup.addstr(2, 4, 'Written by Damian Johnson (atagar at torproject.org)')
       popup.addstr(3, 4, 'Project page: www.atagar.com/nyx')
       popup.addstr(5, 2, 'Released under the GPL v3 (http://www.gnu.org/licenses/gpl.html)')
@@ -231,7 +234,7 @@ def show_count_dialog(title, counts):
       return
 
     if not counts:
-      popup.addstr(1, 2, NO_STATS_MSG, curses.A_BOLD, 'cyan')
+      popup.addstr(1, 2, NO_STATS_MSG, CYAN, BOLD)
     else:
       key_width, val_width, value_total = 3, 1, 0
 
@@ -245,15 +248,15 @@ def show_count_dialog(title, counts):
 
       for y, (k, v) in enumerate(sorted_counts):
         label = '%s %s (%-2i%%)' % (k.ljust(key_width), str(v).rjust(val_width), v * 100 / value_total)
-        x = popup.addstr(y + 1, 2, label, curses.A_BOLD, 'green')
+        x = popup.addstr(y + 1, 2, label, GREEN, BOLD)
 
         for j in range(graph_width * v / value_total):
-          popup.addstr(y + 1, x + j + 1, ' ', curses.A_STANDOUT, 'red')
+          popup.addstr(y + 1, x + j + 1, ' ', RED, HIGHLIGHT)
 
       popup.addstr(height - 2, 2, 'Press any key...')
 
     popup.win.box()
-    popup.addstr(0, 0, title, curses.A_STANDOUT)
+    popup.addstr(0, 0, title, HIGHLIGHT)
     popup.win.refresh()
 
     curses.cbreak()
@@ -292,7 +295,7 @@ def show_sort_dialog(title, options, old_selection, option_colors):
       while len(new_selections) < len(old_selection):
         popup.win.erase()
         popup.win.box()
-        popup.addstr(0, 0, title, curses.A_STANDOUT)
+        popup.addstr(0, 0, title, HIGHLIGHT)
 
         _draw_sort_selection(popup, 1, 2, 'Current Order: ', old_selection, option_colors)
         _draw_sort_selection(popup, 2, 2, 'New Order: ', new_selections, option_colors)
@@ -303,7 +306,7 @@ def show_sort_dialog(title, options, old_selection, option_colors):
         row, col = 4, 0
 
         for i in range(len(selection_options)):
-          option_format = curses.A_STANDOUT if cursor_location == i else curses.A_NORMAL
+          option_format = HIGHLIGHT if cursor_location == i else NORMAL
           popup.addstr(row, col * 19 + 2, selection_options[i], option_format)
           col += 1
 
@@ -356,19 +359,18 @@ def _draw_sort_selection(popup, y, x, prefix, options, option_colors):
     option_colors - mappings of options to their color
   """
 
-  popup.addstr(y, x, prefix, curses.A_BOLD)
+  popup.addstr(y, x, prefix, BOLD)
   x += len(prefix)
 
   for i in range(len(options)):
     sort_type = options[i]
-    sort_color = ui_tools.get_color(option_colors.get(sort_type, 'white'))
-    popup.addstr(y, x, sort_type, sort_color | curses.A_BOLD)
+    popup.addstr(y, x, sort_type, option_colors.get(sort_type, WHITE), BOLD)
     x += len(sort_type)
 
     # comma divider between options, if this isn't the last
 
     if i < len(options) - 1:
-      popup.addstr(y, x, ', ', curses.A_BOLD)
+      popup.addstr(y, x, ', ', BOLD)
       x += 2
 
 
@@ -405,11 +407,11 @@ def show_menu(title, options, old_selection):
     while True:
       popup.win.erase()
       popup.win.box()
-      popup.addstr(0, 0, title, curses.A_STANDOUT)
+      popup.addstr(0, 0, title, HIGHLIGHT)
 
       for i in range(len(options)):
         label = options[i]
-        format = curses.A_STANDOUT if i == selection else curses.A_NORMAL
+        format = HIGHLIGHT if i == selection else NORMAL
         tab = '> ' if i == old_selection else '  '
         popup.addstr(i + 1, 2, tab)
         popup.addstr(i + 1, 4, ' %s ' % label, format)
@@ -556,9 +558,9 @@ def _draw(popup, title, lines, entry_color, scroll, show_line_numbers):
       continue
 
     if show_line_numbers:
-      popup.addstr(y, 2, str(i + 1).rjust(line_number_width), curses.A_BOLD, LINE_NUMBER_COLOR)
+      popup.addstr(y, 2, str(i + 1).rjust(line_number_width), LINE_NUMBER_COLOR, BOLD)
 
-    x, y = popup.addstr_wrap(y, width, keyword, width, offset, color, curses.A_BOLD)
+    x, y = popup.addstr_wrap(y, width, keyword, width, offset, color, BOLD)
     x, y = popup.addstr_wrap(y, x + 1, value, width, offset, color)
 
     y += 1
@@ -567,5 +569,5 @@ def _draw(popup, title, lines, entry_color, scroll, show_line_numbers):
       break
 
   popup.win.box()
-  popup.addstr(0, 0, title, curses.A_STANDOUT)
+  popup.addstr(0, 0, title, HIGHLIGHT)
   popup.win.refresh()
diff --git a/nyx/settings/attributes.cfg b/nyx/settings/attributes.cfg
index 1942de9..fcc78f3 100644
--- a/nyx/settings/attributes.cfg
+++ b/nyx/settings/attributes.cfg
@@ -1,31 +1,31 @@
 # General configuration data used by nyx.
 
-attr.flag_colors Authority => white
-attr.flag_colors BadExit => red
-attr.flag_colors BadDirectory => red
-attr.flag_colors Exit => cyan
-attr.flag_colors Fast => yellow
-attr.flag_colors Guard => green
-attr.flag_colors HSDir => magenta
-attr.flag_colors Named => blue
-attr.flag_colors Stable => blue
-attr.flag_colors Running => yellow
-attr.flag_colors Unnamed => magenta
-attr.flag_colors Valid => green
-attr.flag_colors V2Dir => cyan
-attr.flag_colors V3Dir => white
+attr.flag_colors Authority => White
+attr.flag_colors BadExit => Red
+attr.flag_colors BadDirectory => Red
+attr.flag_colors Exit => Cyan
+attr.flag_colors Fast => Yellow
+attr.flag_colors Guard => Green
+attr.flag_colors HSDir => Magenta
+attr.flag_colors Named => Blue
+attr.flag_colors Stable => Blue
+attr.flag_colors Running => Yellow
+attr.flag_colors Unnamed => Magenta
+attr.flag_colors Valid => Green
+attr.flag_colors V2Dir => Cyan
+attr.flag_colors V3Dir => White
 
-attr.version_status_colors new => blue
-attr.version_status_colors new in series => blue
-attr.version_status_colors obsolete => red
-attr.version_status_colors recommended => green
-attr.version_status_colors old => red
-attr.version_status_colors unrecommended => red
-attr.version_status_colors unknown => cyan
+attr.version_status_colors new => Blue
+attr.version_status_colors new in series => Blue
+attr.version_status_colors obsolete => Red
+attr.version_status_colors recommended => Green
+attr.version_status_colors old => Red
+attr.version_status_colors unrecommended => Red
+attr.version_status_colors unknown => Cyan
 
-attr.hibernate_color awake => green
-attr.hibernate_color soft => yellow
-attr.hibernate_color hard => red
+attr.hibernate_color awake => Green
+attr.hibernate_color soft => Yellow
+attr.hibernate_color hard => Red
 
 attr.graph.title bandwidth => Bandwidth
 attr.graph.title connections => Connection Count
@@ -39,56 +39,56 @@ attr.graph.header.secondary bandwidth => Upload
 attr.graph.header.secondary connections => Outbound
 attr.graph.header.secondary resources => Memory
 
-attr.log_color DEBUG => magenta
-attr.log_color INFO => blue
-attr.log_color NOTICE => green
-attr.log_color WARN => yellow
-attr.log_color ERR => red
+attr.log_color DEBUG => Magenta
+attr.log_color INFO => Blue
+attr.log_color NOTICE => Green
+attr.log_color WARN => Yellow
+attr.log_color ERR => Red
 
-attr.log_color NYX_DEBUG => magenta
-attr.log_color NYX_INFO => blue
-attr.log_color NYX_NOTICE => green
-attr.log_color NYX_WARN => yellow
-attr.log_color NYX_ERR => red
+attr.log_color NYX_DEBUG => Magenta
+attr.log_color NYX_INFO => Blue
+attr.log_color NYX_NOTICE => Green
+attr.log_color NYX_WARN => Yellow
+attr.log_color NYX_ERR => Red
 
-attr.log_color CIRC => yellow
-attr.log_color BW => cyan
-attr.log_color NS => blue
-attr.log_color NEWCONSENSUS => blue
-attr.log_color GUARD => yellow
+attr.log_color CIRC => Yellow
+attr.log_color BW => Cyan
+attr.log_color NS => Blue
+attr.log_color NEWCONSENSUS => Blue
+attr.log_color GUARD => Yellow
 
-attr.connection.category_color Inbound => green
-attr.connection.category_color Outbound => blue
-attr.connection.category_color Exit => red
-attr.connection.category_color Hidden => magenta
-attr.connection.category_color Socks => yellow
-attr.connection.category_color Circuit => cyan
-attr.connection.category_color Directory => magenta
-attr.connection.category_color Control => red
+attr.connection.category_color Inbound => Green
+attr.connection.category_color Outbound => Blue
+attr.connection.category_color Exit => Red
+attr.connection.category_color Hidden => Magenta
+attr.connection.category_color Socks => Yellow
+attr.connection.category_color Circuit => Cyan
+attr.connection.category_color Directory => Magenta
+attr.connection.category_color Control => Red
 
-attr.connection.sort_color Category => red
-attr.connection.sort_color Uptime => yellow
-attr.connection.sort_color Listing => green
-attr.connection.sort_color Ip Address => blue
-attr.connection.sort_color Port => blue
-attr.connection.sort_color Fingerprint => cyan
-attr.connection.sort_color Nickname => cyan
-attr.connection.sort_color Country => blue
+attr.connection.sort_color Category => Red
+attr.connection.sort_color Uptime => Yellow
+attr.connection.sort_color Listing => Green
+attr.connection.sort_color Ip Address => Blue
+attr.connection.sort_color Port => Blue
+attr.connection.sort_color Fingerprint => Cyan
+attr.connection.sort_color Nickname => Cyan
+attr.connection.sort_color Country => Blue
 
-attr.config.category_color General => green
-attr.config.category_color Client => blue
-attr.config.category_color Relay => yellow
-attr.config.category_color Directory => magenta
-attr.config.category_color Authority => red
-attr.config.category_color Hidden Service => cyan
-attr.config.category_color Testing => white
-attr.config.category_color Unknown => white
+attr.config.category_color General => Green
+attr.config.category_color Client => Blue
+attr.config.category_color Relay => Yellow
+attr.config.category_color Directory => Magenta
+attr.config.category_color Authority => Red
+attr.config.category_color Hidden Service => Cyan
+attr.config.category_color Testing => White
+attr.config.category_color Unknown => White
 
-attr.config.sort_color Name => blue
-attr.config.sort_color Value => cyan
-attr.config.sort_color Value Type => green
-attr.config.sort_color Category => red
-attr.config.sort_color Usage => yellow
-attr.config.sort_color Summary => green
-attr.config.sort_color Description => white
-attr.config.sort_color Man Page Entry => blue
+attr.config.sort_color Name => Blue
+attr.config.sort_color Value => Cyan
+attr.config.sort_color Value Type => Green
+attr.config.sort_color Category => Red
+attr.config.sort_color Usage => Yellow
+attr.config.sort_color Summary => Green
+attr.config.sort_color Description => White
+attr.config.sort_color Man Page Entry => Blue
diff --git a/nyx/starter.py b/nyx/starter.py
index 65a78d2..e46d494 100644
--- a/nyx/starter.py
+++ b/nyx/starter.py
@@ -4,6 +4,8 @@ information. This starts the application, parsing arguments and getting a Tor
 connection.
 """
 
+from __future__ import absolute_import
+
 import curses
 import locale
 import logging
diff --git a/nyx/util/panel.py b/nyx/util/panel.py
index 4a94133..26b0af1 100644
--- a/nyx/util/panel.py
+++ b/nyx/util/panel.py
@@ -9,8 +9,9 @@ import curses.ascii
 import curses.textpad
 from threading import RLock
 
-from nyx.util import ui_tools
+import nyx.curses
 
+from nyx.curses import HIGHLIGHT
 from stem.util import conf, log, str_tools
 
 # global ui lock governing all panel instances (curses isn't thread save and
@@ -484,7 +485,7 @@ class Panel(object):
       attr   - text attributes
     """
 
-    format_attr = ui_tools.curses_format(*attributes)
+    format_attr = nyx.curses.curses_attr(*attributes)
 
     if self.win and self.max_x > x and self.max_y > y:
       try:
@@ -506,7 +507,7 @@ class Panel(object):
       attr   - text attributes
     """
 
-    format_attr = ui_tools.curses_format(*attributes)
+    format_attr = nyx.curses.curses_attr(*attributes)
 
     if self.win and self.max_x > x and self.max_y > y:
       try:
@@ -528,7 +529,7 @@ class Panel(object):
       attr - text attributes
     """
 
-    format_attr = ui_tools.curses_format(*attributes)
+    format_attr = nyx.curses.curses_attr(*attributes)
 
     if self.win and self.max_x > x and self.max_y > y:
       try:
@@ -553,7 +554,7 @@ class Panel(object):
       attr - text attributes
     """
 
-    format_attr = ui_tools.curses_format(*attributes)
+    format_attr = nyx.curses.curses_attr(*attributes)
 
     # subwindows need a single character buffer (either in the x or y
     # direction) from actual content to prevent crash when shrank
@@ -712,7 +713,7 @@ class Panel(object):
 
     for i in range(scrollbar_height):
       if i >= slider_top and i <= slider_top + slider_size:
-        self.addstr(i + draw_top, draw_left, ' ', curses.A_STANDOUT)
+        self.addstr(i + draw_top, draw_left, ' ', HIGHLIGHT)
       else:
         self.addstr(i + draw_top, draw_left, ' ')
 
diff --git a/nyx/util/tracker.py b/nyx/util/tracker.py
index 2b2b155..8378034 100644
--- a/nyx/util/tracker.py
+++ b/nyx/util/tracker.py
@@ -461,7 +461,7 @@ class Daemon(threading.Thread):
 
         self._process_pid = tor_pid
         self._process_name = tor_cmd if tor_cmd else 'tor'
-      elif event_type == stem.contorl.State.CLOSED:
+      elif event_type == stem.control.State.CLOSED:
         self._process_pid = None
         self._process_name = None
 
diff --git a/nyx/util/ui_tools.py b/nyx/util/ui_tools.py
index d546415..ccea602 100644
--- a/nyx/util/ui_tools.py
+++ b/nyx/util/ui_tools.py
@@ -6,140 +6,7 @@ import curses
 
 from curses.ascii import isprint
 
-from nyx.util import log, msg
-
-from stem.util import conf, system
-
-COLOR_LIST = {
-  'red': curses.COLOR_RED,
-  'green': curses.COLOR_GREEN,
-  'yellow': curses.COLOR_YELLOW,
-  'blue': curses.COLOR_BLUE,
-  'cyan': curses.COLOR_CYAN,
-  'magenta': curses.COLOR_MAGENTA,
-  'black': curses.COLOR_BLACK,
-  'white': curses.COLOR_WHITE,
-}
-
-DEFAULT_COLOR_ATTR = dict([(color, 0) for color in COLOR_LIST])
-COLOR_ATTR = None
-
-
-def conf_handler(key, value):
-  if key == 'features.color_override':
-    if value not in COLOR_LIST.keys() and value != 'none':
-      raise ValueError(msg('usage.unable_to_set_color_override', color = value))
-
-
-CONFIG = conf.config_dict('nyx', {
-  'features.color_override': 'none',
-  'features.colorInterface': True,
-}, conf_handler)
-
-
-def is_color_supported():
-  """
-  Checks if curses currently supports rendering colors.
-
-  :returns: **True** if colors can be rendered, **False** otherwise
-  """
-
-  return _color_attr() != DEFAULT_COLOR_ATTR
-
-
-def get_color(color):
-  """
-  Provides attribute corresponding to a given text color. Supported colors
-  include:
-
-    * red
-    * green
-    * yellow
-    * blue
-    * cyan
-    * magenta
-    * black
-    * white
-
-  If color support isn't available or colors can't be initialized then this uses the
-  terminal's default coloring scheme.
-
-  :param str color: color attributes to be provided
-
-  :returns: **tuple** color pair used by curses to render the color
-  """
-
-  color_override = get_color_override()
-
-  if color_override:
-    color = color_override
-
-  return _color_attr()[color]
-
-
-def set_color_override(color = None):
-  """
-  Overwrites all requests for color with the given color instead.
-
-  :param str color: color to override all requests with, **None** if color
-    requests shouldn't be overwritten
-
-  :raises: **ValueError** if the color name is invalid
-  """
-
-  nyx_config = conf.get_config('nyx')
-
-  if color is None:
-    nyx_config.set('features.color_override', 'none')
-  elif color in COLOR_LIST.keys():
-    nyx_config.set('features.color_override', color)
-  else:
-    raise ValueError(msg('usage.unable_to_set_color_override', color = color))
-
-
-def get_color_override():
-  """
-  Provides the override color used by the interface.
-
-  :returns: **str** for the color requrests will be overwritten with, **None**
-    if no override is set
-  """
-
-  color_override = CONFIG.get('features.color_override', 'none')
-
-  if color_override == 'none':
-    return None
-  else:
-    return color_override
-
-
-def _color_attr():
-  """
-  Initializes color mappings usable by curses. This can only be done after
-  calling curses.initscr().
-  """
-
-  global COLOR_ATTR
-
-  if COLOR_ATTR is None:
-    if not CONFIG['features.colorInterface']:
-      COLOR_ATTR = DEFAULT_COLOR_ATTR
-    elif curses.has_colors():
-      color_attr = dict(DEFAULT_COLOR_ATTR)
-
-      for color_pair, color_name in enumerate(COLOR_LIST):
-        foreground_color = COLOR_LIST[color_name]
-        background_color = -1  # allows for default (possibly transparent) background
-        curses.init_pair(color_pair + 1, foreground_color, background_color)
-        color_attr[color_name] = curses.color_pair(color_pair + 1)
-
-      log.info('setup.color_support_available')
-      COLOR_ATTR = color_attr
-    else:
-      log.info('setup.color_support_unavailable')
-      COLOR_ATTR = DEFAULT_COLOR_ATTR
-
-  return COLOR_ATTR
+from stem.util import system
 
 
 def disable_acs():
@@ -179,26 +46,6 @@ def get_printable(line, keep_newlines = True):
   return line
 
 
-def curses_format(*attributes):
-  """
-  Provides the curses integer code for a series of attributes.
-
-  :param list attributes: curses attributes or color names
-
-  :returns: **int** that can be used with curses
-  """
-
-  format_attr = curses.A_NORMAL
-
-  for attr in attributes:
-    if isinstance(attr, str):
-      format_attr |= get_color(attr)
-    else:
-      format_attr |= attr
-
-  return format_attr
-
-
 def draw_box(panel, top, left, width, height, *attributes):
   """
   Draws a box in the panel with the given bounds.
@@ -212,24 +59,22 @@ def draw_box(panel, top, left, width, height, *attributes):
     attr   - text attributes
   """
 
-  format_attr = curses_format(*attributes)
-
   # draws the top and bottom
 
-  panel.hline(top, left + 1, width - 2, format_attr)
-  panel.hline(top + height - 1, left + 1, width - 2, format_attr)
+  panel.hline(top, left + 1, width - 2, *attributes)
+  panel.hline(top + height - 1, left + 1, width - 2, *attributes)
 
   # draws the left and right sides
 
-  panel.vline(top + 1, left, height - 2, format_attr)
-  panel.vline(top + 1, left + width - 1, height - 2, format_attr)
+  panel.vline(top + 1, left, height - 2, *attributes)
+  panel.vline(top + 1, left + width - 1, height - 2, *attributes)
 
   # draws the corners
 
-  panel.addch(top, left, curses.ACS_ULCORNER, format_attr)
-  panel.addch(top, left + width - 1, curses.ACS_URCORNER, format_attr)
-  panel.addch(top + height - 1, left, curses.ACS_LLCORNER, format_attr)
-  panel.addch(top + height - 1, left + width - 1, curses.ACS_LRCORNER, format_attr)
+  panel.addch(top, left, curses.ACS_ULCORNER, *attributes)
+  panel.addch(top, left + width - 1, curses.ACS_URCORNER, *attributes)
+  panel.addch(top + height - 1, left, curses.ACS_LLCORNER, *attributes)
+  panel.addch(top + height - 1, left + width - 1, curses.ACS_LRCORNER, *attributes)
 
 
 def get_scroll_position(key, position, page_height, content_height, is_cursor = False):



More information about the tor-commits mailing list