/usr/share/arm/cli/popups.py is in tor-arm 1.4.5.0-1.1.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 | """
Functions for displaying popups in the interface.
"""
import curses
import version
import cli.controller
from util import panel, uiTools
def init(height = -1, width = -1, top = 0, left = 0, belowStatic = 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.
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
belowStatic - positions popup below static content if true
"""
control = cli.controller.getController()
if belowStatic:
stickyHeight = sum([stickyPanel.getHeight() for stickyPanel in control.getStickyPanels()])
else: stickyHeight = 0
popup = panel.Panel(control.getScreen(), "popup", top + stickyHeight, left, height, width)
popup.setVisible(True)
# 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)
if popup.win != None:
panel.CURSES_LOCK.acquire()
return (popup, popup.maxX - 1, popup.maxY)
else: return (None, 0, 0)
def finalize():
"""
Cleans up after displaying a popup, releasing the cureses lock and redrawing
the rest of the display.
"""
cli.controller.getController().requestRedraw()
panel.CURSES_LOCK.release()
def inputPrompt(msg, initialValue = ""):
"""
Prompts the user to enter a string on the control line (which usually
displays the page number and basic controls).
Arguments:
msg - message to prompt the user for input with
initialValue - initial value of the field
"""
panel.CURSES_LOCK.acquire()
control = cli.controller.getController()
msgPanel = control.getPanel("msg")
msgPanel.setMessage(msg)
msgPanel.redraw(True)
userInput = msgPanel.getstr(0, len(msg), initialValue)
control.setMsg()
panel.CURSES_LOCK.release()
return userInput
def showMsg(msg, maxWait = -1, attr = curses.A_STANDOUT):
"""
Displays a single line message on the control line for a set time. Pressing
any key will end the message. This returns the key pressed.
Arguments:
msg - message to be displayed to the user
maxWait - time to show the message, indefinite if -1
attr - attributes with which to draw the message
"""
panel.CURSES_LOCK.acquire()
control = cli.controller.getController()
control.setMsg(msg, attr, True)
if maxWait == -1: curses.cbreak()
else: curses.halfdelay(maxWait * 10)
keyPress = control.getScreen().getch()
control.setMsg()
panel.CURSES_LOCK.release()
return keyPress
def showHelpPopup():
"""
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.
"""
popup, _, height = init(9, 80)
if not popup: return
exitKey = None
try:
control = cli.controller.getController()
pagePanels = control.getDisplayPanels()
# the first page is the only one with multiple panels, and it looks better
# with the log entries first, so reversing the order
pagePanels.reverse()
helpOptions = []
for entry in pagePanels:
helpOptions += entry.getHelp()
# test doing afterward in case of overwriting
popup.win.box()
popup.addstr(0, 0, "Page %i Commands:" % (control.getPage() + 1), curses.A_STANDOUT)
for i in range(len(helpOptions)):
if i / 2 >= height - 2: break
# draws entries in the form '<key>: <description>[ (<selection>)]', for
# instance...
# u: duplicate log entries (hidden)
key, description, selection = helpOptions[i]
if key: description = ": " + description
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)
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
if len(helpOptions) < 13 and height == 9:
popup.addstr(7, 2, "Press any key...")
popup.win.refresh()
curses.cbreak()
exitKey = control.getScreen().getch()
finally: finalize()
if not uiTools.isSelectionKey(exitKey) and \
not uiTools.isScrollKey(exitKey) and \
not exitKey in (curses.KEY_LEFT, curses.KEY_RIGHT):
return exitKey
else: return None
def showAboutPopup():
"""
Presents a popup with author and version information.
"""
popup, _, height = init(9, 80)
if not popup: return
try:
control = cli.controller.getController()
popup.win.box()
popup.addstr(0, 0, "About:", curses.A_STANDOUT)
popup.addstr(1, 2, "arm, version %s (released %s)" % (version.VERSION, version.LAST_MODIFIED), curses.A_BOLD)
popup.addstr(2, 4, "Written by Damian Johnson (atagar@torproject.org)")
popup.addstr(3, 4, "Project page: www.atagar.com/arm")
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.getScreen().getch()
finally: finalize()
def showSortDialog(title, options, oldSelection, optionColors):
"""
Displays a sorting dialog of the form:
Current Order: <previous selection>
New Order: <selections made>
<option 1> <option 2> <option 3> Cancel
Options are colored when among the "Current Order" or "New Order", but not
when an option below them. If cancel is selected or the user presses escape
then this returns None. Otherwise, the new ordering is provided.
Arguments:
title - title displayed for the popup window
options - ordered listing of option labels
oldSelection - current ordering
optionColors - mappings of options to their color
"""
popup, _, _ = init(9, 80)
if not popup: return
newSelections = [] # new ordering
try:
cursorLoc = 0 # index of highlighted option
curses.cbreak() # wait indefinitely for key presses (no timeout)
selectionOptions = list(options)
selectionOptions.append("Cancel")
while len(newSelections) < len(oldSelection):
popup.win.erase()
popup.win.box()
popup.addstr(0, 0, title, curses.A_STANDOUT)
_drawSortSelection(popup, 1, 2, "Current Order: ", oldSelection, optionColors)
_drawSortSelection(popup, 2, 2, "New Order: ", newSelections, optionColors)
# presents remaining options, each row having up to four options with
# spacing of nineteen cells
row, col = 4, 0
for i in range(len(selectionOptions)):
optionFormat = curses.A_STANDOUT if cursorLoc == i else curses.A_NORMAL
popup.addstr(row, col * 19 + 2, selectionOptions[i], optionFormat)
col += 1
if col == 4: row, col = row + 1, 0
popup.win.refresh()
key = cli.controller.getController().getScreen().getch()
if key == curses.KEY_LEFT:
cursorLoc = max(0, cursorLoc - 1)
elif key == curses.KEY_RIGHT:
cursorLoc = min(len(selectionOptions) - 1, cursorLoc + 1)
elif key == curses.KEY_UP:
cursorLoc = max(0, cursorLoc - 4)
elif key == curses.KEY_DOWN:
cursorLoc = min(len(selectionOptions) - 1, cursorLoc + 4)
elif uiTools.isSelectionKey(key):
selection = selectionOptions[cursorLoc]
if selection == "Cancel": break
else:
newSelections.append(selection)
selectionOptions.remove(selection)
cursorLoc = min(cursorLoc, len(selectionOptions) - 1)
elif key == 27: break # esc - cancel
finally: finalize()
if len(newSelections) == len(oldSelection):
return newSelections
else: return None
def _drawSortSelection(popup, y, x, prefix, options, optionColors):
"""
Draws a series of comma separated sort selections. The whole line is bold
and sort options also have their specified color. Example:
Current Order: Man Page Entry, Option Name, Is Default
Arguments:
popup - panel in which to draw sort selection
y - vertical location
x - horizontal location
prefix - initial string description
options - sort options to be shown
optionColors - mappings of options to their color
"""
popup.addstr(y, x, prefix, curses.A_BOLD)
x += len(prefix)
for i in range(len(options)):
sortType = options[i]
sortColor = uiTools.getColor(optionColors.get(sortType, "white"))
popup.addstr(y, x, sortType, sortColor | curses.A_BOLD)
x += len(sortType)
# comma divider between options, if this isn't the last
if i < len(options) - 1:
popup.addstr(y, x, ", ", curses.A_BOLD)
x += 2
def showMenu(title, options, oldSelection):
"""
Provides menu with options laid out in a single column. User can cancel
selection with the escape key, in which case this proives -1. Otherwise this
returns the index of the selection.
Arguments:
title - title displayed for the popup window
options - ordered listing of options to display
oldSelection - index of the initially selected option (uses the first
selection without a carrot if -1)
"""
maxWidth = max(map(len, options)) + 9
popup, _, _ = init(len(options) + 2, maxWidth)
if not popup: return
key, selection = 0, oldSelection if oldSelection != -1 else 0
try:
# hides the title of the first panel on the page
control = cli.controller.getController()
topPanel = control.getDisplayPanels(includeSticky = False)[0]
topPanel.setTitleVisible(False)
topPanel.redraw(True)
curses.cbreak() # wait indefinitely for key presses (no timeout)
while not uiTools.isSelectionKey(key):
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 == oldSelection else " "
popup.addstr(i + 1, 2, tab)
popup.addstr(i + 1, 4, " %s " % label, format)
popup.win.refresh()
key = control.getScreen().getch()
if key == curses.KEY_UP: selection = max(0, selection - 1)
elif key == curses.KEY_DOWN: selection = min(len(options) - 1, selection + 1)
elif key == 27: selection, key = -1, curses.KEY_ENTER # esc - cancel
finally:
topPanel.setTitleVisible(True)
finalize()
return selection
|