[tor-commits] [nyx/master] Consolidate menu into a single file

atagar at torproject.org atagar at torproject.org
Tue Apr 5 17:06:53 UTC 2016


commit 1dd19b7f9f34d894178f7d603b257a623a78edf9
Author: Damian Johnson <atagar at torproject.org>
Date:   Tue Apr 5 08:29:50 2016 -0700

    Consolidate menu into a single file
    
    All told the menu isn't terribly big, and it's about to get even smaller with
    its rewrite.
---
 nyx/__init__.py      |   1 +
 nyx/controller.py    |   4 +-
 nyx/menu.py          | 652 +++++++++++++++++++++++++++++++++++++++++++++++++++
 nyx/menu/__init__.py |  12 -
 nyx/menu/actions.py  | 306 ------------------------
 nyx/menu/item.py     | 210 -----------------
 nyx/menu/menu.py     | 157 -------------
 setup.py             |   2 +-
 8 files changed, 656 insertions(+), 688 deletions(-)

diff --git a/nyx/__init__.py b/nyx/__init__.py
index 33656f1..fbd765b 100644
--- a/nyx/__init__.py
+++ b/nyx/__init__.py
@@ -27,6 +27,7 @@ __all__ = [
   'controller',
   'curses',
   'log',
+  'menu',
   'panel',
   'popups',
   'starter',
diff --git a/nyx/controller.py b/nyx/controller.py
index e1cc1f8..c4ccc15 100644
--- a/nyx/controller.py
+++ b/nyx/controller.py
@@ -10,7 +10,7 @@ import time
 import threading
 
 import nyx.curses
-import nyx.menu.menu
+import nyx.menu
 import nyx.popups
 
 import nyx.panel
@@ -335,7 +335,7 @@ def start_nyx():
     elif key.match('p'):
       control.set_paused(not control.is_paused())
     elif key.match('m'):
-      nyx.menu.menu.show_menu()
+      nyx.menu.show_menu()
     elif key.match('q'):
       # provides prompt to confirm that nyx should exit
 
diff --git a/nyx/menu.py b/nyx/menu.py
new file mode 100644
index 0000000..1b6b6bf
--- /dev/null
+++ b/nyx/menu.py
@@ -0,0 +1,652 @@
+# Copyright 2016, Damian Johnson and The Tor Project
+# See LICENSE for licensing information
+
+"""
+Display logic for presenting the menu.
+"""
+
+import functools
+
+import nyx.controller
+import nyx.curses
+import nyx.popups
+import nyx.panel.graph
+import nyx.controller
+import nyx.tracker
+
+import stem
+import stem.util.connection
+
+from nyx import tor_controller
+from nyx.curses import RED, WHITE, NORMAL, BOLD, UNDERLINE
+from stem.util import conf, str_tools
+
+CONFIG = conf.config_dict('nyx', {
+  'features.log.showDuplicateEntries': False,
+})
+
+
+def make_menu():
+  """
+  Constructs the base menu and all of its contents.
+  """
+
+  base_menu = Submenu('')
+  base_menu.add(make_actions_menu())
+  base_menu.add(make_view_menu())
+
+  control = nyx.controller.get_controller()
+
+  for page_panel in control.get_display_panels():
+    if page_panel.get_name() == 'graph':
+      base_menu.add(make_graph_menu(page_panel))
+    elif page_panel.get_name() == 'log':
+      base_menu.add(make_log_menu(page_panel))
+    elif page_panel.get_name() == 'connections':
+      base_menu.add(make_connections_menu(page_panel))
+    elif page_panel.get_name() == 'configuration':
+      base_menu.add(make_configuration_menu(page_panel))
+    elif page_panel.get_name() == 'torrc':
+      base_menu.add(make_torrc_menu(page_panel))
+
+  base_menu.add(make_help_menu())
+
+  return base_menu
+
+
+def make_actions_menu():
+  """
+  Submenu consisting of...
+    Close Menu
+    New Identity
+    Reset Tor
+    Pause / Unpause
+    Exit
+  """
+
+  control = nyx.controller.get_controller()
+  controller = tor_controller()
+  header_panel = control.header_panel()
+  actions_menu = Submenu('Actions')
+  actions_menu.add(MenuItem('Close Menu', None))
+  actions_menu.add(MenuItem('New Identity', header_panel.send_newnym))
+  actions_menu.add(MenuItem('Reset Tor', functools.partial(controller.signal, stem.Signal.RELOAD)))
+
+  if control.is_paused():
+    label, arg = 'Unpause', False
+  else:
+    label, arg = 'Pause', True
+
+  actions_menu.add(MenuItem(label, functools.partial(control.set_paused, arg)))
+  actions_menu.add(MenuItem('Exit', control.quit))
+
+  return actions_menu
+
+
+def make_view_menu():
+  """
+  Submenu consisting of...
+    [X] <Page 1>
+    [ ] <Page 2>
+    [ ] etc...
+        Color (Submenu)
+  """
+
+  view_menu = Submenu('View')
+  control = nyx.controller.get_controller()
+
+  if control.get_page_count() > 0:
+    page_group = SelectionGroup(control.set_page, control.get_page())
+
+    for i in range(control.get_page_count()):
+      page_panels = control.get_display_panels(page_number = i)
+      label = ' / '.join([str_tools._to_camel_case(panel.get_name()) for panel in page_panels])
+
+      view_menu.add(SelectionMenuItem(label, page_group, i))
+
+  if nyx.curses.is_color_supported():
+    color_menu = Submenu('Color')
+    color_group = SelectionGroup(nyx.curses.set_color_override, nyx.curses.get_color_override())
+
+    color_menu.add(SelectionMenuItem('All', color_group, None))
+
+    for color in nyx.curses.Color:
+      color_menu.add(SelectionMenuItem(str_tools._to_camel_case(color), color_group, color))
+
+    view_menu.add(color_menu)
+
+  return view_menu
+
+
+def make_help_menu():
+  """
+  Submenu consisting of...
+    Hotkeys
+    About
+  """
+
+  help_menu = Submenu('Help')
+  help_menu.add(MenuItem('Hotkeys', nyx.popups.show_help))
+  help_menu.add(MenuItem('About', nyx.popups.show_about))
+  return help_menu
+
+
+def make_graph_menu(graph_panel):
+  """
+  Submenu for the graph panel, consisting of...
+    [X] <Stat 1>
+    [ ] <Stat 2>
+    [ ] <Stat 2>
+        Resize...
+        Interval (Submenu)
+        Bounds (Submenu)
+
+  Arguments:
+    graph_panel - instance of the graph panel
+  """
+
+  graph_menu = Submenu('Graph')
+
+  # stats options
+
+  stat_group = SelectionGroup(functools.partial(setattr, graph_panel, 'displayed_stat'), graph_panel.displayed_stat)
+  available_stats = graph_panel.stat_options()
+  available_stats.sort()
+
+  for stat_key in ['None'] + available_stats:
+    label = str_tools._to_camel_case(stat_key, divider = ' ')
+    stat_key = None if stat_key == 'None' else stat_key
+    graph_menu.add(SelectionMenuItem(label, stat_group, stat_key))
+
+  # resizing option
+
+  graph_menu.add(MenuItem('Resize...', graph_panel.resize_graph))
+
+  # interval submenu
+
+  interval_menu = Submenu('Interval')
+  interval_group = SelectionGroup(functools.partial(setattr, graph_panel, 'update_interval'), graph_panel.update_interval)
+
+  for interval in nyx.panel.graph.Interval:
+    interval_menu.add(SelectionMenuItem(interval, interval_group, interval))
+
+  graph_menu.add(interval_menu)
+
+  # bounds submenu
+
+  bounds_menu = Submenu('Bounds')
+  bounds_group = SelectionGroup(functools.partial(setattr, graph_panel, 'bounds_type'), graph_panel.bounds_type)
+
+  for bounds_type in nyx.panel.graph.Bounds:
+    bounds_menu.add(SelectionMenuItem(bounds_type, bounds_group, bounds_type))
+
+  graph_menu.add(bounds_menu)
+
+  return graph_menu
+
+
+def make_log_menu(log_panel):
+  """
+  Submenu for the log panel, consisting of...
+    Events...
+    Snapshot...
+    Clear
+    Show / Hide Duplicates
+    Filter (Submenu)
+
+  Arguments:
+    log_panel - instance of the log panel
+  """
+
+  log_menu = Submenu('Log')
+
+  log_menu.add(MenuItem('Events...', log_panel.show_event_selection_prompt))
+  log_menu.add(MenuItem('Snapshot...', log_panel.show_snapshot_prompt))
+  log_menu.add(MenuItem('Clear', log_panel.clear))
+
+  if CONFIG['features.log.showDuplicateEntries']:
+    label, arg = 'Hide', False
+  else:
+    label, arg = 'Show', True
+
+  log_menu.add(MenuItem('%s Duplicates' % label, functools.partial(log_panel.set_duplicate_visability, arg)))
+
+  # filter submenu
+
+  log_filter = log_panel.get_filter()
+
+  filter_menu = Submenu('Filter')
+  filter_group = SelectionGroup(log_filter.select, log_filter.selection())
+
+  filter_menu.add(SelectionMenuItem('None', filter_group, None))
+
+  for option in log_filter.latest_selections():
+    filter_menu.add(SelectionMenuItem(option, filter_group, option))
+
+  filter_menu.add(MenuItem('New...', log_panel.show_filter_prompt))
+  log_menu.add(filter_menu)
+
+  return log_menu
+
+
+def make_connections_menu(conn_panel):
+  """
+  Submenu for the connections panel, consisting of...
+        Sorting...
+        Resolver (Submenu)
+
+  Arguments:
+    conn_panel - instance of the connections panel
+  """
+
+  connections_menu = Submenu('Connections')
+
+  # sorting option
+
+  connections_menu.add(MenuItem('Sorting...', conn_panel.show_sort_dialog))
+
+  # resolver submenu
+
+  conn_resolver = nyx.tracker.get_connection_tracker()
+  resolver_menu = Submenu('Resolver')
+  resolver_group = SelectionGroup(conn_resolver.set_custom_resolver, conn_resolver.get_custom_resolver())
+
+  resolver_menu.add(SelectionMenuItem('auto', resolver_group, None))
+
+  for option in stem.util.connection.Resolver:
+    resolver_menu.add(SelectionMenuItem(option, resolver_group, option))
+
+  connections_menu.add(resolver_menu)
+
+  return connections_menu
+
+
+def make_configuration_menu(config_panel):
+  """
+  Submenu for the configuration panel, consisting of...
+    Save Config...
+    Sorting...
+    Filter / Unfilter Options
+
+  Arguments:
+    config_panel - instance of the configuration panel
+  """
+
+  config_menu = Submenu('Configuration')
+  config_menu.add(MenuItem('Save Config...', config_panel.show_write_dialog))
+  config_menu.add(MenuItem('Sorting...', config_panel.show_sort_dialog))
+  return config_menu
+
+
+def make_torrc_menu(torrc_panel):
+  """
+  Submenu for the torrc panel, consisting of...
+    Reload
+    Show / Hide Comments
+    Show / Hide Line Numbers
+
+  Arguments:
+    torrc_panel - instance of the torrc panel
+  """
+
+  torrc_menu = Submenu('Torrc')
+
+  if torrc_panel._show_comments:
+    label, arg = 'Hide', False
+  else:
+    label, arg = 'Show', True
+
+  torrc_menu.add(MenuItem('%s Comments' % label, functools.partial(torrc_panel.set_comments_visible, arg)))
+
+  if torrc_panel._show_line_numbers:
+    label, arg = 'Hide', False
+  else:
+    label, arg = 'Show', True
+  torrc_menu.add(MenuItem('%s Line Numbers' % label, functools.partial(torrc_panel.set_line_number_visible, arg)))
+
+  return torrc_menu
+
+
+class MenuCursor:
+  """
+  Tracks selection and key handling in the menu.
+  """
+
+  def __init__(self, initial_selection):
+    self._selection = initial_selection
+    self._is_done = False
+
+  def is_done(self):
+    """
+    Provides true if a selection has indicated that we should close the menu.
+    False otherwise.
+    """
+
+    return self._is_done
+
+  def get_selection(self):
+    """
+    Provides the currently selected menu item.
+    """
+
+    return self._selection
+
+  def handle_key(self, key):
+    is_selection_submenu = isinstance(self._selection, Submenu)
+    selection_hierarchy = self._selection.get_hierarchy()
+
+    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.match('up'):
+      self._selection = self._selection.prev()
+    elif key.match('down'):
+      self._selection = self._selection.next()
+    elif key.match('left'):
+      if len(selection_hierarchy) <= 3:
+        # shift to the previous main submenu
+
+        prev_submenu = selection_hierarchy[1].prev()
+        self._selection = prev_submenu.get_children()[0]
+      else:
+        # go up a submenu level
+
+        self._selection = self._selection.get_parent()
+    elif key.match('right'):
+      if is_selection_submenu:
+        # open submenu (same as making a selection)
+
+        if not self._selection.is_empty():
+          self._selection = self._selection.get_children()[0]
+      else:
+        # shift to the next main submenu
+
+        next_submenu = selection_hierarchy[1].next()
+        self._selection = next_submenu.get_children()[0]
+    elif key.match('esc', 'm'):
+      self._is_done = True
+
+
+def show_menu():
+  selection_left = [0]
+
+  def _render(subwindow):
+    x = 0
+
+    for top_level_item in menu.get_children():
+      if top_level_item == selection_hierarchy[1]:
+        selection_left[0] = x
+        attr = UNDERLINE
+      else:
+        attr = NORMAL
+
+      x = subwindow.addstr(x, 0, ' %s ' % top_level_item.get_label()[1], BOLD, attr)
+      subwindow.vline(x, 0, 1)
+      x += 1
+
+  with nyx.curses.CURSES_LOCK:
+    # generates the menu and uses the initial selection of the first item in
+    # the file menu
+
+    menu = make_menu()
+    cursor = MenuCursor(menu.get_children()[0].get_children()[0])
+
+    while not cursor.is_done():
+      selection_hierarchy = cursor.get_selection().get_hierarchy()
+
+      # provide a message saying how to close the menu
+
+      nyx.controller.show_message('Press m or esc to close the menu.', BOLD)
+      nyx.curses.draw(_render, height = 1, background = RED)
+      _draw_submenu(cursor, 1, 1, selection_left[0])
+      cursor.handle_key(nyx.curses.key_input())
+
+      # redraws the rest of the interface if we're rendering on it again
+
+      if not cursor.is_done():
+        nyx.controller.get_controller().redraw()
+
+  nyx.controller.show_message()
+
+
+def _draw_submenu(cursor, level, top, left):
+  selection_hierarchy = cursor.get_selection().get_hierarchy()
+
+  # checks if there's nothing to display
+
+  if len(selection_hierarchy) < level + 2:
+    return
+
+  # fetches the submenu and selection we're displaying
+
+  submenu = selection_hierarchy[level]
+  selection = selection_hierarchy[level + 1]
+
+  # gets the size of the prefix, middle, and suffix columns
+
+  all_label_sets = [entry.get_label() for entry in submenu.get_children()]
+  prefix_col_size = max([len(entry[0]) for entry in all_label_sets])
+  middle_col_size = max([len(entry[1]) for entry in all_label_sets])
+  suffix_col_size = max([len(entry[2]) for entry in all_label_sets])
+
+  # formatted string so we can display aligned menu entries
+
+  label_format = ' %%-%is%%-%is%%-%is ' % (prefix_col_size, middle_col_size, suffix_col_size)
+  menu_width = len(label_format % ('', '', ''))
+  selection_top = submenu.get_children().index(selection) if selection in submenu.get_children() else 0
+
+  def _render(subwindow):
+    for y, menu_item in enumerate(submenu.get_children()):
+      if menu_item == selection:
+        subwindow.addstr(0, y, label_format % menu_item.get_label(), WHITE, BOLD)
+      else:
+        subwindow.addstr(0, y, label_format % menu_item.get_label())
+
+  with nyx.curses.CURSES_LOCK:
+    nyx.curses.draw(_render, top = top, left = left, width = menu_width, height = len(submenu.get_children()), background = RED)
+    _draw_submenu(cursor, level + 1, top + selection_top, left + menu_width)
+
+
+class MenuItem():
+  """
+  Option in a drop-down menu.
+  """
+
+  def __init__(self, label, callback):
+    self._label = label
+    self._callback = callback
+    self._parent = None
+
+  def get_label(self):
+    """
+    Provides a tuple of three strings representing the prefix, label, and
+    suffix for this item.
+    """
+
+    return ('', self._label, '')
+
+  def get_parent(self):
+    """
+    Provides the Submenu we're contained within.
+    """
+
+    return self._parent
+
+  def get_hierarchy(self):
+    """
+    Provides a list with all of our parents, up to the root.
+    """
+
+    my_hierarchy = [self]
+    while my_hierarchy[-1].get_parent():
+      my_hierarchy.append(my_hierarchy[-1].get_parent())
+
+    my_hierarchy.reverse()
+    return my_hierarchy
+
+  def get_root(self):
+    """
+    Provides the base submenu we belong to.
+    """
+
+    if self._parent:
+      return self._parent.get_root()
+    else:
+      return self
+
+  def select(self):
+    """
+    Performs the callback for the menu item, returning true if we should close
+    the menu and false otherwise.
+    """
+
+    if self._callback:
+      control = nyx.controller.get_controller()
+      control.redraw()
+      self._callback()
+
+    return True
+
+  def next(self):
+    """
+    Provides the next option for the submenu we're in, raising a ValueError
+    if we don't have a parent.
+    """
+
+    return self._get_sibling(1)
+
+  def prev(self):
+    """
+    Provides the previous option for the submenu we're in, raising a ValueError
+    if we don't have a parent.
+    """
+
+    return self._get_sibling(-1)
+
+  def _get_sibling(self, offset):
+    """
+    Provides our sibling with a given index offset from us, raising a
+    ValueError if we don't have a parent.
+
+    Arguments:
+      offset - index offset for the sibling to be returned
+    """
+
+    if self._parent:
+      my_siblings = self._parent.get_children()
+
+      try:
+        my_index = my_siblings.index(self)
+        return my_siblings[(my_index + offset) % len(my_siblings)]
+      except ValueError:
+        # We expect a bidirectional references between submenus and their
+        # children. If we don't have this then our menu's screwed up.
+
+        msg = "The '%s' submenu doesn't contain '%s' (children: '%s')" % (self, self._parent, "', '".join(my_siblings))
+        raise ValueError(msg)
+    else:
+      raise ValueError("Menu option '%s' doesn't have a parent" % self)
+
+  def __str__(self):
+    return self._label
+
+
+class Submenu(MenuItem):
+  """
+  Menu item that lists other menu options.
+  """
+
+  def __init__(self, label):
+    MenuItem.__init__(self, label, None)
+    self._children = []
+
+  def get_label(self):
+    """
+    Provides our label with a '>' suffix to indicate that we have suboptions.
+    """
+
+    my_label = MenuItem.get_label(self)[1]
+    return ('', my_label, ' >')
+
+  def add(self, menu_item):
+    """
+    Adds the given menu item to our listing. This raises a ValueError if the
+    item already has a parent.
+
+    Arguments:
+      menu_item - menu option to be added
+    """
+
+    if menu_item.get_parent():
+      raise ValueError("Menu option '%s' already has a parent" % menu_item)
+    else:
+      menu_item._parent = self
+      self._children.append(menu_item)
+
+  def get_children(self):
+    """
+    Provides the menu and submenus we contain.
+    """
+
+    return list(self._children)
+
+  def is_empty(self):
+    """
+    True if we have no children, false otherwise.
+    """
+
+    return not bool(self._children)
+
+  def select(self):
+    return False
+
+
+class SelectionGroup():
+  """
+  Radio button groups that SelectionMenuItems can belong to.
+  """
+
+  def __init__(self, action, selected_arg):
+    self.action = action
+    self.selected_arg = selected_arg
+
+
+class SelectionMenuItem(MenuItem):
+  """
+  Menu item with an associated group which determines the selection. This is
+  for the common single argument getter/setter pattern.
+  """
+
+  def __init__(self, label, group, arg):
+    MenuItem.__init__(self, label, None)
+    self._group = group
+    self._arg = arg
+
+  def is_selected(self):
+    """
+    True if we're the selected item, false otherwise.
+    """
+
+    return self._arg == self._group.selected_arg
+
+  def get_label(self):
+    """
+    Provides our label with a '[X]' prefix if selected and '[ ]' if not.
+    """
+
+    my_label = MenuItem.get_label(self)[1]
+    my_prefix = '[X] ' if self.is_selected() else '[ ] '
+    return (my_prefix, my_label, '')
+
+  def select(self):
+    """
+    Performs the group's setter action with our argument.
+    """
+
+    if not self.is_selected():
+      self._group.action(self._arg)
+
+    return True
diff --git a/nyx/menu/__init__.py b/nyx/menu/__init__.py
deleted file mode 100644
index c195054..0000000
--- a/nyx/menu/__init__.py
+++ /dev/null
@@ -1,12 +0,0 @@
-# Copyright 2016, Damian Johnson and The Tor Project
-# See LICENSE for licensing information
-
-"""
-Resources for displaying the menu.
-"""
-
-__all__ = [
-  'actions',
-  'item',
-  'menu',
-]
diff --git a/nyx/menu/actions.py b/nyx/menu/actions.py
deleted file mode 100644
index 7f25504..0000000
--- a/nyx/menu/actions.py
+++ /dev/null
@@ -1,306 +0,0 @@
-# Copyright 2016, Damian Johnson and The Tor Project
-# See LICENSE for licensing information
-
-"""
-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.tracker
-
-import stem
-import stem.util.connection
-
-from nyx import tor_controller
-from stem.util import conf, str_tools
-
-CONFIG = conf.config_dict('nyx', {
-  'features.log.showDuplicateEntries': False,
-})
-
-
-def make_menu():
-  """
-  Constructs the base menu and all of its contents.
-  """
-
-  base_menu = nyx.menu.item.Submenu('')
-  base_menu.add(make_actions_menu())
-  base_menu.add(make_view_menu())
-
-  control = nyx.controller.get_controller()
-
-  for page_panel in control.get_display_panels():
-    if page_panel.get_name() == 'graph':
-      base_menu.add(make_graph_menu(page_panel))
-    elif page_panel.get_name() == 'log':
-      base_menu.add(make_log_menu(page_panel))
-    elif page_panel.get_name() == 'connections':
-      base_menu.add(make_connections_menu(page_panel))
-    elif page_panel.get_name() == 'configuration':
-      base_menu.add(make_configuration_menu(page_panel))
-    elif page_panel.get_name() == 'torrc':
-      base_menu.add(make_torrc_menu(page_panel))
-
-  base_menu.add(make_help_menu())
-
-  return base_menu
-
-
-def make_actions_menu():
-  """
-  Submenu consisting of...
-    Close Menu
-    New Identity
-    Reset Tor
-    Pause / Unpause
-    Exit
-  """
-
-  control = nyx.controller.get_controller()
-  controller = tor_controller()
-  header_panel = control.header_panel()
-  actions_menu = nyx.menu.item.Submenu('Actions')
-  actions_menu.add(nyx.menu.item.MenuItem('Close Menu', None))
-  actions_menu.add(nyx.menu.item.MenuItem('New Identity', header_panel.send_newnym))
-  actions_menu.add(nyx.menu.item.MenuItem('Reset Tor', functools.partial(controller.signal, stem.Signal.RELOAD)))
-
-  if control.is_paused():
-    label, arg = 'Unpause', False
-  else:
-    label, arg = 'Pause', True
-
-  actions_menu.add(nyx.menu.item.MenuItem(label, functools.partial(control.set_paused, arg)))
-  actions_menu.add(nyx.menu.item.MenuItem('Exit', control.quit))
-
-  return actions_menu
-
-
-def make_view_menu():
-  """
-  Submenu consisting of...
-    [X] <Page 1>
-    [ ] <Page 2>
-    [ ] etc...
-        Color (Submenu)
-  """
-
-  view_menu = nyx.menu.item.Submenu('View')
-  control = nyx.controller.get_controller()
-
-  if control.get_page_count() > 0:
-    page_group = nyx.menu.item.SelectionGroup(control.set_page, control.get_page())
-
-    for i in range(control.get_page_count()):
-      page_panels = control.get_display_panels(page_number = i)
-      label = ' / '.join([str_tools._to_camel_case(panel.get_name()) for panel in page_panels])
-
-      view_menu.add(nyx.menu.item.SelectionMenuItem(label, page_group, i))
-
-  if nyx.curses.is_color_supported():
-    color_menu = nyx.menu.item.Submenu('Color')
-    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 nyx.curses.Color:
-      color_menu.add(nyx.menu.item.SelectionMenuItem(str_tools._to_camel_case(color), color_group, color))
-
-    view_menu.add(color_menu)
-
-  return view_menu
-
-
-def make_help_menu():
-  """
-  Submenu consisting of...
-    Hotkeys
-    About
-  """
-
-  help_menu = nyx.menu.item.Submenu('Help')
-  help_menu.add(nyx.menu.item.MenuItem('Hotkeys', nyx.popups.show_help))
-  help_menu.add(nyx.menu.item.MenuItem('About', nyx.popups.show_about))
-  return help_menu
-
-
-def make_graph_menu(graph_panel):
-  """
-  Submenu for the graph panel, consisting of...
-    [X] <Stat 1>
-    [ ] <Stat 2>
-    [ ] <Stat 2>
-        Resize...
-        Interval (Submenu)
-        Bounds (Submenu)
-
-  Arguments:
-    graph_panel - instance of the graph panel
-  """
-
-  graph_menu = nyx.menu.item.Submenu('Graph')
-
-  # stats options
-
-  stat_group = nyx.menu.item.SelectionGroup(functools.partial(setattr, graph_panel, 'displayed_stat'), graph_panel.displayed_stat)
-  available_stats = graph_panel.stat_options()
-  available_stats.sort()
-
-  for stat_key in ['None'] + available_stats:
-    label = str_tools._to_camel_case(stat_key, divider = ' ')
-    stat_key = None if stat_key == 'None' else stat_key
-    graph_menu.add(nyx.menu.item.SelectionMenuItem(label, stat_group, stat_key))
-
-  # resizing option
-
-  graph_menu.add(nyx.menu.item.MenuItem('Resize...', graph_panel.resize_graph))
-
-  # interval submenu
-
-  interval_menu = nyx.menu.item.Submenu('Interval')
-  interval_group = nyx.menu.item.SelectionGroup(functools.partial(setattr, graph_panel, 'update_interval'), graph_panel.update_interval)
-
-  for interval in nyx.panel.graph.Interval:
-    interval_menu.add(nyx.menu.item.SelectionMenuItem(interval, interval_group, interval))
-
-  graph_menu.add(interval_menu)
-
-  # bounds submenu
-
-  bounds_menu = nyx.menu.item.Submenu('Bounds')
-  bounds_group = nyx.menu.item.SelectionGroup(functools.partial(setattr, graph_panel, 'bounds_type'), graph_panel.bounds_type)
-
-  for bounds_type in nyx.panel.graph.Bounds:
-    bounds_menu.add(nyx.menu.item.SelectionMenuItem(bounds_type, bounds_group, bounds_type))
-
-  graph_menu.add(bounds_menu)
-
-  return graph_menu
-
-
-def make_log_menu(log_panel):
-  """
-  Submenu for the log panel, consisting of...
-    Events...
-    Snapshot...
-    Clear
-    Show / Hide Duplicates
-    Filter (Submenu)
-
-  Arguments:
-    log_panel - instance of the log panel
-  """
-
-  log_menu = nyx.menu.item.Submenu('Log')
-
-  log_menu.add(nyx.menu.item.MenuItem('Events...', log_panel.show_event_selection_prompt))
-  log_menu.add(nyx.menu.item.MenuItem('Snapshot...', log_panel.show_snapshot_prompt))
-  log_menu.add(nyx.menu.item.MenuItem('Clear', log_panel.clear))
-
-  if CONFIG['features.log.showDuplicateEntries']:
-    label, arg = 'Hide', False
-  else:
-    label, arg = 'Show', True
-
-  log_menu.add(nyx.menu.item.MenuItem('%s Duplicates' % label, functools.partial(log_panel.set_duplicate_visability, arg)))
-
-  # filter submenu
-
-  log_filter = log_panel.get_filter()
-
-  filter_menu = nyx.menu.item.Submenu('Filter')
-  filter_group = nyx.menu.item.SelectionGroup(log_filter.select, log_filter.selection())
-
-  filter_menu.add(nyx.menu.item.SelectionMenuItem('None', filter_group, None))
-
-  for option in log_filter.latest_selections():
-    filter_menu.add(nyx.menu.item.SelectionMenuItem(option, filter_group, option))
-
-  filter_menu.add(nyx.menu.item.MenuItem('New...', log_panel.show_filter_prompt))
-  log_menu.add(filter_menu)
-
-  return log_menu
-
-
-def make_connections_menu(conn_panel):
-  """
-  Submenu for the connections panel, consisting of...
-        Sorting...
-        Resolver (Submenu)
-
-  Arguments:
-    conn_panel - instance of the connections panel
-  """
-
-  connections_menu = nyx.menu.item.Submenu('Connections')
-
-  # sorting option
-
-  connections_menu.add(nyx.menu.item.MenuItem('Sorting...', conn_panel.show_sort_dialog))
-
-  # resolver submenu
-
-  conn_resolver = nyx.tracker.get_connection_tracker()
-  resolver_menu = nyx.menu.item.Submenu('Resolver')
-  resolver_group = nyx.menu.item.SelectionGroup(conn_resolver.set_custom_resolver, conn_resolver.get_custom_resolver())
-
-  resolver_menu.add(nyx.menu.item.SelectionMenuItem('auto', resolver_group, None))
-
-  for option in stem.util.connection.Resolver:
-    resolver_menu.add(nyx.menu.item.SelectionMenuItem(option, resolver_group, option))
-
-  connections_menu.add(resolver_menu)
-
-  return connections_menu
-
-
-def make_configuration_menu(config_panel):
-  """
-  Submenu for the configuration panel, consisting of...
-    Save Config...
-    Sorting...
-    Filter / Unfilter Options
-
-  Arguments:
-    config_panel - instance of the configuration panel
-  """
-
-  config_menu = nyx.menu.item.Submenu('Configuration')
-  config_menu.add(nyx.menu.item.MenuItem('Save Config...', config_panel.show_write_dialog))
-  config_menu.add(nyx.menu.item.MenuItem('Sorting...', config_panel.show_sort_dialog))
-  return config_menu
-
-
-def make_torrc_menu(torrc_panel):
-  """
-  Submenu for the torrc panel, consisting of...
-    Reload
-    Show / Hide Comments
-    Show / Hide Line Numbers
-
-  Arguments:
-    torrc_panel - instance of the torrc panel
-  """
-
-  torrc_menu = nyx.menu.item.Submenu('Torrc')
-
-  if torrc_panel._show_comments:
-    label, arg = 'Hide', False
-  else:
-    label, arg = 'Show', True
-
-  torrc_menu.add(nyx.menu.item.MenuItem('%s Comments' % label, functools.partial(torrc_panel.set_comments_visible, arg)))
-
-  if torrc_panel._show_line_numbers:
-    label, arg = 'Hide', False
-  else:
-    label, arg = 'Show', True
-  torrc_menu.add(nyx.menu.item.MenuItem('%s Line Numbers' % label, functools.partial(torrc_panel.set_line_number_visible, arg)))
-
-  return torrc_menu
diff --git a/nyx/menu/item.py b/nyx/menu/item.py
deleted file mode 100644
index ef5e359..0000000
--- a/nyx/menu/item.py
+++ /dev/null
@@ -1,210 +0,0 @@
-# Copyright 2016, Damian Johnson and The Tor Project
-# See LICENSE for licensing information
-
-"""
-Menu item, representing an option in the drop-down menu.
-"""
-
-import nyx.controller
-
-
-class MenuItem():
-  """
-  Option in a drop-down menu.
-  """
-
-  def __init__(self, label, callback):
-    self._label = label
-    self._callback = callback
-    self._parent = None
-
-  def get_label(self):
-    """
-    Provides a tuple of three strings representing the prefix, label, and
-    suffix for this item.
-    """
-
-    return ('', self._label, '')
-
-  def get_parent(self):
-    """
-    Provides the Submenu we're contained within.
-    """
-
-    return self._parent
-
-  def get_hierarchy(self):
-    """
-    Provides a list with all of our parents, up to the root.
-    """
-
-    my_hierarchy = [self]
-    while my_hierarchy[-1].get_parent():
-      my_hierarchy.append(my_hierarchy[-1].get_parent())
-
-    my_hierarchy.reverse()
-    return my_hierarchy
-
-  def get_root(self):
-    """
-    Provides the base submenu we belong to.
-    """
-
-    if self._parent:
-      return self._parent.get_root()
-    else:
-      return self
-
-  def select(self):
-    """
-    Performs the callback for the menu item, returning true if we should close
-    the menu and false otherwise.
-    """
-
-    if self._callback:
-      control = nyx.controller.get_controller()
-      control.redraw()
-      self._callback()
-
-    return True
-
-  def next(self):
-    """
-    Provides the next option for the submenu we're in, raising a ValueError
-    if we don't have a parent.
-    """
-
-    return self._get_sibling(1)
-
-  def prev(self):
-    """
-    Provides the previous option for the submenu we're in, raising a ValueError
-    if we don't have a parent.
-    """
-
-    return self._get_sibling(-1)
-
-  def _get_sibling(self, offset):
-    """
-    Provides our sibling with a given index offset from us, raising a
-    ValueError if we don't have a parent.
-
-    Arguments:
-      offset - index offset for the sibling to be returned
-    """
-
-    if self._parent:
-      my_siblings = self._parent.get_children()
-
-      try:
-        my_index = my_siblings.index(self)
-        return my_siblings[(my_index + offset) % len(my_siblings)]
-      except ValueError:
-        # We expect a bidirectional references between submenus and their
-        # children. If we don't have this then our menu's screwed up.
-
-        msg = "The '%s' submenu doesn't contain '%s' (children: '%s')" % (self, self._parent, "', '".join(my_siblings))
-        raise ValueError(msg)
-    else:
-      raise ValueError("Menu option '%s' doesn't have a parent" % self)
-
-  def __str__(self):
-    return self._label
-
-
-class Submenu(MenuItem):
-  """
-  Menu item that lists other menu options.
-  """
-
-  def __init__(self, label):
-    MenuItem.__init__(self, label, None)
-    self._children = []
-
-  def get_label(self):
-    """
-    Provides our label with a '>' suffix to indicate that we have suboptions.
-    """
-
-    my_label = MenuItem.get_label(self)[1]
-    return ('', my_label, ' >')
-
-  def add(self, menu_item):
-    """
-    Adds the given menu item to our listing. This raises a ValueError if the
-    item already has a parent.
-
-    Arguments:
-      menu_item - menu option to be added
-    """
-
-    if menu_item.get_parent():
-      raise ValueError("Menu option '%s' already has a parent" % menu_item)
-    else:
-      menu_item._parent = self
-      self._children.append(menu_item)
-
-  def get_children(self):
-    """
-    Provides the menu and submenus we contain.
-    """
-
-    return list(self._children)
-
-  def is_empty(self):
-    """
-    True if we have no children, false otherwise.
-    """
-
-    return not bool(self._children)
-
-  def select(self):
-    return False
-
-
-class SelectionGroup():
-  """
-  Radio button groups that SelectionMenuItems can belong to.
-  """
-
-  def __init__(self, action, selected_arg):
-    self.action = action
-    self.selected_arg = selected_arg
-
-
-class SelectionMenuItem(MenuItem):
-  """
-  Menu item with an associated group which determines the selection. This is
-  for the common single argument getter/setter pattern.
-  """
-
-  def __init__(self, label, group, arg):
-    MenuItem.__init__(self, label, None)
-    self._group = group
-    self._arg = arg
-
-  def is_selected(self):
-    """
-    True if we're the selected item, false otherwise.
-    """
-
-    return self._arg == self._group.selected_arg
-
-  def get_label(self):
-    """
-    Provides our label with a '[X]' prefix if selected and '[ ]' if not.
-    """
-
-    my_label = MenuItem.get_label(self)[1]
-    my_prefix = '[X] ' if self.is_selected() else '[ ] '
-    return (my_prefix, my_label, '')
-
-  def select(self):
-    """
-    Performs the group's setter action with our argument.
-    """
-
-    if not self.is_selected():
-      self._group.action(self._arg)
-
-    return True
diff --git a/nyx/menu/menu.py b/nyx/menu/menu.py
deleted file mode 100644
index 01aa25e..0000000
--- a/nyx/menu/menu.py
+++ /dev/null
@@ -1,157 +0,0 @@
-# Copyright 2016, Damian Johnson and The Tor Project
-# See LICENSE for licensing information
-
-"""
-Display logic for presenting the menu.
-"""
-
-import nyx.curses
-import nyx.popups
-import nyx.controller
-import nyx.menu.item
-import nyx.menu.actions
-
-from nyx.curses import RED, WHITE, NORMAL, BOLD, UNDERLINE
-
-
-class MenuCursor:
-  """
-  Tracks selection and key handling in the menu.
-  """
-
-  def __init__(self, initial_selection):
-    self._selection = initial_selection
-    self._is_done = False
-
-  def is_done(self):
-    """
-    Provides true if a selection has indicated that we should close the menu.
-    False otherwise.
-    """
-
-    return self._is_done
-
-  def get_selection(self):
-    """
-    Provides the currently selected menu item.
-    """
-
-    return self._selection
-
-  def handle_key(self, key):
-    is_selection_submenu = isinstance(self._selection, nyx.menu.item.Submenu)
-    selection_hierarchy = self._selection.get_hierarchy()
-
-    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.match('up'):
-      self._selection = self._selection.prev()
-    elif key.match('down'):
-      self._selection = self._selection.next()
-    elif key.match('left'):
-      if len(selection_hierarchy) <= 3:
-        # shift to the previous main submenu
-
-        prev_submenu = selection_hierarchy[1].prev()
-        self._selection = prev_submenu.get_children()[0]
-      else:
-        # go up a submenu level
-
-        self._selection = self._selection.get_parent()
-    elif key.match('right'):
-      if is_selection_submenu:
-        # open submenu (same as making a selection)
-
-        if not self._selection.is_empty():
-          self._selection = self._selection.get_children()[0]
-      else:
-        # shift to the next main submenu
-
-        next_submenu = selection_hierarchy[1].next()
-        self._selection = next_submenu.get_children()[0]
-    elif key.match('esc', 'm'):
-      self._is_done = True
-
-
-def show_menu():
-  selection_left = [0]
-
-  def _render(subwindow):
-    x = 0
-
-    for top_level_item in menu.get_children():
-      if top_level_item == selection_hierarchy[1]:
-        selection_left[0] = x
-        attr = UNDERLINE
-      else:
-        attr = NORMAL
-
-      x = subwindow.addstr(x, 0, ' %s ' % top_level_item.get_label()[1], BOLD, attr)
-      subwindow.vline(x, 0, 1)
-      x += 1
-
-  with nyx.curses.CURSES_LOCK:
-    # generates the menu and uses the initial selection of the first item in
-    # the file menu
-
-    menu = nyx.menu.actions.make_menu()
-    cursor = MenuCursor(menu.get_children()[0].get_children()[0])
-
-    while not cursor.is_done():
-      selection_hierarchy = cursor.get_selection().get_hierarchy()
-
-      # provide a message saying how to close the menu
-
-      nyx.controller.show_message('Press m or esc to close the menu.', BOLD)
-      nyx.curses.draw(_render, height = 1, background = RED)
-      _draw_submenu(cursor, 1, 1, selection_left[0])
-      cursor.handle_key(nyx.curses.key_input())
-
-      # redraws the rest of the interface if we're rendering on it again
-
-      if not cursor.is_done():
-        nyx.controller.get_controller().redraw()
-
-  nyx.controller.show_message()
-
-
-def _draw_submenu(cursor, level, top, left):
-  selection_hierarchy = cursor.get_selection().get_hierarchy()
-
-  # checks if there's nothing to display
-
-  if len(selection_hierarchy) < level + 2:
-    return
-
-  # fetches the submenu and selection we're displaying
-
-  submenu = selection_hierarchy[level]
-  selection = selection_hierarchy[level + 1]
-
-  # gets the size of the prefix, middle, and suffix columns
-
-  all_label_sets = [entry.get_label() for entry in submenu.get_children()]
-  prefix_col_size = max([len(entry[0]) for entry in all_label_sets])
-  middle_col_size = max([len(entry[1]) for entry in all_label_sets])
-  suffix_col_size = max([len(entry[2]) for entry in all_label_sets])
-
-  # formatted string so we can display aligned menu entries
-
-  label_format = ' %%-%is%%-%is%%-%is ' % (prefix_col_size, middle_col_size, suffix_col_size)
-  menu_width = len(label_format % ('', '', ''))
-  selection_top = submenu.get_children().index(selection) if selection in submenu.get_children() else 0
-
-  def _render(subwindow):
-    for y, menu_item in enumerate(submenu.get_children()):
-      if menu_item == selection:
-        subwindow.addstr(0, y, label_format % menu_item.get_label(), WHITE, BOLD)
-      else:
-        subwindow.addstr(0, y, label_format % menu_item.get_label())
-
-  with nyx.curses.CURSES_LOCK:
-    nyx.curses.draw(_render, top = top, left = left, width = menu_width, height = len(submenu.get_children()), background = RED)
-    _draw_submenu(cursor, level + 1, top + selection_top, left + menu_width)
diff --git a/setup.py b/setup.py
index b446c67..d83c8aa 100644
--- a/setup.py
+++ b/setup.py
@@ -99,7 +99,7 @@ setup(
   author = nyx.__author__,
   author_email = nyx.__contact__,
   url = nyx.__url__,
-  packages = ['nyx', 'nyx.panel', 'nyx.menu'],
+  packages = ['nyx', 'nyx.panel'],
   keywords = 'tor onion controller',
   install_requires = ['stem>=1.4.1'],
   package_data = {'nyx': ['settings/*', 'resources/*']},



More information about the tor-commits mailing list