[tor-commits] [nyx/master] Revise popup dialogs to use 'with'

atagar at torproject.org atagar at torproject.org
Fri May 22 16:39:54 UTC 2015


commit 7121ff8a9b5db7c970b9ec920ccabbc6607dc856
Author: Damian Johnson <atagar at torproject.org>
Date:   Fri May 22 09:39:07 2015 -0700

    Revise popup dialogs to use 'with'
    
    Our popup dialogs acquire a curses lock for its duration. We were doing it via
    a init() and finalize() method, which were always used in a try/finally block.
    However, this is exactly the use case for python's 'with' keyword so using that
    instead.
    
    Not entirely happy with this code (it's still pending a rewrite), but better.
---
 nyx/connections/count_popup.py      |  106 +++++------
 nyx/connections/descriptor_popup.py |   68 ++++---
 nyx/log_panel.py                    |    8 +-
 nyx/menu/menu.py                    |   73 ++++----
 nyx/popups.py                       |  348 ++++++++++++++++-------------------
 5 files changed, 281 insertions(+), 322 deletions(-)

diff --git a/nyx/connections/count_popup.py b/nyx/connections/count_popup.py
index 00aeb31..e61dc26 100644
--- a/nyx/connections/count_popup.py
+++ b/nyx/connections/count_popup.py
@@ -28,84 +28,80 @@ def showCountDialog(count_type, counts):
   no_stats_msg = "Usage stats aren't available yet, press any key..."
 
   if is_no_stats:
-    popup, width, height = nyx.popups.init(3, len(no_stats_msg) + 4)
+    height, width = 3, len(no_stats_msg) + 4
   else:
-    popup, width, height = nyx.popups.init(4 + max(1, len(counts)), 80)
+    height, width = 4 + max(1, len(counts)), 80
 
-  if not popup:
-    return
+  with nyx.popups.popup_window(height, width) as (popup, height, width):
+    if popup:
+      control = nyx.controller.get_controller()
 
-  try:
-    control = nyx.controller.get_controller()
+      popup.win.box()
 
-    popup.win.box()
+      # dialog title
 
-    # dialog title
+      if count_type == CountType.CLIENT_LOCALE:
+        title = 'Client Locales'
+      elif count_type == CountType.EXIT_PORT:
+        title = 'Exiting Port Usage'
+      else:
+        title = ''
+        log.warn('Unrecognized count type: %s' % count_type)
 
-    if count_type == CountType.CLIENT_LOCALE:
-      title = 'Client Locales'
-    elif count_type == CountType.EXIT_PORT:
-      title = 'Exiting Port Usage'
-    else:
-      title = ''
-      log.warn('Unrecognized count type: %s' % count_type)
+      popup.addstr(0, 0, title, curses.A_STANDOUT)
 
-    popup.addstr(0, 0, title, curses.A_STANDOUT)
+      if is_no_stats:
+        popup.addstr(1, 2, no_stats_msg, curses.A_BOLD, 'cyan')
+      else:
+        sorted_counts = sorted(counts.iteritems(), key=operator.itemgetter(1))
+        sorted_counts.reverse()
 
-    if is_no_stats:
-      popup.addstr(1, 2, no_stats_msg, curses.A_BOLD, 'cyan')
-    else:
-      sorted_counts = sorted(counts.iteritems(), key=operator.itemgetter(1))
-      sorted_counts.reverse()
+        # constructs string formatting for the max key and value display width
 
-      # constructs string formatting for the max key and value display width
+        key_width, val_width, value_total = 3, 1, 0
 
-      key_width, val_width, value_total = 3, 1, 0
+        for k, v in sorted_counts:
+          key_width = max(key_width, len(k))
+          val_width = max(val_width, len(str(v)))
+          value_total += v
 
-      for k, v in sorted_counts:
-        key_width = max(key_width, len(k))
-        val_width = max(val_width, len(str(v)))
-        value_total += v
+        # extra space since we're adding usage informaion
 
-      # extra space since we're adding usage informaion
-
-      if count_type == CountType.EXIT_PORT:
-        key_width += EXIT_USAGE_WIDTH
+        if count_type == CountType.EXIT_PORT:
+          key_width += EXIT_USAGE_WIDTH
 
-      label_format = '%%-%is %%%ii (%%%%%%-2i)' % (key_width, val_width)
+        label_format = '%%-%is %%%ii (%%%%%%-2i)' % (key_width, val_width)
 
-      for i in range(height - 4):
-        k, v = sorted_counts[i]
+        for i in range(height - 4):
+          k, v = sorted_counts[i]
 
-        # includes a port usage column
+          # includes a port usage column
 
-        if count_type == CountType.EXIT_PORT:
-          usage = connection.port_usage(k)
+          if count_type == CountType.EXIT_PORT:
+            usage = connection.port_usage(k)
 
-          if usage:
-            key_format = '%%-%is   %%s' % (key_width - EXIT_USAGE_WIDTH)
-            k = key_format % (k, usage[:EXIT_USAGE_WIDTH - 3])
+            if usage:
+              key_format = '%%-%is   %%s' % (key_width - EXIT_USAGE_WIDTH)
+              k = key_format % (k, usage[:EXIT_USAGE_WIDTH - 3])
 
-        label = label_format % (k, v, v * 100 / value_total)
-        popup.addstr(i + 1, 2, label, curses.A_BOLD, 'green')
+          label = label_format % (k, v, v * 100 / value_total)
+          popup.addstr(i + 1, 2, label, curses.A_BOLD, 'green')
 
-        # All labels have the same size since they're based on the max widths.
-        # If this changes then this'll need to be the max label width.
+          # All labels have the same size since they're based on the max widths.
+          # If this changes then this'll need to be the max label width.
 
-        label_width = len(label)
+          label_width = len(label)
 
-        # draws simple bar graph for percentages
+          # draws simple bar graph for percentages
 
-        fill_width = v * (width - 4 - label_width) / value_total
+          fill_width = v * (width - 4 - label_width) / value_total
 
-        for j in range(fill_width):
-          popup.addstr(i + 1, 3 + label_width + j, ' ', curses.A_STANDOUT, 'red')
+          for j in range(fill_width):
+            popup.addstr(i + 1, 3 + label_width + j, ' ', curses.A_STANDOUT, 'red')
 
-      popup.addstr(height - 2, 2, 'Press any key...')
+        popup.addstr(height - 2, 2, 'Press any key...')
 
-    popup.win.refresh()
+      popup.win.refresh()
 
-    curses.cbreak()
-    control.key_input()
-  finally:
-    nyx.popups.finalize()
+      curses.cbreak()
+      control.key_input()
diff --git a/nyx/connections/descriptor_popup.py b/nyx/connections/descriptor_popup.py
index f05631a..0409ab8 100644
--- a/nyx/connections/descriptor_popup.py
+++ b/nyx/connections/descriptor_popup.py
@@ -66,43 +66,37 @@ def show_descriptor_popup(conn_panel):
 
       popup_height, popup_width = get_preferred_size(display_text, conn_panel.max_x, show_line_number)
 
-      popup, _, height = nyx.popups.init(popup_height, popup_width)
-
-      if not popup:
-        break
-
-      scroll, is_changed = 0, True
-
-      try:
-        while not is_done:
-          if is_changed:
-            draw(popup, fingerprint, display_text, display_color, scroll, show_line_number)
-            is_changed = False
-
-          key = control.key_input()
-
-          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
-            # can't scroll to the bottom if there's a multi-line being
-            # displayed. However, trying to correct this introduces a big can
-            # of worms and after hours decided that this isn't worth the
-            # effort...
-
-            new_scroll = ui_tools.get_scroll_position(key, scroll, height - 2, len(display_text))
-
-            if scroll != new_scroll:
-              scroll, is_changed = new_scroll, True
-          elif key.is_selection() or key.match('d'):
-            is_done = True  # closes popup
-          elif key.match('left', 'right'):
-            # navigation - pass on to conn_panel and recreate popup
-
-            conn_panel.handle_key(panel.KeyInput(curses.KEY_UP) if key.match('left') else panel.KeyInput(curses.KEY_DOWN))
-            break
-      finally:
-        nyx.popups.finalize()
+      with nyx.popups.popup_window(popup_height, popup_width) as (popup, _, height):
+        if popup:
+          scroll, is_changed = 0, True
+
+          while not is_done:
+            if is_changed:
+              draw(popup, fingerprint, display_text, display_color, scroll, show_line_number)
+              is_changed = False
+
+            key = control.key_input()
+
+            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
+              # can't scroll to the bottom if there's a multi-line being
+              # displayed. However, trying to correct this introduces a big can
+              # of worms and after hours decided that this isn't worth the
+              # effort...
+
+              new_scroll = ui_tools.get_scroll_position(key, scroll, height - 2, len(display_text))
+
+              if scroll != new_scroll:
+                scroll, is_changed = new_scroll, True
+            elif key.is_selection() or key.match('d'):
+              is_done = True  # closes popup
+            elif key.match('left', 'right'):
+              # navigation - pass on to conn_panel and recreate popup
+
+              conn_panel.handle_key(panel.KeyInput(curses.KEY_UP) if key.match('left') else panel.KeyInput(curses.KEY_DOWN))
+              break
   finally:
     conn_panel.set_title_visible(True)
     conn_panel.redraw(True)
diff --git a/nyx/log_panel.py b/nyx/log_panel.py
index 4180f57..4912cd7 100644
--- a/nyx/log_panel.py
+++ b/nyx/log_panel.py
@@ -140,10 +140,8 @@ class LogPanel(panel.Panel, threading.Thread):
 
     # allow user to enter new types of events to log - unchanged if left blank
 
-    popup, width, height = nyx.popups.init(11, 80)
-
-    if popup:
-      try:
+    with nyx.popups.popup_window(11, 80) as (popup, width, height):
+      if popup:
         # displays the available flags
 
         popup.win.box()
@@ -167,8 +165,6 @@ class LogPanel(panel.Panel, threading.Thread):
               self.redraw(True)
           except ValueError as exc:
             nyx.popups.show_msg('Invalid flags: %s' % str(exc), 2)
-      finally:
-        nyx.popups.finalize()
 
   def show_snapshot_prompt(self):
     """
diff --git a/nyx/menu/menu.py b/nyx/menu/menu.py
index 59f9a4e..34e9a67 100644
--- a/nyx/menu/menu.py
+++ b/nyx/menu/menu.py
@@ -76,64 +76,59 @@ class MenuCursor:
 
 
 def show_menu():
-  popup, _, _ = nyx.popups.init(1, below_static = False)
+  with nyx.popups.popup_window(1, below_static = False) as (popup, width, height):
+    if popup:
+      control = nyx.controller.get_controller()
 
-  if not popup:
-    return
-
-  control = nyx.controller.get_controller()
+      # generates the menu and uses the initial selection of the first item in
+      # the file menu
 
-  try:
-    # 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])
 
-    menu = nyx.menu.actions.make_menu()
-    cursor = MenuCursor(menu.get_children()[0].get_children()[0])
+      while not cursor.is_done():
+        # sets the background color
 
-    while not cursor.is_done():
-      # sets the background color
+        popup.win.clear()
+        popup.win.bkgd(' ', curses.A_STANDOUT | ui_tools.get_color("red"))
+        selection_hierarchy = cursor.get_selection().get_hierarchy()
 
-      popup.win.clear()
-      popup.win.bkgd(' ', curses.A_STANDOUT | ui_tools.get_color("red"))
-      selection_hierarchy = cursor.get_selection().get_hierarchy()
+        # provide a message saying how to close the menu
 
-      # 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.', curses.A_BOLD, True)
+        # renders the menu bar, noting where the open submenu is positioned
 
-      # renders the menu bar, noting where the open submenu is positioned
+        draw_left, selection_left = 0, 0
 
-      draw_left, selection_left = 0, 0
+        for top_level_item in menu.get_children():
+          draw_format = curses.A_BOLD
 
-      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
+            selection_left = draw_left
 
-        if top_level_item == selection_hierarchy[1]:
-          draw_format |= curses.A_UNDERLINE
-          selection_left = draw_left
+          draw_label = ' %s ' % top_level_item.get_label()[1]
+          popup.addstr(0, draw_left, draw_label, draw_format)
+          popup.addch(0, draw_left + len(draw_label), curses.ACS_VLINE)
 
-        draw_label = ' %s ' % top_level_item.get_label()[1]
-        popup.addstr(0, draw_left, draw_label, draw_format)
-        popup.addch(0, draw_left + len(draw_label), curses.ACS_VLINE)
+          draw_left += len(draw_label) + 1
 
-        draw_left += len(draw_label) + 1
+        # recursively shows opened submenus
 
-      # recursively shows opened submenus
+        _draw_submenu(cursor, 1, 1, selection_left)
 
-      _draw_submenu(cursor, 1, 1, selection_left)
+        popup.win.refresh()
 
-      popup.win.refresh()
+        curses.cbreak()
+        cursor.handle_key(control.key_input())
 
-      curses.cbreak()
-      cursor.handle_key(control.key_input())
+        # redraws the rest of the interface if we're rendering on it again
 
-      # redraws the rest of the interface if we're rendering on it again
+        if not cursor.is_done():
+          control.redraw()
 
-      if not cursor.is_done():
-        control.redraw()
-  finally:
-    control.set_msg()
-    nyx.popups.finalize()
+  control.set_msg()
 
 
 def _draw_submenu(cursor, level, top, left):
diff --git a/nyx/popups.py b/nyx/popups.py
index 2f98e73..355d7f1 100644
--- a/nyx/popups.py
+++ b/nyx/popups.py
@@ -10,52 +10,55 @@ from nyx import __version__, __release_date__
 from nyx.util import panel, ui_tools
 
 
-def init(height = -1, width = -1, top = 0, left = 0, below_static = True):
+def popup_window(height = -1, width = -1, top = 0, left = 0, below_static = True):
   """
-  Preparation for displaying a popup. This creates a popup with a valid
-  subwindow instance. If that's successful then the curses lock is acquired
-  and this returns a tuple of the...
-  (popup, draw width, draw height)
-  Otherwise this leaves curses unlocked and returns None.
+  Provides a popup dialog you can use in a 'with' block...
 
-  Arguments:
-    height      - maximum height of the popup
-    width       - maximum width of the popup
-    top         - top position, relative to the sticky content
-    left        - left position from the screen
-    below_static - positions popup below static content if true
-  """
+    with popup_window(5, 10) as (popup, width, height):
+      if popup:
+        ... do stuff...
 
-  control = nyx.controller.get_controller()
+  This popup has a lock on the curses interface for the duration of the block,
+  preventing other draw operations from taking place. If the popup isn't
+  visible then the popup it returns will be **None**.
 
-  if below_static:
-    sticky_height = sum([sticky_panel.get_height() for sticky_panel in control.get_sticky_panels()])
-  else:
-    sticky_height = 0
+  :param int height: maximum height of the popup
+  :param int width: maximum width of the popup
+  :param int top: top position, relative to the sticky content
+  :param int left: left position from the screen
+  :param bool below_static: positions popup below static content if True
+
+  :returns: tuple of the form (subwindow, width, height) when used in a with block
+  """
 
-  popup = panel.Panel(control.get_screen(), 'popup', top + sticky_height, left, height, width)
-  popup.set_visible(True)
+  class _Popup(object):
+    def __enter__(self):
+      control = nyx.controller.get_controller()
 
-  # Redraws the popup to prepare a subwindow instance. If none is spawned then
-  # the panel can't be drawn (for instance, due to not being visible).
+      if below_static:
+        sticky_height = sum([sticky_panel.get_height() for sticky_panel in control.get_sticky_panels()])
+      else:
+        sticky_height = 0
 
-  popup.redraw(True)
+      popup = panel.Panel(control.get_screen(), 'popup', top + sticky_height, left, height, width)
+      popup.set_visible(True)
 
-  if popup.win is not None:
-    panel.CURSES_LOCK.acquire()
-    return (popup, popup.max_x - 1, popup.max_y)
-  else:
-    return (None, 0, 0)
+      # Redraws the popup to prepare a subwindow instance. If none is spawned then
+      # the panel can't be drawn (for instance, due to not being visible).
 
+      popup.redraw(True)
 
-def finalize():
-  """
-  Cleans up after displaying a popup, releasing the cureses lock and redrawing
-  the rest of the display.
-  """
+      if popup.win is not None:
+        panel.CURSES_LOCK.acquire()
+        return (popup, popup.max_x - 1, popup.max_y)
+      else:
+        return (None, 0, 0)
 
-  nyx.controller.get_controller().request_redraw()
-  panel.CURSES_LOCK.release()
+    def __exit__(self, exit_type, value, traceback):
+      nyx.controller.get_controller().request_redraw()
+      panel.CURSES_LOCK.release()
+
+  return _Popup()
 
 
 def input_prompt(msg, initial_value = ''):
@@ -112,68 +115,61 @@ def show_help_popup():
   properly, this is an arrow, enter, or scroll key then this returns None.
   """
 
-  popup, _, height = init(9, 80)
-
-  if not popup:
-    return
-
-  exit_key = None
-
-  try:
-    control = nyx.controller.get_controller()
-    page_panels = control.get_display_panels()
+  with popup_window(9, 80) as (popup, _, height):
+    if popup:
+      exit_key = None
+      control = nyx.controller.get_controller()
+      page_panels = control.get_display_panels()
 
-    # the first page is the only one with multiple panels, and it looks better
-    # with the log entries first, so reversing the order
+      # the first page is the only one with multiple panels, and it looks better
+      # with the log entries first, so reversing the order
 
-    page_panels.reverse()
+      page_panels.reverse()
 
-    help_options = []
+      help_options = []
 
-    for entry in page_panels:
-      help_options += entry.get_help()
+      for entry in page_panels:
+        help_options += entry.get_help()
 
-    # test doing afterward in case of overwriting
+      # 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.win.box()
+      popup.addstr(0, 0, 'Page %i Commands:' % (control.get_page() + 1), curses.A_STANDOUT)
 
-    for i in range(len(help_options)):
-      if i / 2 >= height - 2:
-        break
+      for i in range(len(help_options)):
+        if i / 2 >= height - 2:
+          break
 
-      # draws entries in the form '<key>: <description>[ (<selection>)]', for
-      # instance...
-      # u: duplicate log entries (hidden)
+        # draws entries in the form '<key>: <description>[ (<selection>)]', for
+        # instance...
+        # u: duplicate log entries (hidden)
 
-      key, description, selection = help_options[i]
+        key, description, selection = help_options[i]
 
-      if key:
-        description = ': ' + description
+        if key:
+          description = ': ' + description
 
-      row = (i / 2) + 1
-      col = 2 if i % 2 == 0 else 41
+        row = (i / 2) + 1
+        col = 2 if i % 2 == 0 else 41
 
-      popup.addstr(row, col, key, curses.A_BOLD)
-      col += len(key)
-      popup.addstr(row, col, description)
-      col += len(description)
+        popup.addstr(row, col, key, curses.A_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 + len(selection), ')')
+        if selection:
+          popup.addstr(row, col, ' (')
+          popup.addstr(row, col + 2, selection, curses.A_BOLD)
+          popup.addstr(row, col + 2 + len(selection), ')')
 
-    # tells user to press a key if the lower left is unoccupied
+      # tells user to press a key if the lower left is unoccupied
 
-    if len(help_options) < 13 and height == 9:
-      popup.addstr(7, 2, 'Press any key...')
+      if len(help_options) < 13 and height == 9:
+        popup.addstr(7, 2, 'Press any key...')
 
-    popup.win.refresh()
-    curses.cbreak()
-    exit_key = control.key_input()
-  finally:
-    finalize()
+      popup.win.refresh()
+      curses.cbreak()
+      exit_key = control.key_input()
 
   if not exit_key.is_selection() and not exit_key.is_scroll() and \
     not exit_key.match('left', 'right'):
@@ -187,27 +183,21 @@ def show_about_popup():
   Presents a popup with author and version information.
   """
 
-  popup, _, height = init(9, 80)
-
-  if not popup:
-    return
+  with popup_window(9, 80) as (popup, _, height):
+    if popup:
+      control = nyx.controller.get_controller()
 
-  try:
-    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(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)')
-    popup.addstr(7, 2, 'Press any key...')
-    popup.win.refresh()
+      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(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)')
+      popup.addstr(7, 2, 'Press any key...')
+      popup.win.refresh()
 
-    curses.cbreak()
-    control.key_input()
-  finally:
-    finalize()
+      curses.cbreak()
+      control.key_input()
 
 
 def show_sort_dialog(title, options, old_selection, option_colors):
@@ -230,66 +220,59 @@ def show_sort_dialog(title, options, old_selection, option_colors):
     option_colors - mappings of options to their color
   """
 
-  popup, _, _ = init(9, 80)
+  with popup_window(9, 80) as (popup, _, _):
+    if popup:
+      new_selections = []  # new ordering
+      cursor_location = 0     # index of highlighted option
+      curses.cbreak()         # wait indefinitely for key presses (no timeout)
 
-  if not popup:
-    return
+      selection_options = list(options)
+      selection_options.append('Cancel')
 
-  new_selections = []  # new ordering
+      while len(new_selections) < len(old_selection):
+        popup.win.erase()
+        popup.win.box()
+        popup.addstr(0, 0, title, curses.A_STANDOUT)
 
-  try:
-    cursor_location = 0     # index of highlighted option
-    curses.cbreak()         # wait indefinitely for key presses (no timeout)
+        _draw_sort_selection(popup, 1, 2, 'Current Order: ', old_selection, option_colors)
+        _draw_sort_selection(popup, 2, 2, 'New Order: ', new_selections, option_colors)
 
-    selection_options = list(options)
-    selection_options.append('Cancel')
+        # presents remaining options, each row having up to four options with
+        # spacing of nineteen cells
 
-    while len(new_selections) < len(old_selection):
-      popup.win.erase()
-      popup.win.box()
-      popup.addstr(0, 0, title, curses.A_STANDOUT)
+        row, col = 4, 0
 
-      _draw_sort_selection(popup, 1, 2, 'Current Order: ', old_selection, option_colors)
-      _draw_sort_selection(popup, 2, 2, 'New Order: ', new_selections, option_colors)
+        for i in range(len(selection_options)):
+          option_format = curses.A_STANDOUT if cursor_location == i else curses.A_NORMAL
+          popup.addstr(row, col * 19 + 2, selection_options[i], option_format)
+          col += 1
 
-      # presents remaining options, each row having up to four options with
-      # spacing of nineteen cells
+          if col == 4:
+            row, col = row + 1, 0
 
-      row, col = 4, 0
+        popup.win.refresh()
 
-      for i in range(len(selection_options)):
-        option_format = curses.A_STANDOUT if cursor_location == i else curses.A_NORMAL
-        popup.addstr(row, col * 19 + 2, selection_options[i], option_format)
-        col += 1
+        key = nyx.controller.get_controller().key_input()
 
-        if col == 4:
-          row, col = row + 1, 0
+        if key.match('left'):
+          cursor_location = max(0, cursor_location - 1)
+        elif key.match('right'):
+          cursor_location = min(len(selection_options) - 1, cursor_location + 1)
+        elif key.match('up'):
+          cursor_location = max(0, cursor_location - 4)
+        elif key.match('down'):
+          cursor_location = min(len(selection_options) - 1, cursor_location + 4)
+        elif key.is_selection():
+          selection = selection_options[cursor_location]
 
-      popup.win.refresh()
-
-      key = nyx.controller.get_controller().key_input()
-
-      if key.match('left'):
-        cursor_location = max(0, cursor_location - 1)
-      elif key.match('right'):
-        cursor_location = min(len(selection_options) - 1, cursor_location + 1)
-      elif key.match('up'):
-        cursor_location = max(0, cursor_location - 4)
-      elif key.match('down'):
-        cursor_location = min(len(selection_options) - 1, cursor_location + 4)
-      elif key.is_selection():
-        selection = selection_options[cursor_location]
-
-        if selection == 'Cancel':
-          break
-        else:
-          new_selections.append(selection)
-          selection_options.remove(selection)
-          cursor_location = min(cursor_location, len(selection_options) - 1)
-      elif key == 27:
-        break  # esc - cancel
-  finally:
-    finalize()
+          if selection == 'Cancel':
+            break
+          else:
+            new_selections.append(selection)
+            selection_options.remove(selection)
+            cursor_location = min(cursor_location, len(selection_options) - 1)
+        elif key == 27:
+          break  # esc - cancel
 
   if len(new_selections) == len(old_selection):
     return new_selections
@@ -343,50 +326,45 @@ def show_menu(title, options, old_selection):
   """
 
   max_width = max(map(len, options)) + 9
-  popup, _, _ = init(len(options) + 2, max_width)
 
-  if not popup:
-    return
+  with popup_window(len(options) + 2, max_width) as (popup, _, _):
+    if popup:
+      selection = old_selection if old_selection != -1 else 0
 
-  selection = old_selection if old_selection != -1 else 0
+      # hides the title of the first panel on the page
 
-  try:
-    # hides the title of the first panel on the page
+      control = nyx.controller.get_controller()
+      top_panel = control.get_display_panels(include_sticky = False)[0]
+      top_panel.set_title_visible(False)
+      top_panel.redraw(True)
 
-    control = nyx.controller.get_controller()
-    top_panel = control.get_display_panels(include_sticky = False)[0]
-    top_panel.set_title_visible(False)
-    top_panel.redraw(True)
+      curses.cbreak()   # wait indefinitely for key presses (no timeout)
 
-    curses.cbreak()   # wait indefinitely for key presses (no timeout)
+      while True:
+        popup.win.erase()
+        popup.win.box()
+        popup.addstr(0, 0, title, curses.A_STANDOUT)
 
-    while True:
-      popup.win.erase()
-      popup.win.box()
-      popup.addstr(0, 0, title, curses.A_STANDOUT)
+        for i in range(len(options)):
+          label = options[i]
+          format = curses.A_STANDOUT if i == selection else curses.A_NORMAL
+          tab = '> ' if i == old_selection else '  '
+          popup.addstr(i + 1, 2, tab)
+          popup.addstr(i + 1, 4, ' %s ' % label, format)
 
-      for i in range(len(options)):
-        label = options[i]
-        format = curses.A_STANDOUT if i == selection else curses.A_NORMAL
-        tab = '> ' if i == old_selection else '  '
-        popup.addstr(i + 1, 2, tab)
-        popup.addstr(i + 1, 4, ' %s ' % label, format)
+        popup.win.refresh()
 
-      popup.win.refresh()
+        key = control.key_input()
 
-      key = control.key_input()
-
-      if key.match('up'):
-        selection = max(0, selection - 1)
-      elif key.match('down'):
-        selection = min(len(options) - 1, selection + 1)
-      elif key.is_selection():
-        break
-      elif key.match('esc'):
-        selection = -1
-        break
-  finally:
-    top_panel.set_title_visible(True)
-    finalize()
+        if key.match('up'):
+          selection = max(0, selection - 1)
+        elif key.match('down'):
+          selection = min(len(options) - 1, selection + 1)
+        elif key.is_selection():
+          break
+        elif key.match('esc'):
+          selection = -1
+          break
 
+  top_panel.set_title_visible(True)
   return selection



More information about the tor-commits mailing list