[tor-commits] [arm/master] Replacing key integer codes with KeyInput class

atagar at torproject.org atagar at torproject.org
Tue Oct 7 16:53:30 UTC 2014


commit 7847a7521ba3423db2ac070bf8c52f145e7ac9b3
Author: Damian Johnson <atagar at torproject.org>
Date:   Tue Oct 7 09:51:51 2014 -0700

    Replacing key integer codes with KeyInput class
    
    Curses' getch() method returns an integer code for the pressed key. This is
    well and good, but needed both ui_tools helper functions and a lotta checks
    like...
    
      if key in (ord('s'), ord('S')):
    
    Wrapping user input with a KeyInput class that both makes this nicer...
    
      if key.match('s'):
    
    ... and includes the ui_tools helpers.
---
 arm/config_panel.py                 |   22 +++++++------
 arm/connections/conn_panel.py       |   16 +++++-----
 arm/connections/count_popup.py      |    2 +-
 arm/connections/descriptor_popup.py |   10 +++---
 arm/controller.py                   |   25 +++++++++------
 arm/graphing/graph_panel.py         |   54 +++++++++++++++----------------
 arm/header_panel.py                 |    4 +--
 arm/log_panel.py                    |   14 ++++----
 arm/menu/menu.py                    |   17 ++++------
 arm/popups.py                       |   60 +++++++++++++++++------------------
 arm/torrc_panel.py                  |    8 ++---
 arm/util/panel.py                   |   56 ++++++++++++++++++++++++++++++++
 arm/util/ui_tools.py                |   39 ++++-------------------
 13 files changed, 181 insertions(+), 146 deletions(-)

diff --git a/arm/config_panel.py b/arm/config_panel.py
index 637b354..dbae8de 100644
--- a/arm/config_panel.py
+++ b/arm/config_panel.py
@@ -361,7 +361,7 @@ class ConfigPanel(panel.Panel):
 
   def handle_key(self, key):
     with self.vals_lock:
-      if ui_tools.is_scroll_key(key):
+      if key.is_scroll():
         page_height = self.get_preferred_size()[0] - 1
         detail_panel_height = CONFIG["features.config.selectionDetails.height"]
 
@@ -372,7 +372,7 @@ class ConfigPanel(panel.Panel):
 
         if is_changed:
           self.redraw(True)
-      elif ui_tools.is_selection_key(key) and self._get_config_options():
+      elif key.is_selection() and self._get_config_options():
         # Prompts the user to edit the selected configuration value. The
         # interface is locked to prevent updates between setting the value
         # and showing any errors.
@@ -417,12 +417,12 @@ class ConfigPanel(panel.Panel):
               self.redraw(True)
             except Exception as exc:
               popups.show_msg("%s (press any key)" % exc)
-      elif key in (ord('a'), ord('A')):
+      elif key.match('a'):
         self.show_all = not self.show_all
         self.redraw(True)
-      elif key in (ord('s'), ord('S')):
+      elif key.match('s'):
         self.show_sort_dialog()
-      elif key in (ord('v'), ord('V')):
+      elif key.match('v'):
         self.show_write_dialog()
       else:
         return False
@@ -468,9 +468,9 @@ class ConfigPanel(panel.Panel):
           height = new_height
           is_option_line_separate = True
 
-      key, selection = 0, 2
+      selection = 2
 
-      while not ui_tools.is_selection_key(key):
+      while True:
         # if the popup has been resized then recreate it (needed for the
         # proper border height)
 
@@ -525,12 +525,14 @@ class ConfigPanel(panel.Panel):
 
         popup.win.refresh()
 
-        key = arm.controller.get_controller().get_screen().getch()
+        key = arm.controller.get_controller().key_input()
 
-        if key == curses.KEY_LEFT:
+        if key.match('left'):
           selection = max(0, selection - 1)
-        elif key == curses.KEY_RIGHT:
+        elif key.match('right'):
           selection = min(len(selection_options) - 1, selection + 1)
+        elif key.is_selection():
+          break
 
       if selection in (0, 1):
         loaded_torrc, prompt_canceled = tor_config.get_torrc(), False
