[tor-commits] [nyx/master] Rewrite show_help_popup()

atagar at torproject.org atagar at torproject.org
Thu Mar 24 16:56:40 UTC 2016


commit 40534bda29f6f7e090550b0a44e4a3af91fbc61a
Author: Damian Johnson <atagar at torproject.org>
Date:   Thu Mar 24 09:52:15 2016 -0700

    Rewrite show_help_popup()
    
    Bit of an experiment in splitting our Panel class from its responsibilities as
    a curses subwindow. With this new pattern our draw function gets a subwindow
    argument it can draw into. It only has draw functions (and the curses lock)
    within that context.
    
    Thus far results are promissing but don't be surprised if this changes again. :P
---
 nyx/curses.py | 120 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 nyx/popups.py |  87 ++++++++++++++++++------------------------
 2 files changed, 157 insertions(+), 50 deletions(-)

diff --git a/nyx/curses.py b/nyx/curses.py
index 3e88994..67305c6 100644
--- a/nyx/curses.py
+++ b/nyx/curses.py
@@ -20,6 +20,12 @@ if we want Windows support in the future too.
   disable_acs - renders replacements for ACS characters
   is_wide_characters_supported - checks if curses supports wide character
 
+  draw - renders subwindow that can be drawn into
+
+  Subwindow - subwindow that can be drawn within
+    |- addstr - draws a string
+    +- box - draws box with the given dimensions
+
   KeyInput - user keyboard input
     |- match - checks if this matches the given inputs
     |- is_scroll - true if key is used for scrolling
@@ -370,6 +376,120 @@ def is_wide_characters_supported():
   return False
 
 
+def draw(func, left = 0, top = 0, width = None, height = None):
+  """
+  Renders subwindow using the given draw function.
+
+  :param function func: draw function for rendering the subwindow
+  :param int left: left position of the panel
+  :param int top: top position of the panel
+  :param int width: panel width, uses all available space if **None**
+  :param int height: panel height, uses all available space if **None**
+  """
+
+  with CURSES_LOCK:
+    max_height, max_width = CURSES_SCREEN.getmaxyx()
+
+    subwindow_width = max(0, max_width - left)
+    subwindow_height = max(0, max_height - top)
+
+    if width:
+      subwindow_width = min(width, subwindow_width)
+
+    if height:
+      subwindow_height = min(height, subwindow_height)
+
+    curses_subwindow = CURSES_SCREEN.subwin(subwindow_height, subwindow_width, top, left)
+    curses_subwindow.erase()
+    func(Subwindow(subwindow_width, subwindow_height, curses_subwindow))
+    curses_subwindow.refresh()
+
+
+class Subwindow(object):
+  """
+  Subwindow that can be drawn within.
+
+  :var int width: subwindow width
+  :var int height: subwindow height
+  """
+
+  def __init__(self, width, height, curses_subwindow):
+    self.width = width
+    self.height = height
+    self._curses_subwindow = curses_subwindow
+
+  def addstr(self, x, y, msg, *attr):
+    """
+    Draws a string in the subwindow.
+
+    :param int x: horizontal location
+    :param int y, vertical location
+    :param str msg: string to be written
+    :param list attr: text attributes to apply
+    """
+
+    if self.width > x and self.height > y:
+      try:
+        cropped_msg = msg[:self.width - x]
+        self._curses_subwindow.addstr(y, x, cropped_msg, curses_attr(*attr))
+        return x + len(cropped_msg)
+      except:
+        pass
+
+    return x
+
+  def box(self, left = 0, top = 0, width = None, height = None, *attr):
+    """
+    Draws a box with the given bounds.
+
+    :param int left: left position of the box
+    :param int top: top position of the box
+    :param int width: box width, uses all available space if **None**
+    :param int height: box height, uses all available space if **None**
+    :param list attr: text attributes to apply
+    """
+
+    max_width = self.width - left
+    max_height = self.height - top
+
+    width = max_width if width is None else min(width, max_width)
+    height = max_height if height is None else min(height, max_height)
+
+    self._hline(left + 1, top, width - 2, *attr)  # top
+    self._hline(left + 1, top + height - 1, width - 2, *attr)  # bottom
+    self._vline(left, top + 1, height - 2, *attr)  # left
+    self._vline(left + width - 1, top + 1, height - 2, *attr)  # right
+
+    self._addch(left, top, curses.ACS_ULCORNER, *attr)  # upper left corner
+    self._addch(left, top + height - 1, curses.ACS_LLCORNER, *attr)  # lower left corner
+    self._addch(left + width - 1, top, curses.ACS_URCORNER, *attr)  # upper right corner
+    self._addch(left + width - 1, top + height - 1, curses.ACS_LRCORNER, *attr)  # lower right corner
+
+  def _addch(self, x, y, char, *attr):
+    if self.width > x and self.height > y:
+      try:
+        self._curses_subwindow.addch(y, x, char, curses_attr(*attr))
+        return x + 1
+      except:
+        pass
+
+    return x
+
+  def _hline(self, x, y, length, *attr):
+    if self.width > x and self.height > y:
+      try:
+        self._curses_subwindow.hline(y, x, curses.ACS_HLINE | curses_attr(*attr), min(length, self.width - x))
+      except:
+        pass
+
+  def _vline(self, x, y, length, *attr):
+    if self.width > x and self.height > y:
+      try:
+        self._curses_subwindow.vline(y, x, curses.ACS_VLINE | curses_attr(*attr), min(length, self.height - y))
+      except:
+        pass
+
+
 class KeyInput(object):
   """
   Keyboard input by the user.
diff --git a/nyx/popups.py b/nyx/popups.py
index 1f19189..25ff618 100644
--- a/nyx/popups.py
+++ b/nyx/popups.py
@@ -77,71 +77,58 @@ def popup_window(height = -1, width = -1, top = 0, left = 0, below_static = True
 
 def show_help_popup():
   """
