commit 3cc2ecf82d693a782c11d5a4f6fb41e94e7b5d05 Author: Damian Johnson atagar@torproject.org Date: Sat Jun 11 20:55:34 2011 -0700
Binding handlers for the view submenu
Submenu consisting of page and color selection. --- src/cli/configPanel.py | 2 +- src/cli/connections/connPanel.py | 2 +- src/cli/controller.py | 48 +++++++++++++++++++++++++++---------- src/cli/descriptorPopup.py | 2 +- src/cli/menu/actions.py | 44 ++++++++++++++++++++++++++++------ src/cli/menu/item.py | 46 ++++++++++++++++++++++++++++++++++++ src/cli/popups.py | 2 +- src/cli/torrcPanel.py | 2 +- src/util/uiTools.py | 36 ++++++++++++++++++++++++++-- 9 files changed, 155 insertions(+), 29 deletions(-)
diff --git a/src/cli/configPanel.py b/src/cli/configPanel.py index c44d295..c2e290d 100644 --- a/src/cli/configPanel.py +++ b/src/cli/configPanel.py @@ -172,7 +172,7 @@ class ConfigPanel(panel.Panel): """
def __init__(self, stdscr, configType, config=None): - panel.Panel.__init__(self, stdscr, "configState", 0) + panel.Panel.__init__(self, stdscr, "configuration", 0)
self.sortOrdering = DEFAULT_SORT_ORDER self._config = dict(DEFAULT_CONFIG) diff --git a/src/cli/connections/connPanel.py b/src/cli/connections/connPanel.py index 45750c3..8b86c65 100644 --- a/src/cli/connections/connPanel.py +++ b/src/cli/connections/connPanel.py @@ -31,7 +31,7 @@ class ConnectionPanel(panel.Panel, threading.Thread): """
def __init__(self, stdscr, config=None): - panel.Panel.__init__(self, stdscr, "conn", 0) + panel.Panel.__init__(self, stdscr, "connections", 0) threading.Thread.__init__(self) self.setDaemon(True)
diff --git a/src/cli/controller.py b/src/cli/controller.py index faf9248..cf17da3 100644 --- a/src/cli/controller.py +++ b/src/cli/controller.py @@ -160,6 +160,14 @@ class Controller:
return self._screen
+ def getPageCount(self): + """ + Provides the number of pages the interface has. This may be zero if all + page panels have been disabled. + """ + + return len(self._pagePanels) + def getPage(self): """ Provides the number belonging to this page. Page numbers start at zero. @@ -167,23 +175,35 @@ class Controller:
return self._page
+ def setPage(self, pageNumber): + """ + Sets the selected page, raising a ValueError if the page number is invalid. + + Arguments: + pageNumber - page number to be selected + """ + + if pageNumber < 0 or pageNumber >= self.getPageCount(): + raise ValueError("Invalid page number: %i" % pageNumber) + + if pageNumber != self._page: + self._page = pageNumber + self._forceRedraw = True + self.setMsg() + def nextPage(self): """ Increments the page number. """
- self._page = (self._page + 1) % len(self._pagePanels) - self._forceRedraw = True - self.setMsg() + self.setPage((self._page + 1) % len(self._pagePanels))
def prevPage(self): """ Decrements the page number. """
- self._page = (self._page - 1) % len(self._pagePanels) - self._forceRedraw = True - self.setMsg() + self.setPage((self._page - 1) % len(self._pagePanels))
def isPaused(self): """ @@ -227,20 +247,22 @@ class Controller:
return list(self._stickyPanels)
- def getDisplayPanels(self, includeSticky = True): + def getDisplayPanels(self, pageNumber = None, includeSticky = True): """ - Provides all panels belonging to the current page and sticky content above - it. This is ordered they way they are presented (top to bottom) on the - page. + Provides all panels belonging to a page and sticky content above it. This + is ordered they way they are presented (top to bottom) on the page.
Arguments: + pageNumber - page number of the panels to be returned, the current + page if None includeSticky - includes sticky panels in the results if true """
+ returnPage = self._page if pageNumber == None else pageNumber + if includeSticky: - return self._stickyPanels + self._pagePanels[self._page] - else: - return list(self._pagePanels[self._page]) + return self._stickyPanels + self._pagePanels[returnPage] + else: return list(self._pagePanels[returnPage])
def getDaemonPanels(self): """ diff --git a/src/cli/descriptorPopup.py b/src/cli/descriptorPopup.py index f75d7e6..5b9f646 100644 --- a/src/cli/descriptorPopup.py +++ b/src/cli/descriptorPopup.py @@ -105,7 +105,7 @@ def showDescriptorPopup(connectionPanel):
# hides the title of the first panel on the page control = controller.getController() - topPanel = control.getDisplayPanels(False)[0] + topPanel = control.getDisplayPanels(includeSticky = False)[0] topPanel.setTitleVisible(False) topPanel.redraw(True)
diff --git a/src/cli/menu/actions.py b/src/cli/menu/actions.py index 751751f..f81a162 100644 --- a/src/cli/menu/actions.py +++ b/src/cli/menu/actions.py @@ -7,7 +7,7 @@ import functools import cli.controller import cli.menu.item
-from util import torTools +from util import torTools, uiTools
def makeMenu(): """ @@ -16,6 +16,7 @@ def makeMenu():
baseMenu = cli.menu.item.Submenu("") baseMenu.add(makeActionsMenu()) + baseMenu.add(makeViewMenu())
logsMenu = cli.menu.item.Submenu("Logs") logsMenu.add(cli.menu.item.MenuItem("Events", None)) @@ -29,13 +30,6 @@ def makeMenu(): logsMenu.add(duplicatesSubmenu) baseMenu.add(logsMenu)
- viewMenu = cli.menu.item.Submenu("View") - viewMenu.add(cli.menu.item.MenuItem("Graph", None)) - viewMenu.add(cli.menu.item.MenuItem("Connections", None)) - viewMenu.add(cli.menu.item.MenuItem("Configuration", None)) - viewMenu.add(cli.menu.item.MenuItem("Configuration File", None)) - baseMenu.add(viewMenu) - graphMenu = cli.menu.item.Submenu("Graph") graphMenu.add(cli.menu.item.MenuItem("Stats", None))
@@ -93,3 +87,37 @@ def makeActionsMenu(): actionsMenu.add(cli.menu.item.MenuItem("Exit", control.quit)) return actionsMenu
+def makeViewMenu(): + """ + Submenu consisting of... + [X] <Page 1> + [ ] <Page 2> + [ ] etc... + Color (Submenu) + """ + + viewMenu = cli.menu.item.Submenu("View") + control = cli.controller.getController() + + if control.getPageCount() > 0: + pageGroup = cli.menu.item.SelectionGroup(control.setPage, control.getPage()) + + for i in range(control.getPageCount()): + pagePanels = control.getDisplayPanels(pageNumber = i, includeSticky = False) + label = " / ".join([uiTools.camelCase(panel.getName()) for panel in pagePanels]) + + viewMenu.add(cli.menu.item.SelectionMenuItem(label, pageGroup, i)) + + if uiTools.isColorSupported(): + colorMenu = cli.menu.item.Submenu("Color") + colorGroup = cli.menu.item.SelectionGroup(uiTools.setColorOverride, uiTools.getColorOverride()) + + colorMenu.add(cli.menu.item.SelectionMenuItem("All", colorGroup, None)) + + for color in uiTools.COLOR_LIST: + colorMenu.add(cli.menu.item.SelectionMenuItem(uiTools.camelCase(color), colorGroup, color)) + + viewMenu.add(colorMenu) + + return viewMenu + diff --git a/src/cli/menu/item.py b/src/cli/menu/item.py index 8c5d314..beaac9c 100644 --- a/src/cli/menu/item.py +++ b/src/cli/menu/item.py @@ -147,3 +147,49 @@ class Submenu(MenuItem): def select(self): return False
+class SelectionGroup(): + """ + Radio button groups that SelectionMenuItems can belong to. + """ + + def __init__(self, action, selectedArg): + self.action = action + self.selectedArg = selectedArg + +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 isSelected(self): + """ + True if we're the selected item, false otherwise. + """ + + return self._arg == self._group.selectedArg + + def getLabel(self): + """ + Provides our label with a "[X]" prefix if selected and "[ ]" if not. + """ + + myLabel = MenuItem.getLabel(self)[1] + myPrefix = "[X] " if self.isSelected() else "[ ] " + return (myPrefix, myLabel, "") + + def select(self): + """ + Performs the group's setter action with our argument. + """ + + if not self.isSelected(): + self._group.action(self._arg) + + return True + diff --git a/src/cli/popups.py b/src/cli/popups.py index 8061c8f..5f1eac2 100644 --- a/src/cli/popups.py +++ b/src/cli/popups.py @@ -280,7 +280,7 @@ def showMenu(title, options, oldSelection): try: # hides the title of the first panel on the page control = cli.controller.getController() - topPanel = control.getDisplayPanels(False)[0] + topPanel = control.getDisplayPanels(includeSticky = False)[0] topPanel.setTitleVisible(False) topPanel.redraw(True)
diff --git a/src/cli/torrcPanel.py b/src/cli/torrcPanel.py index e14a16a..a12cc87 100644 --- a/src/cli/torrcPanel.py +++ b/src/cli/torrcPanel.py @@ -24,7 +24,7 @@ class TorrcPanel(panel.Panel): """
def __init__(self, stdscr, configType, config=None): - panel.Panel.__init__(self, stdscr, "configFile", 0) + panel.Panel.__init__(self, stdscr, "torrc", 0)
self._config = dict(DEFAULT_CONFIG) if config: diff --git a/src/util/uiTools.py b/src/util/uiTools.py index 5999c64..34d9210 100644 --- a/src/util/uiTools.py +++ b/src/util/uiTools.py @@ -18,6 +18,9 @@ COLOR_LIST = {"red": curses.COLOR_RED, "green": curses.COLOR_GREEN, "cyan": curses.COLOR_CYAN, "magenta": curses.COLOR_MAGENTA, "black": curses.COLOR_BLACK, "white": curses.COLOR_WHITE}
+# boolean for if we have color support enabled, None not yet determined +COLOR_IS_SUPPORTED = None + # mappings for getColor() - this uses the default terminal color scheme if # color support is unavailable COLOR_ATTR_INITIALIZED = False @@ -138,6 +141,14 @@ def getPrintable(line, keepNewlines = True): line = "".join([char for char in line if (isprint(char) or (keepNewlines and char == "\n"))]) return line
+def isColorSupported(): + """ + True if the display supports showing color, false otherwise. + """ + + if COLOR_IS_SUPPORTED == None: _initColors() + return COLOR_IS_SUPPORTED + def getColor(color): """ Provides attribute corresponding to a given text color. Supported colors @@ -261,6 +272,24 @@ def cropStr(msg, size, minWordLen = 4, minCrop = 0, endType = Ending.ELLIPSE, ge if getRemainder: return (returnMsg, remainder) else: return returnMsg
+def camelCase(label): + """ + Converts the given string to camel case, ie: + >>> camelCase("I_LIKE_PEPPERJACK!") + 'I Like Pepperjack!' + + Arguments: + label - input string to be converted + """ + + words = [] + for entry in label.split("_"): + if len(entry) == 0: words.append("") + elif len(entry) == 1: words.append(entry.upper()) + else: words.append(entry[0].upper() + entry[1:].lower()) + + return " ".join(words) + def drawBox(panel, top, left, width, height, attr=curses.A_NORMAL): """ Draws a box in the panel with the given bounds. @@ -719,16 +748,17 @@ def _initColors(): calling curses.initscr(). """
- global COLOR_ATTR_INITIALIZED + global COLOR_ATTR_INITIALIZED, COLOR_IS_SUPPORTED if not COLOR_ATTR_INITIALIZED: COLOR_ATTR_INITIALIZED = True + COLOR_IS_SUPPORTED = False if not CONFIG["features.colorInterface"]: return
- try: hasColorSupport = curses.has_colors() + try: COLOR_IS_SUPPORTED = curses.has_colors() except curses.error: return # initscr hasn't been called yet
# initializes color mappings if color support is available - if hasColorSupport: + if COLOR_IS_SUPPORTED: colorpair = 0 log.log(CONFIG["log.cursesColorSupport"], "Terminal color support detected and enabled")
tor-commits@lists.torproject.org