diff --git a/arm/connections/conn_panel.py b/arm/connections/conn_panel.py
index 57c64f5..76b2807 100644
--- a/arm/connections/conn_panel.py
+++ b/arm/connections/conn_panel.py
@@ -262,7 +262,7 @@ class ConnectionPanel(panel.Panel, threading.Thread):
 
   def handle_key(self, key):
     with self.vals_lock:
-      if ui_tools.is_scroll_key(key):
+      if key.is_scroll():
         page_height = self.get_preferred_size()[0] - 1
 
         if self._show_details:
@@ -272,12 +272,12 @@ class ConnectionPanel(panel.Panel, threading.Thread):
 
         if is_changed:
           self.redraw(True)
-      elif ui_tools.is_selection_key(key):
+      elif key.is_selection():
         self._show_details = not self._show_details
         self.redraw(True)
-      elif key in (ord('s'), ord('S')):
+      elif key.match('s'):
         self.show_sort_dialog()
-      elif key in (ord('u'), ord('U')):
+      elif key.match('u'):
         # provides a menu to pick the connection resolver
 
         title = "Resolver Util:"
@@ -298,7 +298,7 @@ class ConnectionPanel(panel.Panel, threading.Thread):
         if selection != -1:
           selected_option = options[selection] if selection != 0 else None
           conn_resolver.set_custom_resolver(selected_option)
-      elif key in (ord('l'), ord('L')):
+      elif key.match('l'):
         # provides a menu to pick the primary information we list connections by
 
         title = "List By:"
@@ -315,12 +315,12 @@ class ConnectionPanel(panel.Panel, threading.Thread):
 
         if selection != -1:
           self.set_listing_type(options[selection])
-      elif key in (ord('d'), ord('D')):
+      elif key.match('d'):
         # presents popup for raw consensus data
         descriptor_popup.show_descriptor_popup(self)
-      elif key in (ord('c'), ord('C')) and self.is_clients_allowed():
+      elif key.match('c') and self.is_clients_allowed():
         count_popup.showCountDialog(count_popup.CountType.CLIENT_LOCALE, self._client_locale_usage)
-      elif key in (ord('e'), ord('E')) and self.is_exits_allowed():
+      elif key.match('e') and self.is_exits_allowed():
         count_popup.showCountDialog(count_popup.CountType.EXIT_PORT, self._exit_port_usage)
       else:
         return False
diff --git a/arm/connections/count_popup.py b/arm/connections/count_popup.py
index 4decf46..1e22870 100644
--- a/arm/connections/count_popup.py
+++ b/arm/connections/count_popup.py
@@ -106,6 +106,6 @@ def showCountDialog(count_type, counts):
     popup.win.refresh()
 
     curses.cbreak()
-    control.get_screen().getch()
+    control.key_input()
   finally:
     arm.popups.finalize()
diff --git a/arm/connections/descriptor_popup.py b/arm/connections/descriptor_popup.py
index af6ee69..d169309 100644
--- a/arm/connections/descriptor_popup.py
+++ b/arm/connections/descriptor_popup.py
@@ -79,9 +79,9 @@ def show_descriptor_popup(conn_panel):
             draw(popup, fingerprint, display_text, display_color, scroll, show_line_number)
             is_changed = False
 
-          key = control.get_screen().getch()
+          key = control.key_input()
 
-          if ui_tools.is_scroll_key(key):
+          if key.is_scroll():
             # TODO: This is a bit buggy in that scrolling is by display_text
             # lines rather than the displayed lines, causing issues when
             # content wraps. The result is that we can't have a scrollbar and
@@ -94,12 +94,12 @@ def show_descriptor_popup(conn_panel):
 
             if scroll != new_scroll:
               scroll, is_changed = new_scroll, True
-          elif ui_tools.is_selection_key(key) or key in (ord('d'), ord('D')):
+          elif key.is_selection() or key.match('d'):
             is_done = True  # closes popup
-          elif key in (curses.KEY_LEFT, curses.KEY_RIGHT):
+          elif key.match('left', 'right'):
             # navigation - pass on to conn_panel and recreate popup
 