-  Presents a popup with instructions for the current page's hotkeys. This
-  returns the user input used to close the popup. If the popup didn't close
-  properly, this is an arrow, enter, or scroll key then this returns None.
-  """
-
-  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
-
-      page_panels.reverse()
+  Presents a popup with instructions for the current page's hotkeys.
 
-      help_options = []
+  :returns: :class:`~nyx.curses.KeyInput` that was pressed to close the popup
+    if it's one panels should act upon, **None** otherwise
+  """
 
-      for entry in page_panels:
-        help_options += entry.get_help()
+  control = nyx.controller.get_controller()
+  sticky_height = sum([sticky_panel.get_height() for sticky_panel in control.get_sticky_panels()])
+  help_options = []
 
-      # test doing afterward in case of overwriting
+  for panel in reversed(control.get_display_panels()):
+    help_options += panel.get_help()
 
-      popup.draw_box()
-      popup.addstr(0, 0, 'Page %i Commands:' % (control.get_page() + 1), HIGHLIGHT)
+  def _render(subwindow):
+    subwindow.box()
+    subwindow.addstr(0, 0, 'Page %i Commands:' % (control.get_page() + 1), HIGHLIGHT)
 
-      for i in range(len(help_options)):
-        if i / 2 >= height - 2:
-          break
+    for i in range(len(help_options)):
+      if i / 2 >= subwindow.height - 2:
+        break
 
-        # draws entries in the form '<key>: <description>[ (<selection>)]', for
-        # instance...
-        # u: duplicate log entries (hidden)
+      # Entries are shown in the form '<key>: <description>[ (<selection>)]',
+      # such as...
+      #
+      #   u: duplicate log entries (hidden)
 
-        key, description, selection = help_options[i]
+      key, description, selection = help_options[i]
 
-        if key:
-          description = ': ' + description
+      x = 2 if i % 2 == 0 else 41
+      y = (i / 2) + 1
 
-        row = (i / 2) + 1
-        col = 2 if i % 2 == 0 else 41
+      x = subwindow.addstr(x, y, key, BOLD)
+      x = subwindow.addstr(x, y, ': ' + description)
 
-        popup.addstr(row, col, key, BOLD)
-        col += len(key)
-        popup.addstr(row, col, description)
-        col += len(description)
+      if selection:
+        x = subwindow.addstr(x, y, ' (')
+        x = subwindow.addstr(x, y, selection, BOLD)
+        x = subwindow.addstr(x, y, ')')
 
-        if selection:
-          popup.addstr(row, col, ' (')
-          popup.addstr(row, col + 2, selection, BOLD)
-          popup.addstr(row, col + 2 + len(selection), ')')
+    # tells user to press a key if the lower left is unoccupied
 
-      # tells user to press a key if the lower left is unoccupied
+    if len(help_options) < 13 and subwindow.height == 9:
+      subwindow.addstr(2, 7, 'Press any key...')
 
-      if len(help_options) < 13 and height == 9:
-        popup.addstr(7, 2, 'Press any key...')
+  with nyx.curses.CURSES_LOCK:
+    nyx.curses.draw(_render, top = sticky_height, width = 80, height = 9)
+    keypress = nyx.curses.key_input()
 
-      popup.win.refresh()
-      exit_key = nyx.curses.key_input()
-
-  if not exit_key.is_selection() and not exit_key.is_scroll() and \
-    not exit_key.match('left', 'right'):
-    return exit_key
-  else:
+  if keypress.is_selection() or keypress.is_scroll() or keypress.match('left', 'right'):
     return None
+  else:
+    return keypress
 
 
 def show_about_popup():



More information about the tor-commits mailing list