[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]
[or-cvs] r23865: {arm} Renaming new config panels. (arm/trunk/src/interface)
Author: atagar
Date: 2010-11-27 21:55:13 +0000 (Sat, 27 Nov 2010)
New Revision: 23865
Added:
arm/trunk/src/interface/configPanel.py
arm/trunk/src/interface/torrcPanel.py
Removed:
arm/trunk/src/interface/configFilePanel.py
arm/trunk/src/interface/configStatePanel.py
Modified:
arm/trunk/src/interface/__init__.py
arm/trunk/src/interface/controller.py
Log:
Renaming new config panels.
Modified: arm/trunk/src/interface/__init__.py
===================================================================
--- arm/trunk/src/interface/__init__.py 2010-11-27 20:49:44 UTC (rev 23864)
+++ arm/trunk/src/interface/__init__.py 2010-11-27 21:55:13 UTC (rev 23865)
@@ -2,5 +2,5 @@
Panels, popups, and handlers comprising the arm user interface.
"""
-__all__ = ["configFilePanel", "configStatePanel", "connPanel", "controller", "descriptorPopup", "fileDescriptorPopup", "headerPanel", "logPanel"]
+__all__ = ["configPanel", "connPanel", "controller", "descriptorPopup", "fileDescriptorPopup", "headerPanel", "logPanel", "torrcPanel"]
Deleted: arm/trunk/src/interface/configFilePanel.py
===================================================================
--- arm/trunk/src/interface/configFilePanel.py 2010-11-27 20:49:44 UTC (rev 23864)
+++ arm/trunk/src/interface/configFilePanel.py 2010-11-27 21:55:13 UTC (rev 23865)
@@ -1,212 +0,0 @@
-"""
-Panel displaying the torrc or armrc with the validation done against it.
-"""
-
-import math
-import curses
-import threading
-
-from util import conf, panel, torConfig, uiTools
-
-DEFAULT_CONFIG = {"features.config.file.showScrollbars": True,
- "features.config.file.maxLinesPerEntry": 8}
-
-TORRC, ARMRC = range(1, 3) # configuration file types that can be displayed
-
-class ConfigFilePanel(panel.Panel):
- """
- Renders the current torrc or armrc with syntax highlighting in a scrollable
- area.
- """
-
- def __init__(self, stdscr, configType, config=None):
- panel.Panel.__init__(self, stdscr, "configFile", 0)
-
- self._config = dict(DEFAULT_CONFIG)
- if config:
- config.update(self._config, {"features.config.file.maxLinesPerEntry": 1})
-
- self.valsLock = threading.RLock()
- self.configType = configType
- self.scroll = 0
- self.showLabel = True # shows top label (hides otherwise)
- self.showLineNum = True # shows left aligned line numbers
- self.stripComments = False # drops comments and extra whitespace
-
- # height of the content when last rendered (the cached value is invalid if
- # _lastContentHeightArgs is None or differs from the current dimensions)
- self._lastContentHeight = 1
- self._lastContentHeightArgs = None
-
- def handleKey(self, key):
- self.valsLock.acquire()
- if uiTools.isScrollKey(key):
- pageHeight = self.getPreferredSize()[0] - 1
- newScroll = uiTools.getScrollPosition(key, self.scroll, pageHeight, self._lastContentHeight)
-
- if self.scroll != newScroll:
- self.scroll = newScroll
- self.redraw(True)
- elif key == ord('n') or key == ord('N'):
- self.showLineNum = not self.showLineNum
- self._lastContentHeightArgs = None
- self.redraw(True)
- elif key == ord('s') or key == ord('S'):
- self.stripComments = not self.stripComments
- self._lastContentHeightArgs = None
- self.redraw(True)
-
- self.valsLock.release()
-
- def draw(self, subwindow, width, height):
- self.valsLock.acquire()
-
- # If true, we assume that the cached value in self._lastContentHeight is
- # still accurate, and stop drawing when there's nothing more to display.
- # Otherwise the self._lastContentHeight is suspect, and we'll process all
- # the content to check if it's right (and redraw again with the corrected
- # height if not).
- trustLastContentHeight = self._lastContentHeightArgs == (width, height)
-
- # restricts scroll location to valid bounds
- self.scroll = max(0, min(self.scroll, self._lastContentHeight - height + 1))
-
- renderedContents, corrections, confLocation = None, {}, None
- if self.configType == TORRC:
- loadedTorrc = torConfig.getTorrc()
- loadedTorrc.getLock().acquire()
- confLocation = loadedTorrc.getConfigLocation()
-
- if not loadedTorrc.isLoaded():
- renderedContents = ["### Unable to load the torrc ###"]
- else:
- renderedContents = loadedTorrc.getDisplayContents(self.stripComments)
- corrections = loadedTorrc.getCorrections()
-
- loadedTorrc.getLock().release()
- else:
- # TODO: The armrc use case is incomplete. There should be equivilant
- # reloading and validation capabilities to the torrc.
- loadedArmrc = conf.getConfig("arm")
- confLocation = loadedArmrc.path
- renderedContents = list(loadedArmrc.rawContents)
-
- # offset to make room for the line numbers
- lineNumOffset = 0
- if self.showLineNum:
- if len(renderedContents) == 0: lineNumOffset = 2
- else: lineNumOffset = int(math.log10(len(renderedContents))) + 2
-
- # draws left-hand scroll bar if content's longer than the height
- scrollOffset = 0
- if self._config["features.config.file.showScrollbars"] and self._lastContentHeight > height - 1:
- scrollOffset = 3
- self.addScrollBar(self.scroll, self.scroll + height - 1, self._lastContentHeight, 1)
-
- displayLine = -self.scroll + 1 # line we're drawing on
-
- # draws the top label
- if self.showLabel:
- sourceLabel = "Tor" if self.configType == TORRC else "Arm"
- locationLabel = " (%s)" % confLocation if confLocation else ""
- self.addstr(0, 0, "%s Configuration File%s:" % (sourceLabel, locationLabel), curses.A_STANDOUT)
-
- for lineNumber in range(0, len(renderedContents)):
- lineText = renderedContents[lineNumber]
- lineText = lineText.rstrip() # remove ending whitespace
-
- # blank lines are hidden when stripping comments
- if self.stripComments and not lineText: continue
-
- # splits the line into its component (msg, format) tuples
- lineComp = {"option": ["", curses.A_BOLD | uiTools.getColor("green")],
- "argument": ["", curses.A_BOLD | uiTools.getColor("cyan")],
- "correction": ["", curses.A_BOLD | uiTools.getColor("cyan")],
- "comment": ["", uiTools.getColor("white")]}
-
- # parses the comment
- commentIndex = lineText.find("#")
- if commentIndex != -1:
- lineComp["comment"][0] = lineText[commentIndex:]
- lineText = lineText[:commentIndex]
-
- # splits the option and argument, preserving any whitespace around them
- strippedLine = lineText.strip()
- optionIndex = strippedLine.find(" ")
- if optionIndex == -1:
- lineComp["option"][0] = lineText # no argument provided
- else:
- optionText = strippedLine[:optionIndex]
- optionEnd = lineText.find(optionText) + len(optionText)
- lineComp["option"][0] = lineText[:optionEnd]
- lineComp["argument"][0] = lineText[optionEnd:]
-
- # gets the correction
- if lineNumber in corrections:
- lineIssue, lineIssueMsg = corrections[lineNumber]
-
- if lineIssue == torConfig.VAL_DUPLICATE:
- lineComp["option"][1] = curses.A_BOLD | uiTools.getColor("blue")
- lineComp["argument"][1] = curses.A_BOLD | uiTools.getColor("blue")
- elif lineIssue == torConfig.VAL_MISMATCH:
- lineComp["argument"][1] = curses.A_BOLD | uiTools.getColor("red")
- lineComp["correction"][0] = " (%s)" % lineIssueMsg
- else:
- # For some types of configs the correction field is simply used to
- # provide extra data (for instance, the type for tor state fields).
- lineComp["correction"][0] = " (%s)" % lineIssueMsg
- lineComp["correction"][1] = curses.A_BOLD | uiTools.getColor("magenta")
-
- # draws the line number
- if self.showLineNum and displayLine < height and displayLine >= 1:
- lineNumStr = ("%%%ii" % (lineNumOffset - 1)) % (lineNumber + 1)
- self.addstr(displayLine, scrollOffset, lineNumStr, curses.A_BOLD | uiTools.getColor("yellow"))
-
- # draws the rest of the components with line wrap
- cursorLoc, lineOffset = lineNumOffset + scrollOffset, 0
- maxLinesPerEntry = self._config["features.config.file.maxLinesPerEntry"]
- displayQueue = [lineComp[entry] for entry in ("option", "argument", "correction", "comment")]
-
- while displayQueue:
- msg, format = displayQueue.pop(0)
-
- maxMsgSize, includeBreak = width - cursorLoc, False
- if len(msg) >= maxMsgSize:
- # message is too long - break it up
- if lineOffset == maxLinesPerEntry - 1:
- msg = uiTools.cropStr(msg, maxMsgSize)
- else:
- includeBreak = True
- msg, remainder = uiTools.cropStr(msg, maxMsgSize, 4, 4, uiTools.END_WITH_HYPHEN, True)
- displayQueue.insert(0, (remainder.strip(), format))
-
- drawLine = displayLine + lineOffset
- if msg and drawLine < height and drawLine >= 1:
- self.addstr(drawLine, cursorLoc, msg, format)
-
- # If we're done, and have added content to this line, then start
- # further content on the next line.
- cursorLoc += len(msg)
- includeBreak |= not displayQueue and cursorLoc != lineNumOffset + scrollOffset
-
- if includeBreak:
- lineOffset += 1
- cursorLoc = lineNumOffset + scrollOffset
-
- displayLine += max(lineOffset, 1)
-
- if trustLastContentHeight and displayLine >= height: break
-
- if not trustLastContentHeight:
- self._lastContentHeightArgs = (width, height)
- newContentHeight = displayLine + self.scroll - 1
-
- if self._lastContentHeight != newContentHeight:
- self._lastContentHeight = newContentHeight
- self.redraw(True)
-
- self.valsLock.release()
-
- def redraw(self, forceRedraw=False, block=False):
- panel.Panel.redraw(self, forceRedraw, block)
-
Copied: arm/trunk/src/interface/configPanel.py (from rev 23862, arm/trunk/src/interface/configStatePanel.py)
===================================================================
--- arm/trunk/src/interface/configPanel.py (rev 0)
+++ arm/trunk/src/interface/configPanel.py 2010-11-27 21:55:13 UTC (rev 23865)
@@ -0,0 +1,336 @@
+"""
+Panel presenting the configuration state for tor or arm. Options can be edited
+and the resulting configuration files saved.
+"""
+
+import curses
+import threading
+
+from util import conf, panel, torTools, torConfig, uiTools
+
+DEFAULT_CONFIG = {"features.config.selectionDetails.height": 6,
+ "features.config.state.showPrivateOptions": False,
+ "features.config.state.showVirtualOptions": False,
+ "features.config.state.colWidth.option": 25,
+ "features.config.state.colWidth.value": 10}
+
+TOR_STATE, ARM_STATE = range(1, 3) # state to be presented
+
+# mappings of option categories to the color for their entries
+CATEGORY_COLOR = {torConfig.GENERAL: "green",
+ torConfig.CLIENT: "blue",
+ torConfig.SERVER: "yellow",
+ torConfig.DIRECTORY: "magenta",
+ torConfig.AUTHORITY: "red",
+ torConfig.HIDDEN_SERVICE: "cyan",
+ torConfig.TESTING: "white",
+ torConfig.UNKNOWN: "white"}
+
+# attributes of a ConfigEntry
+FIELD_CATEGORY, FIELD_OPTION, FIELD_VALUE, FIELD_TYPE, FIELD_ARG_USAGE, FIELD_DESCRIPTION, FIELD_MAN_ENTRY, FIELD_IS_DEFAULT = range(8)
+DEFAULT_SORT_ORDER = (FIELD_CATEGORY, FIELD_MAN_ENTRY, FIELD_IS_DEFAULT)
+FIELD_ATTR = {FIELD_CATEGORY: ("Category", "red"),
+ FIELD_OPTION: ("Option Name", "blue"),
+ FIELD_VALUE: ("Value", "cyan"),
+ FIELD_TYPE: ("Arg Type", "green"),
+ FIELD_ARG_USAGE: ("Arg Usage", "yellow"),
+ FIELD_DESCRIPTION: ("Description", "white"),
+ FIELD_MAN_ENTRY: ("Man Page Entry", "blue"),
+ FIELD_IS_DEFAULT: ("Is Default", "magenta")}
+
+class ConfigEntry():
+ """
+ Configuration option in the panel.
+ """
+
+ def __init__(self, option, type, isDefault, manEntry):
+ self.fields = {}
+ self.fields[FIELD_OPTION] = option
+ self.fields[FIELD_TYPE] = type
+ self.fields[FIELD_IS_DEFAULT] = isDefault
+
+ if manEntry:
+ self.fields[FIELD_MAN_ENTRY] = manEntry.index
+ self.fields[FIELD_CATEGORY] = manEntry.category
+ self.fields[FIELD_ARG_USAGE] = manEntry.argUsage
+ self.fields[FIELD_DESCRIPTION] = manEntry.description
+ else:
+ self.fields[FIELD_MAN_ENTRY] = 99999 # sorts non-man entries last
+ self.fields[FIELD_CATEGORY] = torConfig.UNKNOWN
+ self.fields[FIELD_ARG_USAGE] = ""
+ self.fields[FIELD_DESCRIPTION] = ""
+
+ def get(self, field):
+ """
+ Provides back the value in the given field.
+
+ Arguments:
+ field - enum for the field to be provided back
+ """
+
+ if field == FIELD_VALUE: return self._getValue()
+ else: return self.fields[field]
+
+ def _getValue(self):
+ """
+ Provides the current value of the configuration entry, taking advantage of
+ the torTools caching to effectively query the accurate value. This uses the
+ value's type to provide a user friendly representation if able.
+ """
+
+ confValue = ", ".join(torTools.getConn().getOption(self.get(FIELD_OPTION), [], True))
+
+ # provides nicer values for recognized types
+ if not confValue: confValue = "<none>"
+ elif self.get(FIELD_TYPE) == "Boolean" and confValue in ("0", "1"):
+ confValue = "False" if confValue == "0" else "True"
+ elif self.get(FIELD_TYPE) == "DataSize" and confValue.isdigit():
+ confValue = uiTools.getSizeLabel(int(confValue))
+ elif self.get(FIELD_TYPE) == "TimeInterval" and confValue.isdigit():
+ confValue = uiTools.getTimeLabel(int(confValue), isLong = True)
+
+ return confValue
+
+ def getAttr(self, argTypes):
+ """
+ Provides back a list with the given parameters.
+
+ Arguments:
+ argTypes - list of enums for the arguments to be provided back
+ """
+
+ return [self.get(field) for field in argTypes]
+
+class ConfigStatePanel(panel.Panel):
+ """
+ Renders a listing of the tor or arm configuration state, allowing options to
+ be selected and edited.
+ """
+
+ def __init__(self, stdscr, configType, config=None):
+ panel.Panel.__init__(self, stdscr, "configState", 0)
+
+ self.sortOrdering = DEFAULT_SORT_ORDER
+ self._config = dict(DEFAULT_CONFIG)
+ if config:
+ config.update(self._config, {
+ "features.config.selectionDetails.height": 0,
+ "features.config.state.colWidth.option": 5,
+ "features.config.state.colWidth.value": 5})
+
+ self.sortOrdering = config.getIntCSV("features.config.order", self.sortOrdering, 3, 0, 6)
+
+ self.configType = configType
+ self.confContents = []
+ self.scroller = uiTools.Scroller(True)
+ self.valsLock = threading.RLock()
+
+ # TODO: this will need to be able to listen for SETCONF events (arg!)
+
+ if self.configType == TOR_STATE:
+ conn = torTools.getConn()
+
+ # gets options that differ from their default
+ setOptions = set()
+ configTextQuery = conn.getInfo("config-text", "").strip().split("\n")
+ for entry in configTextQuery: setOptions.add(entry[:entry.find(" ")])
+
+ # for all recognized tor config options, provide their current value
+ configOptionQuery = conn.getInfo("config/names", "").strip().split("\n")
+
+ for lineNum in range(len(configOptionQuery)):
+ # lines are of the form "<option> <type>", like:
+ # UseEntryGuards Boolean
+ line = configOptionQuery[lineNum]
+ confOption, confType = line.strip().split(" ", 1)
+
+ # skips private and virtual entries if not set to show them
+ if not self._config["features.config.state.showPrivateOptions"] and confOption.startswith("__"):
+ continue
+ elif not self._config["features.config.state.showVirtualOptions"] and confType == "Virtual":
+ continue
+
+ manEntry = torConfig.getConfigDescription(confOption)
+ self.confContents.append(ConfigEntry(confOption, confType, not confOption in setOptions, manEntry))
+
+
+ self.setSortOrder() # initial sorting of the contents
+ elif self.configType == ARM_STATE:
+ # loaded via the conf utility
+ armConf = conf.getConfig("arm")
+ for key in armConf.getKeys():
+ self.confContents.append(ConfigEntry("", key, ", ".join(armConf.getValue(key, [], True)), "", "", True))
+
+ def getSelection(self):
+ """
+ Provides the currently selected entry.
+ """
+
+ return self.scroller.getCursorSelection(self.confContents)
+
+ def setSortOrder(self, ordering = None):
+ """
+ Sets the configuration attributes we're sorting by and resorts the
+ contents. If the ordering isn't defined then this resorts based on the
+ last set ordering.
+ """
+
+ self.valsLock.acquire()
+ if ordering: self.sortOrdering = ordering
+ self.confContents.sort(key=lambda i: (i.getAttr(self.sortOrdering)))
+ self.valsLock.release()
+
+ def getSortOrder(self):
+ """
+ Provides the current configuration attributes we're sorting by.
+ """
+
+ return self.sortOrdering
+
+ def handleKey(self, key):
+ self.valsLock.acquire()
+ if uiTools.isScrollKey(key):
+ pageHeight = self.getPreferredSize()[0] - 1
+ detailPanelHeight = self._config["features.config.selectionDetails.height"]
+ if detailPanelHeight > 0 and detailPanelHeight + 2 <= pageHeight:
+ pageHeight -= (detailPanelHeight + 1)
+
+ isChanged = self.scroller.handleKey(key, self.confContents, pageHeight)
+ if isChanged: self.redraw(True)
+ self.valsLock.release()
+
+ def draw(self, subwindow, width, height):
+ self.valsLock.acquire()
+
+ # draws the top label
+ titleLabel = "%s Configuration:" % ("Tor" if self.configType == TOR_STATE else "Arm")
+ self.addstr(0, 0, titleLabel, curses.A_STANDOUT)
+
+ # panel with details for the current selection
+ detailPanelHeight = self._config["features.config.selectionDetails.height"]
+ if detailPanelHeight == 0 or detailPanelHeight + 2 >= height:
+ # no detail panel
+ detailPanelHeight = 0
+ scrollLoc = self.scroller.getScrollLoc(self.confContents, height - 1)
+ cursorSelection = self.scroller.getCursorSelection(self.confContents)
+ else:
+ # Shrink detail panel if there isn't sufficient room for the whole
+ # thing. The extra line is for the bottom border.
+ detailPanelHeight = min(height - 1, detailPanelHeight + 1)
+ scrollLoc = self.scroller.getScrollLoc(self.confContents, height - 1 - detailPanelHeight)
+ cursorSelection = self.scroller.getCursorSelection(self.confContents)
+
+ self._drawSelectionPanel(cursorSelection, width, detailPanelHeight, titleLabel)
+
+ # draws left-hand scroll bar if content's longer than the height
+ scrollOffset = 0
+ if len(self.confContents) > height - detailPanelHeight - 1:
+ scrollOffset = 3
+ self.addScrollBar(scrollLoc, scrollLoc + height - detailPanelHeight - 1, len(self.confContents), 1 + detailPanelHeight)
+
+ # determines the width for the columns
+ optionColWidth, valueColWidth = 0, 0
+
+ # constructs a mapping of entries to their current values
+ # TODO: just skip dynamic widths entirely?
+ entryToValues = {}
+ for entry in self.confContents:
+ entryToValues[entry] = entry.get(FIELD_VALUE)
+ #optionColWidth = max(optionColWidth, len(entry.get(FIELD_OPTION)))
+ #valueColWidth = max(valueColWidth, len(entryToValues[entry]))
+
+ #optionColWidth = min(self._config["features.config.state.colWidth.option"], optionColWidth)
+ #valueColWidth = min(self._config["features.config.state.colWidth.value"], valueColWidth)
+ optionColWidth = self._config["features.config.state.colWidth.option"]
+ valueColWidth = self._config["features.config.state.colWidth.value"]
+ descriptionColWidth = max(0, width - scrollOffset - optionColWidth - valueColWidth - 2)
+
+ for lineNum in range(scrollLoc, len(self.confContents)):
+ entry = self.confContents[lineNum]
+ drawLine = lineNum + detailPanelHeight + 1 - scrollLoc
+
+ # TODO: need to cut off description at the first newline
+ optionLabel = uiTools.cropStr(entry.get(FIELD_OPTION), optionColWidth)
+ valueLabel = uiTools.cropStr(entryToValues[entry], valueColWidth)
+ descriptionLabel = uiTools.cropStr(entry.get(FIELD_DESCRIPTION), descriptionColWidth, None)
+
+ lineFormat = curses.A_NORMAL if entry.get(FIELD_IS_DEFAULT) else curses.A_BOLD
+ if entry.get(FIELD_CATEGORY): lineFormat |= uiTools.getColor(CATEGORY_COLOR[entry.get(FIELD_CATEGORY)])
+ #lineFormat = uiTools.getColor("green") if entry.isDefault else curses.A_BOLD | uiTools.getColor("yellow")
+ if entry == cursorSelection: lineFormat |= curses.A_STANDOUT
+
+ lineTextLayout = "%%-%is %%-%is %%-%is" % (optionColWidth, valueColWidth, descriptionColWidth)
+ lineText = lineTextLayout % (optionLabel, valueLabel, descriptionLabel)
+ self.addstr(drawLine, scrollOffset, lineText, lineFormat)
+
+ if drawLine >= height: break
+
+ self.valsLock.release()
+
+ def _drawSelectionPanel(self, cursorSelection, width, detailPanelHeight, titleLabel):
+ """
+ Renders a panel for the selected configuration option.
+ """
+
+ # border (top)
+ if width >= len(titleLabel):
+ self.win.hline(0, len(titleLabel), curses.ACS_HLINE, width - len(titleLabel))
+ self.win.addch(0, width, curses.ACS_URCORNER)
+
+ # border (sides)
+ self.win.vline(1, 0, curses.ACS_VLINE, detailPanelHeight - 1)
+ self.win.vline(1, width, curses.ACS_VLINE, detailPanelHeight - 1)
+
+ # border (bottom)
+ self.win.addch(detailPanelHeight, 0, curses.ACS_LLCORNER)
+ if width >= 2: self.win.addch(detailPanelHeight, 1, curses.ACS_TTEE)
+ if width >= 3: self.win.hline(detailPanelHeight, 2, curses.ACS_HLINE, width - 2)
+ self.win.addch(detailPanelHeight, width, curses.ACS_LRCORNER)
+
+ selectionFormat = curses.A_BOLD | uiTools.getColor(CATEGORY_COLOR[cursorSelection.get(FIELD_CATEGORY)])
+
+ # first entry:
+ # <option> (<category> Option)
+ optionLabel =" (%s Option)" % torConfig.OPTION_CATEGORY_STR[cursorSelection.get(FIELD_CATEGORY)]
+ self.addstr(1, 2, cursorSelection.get(FIELD_OPTION) + optionLabel, selectionFormat)
+
+ # second entry:
+ # Value: <value> ([default|custom], <type>, usage: <argument usage>)
+ if detailPanelHeight >= 3:
+ valueAttr = []
+ valueAttr.append("default" if cursorSelection.get(FIELD_IS_DEFAULT) else "custom")
+ valueAttr.append(cursorSelection.get(FIELD_TYPE))
+ valueAttr.append("usage: %s" % (cursorSelection.get(FIELD_ARG_USAGE)))
+ valueAttrLabel = ", ".join(valueAttr)
+
+ valueLabelWidth = width - 12 - len(valueAttrLabel)
+ valueLabel = uiTools.cropStr(cursorSelection.get(FIELD_VALUE), valueLabelWidth)
+
+ self.addstr(2, 2, "Value: %s (%s)" % (valueLabel, valueAttrLabel), selectionFormat)
+
+ # remainder is filled with the man page description
+ descriptionHeight = max(0, detailPanelHeight - 3)
+ descriptionContent = "Description: " + cursorSelection.get(FIELD_DESCRIPTION)
+
+ for i in range(descriptionHeight):
+ # checks if we're done writing the description
+ if not descriptionContent: break
+
+ # there's a leading indent after the first line
+ if i > 0: descriptionContent = " " + descriptionContent
+
+ # we only want to work with content up until the next newline
+ if "\n" in descriptionContent:
+ lineContent, descriptionContent = descriptionContent.split("\n", 1)
+ else: lineContent, descriptionContent = descriptionContent, ""
+
+ if i != descriptionHeight - 1:
+ # there's more lines to display
+ msg, remainder = uiTools.cropStr(lineContent, width - 2, 4, 4, uiTools.END_WITH_HYPHEN, True)
+ descriptionContent = remainder.strip() + descriptionContent
+ else:
+ # this is the last line, end it with an ellipse
+ msg = uiTools.cropStr(lineContent, width - 2, 4, 4)
+
+ self.addstr(3 + i, 2, msg, selectionFormat)
+
Deleted: arm/trunk/src/interface/configStatePanel.py
===================================================================
--- arm/trunk/src/interface/configStatePanel.py 2010-11-27 20:49:44 UTC (rev 23864)
+++ arm/trunk/src/interface/configStatePanel.py 2010-11-27 21:55:13 UTC (rev 23865)
@@ -1,336 +0,0 @@
-"""
-Panel presenting the configuration state for tor or arm. Options can be edited
-and the resulting configuration files saved.
-"""
-
-import curses
-import threading
-
-from util import conf, panel, torTools, torConfig, uiTools
-
-DEFAULT_CONFIG = {"features.config.selectionDetails.height": 6,
- "features.config.state.showPrivateOptions": False,
- "features.config.state.showVirtualOptions": False,
- "features.config.state.colWidth.option": 25,
- "features.config.state.colWidth.value": 10}
-
-TOR_STATE, ARM_STATE = range(1, 3) # state to be presented
-
-# mappings of option categories to the color for their entries
-CATEGORY_COLOR = {torConfig.GENERAL: "green",
- torConfig.CLIENT: "blue",
- torConfig.SERVER: "yellow",
- torConfig.DIRECTORY: "magenta",
- torConfig.AUTHORITY: "red",
- torConfig.HIDDEN_SERVICE: "cyan",
- torConfig.TESTING: "white",
- torConfig.UNKNOWN: "white"}
-
-# attributes of a ConfigEntry
-FIELD_CATEGORY, FIELD_OPTION, FIELD_VALUE, FIELD_TYPE, FIELD_ARG_USAGE, FIELD_DESCRIPTION, FIELD_MAN_ENTRY, FIELD_IS_DEFAULT = range(8)
-DEFAULT_SORT_ORDER = (FIELD_CATEGORY, FIELD_MAN_ENTRY, FIELD_IS_DEFAULT)
-FIELD_ATTR = {FIELD_CATEGORY: ("Category", "red"),
- FIELD_OPTION: ("Option Name", "blue"),
- FIELD_VALUE: ("Value", "cyan"),
- FIELD_TYPE: ("Arg Type", "green"),
- FIELD_ARG_USAGE: ("Arg Usage", "yellow"),
- FIELD_DESCRIPTION: ("Description", "white"),
- FIELD_MAN_ENTRY: ("Man Page Entry", "blue"),
- FIELD_IS_DEFAULT: ("Is Default", "magenta")}
-
-class ConfigEntry():
- """
- Configuration option in the panel.
- """
-
- def __init__(self, option, type, isDefault, manEntry):
- self.fields = {}
- self.fields[FIELD_OPTION] = option
- self.fields[FIELD_TYPE] = type
- self.fields[FIELD_IS_DEFAULT] = isDefault
-
- if manEntry:
- self.fields[FIELD_MAN_ENTRY] = manEntry.index
- self.fields[FIELD_CATEGORY] = manEntry.category
- self.fields[FIELD_ARG_USAGE] = manEntry.argUsage
- self.fields[FIELD_DESCRIPTION] = manEntry.description
- else:
- self.fields[FIELD_MAN_ENTRY] = 99999 # sorts non-man entries last
- self.fields[FIELD_CATEGORY] = torConfig.UNKNOWN
- self.fields[FIELD_ARG_USAGE] = ""
- self.fields[FIELD_DESCRIPTION] = ""
-
- def get(self, field):
- """
- Provides back the value in the given field.
-
- Arguments:
- field - enum for the field to be provided back
- """
-
- if field == FIELD_VALUE: return self._getValue()
- else: return self.fields[field]
-
- def _getValue(self):
- """
- Provides the current value of the configuration entry, taking advantage of
- the torTools caching to effectively query the accurate value. This uses the
- value's type to provide a user friendly representation if able.
- """
-
- confValue = ", ".join(torTools.getConn().getOption(self.get(FIELD_OPTION), [], True))
-
- # provides nicer values for recognized types
- if not confValue: confValue = "<none>"
- elif self.get(FIELD_TYPE) == "Boolean" and confValue in ("0", "1"):
- confValue = "False" if confValue == "0" else "True"
- elif self.get(FIELD_TYPE) == "DataSize" and confValue.isdigit():
- confValue = uiTools.getSizeLabel(int(confValue))
- elif self.get(FIELD_TYPE) == "TimeInterval" and confValue.isdigit():
- confValue = uiTools.getTimeLabel(int(confValue), isLong = True)
-
- return confValue
-
- def getAttr(self, argTypes):
- """
- Provides back a list with the given parameters.
-
- Arguments:
- argTypes - list of enums for the arguments to be provided back
- """
-
- return [self.get(field) for field in argTypes]
-
-class ConfigStatePanel(panel.Panel):
- """
- Renders a listing of the tor or arm configuration state, allowing options to
- be selected and edited.
- """
-
- def __init__(self, stdscr, configType, config=None):
- panel.Panel.__init__(self, stdscr, "configState", 0)
-
- self.sortOrdering = DEFAULT_SORT_ORDER
- self._config = dict(DEFAULT_CONFIG)
- if config:
- config.update(self._config, {
- "features.config.selectionDetails.height": 0,
- "features.config.state.colWidth.option": 5,
- "features.config.state.colWidth.value": 5})
-
- self.sortOrdering = config.getIntCSV("features.config.order", self.sortOrdering, 3, 0, 6)
-
- self.configType = configType
- self.confContents = []
- self.scroller = uiTools.Scroller(True)
- self.valsLock = threading.RLock()
-
- # TODO: this will need to be able to listen for SETCONF events (arg!)
-
- if self.configType == TOR_STATE:
- conn = torTools.getConn()
-
- # gets options that differ from their default
- setOptions = set()
- configTextQuery = conn.getInfo("config-text", "").strip().split("\n")
- for entry in configTextQuery: setOptions.add(entry[:entry.find(" ")])
-
- # for all recognized tor config options, provide their current value
- configOptionQuery = conn.getInfo("config/names", "").strip().split("\n")
-
- for lineNum in range(len(configOptionQuery)):
- # lines are of the form "<option> <type>", like:
- # UseEntryGuards Boolean
- line = configOptionQuery[lineNum]
- confOption, confType = line.strip().split(" ", 1)
-
- # skips private and virtual entries if not set to show them
- if not self._config["features.config.state.showPrivateOptions"] and confOption.startswith("__"):
- continue
- elif not self._config["features.config.state.showVirtualOptions"] and confType == "Virtual":
- continue
-
- manEntry = torConfig.getConfigDescription(confOption)
- self.confContents.append(ConfigEntry(confOption, confType, not confOption in setOptions, manEntry))
-
-
- self.setSortOrder() # initial sorting of the contents
- elif self.configType == ARM_STATE:
- # loaded via the conf utility
- armConf = conf.getConfig("arm")
- for key in armConf.getKeys():
- self.confContents.append(ConfigEntry("", key, ", ".join(armConf.getValue(key, [], True)), "", "", True))
-
- def getSelection(self):
- """
- Provides the currently selected entry.
- """
-
- return self.scroller.getCursorSelection(self.confContents)
-
- def setSortOrder(self, ordering = None):
- """
- Sets the configuration attributes we're sorting by and resorts the
- contents. If the ordering isn't defined then this resorts based on the
- last set ordering.
- """
-
- self.valsLock.acquire()
- if ordering: self.sortOrdering = ordering
- self.confContents.sort(key=lambda i: (i.getAttr(self.sortOrdering)))
- self.valsLock.release()
-
- def getSortOrder(self):
- """
- Provides the current configuration attributes we're sorting by.
- """
-
- return self.sortOrdering
-
- def handleKey(self, key):
- self.valsLock.acquire()
- if uiTools.isScrollKey(key):
- pageHeight = self.getPreferredSize()[0] - 1
- detailPanelHeight = self._config["features.config.selectionDetails.height"]
- if detailPanelHeight > 0 and detailPanelHeight + 2 <= pageHeight:
- pageHeight -= (detailPanelHeight + 1)
-
- isChanged = self.scroller.handleKey(key, self.confContents, pageHeight)
- if isChanged: self.redraw(True)
- self.valsLock.release()
-
- def draw(self, subwindow, width, height):
- self.valsLock.acquire()
-
- # draws the top label
- titleLabel = "%s Configuration:" % ("Tor" if self.configType == TOR_STATE else "Arm")
- self.addstr(0, 0, titleLabel, curses.A_STANDOUT)
-
- # panel with details for the current selection
- detailPanelHeight = self._config["features.config.selectionDetails.height"]
- if detailPanelHeight == 0 or detailPanelHeight + 2 >= height:
- # no detail panel
- detailPanelHeight = 0
- scrollLoc = self.scroller.getScrollLoc(self.confContents, height - 1)
- cursorSelection = self.scroller.getCursorSelection(self.confContents)
- else:
- # Shrink detail panel if there isn't sufficient room for the whole
- # thing. The extra line is for the bottom border.
- detailPanelHeight = min(height - 1, detailPanelHeight + 1)
- scrollLoc = self.scroller.getScrollLoc(self.confContents, height - 1 - detailPanelHeight)
- cursorSelection = self.scroller.getCursorSelection(self.confContents)
-
- self._drawSelectionPanel(cursorSelection, width, detailPanelHeight, titleLabel)
-
- # draws left-hand scroll bar if content's longer than the height
- scrollOffset = 0
- if len(self.confContents) > height - detailPanelHeight - 1:
- scrollOffset = 3
- self.addScrollBar(scrollLoc, scrollLoc + height - detailPanelHeight - 1, len(self.confContents), 1 + detailPanelHeight)
-
- # determines the width for the columns
- optionColWidth, valueColWidth = 0, 0
-
- # constructs a mapping of entries to their current values
- # TODO: just skip dynamic widths entirely?
- entryToValues = {}
- for entry in self.confContents:
- entryToValues[entry] = entry.get(FIELD_VALUE)
- #optionColWidth = max(optionColWidth, len(entry.get(FIELD_OPTION)))
- #valueColWidth = max(valueColWidth, len(entryToValues[entry]))
-
- #optionColWidth = min(self._config["features.config.state.colWidth.option"], optionColWidth)
- #valueColWidth = min(self._config["features.config.state.colWidth.value"], valueColWidth)
- optionColWidth = self._config["features.config.state.colWidth.option"]
- valueColWidth = self._config["features.config.state.colWidth.value"]
- descriptionColWidth = max(0, width - scrollOffset - optionColWidth - valueColWidth - 2)
-
- for lineNum in range(scrollLoc, len(self.confContents)):
- entry = self.confContents[lineNum]
- drawLine = lineNum + detailPanelHeight + 1 - scrollLoc
-
- # TODO: need to cut off description at the first newline
- optionLabel = uiTools.cropStr(entry.get(FIELD_OPTION), optionColWidth)
- valueLabel = uiTools.cropStr(entryToValues[entry], valueColWidth)
- descriptionLabel = uiTools.cropStr(entry.get(FIELD_DESCRIPTION), descriptionColWidth, None)
-
- lineFormat = curses.A_NORMAL if entry.get(FIELD_IS_DEFAULT) else curses.A_BOLD
- if entry.get(FIELD_CATEGORY): lineFormat |= uiTools.getColor(CATEGORY_COLOR[entry.get(FIELD_CATEGORY)])
- #lineFormat = uiTools.getColor("green") if entry.isDefault else curses.A_BOLD | uiTools.getColor("yellow")
- if entry == cursorSelection: lineFormat |= curses.A_STANDOUT
-
- lineTextLayout = "%%-%is %%-%is %%-%is" % (optionColWidth, valueColWidth, descriptionColWidth)
- lineText = lineTextLayout % (optionLabel, valueLabel, descriptionLabel)
- self.addstr(drawLine, scrollOffset, lineText, lineFormat)
-
- if drawLine >= height: break
-
- self.valsLock.release()
-
- def _drawSelectionPanel(self, cursorSelection, width, detailPanelHeight, titleLabel):
- """
- Renders a panel for the selected configuration option.
- """
-
- # border (top)
- if width >= len(titleLabel):
- self.win.hline(0, len(titleLabel), curses.ACS_HLINE, width - len(titleLabel))
- self.win.addch(0, width, curses.ACS_URCORNER)
-
- # border (sides)
- self.win.vline(1, 0, curses.ACS_VLINE, detailPanelHeight - 1)
- self.win.vline(1, width, curses.ACS_VLINE, detailPanelHeight - 1)
-
- # border (bottom)
- self.win.addch(detailPanelHeight, 0, curses.ACS_LLCORNER)
- if width >= 2: self.win.addch(detailPanelHeight, 1, curses.ACS_TTEE)
- if width >= 3: self.win.hline(detailPanelHeight, 2, curses.ACS_HLINE, width - 2)
- self.win.addch(detailPanelHeight, width, curses.ACS_LRCORNER)
-
- selectionFormat = curses.A_BOLD | uiTools.getColor(CATEGORY_COLOR[cursorSelection.get(FIELD_CATEGORY)])
-
- # first entry:
- # <option> (<category> Option)
- optionLabel =" (%s Option)" % torConfig.OPTION_CATEGORY_STR[cursorSelection.get(FIELD_CATEGORY)]
- self.addstr(1, 2, cursorSelection.get(FIELD_OPTION) + optionLabel, selectionFormat)
-
- # second entry:
- # Value: <value> ([default|custom], <type>, usage: <argument usage>)
- if detailPanelHeight >= 3:
- valueAttr = []
- valueAttr.append("default" if cursorSelection.get(FIELD_IS_DEFAULT) else "custom")
- valueAttr.append(cursorSelection.get(FIELD_TYPE))
- valueAttr.append("usage: %s" % (cursorSelection.get(FIELD_ARG_USAGE)))
- valueAttrLabel = ", ".join(valueAttr)
-
- valueLabelWidth = width - 12 - len(valueAttrLabel)
- valueLabel = uiTools.cropStr(cursorSelection.get(FIELD_VALUE), valueLabelWidth)
-
- self.addstr(2, 2, "Value: %s (%s)" % (valueLabel, valueAttrLabel), selectionFormat)
-
- # remainder is filled with the man page description
- descriptionHeight = max(0, detailPanelHeight - 3)
- descriptionContent = "Description: " + cursorSelection.get(FIELD_DESCRIPTION)
-
- for i in range(descriptionHeight):
- # checks if we're done writing the description
- if not descriptionContent: break
-
- # there's a leading indent after the first line
- if i > 0: descriptionContent = " " + descriptionContent
-
- # we only want to work with content up until the next newline
- if "\n" in descriptionContent:
- lineContent, descriptionContent = descriptionContent.split("\n", 1)
- else: lineContent, descriptionContent = descriptionContent, ""
-
- if i != descriptionHeight - 1:
- # there's more lines to display
- msg, remainder = uiTools.cropStr(lineContent, width - 2, 4, 4, uiTools.END_WITH_HYPHEN, True)
- descriptionContent = remainder.strip() + descriptionContent
- else:
- # this is the last line, end it with an ellipse
- msg = uiTools.cropStr(lineContent, width - 2, 4, 4)
-
- self.addstr(3 + i, 2, msg, selectionFormat)
-
Modified: arm/trunk/src/interface/controller.py
===================================================================
--- arm/trunk/src/interface/controller.py 2010-11-27 20:49:44 UTC (rev 23864)
+++ arm/trunk/src/interface/controller.py 2010-11-27 21:55:13 UTC (rev 23865)
@@ -20,8 +20,8 @@
import graphing.graphPanel
import logPanel
import connPanel
-import configStatePanel
-import configFilePanel
+import configPanel
+import torrcPanel
import descriptorPopup
import fileDescriptorPopup
@@ -517,8 +517,8 @@
panels["conn"] = connPanel.ConnPanel(stdscr, conn, isBlindMode)
panels["control"] = ControlPanel(stdscr, isBlindMode)
- panels["config"] = configStatePanel.ConfigStatePanel(stdscr, configStatePanel.TOR_STATE, config)
- panels["torrc"] = configFilePanel.ConfigFilePanel(stdscr, configFilePanel.TORRC, config)
+ panels["config"] = configPanel.ConfigStatePanel(stdscr, configPanel.TOR_STATE, config)
+ panels["torrc"] = torrcPanel.ConfigFilePanel(stdscr, torrcPanel.TORRC, config)
# provides error if pid coulnd't be determined (hopefully shouldn't happen...)
if not torPid: log.log(log.WARN, "Unable to resolve tor pid, abandoning connection listing")
@@ -623,7 +623,7 @@
if panels["graph"].currentDisplay == "bandwidth":
panels["graph"].setHeight(panels["graph"].stats["bandwidth"].getContentHeight())
- # TODO: should redraw the configFilePanel
+ # TODO: should redraw the torrcPanel
#panels["torrc"].loadConfig()
sighupTracker.isReset = False
@@ -1582,9 +1582,9 @@
elif page == 2 and (key == ord('s') or key == ord('S')):
# set ordering for config options
titleLabel = "Config Option Ordering:"
- options = [configStatePanel.FIELD_ATTR[i][0] for i in range(8)]
- oldSelection = [configStatePanel.FIELD_ATTR[entry][0] for entry in panels["config"].sortOrdering]
- optionColors = dict([configStatePanel.FIELD_ATTR[i] for i in range(8)])
+ options = [configPanel.FIELD_ATTR[i][0] for i in range(8)]
+ oldSelection = [configPanel.FIELD_ATTR[entry][0] for entry in panels["config"].sortOrdering]
+ optionColors = dict([configPanel.FIELD_ATTR[i] for i in range(8)])
results = showSortDialog(stdscr, panels, isPaused, page, titleLabel, options, oldSelection, optionColors)
if results:
@@ -1592,8 +1592,8 @@
resultEnums = []
for label in results:
- for entryEnum in configStatePanel.FIELD_ATTR:
- if label == configStatePanel.FIELD_ATTR[entryEnum][0]:
+ for entryEnum in configPanel.FIELD_ATTR:
+ if label == configPanel.FIELD_ATTR[entryEnum][0]:
resultEnums.append(entryEnum)
break
@@ -1608,13 +1608,13 @@
# provides prompt
selection = panels["config"].getSelection()
- configOption = selection.get(configStatePanel.FIELD_OPTION)
+ configOption = selection.get(configPanel.FIELD_OPTION)
titleMsg = "%s Value (esc to cancel): " % configOption
panels["control"].setMsg(titleMsg)
panels["control"].redraw(True)
displayWidth = panels["control"].getPreferredSize()[1]
- initialValue = selection.get(configStatePanel.FIELD_VALUE)
+ initialValue = selection.get(configPanel.FIELD_VALUE)
# initial input for the text field
initialText = ""
@@ -1628,12 +1628,12 @@
conn = torTools.getConn()
# if the value's a boolean then allow for 'true' and 'false' inputs
- if selection.get(configStatePanel.FIELD_TYPE) == "Boolean":
+ if selection.get(configPanel.FIELD_TYPE) == "Boolean":
if newConfigValue.lower() == "true": newConfigValue = "1"
elif newConfigValue.lower() == "false": newConfigValue = "0"
try:
- if selection.get(configStatePanel.FIELD_TYPE) == "LineList":
+ if selection.get(configPanel.FIELD_TYPE) == "LineList":
newConfigValue = newConfigValue.split(",")
conn.setOption(configOption, newConfigValue)
@@ -1643,7 +1643,7 @@
configTextQuery = conn.getInfo("config-text", "").strip().split("\n")
for entry in configTextQuery: setOptions.add(entry[:entry.find(" ")])
- selection.fields[configStatePanel.FIELD_IS_DEFAULT] = not configOption in setOptions
+ selection.fields[configPanel.FIELD_IS_DEFAULT] = not configOption in setOptions
panels["config"].redraw(True)
except Exception, exc:
errorMsg = "%s (press any key)" % exc
Copied: arm/trunk/src/interface/torrcPanel.py (from rev 23853, arm/trunk/src/interface/configFilePanel.py)
===================================================================
--- arm/trunk/src/interface/torrcPanel.py (rev 0)
+++ arm/trunk/src/interface/torrcPanel.py 2010-11-27 21:55:13 UTC (rev 23865)
@@ -0,0 +1,212 @@
+"""
+Panel displaying the torrc or armrc with the validation done against it.
+"""
+
+import math
+import curses
+import threading
+
+from util import conf, panel, torConfig, uiTools
+
+DEFAULT_CONFIG = {"features.config.file.showScrollbars": True,
+ "features.config.file.maxLinesPerEntry": 8}
+
+TORRC, ARMRC = range(1, 3) # configuration file types that can be displayed
+
+class ConfigFilePanel(panel.Panel):
+ """
+ Renders the current torrc or armrc with syntax highlighting in a scrollable
+ area.
+ """
+
+ def __init__(self, stdscr, configType, config=None):
+ panel.Panel.__init__(self, stdscr, "configFile", 0)
+
+ self._config = dict(DEFAULT_CONFIG)
+ if config:
+ config.update(self._config, {"features.config.file.maxLinesPerEntry": 1})
+
+ self.valsLock = threading.RLock()
+ self.configType = configType
+ self.scroll = 0
+ self.showLabel = True # shows top label (hides otherwise)
+ self.showLineNum = True # shows left aligned line numbers
+ self.stripComments = False # drops comments and extra whitespace
+
+ # height of the content when last rendered (the cached value is invalid if
+ # _lastContentHeightArgs is None or differs from the current dimensions)
+ self._lastContentHeight = 1
+ self._lastContentHeightArgs = None
+
+ def handleKey(self, key):
+ self.valsLock.acquire()
+ if uiTools.isScrollKey(key):
+ pageHeight = self.getPreferredSize()[0] - 1
+ newScroll = uiTools.getScrollPosition(key, self.scroll, pageHeight, self._lastContentHeight)
+
+ if self.scroll != newScroll:
+ self.scroll = newScroll
+ self.redraw(True)
+ elif key == ord('n') or key == ord('N'):
+ self.showLineNum = not self.showLineNum
+ self._lastContentHeightArgs = None
+ self.redraw(True)
+ elif key == ord('s') or key == ord('S'):
+ self.stripComments = not self.stripComments
+ self._lastContentHeightArgs = None
+ self.redraw(True)
+
+ self.valsLock.release()
+
+ def draw(self, subwindow, width, height):
+ self.valsLock.acquire()
+
+ # If true, we assume that the cached value in self._lastContentHeight is
+ # still accurate, and stop drawing when there's nothing more to display.
+ # Otherwise the self._lastContentHeight is suspect, and we'll process all
+ # the content to check if it's right (and redraw again with the corrected
+ # height if not).
+ trustLastContentHeight = self._lastContentHeightArgs == (width, height)
+
+ # restricts scroll location to valid bounds
+ self.scroll = max(0, min(self.scroll, self._lastContentHeight - height + 1))
+
+ renderedContents, corrections, confLocation = None, {}, None
+ if self.configType == TORRC:
+ loadedTorrc = torConfig.getTorrc()
+ loadedTorrc.getLock().acquire()
+ confLocation = loadedTorrc.getConfigLocation()
+
+ if not loadedTorrc.isLoaded():
+ renderedContents = ["### Unable to load the torrc ###"]
+ else:
+ renderedContents = loadedTorrc.getDisplayContents(self.stripComments)
+ corrections = loadedTorrc.getCorrections()
+
+ loadedTorrc.getLock().release()
+ else:
+ # TODO: The armrc use case is incomplete. There should be equivilant
+ # reloading and validation capabilities to the torrc.
+ loadedArmrc = conf.getConfig("arm")
+ confLocation = loadedArmrc.path
+ renderedContents = list(loadedArmrc.rawContents)
+
+ # offset to make room for the line numbers
+ lineNumOffset = 0
+ if self.showLineNum:
+ if len(renderedContents) == 0: lineNumOffset = 2
+ else: lineNumOffset = int(math.log10(len(renderedContents))) + 2
+
+ # draws left-hand scroll bar if content's longer than the height
+ scrollOffset = 0
+ if self._config["features.config.file.showScrollbars"] and self._lastContentHeight > height - 1:
+ scrollOffset = 3
+ self.addScrollBar(self.scroll, self.scroll + height - 1, self._lastContentHeight, 1)
+
+ displayLine = -self.scroll + 1 # line we're drawing on
+
+ # draws the top label
+ if self.showLabel:
+ sourceLabel = "Tor" if self.configType == TORRC else "Arm"
+ locationLabel = " (%s)" % confLocation if confLocation else ""
+ self.addstr(0, 0, "%s Configuration File%s:" % (sourceLabel, locationLabel), curses.A_STANDOUT)
+
+ for lineNumber in range(0, len(renderedContents)):
+ lineText = renderedContents[lineNumber]
+ lineText = lineText.rstrip() # remove ending whitespace
+
+ # blank lines are hidden when stripping comments
+ if self.stripComments and not lineText: continue
+
+ # splits the line into its component (msg, format) tuples
+ lineComp = {"option": ["", curses.A_BOLD | uiTools.getColor("green")],
+ "argument": ["", curses.A_BOLD | uiTools.getColor("cyan")],
+ "correction": ["", curses.A_BOLD | uiTools.getColor("cyan")],
+ "comment": ["", uiTools.getColor("white")]}
+
+ # parses the comment
+ commentIndex = lineText.find("#")
+ if commentIndex != -1:
+ lineComp["comment"][0] = lineText[commentIndex:]
+ lineText = lineText[:commentIndex]
+
+ # splits the option and argument, preserving any whitespace around them
+ strippedLine = lineText.strip()
+ optionIndex = strippedLine.find(" ")
+ if optionIndex == -1:
+ lineComp["option"][0] = lineText # no argument provided
+ else:
+ optionText = strippedLine[:optionIndex]
+ optionEnd = lineText.find(optionText) + len(optionText)
+ lineComp["option"][0] = lineText[:optionEnd]
+ lineComp["argument"][0] = lineText[optionEnd:]
+
+ # gets the correction
+ if lineNumber in corrections:
+ lineIssue, lineIssueMsg = corrections[lineNumber]
+
+ if lineIssue == torConfig.VAL_DUPLICATE:
+ lineComp["option"][1] = curses.A_BOLD | uiTools.getColor("blue")
+ lineComp["argument"][1] = curses.A_BOLD | uiTools.getColor("blue")
+ elif lineIssue == torConfig.VAL_MISMATCH:
+ lineComp["argument"][1] = curses.A_BOLD | uiTools.getColor("red")
+ lineComp["correction"][0] = " (%s)" % lineIssueMsg
+ else:
+ # For some types of configs the correction field is simply used to
+ # provide extra data (for instance, the type for tor state fields).
+ lineComp["correction"][0] = " (%s)" % lineIssueMsg
+ lineComp["correction"][1] = curses.A_BOLD | uiTools.getColor("magenta")
+
+ # draws the line number
+ if self.showLineNum and displayLine < height and displayLine >= 1:
+ lineNumStr = ("%%%ii" % (lineNumOffset - 1)) % (lineNumber + 1)
+ self.addstr(displayLine, scrollOffset, lineNumStr, curses.A_BOLD | uiTools.getColor("yellow"))
+
+ # draws the rest of the components with line wrap
+ cursorLoc, lineOffset = lineNumOffset + scrollOffset, 0
+ maxLinesPerEntry = self._config["features.config.file.maxLinesPerEntry"]
+ displayQueue = [lineComp[entry] for entry in ("option", "argument", "correction", "comment")]
+
+ while displayQueue:
+ msg, format = displayQueue.pop(0)
+
+ maxMsgSize, includeBreak = width - cursorLoc, False
+ if len(msg) >= maxMsgSize:
+ # message is too long - break it up
+ if lineOffset == maxLinesPerEntry - 1:
+ msg = uiTools.cropStr(msg, maxMsgSize)
+ else:
+ includeBreak = True
+ msg, remainder = uiTools.cropStr(msg, maxMsgSize, 4, 4, uiTools.END_WITH_HYPHEN, True)
+ displayQueue.insert(0, (remainder.strip(), format))
+
+ drawLine = displayLine + lineOffset
+ if msg and drawLine < height and drawLine >= 1:
+ self.addstr(drawLine, cursorLoc, msg, format)
+
+ # If we're done, and have added content to this line, then start
+ # further content on the next line.
+ cursorLoc += len(msg)
+ includeBreak |= not displayQueue and cursorLoc != lineNumOffset + scrollOffset
+
+ if includeBreak:
+ lineOffset += 1
+ cursorLoc = lineNumOffset + scrollOffset
+
+ displayLine += max(lineOffset, 1)
+
+ if trustLastContentHeight and displayLine >= height: break
+
+ if not trustLastContentHeight:
+ self._lastContentHeightArgs = (width, height)
+ newContentHeight = displayLine + self.scroll - 1
+
+ if self._lastContentHeight != newContentHeight:
+ self._lastContentHeight = newContentHeight
+ self.redraw(True)
+
+ self.valsLock.release()
+
+ def redraw(self, forceRedraw=False, block=False):
+ panel.Panel.redraw(self, forceRedraw, block)
+