-            conn_panel.handle_key(curses.KEY_UP if key == curses.KEY_LEFT else curses.KEY_DOWN)
+            conn_panel.handle_key(panel.KeyInput(curses.KEY_UP) if key.match('left') else panel.KeyInput(curses.KEY_DOWN))
             break
       finally:
         arm.popups.finalize()
diff --git a/arm/controller.py b/arm/controller.py
index 09f0f83..48034e9 100644
--- a/arm/controller.py
+++ b/arm/controller.py
@@ -273,6 +273,13 @@ class Controller:
 
     return self._screen
 
+  def key_input(self):
+    """
+    Gets keystroke from the user.
+    """
+
+    return panel.KeyInput(self.get_screen().getch())
+
   def get_page_count(self):
     """
     Provides the number of pages the interface has. This may be zero if all
@@ -652,29 +659,29 @@ def start_arm(stdscr):
       key, override_key = override_key, None
     else:
       curses.halfdelay(CONFIG["features.redrawRate"] * 10)
-      key = stdscr.getch()
+      key = panel.KeyInput(stdscr.getch())
 
-    if key == curses.KEY_RIGHT:
+    if key.match('right'):
       control.next_page()
-    elif key == curses.KEY_LEFT:
+    elif key.match('left'):
       control.prev_page()
-    elif key in (ord('p'), ord('P')):
+    elif key.match('p'):
       control.set_paused(not control.is_paused())
-    elif key in (ord('m'), ord('M')):
+    elif key.match('m'):
       arm.menu.menu.show_menu()
-    elif key in (ord('q'), ord('Q')):
+    elif key.match('q'):
       # provides prompt to confirm that arm should exit
 
       if CONFIG["features.confirmQuit"]:
         msg = "Are you sure (q again to confirm)?"
         confirmation_key = arm.popups.show_msg(msg, attr = curses.A_BOLD)
-        quit_confirmed = confirmation_key in (ord('q'), ord('Q'))
+        quit_confirmed = confirmation_key.match('q')
       else:
         quit_confirmed = True
 
       if quit_confirmed:
         control.quit()
-    elif key in (ord('x'), ord('X')):
+    elif key.match('x'):
       # provides prompt to confirm that arm should issue a sighup
 
       msg = "This will reset Tor's internal state. Are you sure (x again to confirm)?"
@@ -685,7 +692,7 @@ def start_arm(stdscr):
           tor_controller().signal(stem.Signal.RELOAD)
         except IOError as exc:
           log.error("Error detected when reloading tor: %s" % exc.strerror)
-    elif key in (ord('h'), ord('H')):
+    elif key.match('h'):
       override_key = arm.popups.show_help_popup()
     elif key == ord('l') - 96:
       # force redraw when ctrl+l is pressed
diff --git a/arm/graphing/graph_panel.py b/arm/graphing/graph_panel.py
index 68328b8..a034e56 100644
--- a/arm/graphing/graph_panel.py
+++ b/arm/graphing/graph_panel.py
@@ -24,7 +24,7 @@ import arm.controller
 
 import stem.control
 
-from arm.util import panel, tor_controller, ui_tools
+from arm.util import panel, tor_controller
 
 from stem.util import conf, enum, str_tools
 
@@ -315,42 +315,40 @@ class GraphPanel(panel.Panel):
 
     control = arm.controller.get_controller()
 
-    panel.CURSES_LOCK.acquire()
+    with panel.CURSES_LOCK:
+      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()
+          key = control.key_input()
 
-    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()
-        key = control.get_screen().getch()
+          if key.match('down'):
+            # don't grow the graph if it's already consuming the whole display
+            # (plus an extra line for the graph/log gap)
 
-        if key == curses.KEY_DOWN:
-          # don't grow the graph if it's already consuming the whole display
-          # (plus an extra line for the graph/log gap)
+            max_height = self.parent.getmaxyx()[0] - self.top
+            current_height = self.get_height()
 
-          max_height = self.parent.getmaxyx()[0] - self.top
-          current_height = self.get_height()
+            if current_height < max_height + 1:
+              self.set_graph_height(self.graph_height + 1)
+          elif key.match('up'):
+            self.set_graph_height(self.graph_height - 1)
+          elif key.is_selection():
+            break
 
-          if current_height < max_height + 1:
-            self.set_graph_height(self.graph_height + 1)
-        elif key == curses.KEY_UP:
-          self.set_graph_height(self.graph_height - 1)
-        elif ui_tools.is_selection_key(key):
-          break
-
-        control.redraw()
-    finally:
-      control.set_msg()
-      panel.CURSES_LOCK.release()
+          control.redraw()
+      finally:
+        control.set_msg()
 
   def handle_key(self, key):
-    if key in (ord('r'), ord('R')):
+    if key.match('r'):
       self.resize_graph()
-    elif key in (ord('b'), ord('B')):
+    elif key.match('b'):
       # uses the next boundary type
       self.bounds = Bounds.next(self.bounds)
       self.redraw(True)
-    elif key in (ord('s'), ord('S')):
+    elif key.match('s'):
       # provides a menu to pick the graphed stats
 
       available_stats = self.stats.keys()
@@ -377,7 +375,7 @@ class GraphPanel(panel.Panel):
         self.set_stats(None)
       elif selection != -1:
         self.set_stats(available_stats[selection - 1])
-    elif key in (ord('i'), ord('I')):
+    elif key.match('i'):
       # provides menu to pick graph panel update interval
 
       options = [label for (label, _) in UPDATE_INTERVALS]
diff --git a/arm/header_panel.py b/arm/header_panel.py
index 5278ca1..7206870 100644
--- a/arm/header_panel.py
+++ b/arm/header_panel.py
@@ -87,9 +87,9 @@ class HeaderPanel(panel.Panel, threading.Thread):
       arm.popups.show_msg('Requesting a new identity', 1)
 
   def handle_key(self, key):
-    if key in (ord('n'), ord('N')):
+    if key.match('n'):
       self.send_newnym()
-    elif key in (ord('r'), ord('R')) and not self._vals.is_connected:
+    elif key.match('r') and not self._vals.is_connected:
       # TODO: This is borked. Not quite sure why but our attempt to call
       # PROTOCOLINFO fails with a socket error, followed by completely freezing
       # arm. This is exposing two bugs...
diff --git a/arm/log_panel.py b/arm/log_panel.py
index e78ecc7..f0e2a23 100644
--- a/arm/log_panel.py
+++ b/arm/log_panel.py
@@ -863,7 +863,7 @@ class LogPanel(panel.Panel, threading.Thread, logging.Handler):
       raise exc
 
   def handle_key(self, key):
-    if ui_tools.is_scroll_key(key):
+    if key.is_scroll():
       page_height = self.get_preferred_size()[0] - 1
       new_scroll = ui_tools.get_scroll_position(key, self.scroll, page_height, self.last_content_height)
 
@@ -872,18 +872,18 @@ class LogPanel(panel.Panel, threading.Thread, logging.Handler):
         self.scroll = new_scroll
         self.redraw(True)
         self.vals_lock.release()
-    elif key in (ord('u'), ord('U')):
+    elif key.match('u'):
       self.vals_lock.acquire()
       self.set_duplicate_visability(not CONFIG["features.log.showDuplicateEntries"])
       self.redraw(True)
       self.vals_lock.release()
-    elif key in (ord('c'), ord('C')):
+    elif key.match('c'):
       msg = "This will clear the log. Are you sure (c again to confirm)?"
       key_press = arm.popups.show_msg(msg, attr = curses.A_BOLD)
 
-      if key_press in (ord('c'), ord('C')):
+      if key_press.match('c'):
         self.clear()
-    elif key in (ord('f'), ord('F')):
+    elif key.match('f'):
       # Provides menu to pick regular expression filters or adding new ones:
       # for syntax see: http://docs.python.org/library/re.html#regular-expression-syntax
 
@@ -912,9 +912,9 @@ class LogPanel(panel.Panel, threading.Thread, logging.Handler):
 
       if len(self.filter_options) > MAX_REGEX_FILTERS:
         del self.filter_options[MAX_REGEX_FILTERS:]
-    elif key in (ord('e'), ord('E')):
+    elif key.match('e'):
       self.show_event_selection_prompt()
-    elif key in (ord('a'), ord('A')):
+    elif key.match('a'):
       self.show_snapshot_prompt()
     else:
       return False
diff --git a/arm/menu/menu.py b/arm/menu/menu.py
index 435725b..e8fe297 100644
--- a/arm/menu/menu.py
+++ b/arm/menu/menu.py
@@ -40,17 +40,17 @@ class MenuCursor:
     is_selection_submenu = isinstance(self._selection, arm.menu.item.Submenu)
     selection_hierarchy = self._selection.get_hierarchy()
 
-    if ui_tools.is_selection_key(key):
+    if key.is_selection():
       if is_selection_submenu:
         if not self._selection.is_empty():
           self._selection = self._selection.get_children()[0]
       else:
         self._is_done = self._selection.select()
-    elif key == curses.KEY_UP:
+    elif key.match('up'):
       self._selection = self._selection.prev()
-    elif key == curses.KEY_DOWN:
+    elif key.match('down'):
       self._selection = self._selection.next()
-    elif key == curses.KEY_LEFT:
+    elif key.match('left'):
       if len(selection_hierarchy) <= 3:
         # shift to the previous main submenu
 
@@ -60,7 +60,7 @@ class MenuCursor:
         # go up a submenu level
 
         self._selection = self._selection.get_parent()
-    elif key == curses.KEY_RIGHT:
+    elif key.match('right'):
       if is_selection_submenu:
         # open submenu (same as making a selection)
 
@@ -71,9 +71,7 @@ class MenuCursor:
 
         next_submenu = selection_hierarchy[1].next()
         self._selection = next_submenu.get_children()[0]
-    elif key in (27, ord('m'), ord('M')):
-      # close menu
-
+    elif key.match('esc', 'm'):
       self._is_done = True
 
 
@@ -127,8 +125,7 @@ def show_menu():
       popup.win.refresh()
 
       curses.cbreak()
-      key = control.get_screen().getch()
-      cursor.handle_key(key)
+      cursor.handle_key(control.key_input())
 
       # redraws the rest of the interface if we're rendering on it again
 
diff --git a/arm/popups.py b/arm/popups.py
index 59cf40a..e852b95 100644
--- a/arm/popups.py
+++ b/arm/popups.py
@@ -91,20 +91,18 @@ def show_msg(msg, max_wait = -1, attr = curses.A_STANDOUT):
     attr    - attributes with which to draw the message
   """
 
