commit 3cc2ecf82d693a782c11d5a4f6fb41e94e7b5d05
Author: Damian Johnson <atagar(a)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")