-  panel.CURSES_LOCK.acquire()
-  control = arm.controller.get_controller()
-  control.set_msg(msg, attr, True)
+  with panel.CURSES_LOCK:
+    control = arm.controller.get_controller()
+    control.set_msg(msg, attr, True)
 
-  if max_wait == -1:
-    curses.cbreak()
-  else:
-    curses.halfdelay(max_wait * 10)
+    if max_wait == -1:
+      curses.cbreak()
+    else:
+      curses.halfdelay(max_wait * 10)
 
-  key_press = control.get_screen().getch()
-  control.set_msg()
-  panel.CURSES_LOCK.release()
-
-  return key_press
+    key_press = control.key_input()
+    control.set_msg()
+    return key_press
 
 
 def show_help_popup():
@@ -173,13 +171,12 @@ def show_help_popup():
 
     popup.win.refresh()
     curses.cbreak()
-    exit_key = control.get_screen().getch()
+    exit_key = control.key_input()
   finally:
     finalize()
 
-  if not ui_tools.is_selection_key(exit_key) and \
-    not ui_tools.is_scroll_key(exit_key) and \
-    exit_key not in (curses.KEY_LEFT, curses.KEY_RIGHT):
+  if not exit_key.is_selection() and not exit_key.is_scroll() and \
+    not exit_key.match('left', 'right'):
     return exit_key
   else:
     return None
@@ -208,7 +205,7 @@ def show_about_popup():
     popup.win.refresh()
 
     curses.cbreak()
-    control.get_screen().getch()
+    control.key_input()
   finally:
     finalize()
 
@@ -270,17 +267,17 @@ def show_sort_dialog(title, options, old_selection, option_colors):
 
       popup.win.refresh()
 
-      key = arm.controller.get_controller().get_screen().getch()
+      key = arm.controller.get_controller().key_input()
 
-      if key == curses.KEY_LEFT:
+      if key.match('left'):
         cursor_location = max(0, cursor_location - 1)
-      elif key == curses.KEY_RIGHT:
+      elif key.match('right'):
         cursor_location = min(len(selection_options) - 1, cursor_location + 1)
-      elif key == curses.KEY_UP:
+      elif key.match('up'):
         cursor_location = max(0, cursor_location - 4)
-      elif key == curses.KEY_DOWN:
+      elif key.match('down'):
         cursor_location = min(len(selection_options) - 1, cursor_location + 4)
-      elif ui_tools.is_selection_key(key):
+      elif key.is_selection():
         selection = selection_options[cursor_location]
 
         if selection == "Cancel":
@@ -351,7 +348,7 @@ def show_menu(title, options, old_selection):
   if not popup:
     return
 
-  key, selection = 0, old_selection if old_selection != -1 else 0
+  selection = old_selection if old_selection != -1 else 0
 
   try:
     # hides the title of the first panel on the page
@@ -363,7 +360,7 @@ def show_menu(title, options, old_selection):
 
     curses.cbreak()   # wait indefinitely for key presses (no timeout)
 
-    while not ui_tools.is_selection_key(key):
+    while True:
       popup.win.erase()
       popup.win.box()
       popup.addstr(0, 0, title, curses.A_STANDOUT)
@@ -377,14 +374,17 @@ def show_menu(title, options, old_selection):
 
       popup.win.refresh()
 
-      key = control.get_screen().getch()
+      key = control.key_input()
 
-      if key == curses.KEY_UP:
+      if key.match('up'):
         selection = max(0, selection - 1)
-      elif key == curses.KEY_DOWN:
+      elif key.match('down'):
         selection = min(len(options) - 1, selection + 1)
-      elif key == 27:
-        selection, key = -1, curses.KEY_ENTER  # esc - cancel
+      elif key.is_selection():
+        break
+      elif key.match('esc'):
+        selection = -1
+        break
   finally:
     top_panel.set_title_visible(True)
     finalize()
diff --git a/arm/torrc_panel.py b/arm/torrc_panel.py
index 2a3fb23..b7fa71a 100644
--- a/arm/torrc_panel.py
+++ b/arm/torrc_panel.py
@@ -123,18 +123,18 @@ class TorrcPanel(panel.Panel):
 
   def handle_key(self, key):
     with self.vals_lock:
-      if ui_tools.is_scroll_key(key):
+      if key.is_scroll():
         page_height = self.get_preferred_size()[0] - 1
         new_scroll = ui_tools.get_scroll_position(key, self.scroll, page_height, self._last_content_height)
 
         if self.scroll != new_scroll:
           self.scroll = new_scroll
           self.redraw(True)
-      elif key in (ord('n'), ord('N')):
+      elif key.match('n'):
         self.set_line_number_visible(not self.show_line_num)
-      elif key in (ord('s'), ord('S')):
+      elif key.match('s'):
         self.set_comments_visible(self.strip_comments)
-      elif key in (ord('r'), ord('R')):
+      elif key.match('r'):
         self.reload_torrc()
       else:
         return False
diff --git a/arm/util/panel.py b/arm/util/panel.py
index d68268a..fb6ae0e 100644
--- a/arm/util/panel.py
+++ b/arm/util/panel.py
@@ -18,6 +18,20 @@ from stem.util import log
 
 CURSES_LOCK = RLock()
 
+SCROLL_KEYS = (curses.KEY_UP, curses.KEY_DOWN, curses.KEY_PPAGE, curses.KEY_NPAGE, curses.KEY_HOME, curses.KEY_END)
+
+SPECIAL_KEYS = {
+  'up': curses.KEY_UP,
+  'down': curses.KEY_DOWN,
+  'left': curses.KEY_LEFT,
+  'right': curses.KEY_RIGHT,
+  'home': curses.KEY_HOME,
+  'end': curses.KEY_END,
+  'page_up': curses.KEY_PPAGE,
+  'page_down': curses.KEY_NPAGE,
+  'esc': 27,
+}
+
 
 # tags used by addfstr - this maps to functor/argument combinations since the
 # actual values (in the case of color attributes) might not yet be initialized
@@ -806,3 +820,45 @@ class Panel():
       log.debug("recreating panel '%s' with the dimensions of %i/%i" % (self.get_name(), new_height, new_width))
 
     return recreate
+
+
+class KeyInput(object):
+  """
+  Keyboard input by the user.
+  """
+
+  def __init__(self, key):
+    self._key = key  # pressed key as an integer
+
+  def match(self, *keys):
+    """
+    Checks if we have a case insensitive match with the given key. Beside
+    characters, this also recognizes: up, down, left, right, home, end,
+    page_up, page_down, and esc.
+    """
+
+    for key in keys:
+      if key in SPECIAL_KEYS:
+        if self._key == SPECIAL_KEYS[key]:
+          return True
+      elif len(key) == 1:
+        if self._key in (ord(key.lower()), ord(key.upper())):
+          return True
+      else:
+        raise ValueError("%s wasn't among our recognized key codes" % key)
+
+    return False
+
+  def is_scroll(self):
+    """
+    True if the key is used for scrolling, false otherwise.
+    """
+
+    return self._key in SCROLL_KEYS
+
+  def is_selection(self):
+    """
+    True if the key matches the enter or space keys.
+    """
+
+    return self._key in (curses.KEY_ENTER, 10, ord(' '))
diff --git a/arm/util/ui_tools.py b/arm/util/ui_tools.py
index d2a6f43..a1064ee 100644
--- a/arm/util/ui_tools.py
+++ b/arm/util/ui_tools.py
@@ -24,8 +24,6 @@ COLOR_LIST = {
 DEFAULT_COLOR_ATTR = dict([(color, 0) for color in COLOR_LIST])
 COLOR_ATTR = None
 
-SCROLL_KEYS = (curses.KEY_UP, curses.KEY_DOWN, curses.KEY_PPAGE, curses.KEY_NPAGE, curses.KEY_HOME, curses.KEY_END)
-
 
 def conf_handler(key, value):
   if key == 'features.color_override':
@@ -211,29 +209,6 @@ def draw_box(panel, top, left, width, height, attr=curses.A_NORMAL):
   panel.addch(top + height - 1, left, curses.ACS_LLCORNER, attr)
 
 
-def is_selection_key(key):
-  """
-  Returns true if the keycode matches the enter or space keys.
-
-  Argument:
-    key - keycode to be checked
-  """
-
-  return key in (curses.KEY_ENTER, 10, ord(' '))
-
-
-def is_scroll_key(key):
-  """
-  Returns true if the keycode is recognized by the get_scroll_position function
-  for scrolling.
-
-  Argument:
-    key - keycode to be checked
-  """
-
-  return key in SCROLL_KEYS
-
-
 def get_scroll_position(key, position, page_height, content_height, is_cursor = False):
   """
   Parses navigation keys, providing the new scroll possition the panel should
@@ -254,20 +229,20 @@ def get_scroll_position(key, position, page_height, content_height, is_cursor =
     is_cursor      - tracks a cursor position rather than scroll if true
   """
 
-  if is_scroll_key(key):
+  if key.is_scroll():
     shift = 0
 
-    if key == curses.KEY_UP:
+    if key.match('up'):
       shift = -1
-    elif key == curses.KEY_DOWN:
+    elif key.match('down'):
       shift = 1
-    elif key == curses.KEY_PPAGE:
+    elif key.match('page_up'):
       shift = -page_height + 1 if is_cursor else -page_height
-    elif key == curses.KEY_NPAGE:
+    elif key.match('page_down'):
       shift = page_height - 1 if is_cursor else page_height
-    elif key == curses.KEY_HOME:
+    elif key.match('home'):
       shift = -content_height
-    elif key == curses.KEY_END:
+    elif key.match('end'):
       shift = content_height
 
     # returns the shift, restricted to valid bounds



More information about the tor-commits mailing list