Files
codezero/tools/cml2-tools/cmlconfigure.py
Bahadir Balban 893b68c643 CML2 value width increased to 32.
This little patch increases the notorious 8-char wide value width in
the cml2 configurator to 32 characters. Now all values are properly visible.
2009-09-19 21:28:24 +03:00

3324 lines
137 KiB
Python
Executable File

#!/usr/bin/env python
#
# cmlconfigure.py -- CML2 configurator front ends
# by Eric S. Raymond, <esr@thyrsus.com>
#
# Here is the actual code for the configurator front ends.
#
import sys
if sys.version[0] < '2':
print "Python 2.0 or later is required for this program."
raise SystemExit, 1
import os, string, getopt, cmd, re, time
import cml, cmlsystem, webbrowser
# Globals
debug = list = 0
force_batch = force_tty = force_curses = force_x = force_q = force_debugger = None
readlog = proflog = None
banner = ""
configuration = None
current_node = None
helpwin = None
# User-visible strings in the configurator. Separated out in order to
# support internationalization.
_eng = {
"ABORTED":"Configurator aborted.",
"ANCESTORBUTTON":"Show ancestors of...",
"BACKBUTTON":"Back",
"BADBOOL":"Bad value for boolean or trit.",
"BADOPTION":"cmlconfigure: unknown option on command line.\n",
"BADREQUIRE":"Some requirements are violated: ",
"BADVERIFY":"ERROR===>Expected <%s>=<%s>, instead of <%s>",
"BOOLEAN":"`y' and `n' can only be applied to booleans or tristates",
"CANCEL":"Cancel",
"CANNOTSET":" Can't assign this value for bool or trit symbol.",
"CANTGO":"Cannot go to that symbol (it's probably derived).",
"CCAPHELP":"C -- show constraints (all, or including specified symbol)",
"CHARINVAL":"Invalid character in numeric field",
"CHELP":"c -- clear the configuration",
"CMDHELP":"Command summary",
"COMPILEOK": "Compilation OK.",
"COMPILEFAIL": "Compilation failed.",
"CONFIRM":"Confirmation",
"CONSTRAINTS":"Constraints:",
"CURSQUERY":"Type '?' for help",
"CURSESSET":"Curses mode set symbol %s to value %s",
"DEBUG": "Debugging %s ruleset.",
"DEFAULT":"Default: ",
"DEPENDENTBUTTON":"Show dependents of...",
"DERIVED":"Symbol %s is derived and cannot be set.",
"DONE":"Done",
"EDITING":"Editing %s",
"EFFECTS":"Side effects:",
"EMPTYSEARCH":"You must enter a regular expression to search for",
"SUPPRESSBUTTON":"Suppress",
"SUPPRESSOFF":"Suppression turned off",
"SUPPRESSON":"Suppression turned on",
"ECAPHELP": "E -- dump the state of the binding history",
"EHELP": "e -- examine specified symbol",
"EXIT":"Exit",
"EXITCONFIRM":"[Press q to exit, any other key to continue]",
"FHELP": "f -- freeze specified symbol",
"FIELDEDIT":"Field Editor help",
"FILEBUTTON":"File",
"FREEZEBUTTON":"Freeze",
"FREEZE":"Freeze all symbols with a user-supplied value?",
"FREEZELABEL":"(FROZEN)",
"FROZEN":"This symbol has been frozen and cannot be modified.",
"GHELP":"g -- go to a named symbol or menu (follow g with the label)",
"GO":"Go",
"GOBUTTON":"Go to...",
"GOTOBYNAME":"Go to symbol by name: ",
"GPROMPT":"Symbol to set or edit: ",
"HELPBANNER": "Press ? for help on current symbol, h for command help",
"HELPBUTTON":"Help",
"HELPFOR":"Help for %s",
"HSEARCHBUTTON":"Search help text...",
"ICAPHELP":"I -- read in and freeze a configuration (follow I with the filename)",
"IHELP":"i -- read in a configuration (follow i with the filename)",
"INCCHANGES":"%d change(s) from %s",
"INFO":"Information",
"INVISIBLE":"Symbol is invisible",
"INVISILOCK":" and invisibility is locked.",
"INVISINONE":"Symbol is invisible (no visibility predicate).",
"INVISOUT":"Symbol %s is currently invisible and will not be saved out.",
"LOADFILE":"Load configuration from: ",
"LOADBUTTON":"Load...",
"LOADFAIL":"Loading '%s' failed, continuing...",
"LOADFREEZE":"Load frozen configuration from: ",
"MDISABLED":"Module-valued symbols are not enabled",
"MHELP":"m -- set the value of the selected symbol to m",
"MNOTVALID":" m is not a valid value for %s",
"MORE":"(More lines omitted...)",
"NAVBUTTON":"Navigation",
"NEW":"(NEW)",
"NHELP":"n -- set the value of the selected symbol to n",
"NNOTVALID":" n is not a valid value for %s",
"NOANCEST":"No ancestors.",
"NOCMDLINE":"%s is the wrong type to be set from the command line",
"NOCURSES":"Your Python seems to be lacking curses support.",
"NODEPS":"No dependents.",
"NOFILE":"cmlconfigure: '%s' does not exist or is unreadable.",
"NOHELP":"No help available for %s",
"NOMATCHES":"No matches.",
"NOMENU":"Symbol %s is not in a menu.",
"NONEXIST":"No such symbol or menu as %s.",
"NOPOP":"Can't pop back further",
"NOSAVEP":"No save predicate",
"NOSUCHAS":"No such symbol as",
"NOSYMBOL":"No symbol is currently selected.",
"NOTKINTER":"Can't find tkinter support, falling back to curses mode...",
"NOTSAVED":"Configuration not saved",
"NOVISIBLE":"No visible items on starting menu.",
"OK":"Operation complete",
"OUTOFBOUNDS":"%s no good; legal values are in %s",
"PAGEPROMPT":"[Press Enter to continue] ",
"PARAMS":" Config = %s, prefix = %s",
"PDHELP":"p -- print the configuration",
"PHELP":"p -- back up to previous symbol",
"POSTMORTEM":"The ruleset was inconsistent.",
"PRESSANY":"[Press any key to continue]",
"PROBLEM":"Problem",
"QHELP":"q -- quit, discarding changes",
"QUITBUTTON":"Quit",
"QUITCONFIRM":"Really exit without saving?",
"RADIOBAD":" That's not a valid selection.",
"DISCRETEVALS":"%s may have these values:\n",
"REALLY":"Really exit without saving?",
"ROLLBACK":"%s=%s would have violated these requirements:",
"SAVEABLE":"Symbol is saveable.",
"SAVEAS":"Save As...",
"SAVEBUTTON":"Save & Exit",
"SAVECONFIRM":"Save confirmation",
"SAVEEND":"Done",
"SAVEFILE":"Save configuration to: ",
"SAVESTART":"Saving %s",
"SAVING":"Saving...",
"SEARCHBUTTON":"Search symbols...",
"SEARCHFAIL":"No matches.",
"SEARCHINVAL":"Invalid Regular Expression:",
"SEARCHHELP":"Search help text for: ",
"SEARCHSYMBOLS":"Search symbols for regular expression: ",
"SETCOUNT":"Symbol has been set %d time(s)",
"SHELP":"s -- save the configuration (follow with a filename)",
"SHOW_ANC":"Show ancestors of symbol: ",
"SHOW_DEP":"Show dependents of symbol: ",
"SIDEEFFECTS":"Side Effects",
"SIDEFROM":"Side effects from %s:",
"SKIPCALLED":" Skip-to-query called from %s",
"SKIPEXIT":" Skip-to-query arrived at %s",
"SYMUNKNOWN":"cmlconfigure: unknown symbol %s\n",
"TERMNOTSET":"TERM is not set.",
"TERMTOOSMALL":"Your terminal is too small to support curses mode.",
"TOOLONG":"String is too long to edit. Use cmlconfigure -t.",
"TRIT":"`m' can only be applied to tristates",
"TTYQUERY":"Type '?' at any prompt for help, 'h' for command help.",
"TTYSUMMARY":" Command summary:",
"UHELP":"u -- toggle interactive flag.",
"UNSUPPRESSBUTTON":"Unsuppress",
"UNKNOWN":"Unknown command %s -- type `h' for a command summary",
"UPBUTTON":"Up",
"UNSAVEABLE":"Symbol is unsaveable.",
"VCAPHELP":"V -- verify that a symbol has a given value",
"VERBOSITY": "Verbosity level is %d",
"VERSION":", version %s.",
"VHELP": "v -- increase the verbosity level, or set to numeric argument",
"VISIBLE":"Symbol is visible",
"VISIBILITY":"Visibility: ",
"WARNING":"Warning",
"WELCOME":"Welcome to the %s",
"XHELP":"x -- exit, saving the configuration",
"YHELP":"y -- set the value of the selected symbol to y",
"YNOTVALID":" y is not a valid value for %s",
"TTYHELP":"""
Type '?' to see help text associated with the current symbol.
Typing Return accepts the default for the query.
Each prompt consists of a label, followed by a colon, followed by prompt text,
followed by a value and a bracketed range indication. The brackets indicate
whether the symbol is bool [] modular <> or integer/string (). The current
value in the brackets may be blank (indicating bool or trit n), 'M' (indicating
trit m) or an integer or string literal. If `?' follows, it means help is
available for this symbol.
""",
"CURSHELP":"""\
Use up- and down-arrows to change the current selection. Use spacebar or Enter
to enter a selected sub-menu, or to toggle the value of a selected boolean
symbol, or to cycle through the possible y/m/n values of a selected tristate
symbol, or to begin editing the value of a symbol that has string or decimal
or hexadecimal type. Use left-arrow to back out of a menu.
'y', 'm', and 'n' set boolean or trit symbols to the corresponding values.
When you are editing a symbol value, the highlight on its value field will be
switched off. A subset of Emacs's default key bindings is available to edit
the field; to see details, enter such a field (by typing a space with the
symbol selected) and press your tab key. Other characters will usually be
inserted at the cursor location. Press up-arrow or down-arrow or Enter to
stop editing a field.
Type `x' to save configuration and exit, `q' to exit without saving, `s' to
save the configuration to a named file, and `i' to read in a configuration by
filename. -I reads in a configuration and freezes the variables.
Type '?' to see any help text associated with the current symbol. Type 'h'
to see this command summary again. Some expert commands are documented
in separate help; press TAB from this help screen to see it.\
""",
"EDITHELP":"""\
Numbers and short strings can be edited in their display fields near the
left edge of the screen. To edit longer strings, enter a right arrow at the
left edge of the display field. This will pop up a wide window in which you
can edit the value. The field editor supports a subset of Emacs's default
key bindings.
Ctrl-A Go to left edge of window.
Ctrl-B Cursor left, wrapping to previous line if appropriate.
Ctrl-D Delete character under cursor.
Ctrl-E Go to right edge (nospaces off) or end of line (nospaces on).
Ctrl-F Cursor right, wrapping to next line when appropriate.
Ctrl-H Delete character backward.
Ctrl-J Terminate if the window is 1 line, otherwise insert newline.
Ctrl-K If line is blank, delete it, otherwise clear to end of line.
Ctrl-L Refresh screen
Ctrl-N Cursor down; move down one line.
Ctrl-O Insert a blank line at cursor location.
Ctrl-P Cursor up; move up one line.
KEY_LEFT = Ctrl-B, KEY_RIGHT = Ctrl-F, KEY_UP = Ctrl-P, KEY_DOWN = Ctrl-N
KEY_BACKSPACE = Ctrl-h
To leave the field editor, press Enter or carriage return, or use the up and
down arrows. Ctrl-g leaves the field editor ant revers to the unedited value.\
""",
"EXPERTHELP":"""\
Here are the expert commands:
/ -- Search for symbols matching a given regular expression in either
the symbol name or prompt text.
g -- Go to symbol. Go directly to symbol. Do not pass go, do not collect $200.
If the target symbol is suppressed, clear the suppression flag.
i -- Load a configuration. Set all the variables in the given config file.
I -- Load a configuration. Set all the variables in the config file,
then freeze them so they can't be overridden.
e -- Examine the value of the named symbol.
S -- Toggle the suppression flag (normally on). When the suppression flag
is off, invisible symbols are not skipped.
Type 'h' to see the help for ordinary commands.
""",
"TKCMDHELP":"""\
The main window is a configuration menu browser. It presents you with a menu of
symbols and sub-menu buttons. If there is help available for a symbol or menu,
there will be an active `Help' button off to the right. In the help window,
URLS are live; clicking on them will launch a browser.
You can set boolean or tristate symbols simply by clicking on the appropriate
value. You can change the values of numeric and string symbols by editing
their fill-in fields.
In the file menu, the `Load' command allows you to load a configuration file.
Values in the configuration file are set as though the user had selected them.
In the file menu, the `Freeze' command freezes all symbols that have
been set by user actions (including loading configuration files) so they
won't be queried again and cannot subsequently be overridden.
In the File menu, the `Save' command saves the configuration file to the
location specified by configurator command-line options). The `Save As...'
command saves the defconfig file (only) to a named location.
The Back command in the Navigation menu (and the toolbar button) returns you to
the menu visited before this one. When you back out of a sub-menu, the label
on its button is highlighted in blue.
The Go command in the Navigation menu moves you to a named menu, or to the
menu containing a named symbol. After a `Go' command the target is
highlighted blue.
The Search command in the Navigation menu matches a given regular expression
against the names and prompt text of all configuration symbols. It generates
a menu that includes all hits, and turns off the elisions flag.
The Suppress/Unsuppress command in the Navigation toggles whether suppressed
symbols are visible or not. Normally, suppressed symbols are invisible and the
menu entry reads `Unsuppress'. The suppression flag may also be cleared by
using the `Go' command to visit a suppressed symbol, or by doing a Search.
""",
"CLIHELP":"""\
Usage: clmlconfigure.py [-tcxqbs] [-[Dd] sym] [-[Ii] file]
[-B banner] [-o output] [-v]
-t force tty (line-oriented) mode
-c force curses (screen-oriented) mode
-x force default X mode
-q force expermintal tree-widget-based X interface
-b batch mode (process command-line options only).
-s debugger mode
-d sym[=val] set a symbol
-D sym[=val] set and freeze a symbol
-i include a config file
-I include a config file, frozen
-B banner set banner string
-o file write config to specified file
-v increment debug level
""",
}
# Eventually, do more intelligent selection using LOCALE
lang = _eng
# Shared code
def cgenvalue(symbol):
"Generate an appropriate prompt for the given symbol."
value = symbol.eval()
if symbol.type in ("bool", "trit"):
if symbol.type == "trit" and configuration.trits_enabled:
format = "<%s>"
else:
format = "[%s]"
if value == cml.y:
return format % "Y"
elif value == cml.m:
return format % "m"
elif value == cml.n:
return format % " "
elif symbol.type == "choices":
if symbol.menuvalue:
return symbol.menuvalue.prompt
elif symbol.default:
return symbol.default.prompt
else:
return "??"
elif symbol.type == "message":
return ""
elif symbol.type == "menu":
return "-->" # Effective only in menuconfig
elif symbol.type in ("decimal", "string"):
return str(value)
elif symbol.type == "hexadecimal":
return "0x%x" % value
else:
return "??"
def cgenprompt(symbol, novice=1):
"Decorate a symbol prompt string according to its warndepend conditions."
res = ""
for warndepend in symbol.warnings:
if novice:
res += warndepend.name + ", "
else:
res += warndepend.name[0] + ", "
if symbol.warnings:
res = " (" + res[:-2] + ")"
return symbol.prompt + res
def interactively_visible(symbol):
"Should a symbol be visible interactively?"
return configuration.is_visible(symbol) and not symbol.frozen()
# Line-oriented interface
class tty_style_base(cmd.Cmd):
"A class for browsing a CML2 menu subtree with line-oriented commands."
def set_symbol(self, symbol, value, freeze=0):
"Set the value of a symbol -- line-oriented error messages."
if symbol.is_numeric() and symbol.range:
if not configuration.range_check(symbol, value):
print lang["OUTOFBOUNDS"] % (value, symbol.range,)
return
(ok, effects, violations) = configuration.set_symbol(symbol, value, freeze)
if effects:
print lang["EFFECTS"]
sys.stdout.write(string.join(effects, "\n") + "\n\n")
if ok:
if not interactively_visible(symbol):
print lang["INVISOUT"] % symbol.name
else:
print lang["ROLLBACK"] % (symbol.name, value)
sys.stdout.write(string.join(map(repr, violations), "\n") + "\n")
def page(self, text):
text = string.split(text, "\n")
pagedepth = os.environ.get("LINES")
if pagedepth:
pagedepth = int(pagedepth)
else:
pagedepth = 24
base = 0
try:
while base < len(text):
for i in range(base, base+pagedepth):
if i >= len(text):
break;
print text[i]
base = base + pagedepth
raw_input(lang["PAGEPROMPT"])
except KeyboardInterrupt:
print ""
def __init__(self, config, mybanner):
cmd.Cmd.__init__(self)
self.config = config
if mybanner and configuration.banner.find("%s") > -1:
self.banner = configuration.banner % mybanner
elif mybanner:
self.banner = mybanner
else:
self.banner = configuration.banner
self.current = configuration.start;
def do_g(self, line):
if configuration.dictionary.has_key(line):
self.current = configuration.dictionary[line]
configuration.visit(self.current)
if not interactively_visible(self.current) and not self.current.frozen():
print lang["SUPPRESSOFF"]
self.suppressions = 0
if self.current.type in ("menu", "message"):
self.skip_to_query(configuration.next_node, 1)
else:
print lang["NONEXIST"] % line
def do_i(self, line):
file = string.strip(line)
try:
(changes, errors) = configuration.load(file, freeze=0)
except IOError:
print lang["LOADFAIL"] % file
else:
if errors:
print errors
print lang["INCCHANGES"] % (changes,file)
if configuration.side_effects:
sys.stdout.write(string.join(configuration.side_effects, "\n") + "\n")
def do_I(self, line):
file = string.strip(line)
try:
(changes, errors) = configuration.load(file, freeze=1)
except IOError:
print lang["LOADFAIL"] % file
else:
if errors:
print errors
print lang["INCCHANGES"] % (changes,file)
if configuration.side_effects:
sys.stdout.write(string.join(configuration.side_effects, "\n") + "\n")
def do_y(self, line):
if not line:
target = self.current
else:
# Undocumented feature -- "y FOO" sets FOO to y.
line = string.strip(line)
if not configuration.dictionary.has_key(line):
print lang["NONEXIST"] % line
return
else:
target = configuration.dictionary[line]
if not target.type in ("trit", "bool"):
print lang["YNOTVALID"] % (target.name)
else:
self.set_symbol(target, cml.y)
return None
def do_Y(self, line):
self.do_y(line)
def do_m(self, line):
if not line:
target = self.current
else:
# Undocumented feature -- "m FOO" sets FOO to m.
line = string.strip(line)
if not configuration.dictionary.has_key(line):
print lang["NONEXIST"] % line
return
else:
target = configuration.dictionary[line]
if not target.type == "trit":
print lang["MNOTVALID"] % (target.name)
elif not configuration.trits_enabled:
print lang["MNOTVALID"] % (target.name)
else:
self.set_symbol(target, cml.m)
return None
def do_M(self, line):
self.do_m(line)
def do_n(self, line):
if not line:
target = self.current
else:
# Undocumented feature -- "n FOO" sets FOO to n.
line = string.strip(line)
if not configuration.dictionary.has_key(line):
print lang["NONEXIST"] % line
return
else:
target = configuration.dictionary[line]
if not target.type in ("trit", "bool"):
print lang["NNOTVALID"] % (target.name)
else:
self.set_symbol(target, cml.n)
return None
def do_N(self, line):
self.do_n(line)
def do_s(self, line):
file = string.strip(line)
failure = configuration.save(file, cml.Baton(lang["SAVESTART"] % file, lang["SAVEEND"]))
if failure:
print failure
def do_x(self, dummy):
# Terminate this cmd instance, saving configuration
self.do_s(config)
return 1
def do_C(self, line):
# Show constraints (all, or all including specified symbol).
filter = None
if line:
line = line.strip()
if configuration.dictionary.has_key(line):
filter = configuration.dictionary[line]
for i in range(len(configuration.constraints)):
constraint = configuration.constraints[i].predicate
reduced = configuration.reduced[i]
if reduced in (cml.n, cml.m, cml.y):
continue
if filter and filter not in cml.flatten_expr(constraint):
continue
if constraint == reduced:
print cml.display_expression(reduced)[1:-1]
else:
print "%s -> %s" % (constraint,cml.display_expression(reduced)[1:-1])
return 0
def do_q(self, line):
# Terminate this cmd instance, not saving configuration
raise SystemExit, 1
def do_v(self, line):
# Set the debug flag
if not line:
configuration.debug += 1
else:
configuration.debug = int(line)
print lang["VERBOSITY"] % configuration.debug
return 0
def do_e(self, line):
# Examine the state of a given symbol
symbol = string.strip(line)
if configuration.dictionary.has_key(symbol):
entry = configuration.dictionary[symbol]
print entry
if entry.constraints:
print lang["CONSTRAINTS"]
for wff in entry.constraints:
print cml.display_expression(wff)
if interactively_visible(entry):
print lang["VISIBLE"]
elif entry.visibility is None:
print lang["INVISINONE"]
elif configuration.eval_frozen(entry.visibility):
print lang["INVISIBLE"] + lang["INVISILOCK"]
else:
print lang["INVISIBLE"]
if entry.saveability == None:
print lang["NOSAVEP"]
if configuration.saveable(entry):
print lang["SAVEABLE"]
else:
print lang["UNSAVEABLE"]
if entry.setcount:
print lang["SETCOUNT"] % entry.setcount
else:
print lang["NOSUCHAS"], symbol
return 0
def do_E(self, dummy):
# Dump the state of the bindings stack
print configuration.binddump()
return 0
def do_S(self, dummy):
# Toggle the suppressions flag
configuration.suppressions = not configuration.suppressions
if configuration.suppressions:
print lang["SUPPRESSON"]
else:
print lang["SUPPRESSOFF"]
return 0
def help_e(self):
print lang["EHELP"]
def help_E(self):
print lang["ECAPHELP"]
def help_g(self):
print lang["GHELP"]
def help_i(self):
print lang["IHELP"]
def help_I(self):
print lang["ICAPHELP"]
def help_y(self):
print lang["YHELP"]
def help_m(self):
print lang["MHELP"]
def help_n(self):
print lang["NHELP"]
def help_s(self):
print lang["SHELP"]
def help_q(self):
print lang["QHELP"]
def help_v(self):
print lang["VHELP"]
def help_x(self):
print lang["XHELP"]
def do_help(self, line):
line = line.strip()
if configuration.dictionary.has_key(line):
target = configuration.dictionary[line]
else:
target = self.current
help = target.help()
if help:
self.page(help)
else:
print lang["NOHELP"] % (self.current.name,)
class tty_style_menu(tty_style_base):
"Interface for configuring with line-oriented commands."
def skip_to_query(self, function, showbase=0):
configuration.debug_emit(2, lang["SKIPCALLED"] % (self.current.name,))
if showbase:
if self.current.type == "menu":
self.menu_banner(self.current)
configuration.visit(self.current)
while 1:
self.current = function(self.current)
if self.current == configuration.start:
break;
elif self.current.is_symbol() and self.current.frozen():
# sys.stdout.write(self.generate_prompt(self.current) + lang["FREEZELABEL"] + "\n")
continue
elif not interactively_visible(self.current):
continue
elif self.current.type in ("menu", "choices"):
self.menu_banner(self.current)
configuration.visit(self.current)
if not self.current.type in ("message", "menu"):
break;
configuration.debug_emit(2, lang["SKIPEXIT"] % (self.current.name,))
if self.current == configuration.start:
self.do_s(config)
raise SystemExit
def menu_banner(self, menu):
sys.stdout.write("*\n* %s: %s\n*\n" % (menu.name, menu.prompt))
def generate_prompt(self, symbol):
leader = " " * symbol.depth
genpart = cgenvalue(symbol)
if symbol.help and not symbol.frozen():
havehelp = "?"
else:
havehelp = ""
if configuration.is_new(symbol):
genpart += " " + lang["NEW"]
if symbol.type in ("bool", "trit"):
return leader+"%s: %s %s%s: " % (symbol.name, cgenprompt(symbol), genpart, havehelp)
elif symbol.enum:
dflt = cml.evaluate(symbol, debug)
if symbol.frozen():
p = ""
else:
p = leader + lang["DISCRETEVALS"] % (cgenprompt(symbol),)
selected = ""
for (label, value) in symbol.range:
if value == dflt:
selected = "(" + label + ")"
if not symbol.frozen():
p = p + leader + "%2d: %s\n" % (value, label)
return p + leader + "%s: %s %s%s: " % (symbol.name, cgenprompt(symbol),selected, havehelp)
elif symbol.type in ("decimal", "hexadecimal", "string"):
dflt = cml.evaluate(symbol, debug)
return leader + "%s: %s (%s)%s: " % (symbol.name, cgenprompt(symbol), cgenvalue(symbol), havehelp)
elif symbol.type == "choices":
if symbol.frozen():
p = ""
else:
p = leader + lang["DISCRETEVALS"] % (cgenprompt(symbol))
index = 0
selected= ""
for v in symbol.items:
index = index + 1
if not symbol.frozen():
p = p + leader + "%2d: %s%s%s\n" % (index, v.name, " " * (32 - len(v.name)), v.prompt)
if v.eval():
selected = v.name
return p + leader + "%s: %s (%s)%s: " % (symbol.name, cgenprompt(symbol),selected, havehelp)
def __init__(self, config=None, mybanner=""):
tty_style_base.__init__(self, config=config, mybanner=mybanner)
self.skip_to_query(configuration.next_node, 1)
# This handles the case that all variables were frozen.
self.prompt = self.generate_prompt(self.current)
def do_p(self, dummy):
self.skip_to_query(configuration.previous_node)
return None
def do_y(self, line):
tty_style_base.do_y(self, line)
if not line:
self.skip_to_query(configuration.next_node)
def do_m(self, line):
tty_style_base.do_m(self, line)
if not line:
self.skip_to_query(configuration.next_node)
def do_n(self, line):
tty_style_base.do_n(self, line)
if not line:
self.skip_to_query(configuration.next_node)
def do_h(self, dummy):
self.page(string.join(map(lambda x: lang[x],
("TTYSUMMARY",
"GHELP", "IHELP", "ICAPHELP", "YHELP",
"MHELP", "NHELP", "PHELP", "SHELP",
"QHELP", "XHELP", "TTYHELP")), "\n"))
def default(self, line):
v = string.strip(line)
if self.current.type == 'choices':
try:
ind = string.atoi(v)
except ValueError:
ind = -1
if ind <= 0 or ind > len(self.current.items):
print lang["RADIOBAD"]
else:
# print lang["TTYSETTING"] % (`self.current.items[ind - 1]`)
self.set_symbol(self.current.items[ind - 1], cml.y)
self.skip_to_query(configuration.next_node)
elif self.current.type in ("bool", "trit"):
print lang["CANNOTSET"]
else:
self.set_symbol(self.current, v)
self.skip_to_query(configuration.next_node)
return None
def emptyline(self):
if self.current and self.current.type == "choices":
if self.current.default:
# print lang["TTYSETTING"] % (`self.current.default`)
self.set_symbol(self.current.default, cml.y)
self.skip_to_query(configuration.next_node)
return 0
def help_p(self):
print lang["PHELP"]
def postcmd(self, stop, dummy):
if stop:
return stop
self.prompt = self.generate_prompt(self.current)
return None
class debugger_style_menu(tty_style_base):
"Ruleset-debugger class."
def __init__(self, config=None, mybanner=""):
tty_style_base.__init__(self, config=config, mybanner=mybanner)
configuration.debug += 1
self.prompt = "> "
def do_l(self, line):
import cmlcompile
newsystem = cmlcompile.compile(debug=0, arguments=None, profile=0, endtok=line)
if newsystem:
global configuration
configuration = cmlsystem.CMLSystem(newsystem)
print lang["COMPILEOK"]
else:
print lang["COMPILEFAIL"]
return 0
def do_V(self,line):
print "V",line
for setting in line.split():
symbol,expected=setting.split('=')
if not configuration.dictionary.has_key(symbol):
sys.stderr.write((lang["NONEXIST"] % symbol) + "\n")
print lang["NONEXIST"] % line
continue
dictsym = configuration.dictionary[symbol]
dictval = cml.evaluate(dictsym)
if dictval != \
configuration.value_from_string(dictsym,expected):
errstr = lang["BADVERIFY"] % (symbol,expected,dictval)
print errstr
sys.stderr.write(errstr + '\n')
return 0
def do_y(self, line):
print line + "=y"
if not line:
print lang["NOSYMBOL"]
else:
tty_style_base.do_y(self, line)
if configuration.debug:
print configuration.binddump()
def do_m(self, line):
print line + "=m"
if not line:
print lang["NOSYMBOL"]
else:
tty_style_base.do_m(self, line)
if configuration.debug:
print configuration.binddump()
def do_n(self, line):
print line + "=n"
if not line:
print lang["NOSYMBOL"]
else:
tty_style_base.do_n(self, line)
if configuration.debug:
print configuration.binddump()
def do_f(self, line):
print "f", line
line = line.strip()
if not line:
print lang["NOSYMBOL"]
elif not configuration.dictionary.has_key(line):
print lang["NONEXIST"] % line
else:
configuration.dictionary[line].freeze()
return None
def do_c(self, line):
print "c", line
configuration.clear()
return None
def do_p(self, line):
print "p", line
configuration.save(sys.stdout, baton=None, all=1)
return None
def do_u(self, line):
print "u", line
configuration.interactive = not configuration.interactive
return None
def do_h(self, line):
print string.join(map(lambda x: lang[x],
("TTYSUMMARY",
"YHELP", "MHELP", "NHELP", "PDHELP",
"FHELP", "CHELP",
"EHELP", "ECAPHELP", "CCAPHELP",
"IHELP", "ICAPHELP", "SHELP", "UHELP",
"QHELP", "XHELP", "VCAPHELP", "VHELP")), "\n")
def default(self, line):
if line.strip()[0] == "#":
print line
else:
print "?"
return 0
def emptyline(self):
print ""
return 0
def do_help(self, line):
if not line:
self.do_h(line)
else:
tty_style_base.do_help(self, line)
return None
def help_f(self):
print lang["FHELP"]
def help_c(self):
print lang["CHELP"]
def help_V(self):
print lang["VCAPHELP"]
def help_p(self):
print lang["PDHELP"]
def do_EOF(self, line):
print ""
self.do_q(line)
return 1
# Curses interface
class MenuBrowser:
"Support abstract browser operations on a stack of indexable objects."
def __init__(self, mydebug=0, errout=sys.stderr):
self.page_stack = []
self.selection_stack = []
self.viewbase_stack = []
self.viewport_height = 0
self.debug = mydebug
self.errout = errout
def match(self, a, b):
"Browseable-object comparison."
return a == b
def push(self, browseable, selected=None):
"Push a browseable object onto the location stack."
if self.debug:
self.errout.write("MenuBrowser.push(): pushing %s=@%d, selection=%s\n" % (browseable, id(browseable), `selected`))
selnum = 0
if selected == None:
if self.debug:
self.errout.write("MenuBrowser.push(): selection defaulted\n")
else:
for i in range(len(browseable)):
selnum = len(browseable) - i - 1
if self.match(browseable[selnum], selected):
break
if self.debug:
self.errout.write("MenuBrowser.push(): selection set to %d\n" % (selnum))
self.page_stack.append(browseable)
self.selection_stack.append(selnum)
self.viewbase_stack.append(selnum - selnum % self.viewport_height)
if self.debug:
object = self.page_stack[-1]
selection = self.selection_stack[-1]
viewbase = self.viewbase_stack[-1]
self.errout.write("MenuBrowser.push(): pushed %s=@%d->%d, selection=%d, viewbase=%d\n" % (object, id(object), len(self.page_stack), selection, viewbase))
def pop(self):
"Pop a browseable object off the location stack."
if not self.page_stack:
if self.debug:
self.errout.write("MenuBrowser.pop(): stack empty\n")
return None
else:
item = self.page_stack[-1]
self.page_stack = self.page_stack[:-1]
self.selection_stack = self.selection_stack[:-1]
self.viewbase_stack = self.viewbase_stack[:-1]
if self.debug:
if len(self.page_stack) == 0:
self.errout.write("MenuBrowser.pop(): stack is empty.")
else:
self.errout.write("MenuBrowser.pop(): new level %d, object=@%d, selection=%d, viewbase=%d\n" % (len(self.page_stack), id(self.page_stack[-1]), self.selection_stack[-1], self.viewbase_stack[-1]))
return item
def stackdepth(self):
"Return the current stack depth."
return len(self.page_stack)
def list(self):
"Return all elements of the current object that ought to be visible."
if not self.page_stack:
return None
object = self.page_stack[-1]
viewbase = self.viewbase_stack[-1]
if self.debug:
self.errout.write("MenuBrowser.list(): stack level %d. object @%d, listing %s\n" % (len(self.page_stack)-1, id(object), object[viewbase:viewbase+self.viewport_height]))
# This requires a slice method
return object[viewbase:viewbase+self.viewport_height]
def top(self):
"Return the top-of-stack menu"
if self.debug >= 2:
self.errout.write("MenuBrowser.top(): level=%d, @%d\n" % (len(self.page_stack)-1,id(self.page_stack[-1])))
return self.page_stack[-1]
def selected(self):
"Return the currently selected element in the top menu."
object = self.page_stack[-1]
selection = self.selection_stack[-1]
if self.debug:
self.errout.write("MenuBrowser.selected(): at %d, object=@%d, %s\n" % (len(self.page_stack)-1, id(object), self.selection_stack[-1]))
return object[selection]
def viewbase(self):
"Return the viewport base of the current menu."
object = self.page_stack[-1]
base = self.viewbase_stack[-1]
if self.debug:
self.errout.write("MenuBrowser.viewbase(): at level=%d, object=@%d, %d\n" % (len(self.page_stack)-1, id(object), base,))
return base
def thumb(self):
"Return top and bottom boundaries of a thumb scaled to the viewport."
object = self.page_stack[-1]
windowscale = float(self.viewport_height) / float(len(object))
thumb_top = self.viewbase() * windowscale
thumb_bottom = thumb_top + windowscale * self.viewport_height - 1
return (thumb_top, thumb_bottom)
def move(self, delta=1, wrap=0):
"Move the selection on the current item downward."
if delta == 0:
return
object = self.page_stack[-1]
oldloc = self.selection_stack[-1]
# Change the selection. Requires a length method
if oldloc + delta in range(len(object)):
newloc = oldloc + delta
elif wrap:
newloc = (oldloc + delta) % len(object)
elif delta > 0:
newloc = len(object) - 1
else:
newloc = 0
return self.goto(newloc)
def goto(self, newloc):
"Move the selection to the menu item with the given number."
oldloc = self.selection_stack[-1]
self.selection_stack[-1] = newloc
# When the selection is moved out of the viewport, move the viewbase
# just part enough to track it.
oldbase = self.viewbase_stack[-1]
if newloc in range(oldbase, oldbase + self.viewport_height):
pass
elif newloc < oldbase:
self.viewbase_stack[-1] = newloc
else:
self.scroll(newloc - (oldbase + self.viewport_height) + 1)
if self.debug:
self.errout.write("MenuBrowser.down(): at level=%d, object=@%d, old selection=%d, new selection = %d, new base = %d\n" % (len(self.page_stack)-1, id(self.page_stack[-1]), oldloc, newloc, self.viewbase_stack[-1]))
return (oldloc != newloc)
def scroll(self, delta=1, wrap=0):
"Scroll the viewport up or down in the current option."
object = self.page_stack[-1]
if not wrap:
oldbase = self.viewbase_stack[-1]
if delta > 0 and oldbase+delta > len(object)-self.viewport_height:
return
elif delta < 0 and oldbase + delta < 0:
return
self.viewbase_stack[-1] = (self.viewbase_stack[-1] + delta) % len(object)
def dump(self):
"Dump the whole stack of objects."
self.errout.write("Viewport height: %d\n" % (self.viewport_height,))
for i in range(len(self.page_stack)):
self.errout.write("Page: %d\n" % (i,))
self.errout.write("Selection: %d\n" % (self.selection_stack[i],))
self.errout.write(`self.page_stack[i]` + "\n");
def next(self, wrap=0):
return self.move(1, wrap)
def previous(self, wrap=0):
return self.move(-1, wrap)
def page_down(self):
return self.move(2*self.viewport_height-1)
def page_up(self):
return self.move(-(2*self.viewport_height-1))
class PopupBaton:
"A popup window with a twirly-baton."
def __init__(self, startmsg, master):
self.subwin = master.window.subwin(3, len(startmsg)+3,
(master.lines-3)/2,
(master.columns-len(startmsg)-3)/2)
self.subwin.clear()
self.subwin.box()
self.subwin.addstr(1,1, startmsg)
self.subwin.refresh()
self.count = 0
def twirl(self, ch=None):
if ch:
self.subwin.addch(ch)
else:
self.subwin.addch("-/|\\"[self.count % 4])
self.subwin.addch("\010")
self.subwin.refresh()
self.count = self.count + 1
def end(self, msg=None):
pass
class WindowBaton:
"Put a twirly-baton at the upper right corner to indicate activity."
def __init__(self, master):
self.master = master
self.count = 0
def twirl(self, ch=None):
if ch:
self.master.window.addch(0, self.master.columns-1, ch)
else:
self.master.window.addch(0, self.master.columns-1, "-/|\\"[self.count % 4])
self.master.window.addch("\010")
self.master.window.refresh()
self.count = self.count + 1
def end(self, dummy=None):
self.master.window.addch(0, self.master.columns-1, " ")
self.master.window.refresh()
pass
class curses_style_menu:
"Command interpreter for line-oriented configurator."
input_nmatch = re.compile(r">>>.*\(([0-9]+)\)$")
valwidth = 32 # This is a constant
def __init__(self, stdscr, config, mybanner):
if mybanner and configuration.banner.find("%s") > -1:
self.banner = configuration.banner % mybanner
elif mybanner:
self.banner = mybanner
else:
self.banner = configuration.banner
self.input_queue = []
self.menus = self.values = self.textbox = None
self.window = stdscr
self.msgbuf = ""
self.lastmenu = None
menudebug = 0
if configuration.debug > 1:
menudebug = configuration.debug - 2
self.menus = MenuBrowser(menudebug,configuration.errout)
(self.lines, self.columns) = self.window.getmaxyx()
if self.lines < 9 or self.columns < 60:
raise "TERMTOOSMALL"
self.menus.viewport_height = self.lines-2 + (not configuration.expert_tie or cml.evaluate(configuration.expert_tie) != cml.n)
if curses.has_colors():
curses.init_pair(curses.COLOR_CYAN, curses.COLOR_CYAN, curses.COLOR_BLACK)
curses.init_pair(curses.COLOR_GREEN, curses.COLOR_GREEN, curses.COLOR_BLACK)
self.window.clear()
self.window.scrollok(0)
self.window.idlok(1)
# Most of the work gets done here
self.interact(config)
# Input (with logging support)
def getch(self, win):
if not readlog:
try:
ch = win.getch()
except KeyboardInterrupt:
curses.endwin()
raise
else:
time.sleep(1)
if self.input_queue:
ch = self.input_queue[0]
self.input_queue = self.input_queue[1:]
while 1:
line = readlog.readline()
if line == "":
ch = -1
break
m = curses_style_menu.input_nmatch.match(line)
if m:
ch = string.atoi(m.group(1))
break
if configuration.debug:
configuration.debug_emit(1, ">>> '%s' (%d)"% (curses.keyname(ch), ch))
return ch
def ungetch(self, c):
if readlog:
self.input_queue = c + self.input_queue
else:
curses.ungetch(c)
# Notification
def help_popup(self, instructions, msglist, beep=1):
"Pop up a help message."
if configuration.debug:
configuration.errout.write("***" + lang[instructions] + "\n")
configuration.errout.write(string.join(msglist, "\n"))
msgwidth = 0
pad = 2 # constant, must be >= 1
msgparts = []
for line in msglist:
unemitted = line
ww = self.columns - pad
while unemitted:
msgparts.append(unemitted[:ww])
unemitted = unemitted[ww:]
if len(msgparts) > self.lines - pad*2 - 1:
msgparts = msgparts[:self.lines - pad*2 - 2] + [lang["MORE"]]
for msg in msgparts:
if len(msg) > msgwidth:
msgwidth = len(msg)
msgwidth = min(self.columns - pad*2, msgwidth)
start_x = (self.columns - msgwidth) / 2
start_y = (self.lines - len(msgparts)) / 2
leave = lang[instructions]
msgwidth = max(msgwidth, len(leave))
subwin = self.window.subwin(len(msgparts)+1+pad*2, msgwidth+pad*2,
start_y-pad, start_x-pad)
subwin.clear()
for i in range(len(msgparts)):
subwin.addstr(pad+i, pad + int((msgwidth-len(msgparts[i]))/2),
msgparts[i], curses.A_BOLD)
subwin.addstr(pad*2+len(msgparts)-1, pad+int((msgwidth-len(leave))/2),
leave)
subwin.box()
if beep:
curses.beep()
self.window.noutrefresh()
subwin.noutrefresh()
curses.doupdate()
value = self.getch(self.window)
subwin.clear()
subwin.noutrefresh()
self.window.noutrefresh()
curses.doupdate()
return value
def query_popup(self, prompt, initval=None):
"Pop up a window to accept a string."
maxsymwidth = self.columns - len(prompt) - 10
if initval and len(initval) > maxsymwidth:
self.help_popup("PRESSANY", (lang["TOOLONG"],), beep=1)
return initval
gwinwidth = (len(prompt) + maxsymwidth)
start_y = self.lines/2-3
start_x = (self.columns - gwinwidth)/2
subwin = self.window.subwin(3, 2+gwinwidth, start_y-1, start_x-1)
subwin.clear()
subwin.box()
self.window.addstr(start_y, start_x, prompt, curses.A_BOLD)
self.window.refresh()
subwin.refresh()
subsubwin = subwin.subwin(1,maxsymwidth,start_y,start_x+len(prompt))
if initval:
subsubwin.addstr(0, 0, initval[:maxsymwidth-1])
subsubwin.touchwin()
configuration.debug_emit(1, "+++ %s"% (prompt,))
textbox = curses.textpad.Textbox(subsubwin)
popupval = textbox.edit()
self.window.touchwin()
if initval and textbox.lastcmd == curses.ascii.BEL:
return initval
else:
return popupval
# Symbol state changes
def set_symbol(self, sym, val, freeze=0):
"Try to set a symbol, display constraints in a popup if it fails."
configuration.debug_emit(1, lang["CURSESSET"] % (sym.name, val))
(ok, effects, violations) = configuration.set_symbol(sym, val, freeze)
if ok:
if not interactively_visible(sym):
self.help_popup("PRESSANY", [lang["INVISOUT"] % sym.name], beep=1)
else:
effects.append("\n")
self.help_popup("PRESSANY",
effects + [lang["BADREQUIRE"]] + map(repr, violations), beep=1)
# User interaction
def in_menu(self):
"Return 1 if we're in a symbol menu, 0 otherwise"
return isinstance(self.menus.selected(), cml.ConfigSymbol)
def recompute(self, here):
"Recompute the visible-members set for the given menu."
# First, make sure any choices menus immediately
# below this one get their defaults asserted. Has
# to be done here because the visibility of stuff
# in a menu may depend on a choice submenu before
# it, so we need the default value to be hardened,
map(configuration.visit, here.items)
# Now compute visibilities.
visible = filter(lambda x, m=here: hasattr(m, 'nosuppressions') or interactively_visible(x), here.items)
lookingat = self.menus.selected()
if lookingat in visible:
selected = self.menus.selected()
self.menus.pop()
self.menus.push(visible, selected)
self.seek_mutable(1)
else:
if configuration.suppressions:
configuration.debug_emit(1, lang["SUPPRESSOFF"])
configuration.suppressions = 0
self.help_popup("PRESSANY", (lang["SUPPRESSOFF"],), beep=1)
selected = self.menus.selected()
self.menus.pop()
self.menus.push(here.items, selected)
# We've recomputed the top-of-stack item,
# so we must regenerate all associated prompts.
self.values = map(cgenvalue, self.menus.top())
def redisplay(self, repaint):
"Repaint the screen."
sel_symbol = current_line = None
if self.banner and self.in_menu():
title = self.msgbuf + (" " * (self.columns - len(self.msgbuf) - len(self.banner) -1)) + self.banner
else:
title = (" " * ((self.columns-len(self.msgbuf)) / 2)) + self.msgbuf
self.menus.viewport_height = self.lines-2 + (not configuration.expert_tie or cml.evaluate(configuration.expert_tie) != cml.n)
self.window.move(0, 0)
self.window.clrtoeol()
self.window.addstr(title, curses.A_BOLD)
(thumb_top, thumb_bottom) = self.menus.thumb()
# Display the current band of entries
screenlines = self.menus.list()
if self.in_menu():
screenvals = self.values[self.menus.viewbase():self.menus.viewbase()+self.menus.viewport_height]
configuration.debug_emit(1, "screenvals: " + `screenvals`)
else:
current_prompt = None
# To change the number of lines on the screen that this paints,
# change the initialization of the viewport_height member.
for i in range(self.menus.viewport_height):
self.window.move(i+1, 0)
self.window.clrtoeol()
if len(self.menus.top()) <= self.menus.viewport_height:
thumb = None
elif i <= thumb_bottom and i >= thumb_top:
thumb = curses.ACS_CKBOARD
else:
thumb = curses.ACS_VLINE
if i < len(screenlines):
child = screenlines[i]
if type(child) is type(""):
self.window.addstr(i+1, 0, child)
elif child.type == "message":
self.window.addstr(i+1, 0, child.prompt + " ")
self.window.hline(i+1, len(child.prompt) + 2,
curses.ACS_HLINE, self.columns-len(child.prompt)-3)
else:
if child == self.menus.selected():
lpointer = ">"
rpointer = "<"
highlight = curses.A_REVERSE
current_line = i
current_prompt = screenvals[i]
sel_symbol = child
else:
lpointer = rpointer = " "
highlight = curses.A_NORMAL
if curses.has_colors():
if child.frozen():
highlight=curses.color_pair(curses.COLOR_CYAN)
#elif child.inspected:
# highlight=curses.color_pair(curses.COLOR_GREEN)
elif child.setcount or child.included:
highlight=curses.color_pair(curses.COLOR_GREEN)
# OK, now assemble the rest of the line
leftpart = (" " * child.depth) + cgenprompt(child, not configuration.expert_tie or not cml.evaluate(configuration.expert_tie))
if configuration.is_new(child):
leftpart = leftpart + " " + lang["NEW"]
if child.frozen():
leftpart = leftpart + " " + lang["FREEZELABEL"]
if child.help():
helpflag = "?"
else:
helpflag = ""
rightpart = "=" + child.name + helpflag
# Now make sure the information will fit in the line
fixedlen = 1+curses_style_menu.valwidth+1+len(rightpart)+(thumb!=None) + 1
leftpart = leftpart[:self.columns-fixedlen]
filler = " " * (self.columns - len(leftpart) - fixedlen)
line = leftpart + filler + rightpart
# Write it
self.window.move(i+1, 0)
self.window.addstr(lpointer)
if "edit" in repaint and child == self.menus.selected():
self.window.move(i+1, curses_style_menu.valwidth+2)
self.window.attron(highlight)
else:
self.window.attron(highlight)
valstring = screenvals[i][:curses_style_menu.valwidth]
self.window.addstr(valstring + (" " * (curses_style_menu.valwidth - len(valstring))) + " ")
self.window.addstr(line)
self.window.attroff(highlight)
# Ignore error from writing to last cell of
# last line; the old curses module in 1.5.2
# doesn't like this. The try/catch around the
# thumb write does the same thing.
try:
self.window.addstr(rpointer)
except:
pass
if thumb:
try:
self.window.addch(i+1, self.columns-1, thumb)
except:
pass
if not configuration.expert_tie or not cml.evaluate(configuration.expert_tie):
self.window.move(self.lines-1, 0)
self.window.clrtoeol()
helpbanner = lang["HELPBANNER"]
title = " " * ((self.columns - len(helpbanner))/2) + helpbanner
self.window.addstr(title, curses.A_BOLD)
if type(self.menus.selected()) is not type(""):
self.window.move(current_line + 1, 0)
if "main" in repaint or "edithelp" in repaint:
self.window.noutrefresh()
if "edit" in repaint:
self.textbox.win.touchwin()
self.textbox.win.noutrefresh()
curses.doupdate()
return (current_line, current_prompt, sel_symbol)
def accept_field(self, selected, value, oldval):
"Process the contents of a field edit."
base = 0
if selected.type == "hexadecimal":
base = 16
if oldval[:2] != "0x":
value = "0x" + value
value = string.strip(value)
if selected.is_numeric():
value = int(value, base)
if not configuration.range_check(selected, value):
self.help_popup("PRESSANY",
(lang["OUTOFBOUNDS"] % (value, selected.range,),))
return
self.set_symbol(selected, value)
def symbol_menu_command(self, cmd, operand):
"Handle commands that don't directly hack the screen or exit."
recompute = 0
if cmd == curses.KEY_LEFT:
if self.menus.stackdepth() <= 1:
self.msgbuf = lang["NOPOP"]
else:
self.menus.pop()
self.lastmenu = self.menus.selected()
recompute = 1
elif cmd == ord('y'):
if not self.in_menu():
self.help_popup("PRESSANY", (lang["NOSYMBOL"],))
elif operand.type in ("bool", "trit"):
self.set_symbol(operand, cml.y)
else:
self.help_popup("PRESSANY", (lang["BOOLEAN"],))
recompute = 1
if operand.menu.type != "choices":
self.ungetch(curses.KEY_DOWN)
elif cmd == ord('m'):
if not self.in_menu():
self.help_popup("PRESSANY", (lang["NOSYMBOL"],))
elif not configuration.trits_enabled:
self.help_popup("PRESSANY", (lang["MDISABLED"],))
elif operand.type == "trit":
self.set_symbol(operand, cml.m)
elif operand.type == "bool":
self.set_symbol(operand, cml.y) # Shortcut from old menuconfig
else:
self.help_popup("PRESSANY", (lang["TRIT"],))
recompute = 1
if operand.menu.type != "choices":
self.ungetch(curses.KEY_DOWN)
elif cmd == ord('n'):
if not self.in_menu():
self.help_popup("PRESSANY", (lang["NOSYMBOL"],))
elif operand.type in ("bool", "trit") and \
operand.menu.type != "choices":
self.set_symbol(operand, cml.n)
else:
self.help_popup("PRESSANY", (lang["BOOLEAN"],))
recompute = 1
if operand.menu.type != "choices":
self.ungetch(curses.KEY_DOWN)
elif cmd == ord('i'):
file = self.query_popup(lang["LOADFILE"])
try:
(changes, errors) = configuration.load(file, freeze=0)
except:
self.help_popup("PRESSANY", [lang["LOADFAIL"] % file])
else:
if errors:
self.help_popup("PRESSANY", (errors,))
else:
# Note, we don't try to display side effects here.
# From a file include, there are very likely to
# be more of them than can fit in a popup.
self.help_popup("PRESSANY",
[lang["INCCHANGES"]%(changes,file)], beep=0)
recompute = 1
elif cmd == ord('I'):
file = self.query_popup(lang["LOADFILE"])
try:
(changes, errors) = configuration.load(file, freeze=0)
except:
self.help_popup("PRESSANY", [lang["LOADFAIL"] % file])
else:
if errors:
self.help_popup("PRESSANY", (errors,))
else:
# Note, we don't try to display side effects here.
# From a file include, there are very likely to
# be more of them than can fit in a popup.
self.help_popup("PRESSANY",
[lang["INCCHANGES"]%(changes,file)], beep=0)
recompute = 1
elif cmd == ord('S'):
configuration.suppressions = not configuration.suppressions
recompute = 1
elif cmd == ord('/'):
pattern = self.query_popup(lang["SEARCHSYMBOLS"])
if pattern:
try:
hits = configuration.symbolsearch(pattern)
except re.error, detail:
self.help_popup("PRESSANY",
(lang["SEARCHINVAL"], str(detail)))
else:
configuration.debug_emit(1, "hits: " + str(hits))
if len(hits.items):
self.menus.push(hits.items)
else:
self.help_popup("PRESSANY", (lang["SEARCHFAIL"],))
recompute = 1
elif cmd == ord('s'):
failure = configuration.save(config,
PopupBaton(lang["SAVING"], self))
if failure:
self.help_popup("PRESSANY", [failure])
else:
self.help_popup("PRESSANY",
(lang["UNKNOWN"]%(curses.keyname(cmd)),))
return recompute
def seek_mutable(self, direction, movefirst=0):
if movefirst:
self.menus.move(delta=direction, wrap=1)
while self.menus.selected().type =="message" \
or self.menus.selected().frozen():
self.menus.move(delta=direction, wrap=1)
def interact(self, config):
"Configuration through a curses-based UI"
self.menus.push(configuration.start.items)
while not interactively_visible(self.menus.selected()):
if not self.menus.move(1):
self.help_popup("PRESSANY", (lang["NOVISIBLE"],), beep=1)
raise SystemExit, 1
recompute = 1
repaint = ["main"]
#curses.ungetch(curses.ascii.TAB) # Get to a help screen.
while 1:
if isinstance(self.menus.selected(), cml.ConfigSymbol):
# In theory we could optimize this by only computing
# visibilities for children we have space to display,
# but never mind. We'll settle for recomputing only
# when a variable changes value.
here = self.menus.selected().menu
configuration.visit(here)
if recompute:
self.recompute(here)
recompute = 0
# Clear the decks, issue the current menu title
self.msgbuf = here.prompt
sel_symbol = None
# Repaint the screen
(current_line,current_prompt,sel_symbol) = self.redisplay(repaint)
newval = current_prompt
# OK, here is the command interpretation
if "edit" in repaint:
cmd = self.getch(self.textbox.win)
else:
cmd = self.getch(self.window)
if "edithelp" in repaint:
repaint = ["main", "edit"]
self.textbox.win.move(oldy, oldx)
self.menus.pop()
continue
elif "edit" in repaint:
if cmd in (curses.KEY_DOWN, curses.KEY_UP, curses.KEY_ENTER,
curses.ascii.NL, curses.ascii.CR, curses.ascii.BEL,
ord(curses.ascii.ctrl('p')), ord(curses.ascii.ctrl('n'))):
if cmd != curses.ascii.BEL:
newval = self.textbox.gather()
self.accept_field(sel_symbol,
newval,
current_prompt)
# allow window to be deallocated
self.textbox = None
recompute = 1
repaint = ["main"]
self.msgbuf = ""
if cmd in (curses.KEY_DOWN, curses.KEY_UP):
self.ungetch(cmd)
elif curses.ascii.isprint(cmd):
if sel_symbol.type == "decimal" and not curses.ascii.isdigit(cmd):
curses.beep()
elif sel_symbol.type == "hexadecimal" and not curses.ascii.isxdigit(cmd):
curses.beep()
else:
self.textbox.do_command(cmd)
elif cmd == curses.ascii.TAB:
self.msgbuf = lang["FIELDEDIT"]
self.menus.push(string.split(lang["EDITHELP"], "\n"))
(oldy, oldx) = self.textbox.win.getyx()
repaint = ["edithelp"]
continue
elif cmd == curses.KEY_RIGHT and self.textbox.win.getyx()[1]>=curses_style_menu.valwidth:
oldval = newval
newval = self.query_popup(sel_symbol.name+": ", oldval)
if newval:
self.accept_field(sel_symbol, newval, oldval)
self.textbox.win.clear()
self.textbox.win.addstr(0, 0, newval[0:curses_style_menu.valwidth])
recompute = 1
self.textbox = None
repaint = ["main"]
else:
self.textbox.do_command(cmd)
else:
if cmd == curses.ascii.FF:
self.window.touchwin()
self.window.refresh()
elif cmd == curses.KEY_RESIZE or cmd == -1:
# Second test works around a bug in the old curses module
# it gives back a -1 on resizes instead of KEY_RESIZE.
(self.lines, self.columns) = self.window.getmaxyx()
self.menus.viewport_height = self.lines-1
recompute = 1
elif cmd in (curses.ascii.TAB, ord('h')):
if self.in_menu():
self.menus.push(string.split(lang["CURSHELP"], "\n"))
self.msgbuf = lang["WELCOME"] % (configuration.banner) \
+ lang["VERSION"] % (cml.version,) \
+ " " + lang["CURSQUERY"]
self.helpmode = 1
elif self.helpmode == 1:
self.menus.pop()
self.menus.push(string.split(lang["EXPERTHELP"], "\n"))
self.msgbuf = lang["CMDHELP"]
self.helpmode = 2
else:
self.menus.pop()
recompute = 1
elif cmd == ord('e'):
if not self.in_menu():
self.help_popup("PRESSANY", (lang["NOSYMBOL"],))
else:
self.help_popup("PRESSANY", (str(sel_symbol),), beep=0)
elif cmd == ord('g'):
symname = self.query_popup(lang["GPROMPT"])
if not configuration.dictionary.has_key(symname):
self.help_popup("PRESSANY", (lang["NONEXIST"] % symname,))
else:
entry = configuration.dictionary[symname]
if entry.type in ("menu", "choices"):
self.menus.push(entry.items)
recompute = 1
elif entry.type == "message" or not entry.menu:
self.help_popup("PRESSANY", (lang["CANTGO"],))
else:
self.menus.push(entry.menu.items, entry)
recompute = 1
elif cmd == ord('?'):
if not self.in_menu():
self.help_popup("PRESSANY", (lang["NOSYMBOL"],))
else:
help = sel_symbol.help()
if help:
self.msgbuf = lang["HELPFOR"] % (sel_symbol.name,)
self.menus.push(string.split(help, "\n"))
else:
self.help_popup("PRESSANY",
(lang["NOHELP"] % (sel_symbol.name,),))
elif cmd in (curses.KEY_DOWN, curses.ascii.ctrl('n')):
if not self.in_menu():
self.menus.scroll(1)
else:
self.seek_mutable(1, 1)
elif cmd == curses.KEY_UP:
if not self.in_menu():
self.menus.scroll(-1)
else:
self.seek_mutable(-1, 1)
elif cmd in (curses.KEY_NPAGE, curses.ascii.ctrl('p')):
if self.in_menu():
self.menus.page_down()
notlast = (self.menus.selected() != self.menus.list()[-1])
self.seek_mutable(notlast)
elif cmd == curses.KEY_PPAGE:
if self.in_menu():
self.menus.page_up()
notlast = (self.menus.selected() != self.menus.list()[-1])
self.seek_mutable(notlast)
elif cmd == curses.KEY_HOME:
if self.in_menu():
self.menus.goto(0)
self.seek_mutable(0)
elif cmd == curses.KEY_END:
if self.in_menu():
self.menus.goto(len(self.menus.list())-1)
self.seek_mutable(0)
# This guard intercepts all other commands in helpmode
elif not self.in_menu():
if self.menus.stackdepth() == 1:
here = configuration.start.items[0]
while not interactively_visible(here):
here = configuration.next_node(here)
self.menus.push(here.menu.items, here)
else:
self.menus.pop()
# Following commands are not executed in helpmode
elif cmd == ord('x'):
failure = configuration.save(config,
PopupBaton(lang["SAVING"], self))
if failure:
self.help_popup("PRESSANY", [failure])
break
elif cmd == ord('q'):
if configuration.commits == 0:
break
cmd = self.help_popup("EXITCONFIRM", (lang["REALLY"],), beep=0)
if cmd == ord('q'):
raise SystemExit, 1
elif cmd in (curses.KEY_ENTER,ord(' '),ord('\r'),ord('\n'),curses.KEY_RIGHT) :
# Operate on the current object
if sel_symbol.type == "message":
curses.beep()
elif sel_symbol.type in ("menu", "choices"):
self.menus.push(sel_symbol.items)
sel_symbol.inspected += 1
while not interactively_visible(self.menus.selected()) or self.menus.selected().type == "message":
if not self.menus.move(1, 0):
break
elif here.type == "choices" and sel_symbol.eval():
pass
elif cmd == curses.KEY_RIGHT:
pass
elif sel_symbol.type == "bool" or (sel_symbol.type == "trit" and not configuration.trits_enabled):
if sel_symbol.eval() == cml.y:
toggled = cml.n
else:
toggled = cml.y
self.set_symbol(sel_symbol, toggled)
elif sel_symbol.type == "trit":
if sel_symbol.eval() == cml.y:
toggled = cml.n
elif sel_symbol.eval() == cml.m:
toggled = cml.y
else:
toggled = cml.m
self.set_symbol(sel_symbol, toggled)
else:
win = curses.newwin(1, curses_style_menu.valwidth+1, current_line+1, 1)
self.textbox = curses.textpad.Textbox(win)
self.textbox.win.addstr(0, 0, current_prompt[:curses_style_menu.valwidth])
newval = current_prompt
self.textbox.win.move(0, 0)
self.msgbuf = lang["EDITING"] % (sel_symbol.name[:self.columns-1],)
repaint = ["main", "edit"]
recompute = 1
else:
recompute = self.symbol_menu_command(cmd, sel_symbol)
# Tkinter interface
# This is wrapped in try/expect in case the Tkinter import fails.
# We need the import here because these classes have Frame as a parent.
try:
from Tkinter import *
from tree import *
class ValidatedField(Frame):
"Accept a string, decimal or hex value in a labeled field."
def __init__(self, master, symbol, prompt, variable, hook):
Frame.__init__(self, master)
self.symbol = symbol
self.hook = hook
self.fieldval = variable
self.L = Label(self, text=prompt, anchor=W)
self.L.pack(side=LEFT)
self.E = Entry(self, textvar=self.fieldval)
self.E.pack({'side':'left', 'expand':YES, 'fill':X})
self.E.bind('<Return>', self.handlePost)
self.E.bind('<Enter>', self.handleEnter)
self.fieldval.set(str(cml.evaluate(symbol)))
self.errorwin = None
def handleEnter(self, dummy):
self.E.bind('<Leave>', self.handlePost)
def handlePost(self, event):
if self.errorwin:
return
self.E.bind('<Leave>', lambda e: None)
result = string.strip(self.fieldval.get())
if self.symbol.type == "decimal":
if not re.compile("[" + string.digits +"]+$").match(result):
self.error_popup(title=lang["PROBLEM"],
banner=self.symbol.name,
text=lang["CHARINVAL"])
return
elif self.symbol.type == "hexadecimal":
if not re.compile("(0x)?["+string.hexdigits+"]+$").match(result):
self.error_popup(title=lang["PROBLEM"],
banner=self.symbol.name,
text=lang["CHARINVAL"])
return
apply(self.hook, (self.symbol, result))
def error_popup(self, title, mybanner, text):
self.errorwin = Toplevel()
self.errorwin.title(title)
self.errorwin.iconname(title)
Label(self.errorwin, text=mybanner).pack()
Label(self.errorwin, text=text).pack()
Button(self.errorwin, text=lang["DONE"],
command=lambda x=self.errorwin: Widget.destroy(x), bd=2).pack()
class PromptGo(Frame):
"Accept a string value in a browser-like prompt window."
def __init__(self, master, label, command):
Frame.__init__(self, master)
# We really want to do this to make the window appear
# within the workframe:
#self.promptframe = Frame(master)
# Unfortunately, the scroll function in the canvas seems
# to get confused when we try this
self.promptframe = Frame(Toplevel())
self.promptframe.master.bind('<Destroy>', self.handleDestroy);
self.fieldval = StringVar(self.promptframe)
self.promptframe.L = Label(self.promptframe,
text=lang[label], anchor=W)
self.promptframe.L.pack(side=LEFT)
self.promptframe.E = Entry(self.promptframe, textvar=self.fieldval)
self.promptframe.E.pack({'side':'left', 'expand':YES, 'fill':X})
self.promptframe.E.bind('<Return>', self.dispatch)
self.promptframe.E.focus_set()
self.command = command
Button(self.promptframe, text=lang["GO"],
command=self.dispatch, bd=2).pack()
self.promptframe.pack()
# Scroll to top of canvas and refresh/resize
self.master.menuframe.resetscroll()
self.master.refresh()
def dispatch(self, dummy=None):
apply(self.command, (self.fieldval.get(),))
# if PromptGo is implemented as top level widget this is not
# sufficient:
#self.promptframe.destroy()
# instead the top level widget must be destroyed
self.promptframe.master.destroy()
def handleDestroy(self, dummy=None):
apply(self.command, (None,))
class ScrolledFrame(Frame):
"A Frame object with a scrollbar on the right."
def __init__(self, master, **kw):
apply(Frame.__init__, (self, master), kw)
self.scrollbar = Scrollbar(self, orient=VERTICAL)
self.canvas = Canvas(self, yscrollcommand=self.scrollbar.set)
self.scrollbar.config(command=self.canvas.yview)
self.scrollbar.pack(fill=Y, side=RIGHT)
self.canvas.pack(side=LEFT, fill=BOTH, expand=YES)
# create the inner frame
self.inner = Frame(self.canvas)
# track changes to its size
self.inner.bind('<Configure>', self.__configure)
# place the frame inside the canvas
# (this also runs the __configure method)
self.canvas.create_window(0, 0, window=self.inner, anchor=NW)
def showscroll(self, flag):
if flag:
self.canvas.pack_forget()
self.scrollbar.pack(fill=Y, side=RIGHT)
self.canvas.pack(side=LEFT, fill=BOTH, expand=YES)
else:
self.scrollbar.pack_forget()
def resetscroll(self, loc=0.0):
self.canvas.yview("moveto", loc)
def __configure(self, dummy):
# update the scrollbars to match the size of the inner frame
size = self.inner.winfo_reqwidth(), self.inner.winfo_reqheight()
self.canvas.config(scrollregion="0 0 %s %s" % size)
class ScrolledText(Frame):
def __init__(self,parent=None,text=None,file=None,height=10,**kw):
apply(Frame.__init__,(self,parent),kw)
self.makewidgets(height)
self.settext(text,file)
def makewidgets(self,ht):
sbar=Scrollbar(self)
text=Text(self,relief=SUNKEN,height=ht)
sbar.config(command=text.yview)
text.config(yscrollcommand=sbar.set)
sbar.pack(side=RIGHT,fill=Y)
text.pack(side=LEFT,expand=YES,fill=BOTH)
self.text=text
def settext(self, text=None,file=None):
if file:
text=open(file,'r').read()
elif text:
self.text.delete('1.0',END)
self.text.insert('1.0',text)
else:
text='None'
self.text.delete('1.0',END)
# Routine to get contents of subtree. Supply this for a different
# type of app argument is the node object being expanded should return
# list of 4-tuples in the form: (label, unique identifier, closed
# icon, open icon) where:
# label - the name to be displayed
# unique identifier - an internal fully unique name
# closed icon - PhotoImage of closed item
# open icon - PhotoImage of open item, or None if not openable
def my_get_contents(node):
menus=[]
options=[]
cmlnode=node.id
for child in cmlnode.items:
if interactively_visible(child):
if child.type =="menu" and cmlnode.items :
menus.append((child.prompt, child, shut_icon, open_icon))
else:
options.append((child.prompt, child, file_icon, None))
menus.sort()
options.sort()
return options+menus
class myTree(Tree):
def __init__(self,master,**kw):
apply(Tree.__init__,(self,master),kw)
def update_node(self,node=None):
if node==None:
node=self.pos
if node.id.type in ("trit","bool"):
if node.id.yes=="yes" and node.id.eval()==cml.n:
node.id.yes="no"
x1,y1=self.coords(node.symbol)
self.delete(node.symbol)
node.symbol=self.create_image(x1,y1,image=no_icon)
elif node.id.yes=="no" and \
(node.id.eval()==cml.y or node.id.eval()==cml.m):
node.id.yes="yes"
x1,y1=self.coords(node.symbol)
self.delete(node.symbol)
node.symbol=self.create_image(x1,y1,image=yes_icon)
def update_tree(self):
#old cursor position
oldpos=self.pos.full_id()
#get expanded node list
n=self.root.expanded()
#redraw whole tree again
self.root.toggle_state(0)
for j in n:
self.root.expand(j)
self.move_cursor(self.root.expand(oldpos[1:]))
def makehelpwin(title, mybanner, text):
# help message window with a self-destruct button
makehelpwin = Toplevel()
makehelpwin.title(title)
makehelpwin.iconname(title)
if mybanner:
Label(makehelpwin, text=mybanner).pack()
textframe = Frame(makehelpwin)
scroll = Scrollbar(textframe)
makehelpwin.textwidget = Text(textframe, setgrid=TRUE)
textframe.pack(side=TOP, expand=YES, fill=BOTH)
makehelpwin.textwidget.config(yscrollcommand=scroll.set)
makehelpwin.textwidget.pack(side=LEFT, expand=YES, fill=BOTH)
scroll.config(command=makehelpwin.textwidget.yview)
scroll.pack(side=RIGHT, fill=BOTH)
Button(makehelpwin, text=lang["DONE"],
command=lambda x=makehelpwin: x.destroy(), bd=2).pack()
makehelpwin.textwidget.tag_config('url', foreground='blue', underline=YES)
makehelpwin.textwidget.tag_bind('url', '<Button-1>', launch_browser)
makehelpwin.textwidget.tag_bind('url', '<Enter>', lambda event, x=makehelpwin.textwidget: x.config(cursor='hand2'))
makehelpwin.textwidget.tag_bind('url', '<Leave>', lambda event, x=makehelpwin.textwidget: x.config(cursor='xterm'))
tag_urls(makehelpwin.textwidget, text)
makehelpwin.textwidget.config(state=DISABLED) # prevent editing
makehelpwin.lift()
def tag_urls(textwidget, text):
getURL = re.compile('((?:http|ftp|mailto|file)://[-.~/_?=#%\w]+\w)')
textlist = getURL.split(text)
for n in range(len(textlist)):
if n % 2 == 1:
textwidget.insert(END, textlist[n], ('url', textlist[n]))
else:
textwidget.insert(END, textlist[n])
def launch_browser(event):
url = event.widget.tag_names(CURRENT)[1]
webbrowser.open(url)
def make_icon_window(base, image):
try:
# Some older pythons will error out on this
icon_image = PhotoImage(data=image)
icon_window = Toplevel()
Label(icon_window, image=icon_image, bg='black').pack()
base.master.iconwindow(icon_window)
# Avoid TkInter brain death. PhotoImage objects go out of
# scope when the enclosing function returns. Therefore
# we have to explicitly link them to something.
base.keepalive.append(icon_image)
except:
pass
def get_contents(node):
global open_icon, shut_icon, file_icon, yes_icon, no_icon
open_icon=PhotoImage(
data='R0lGODlhEAANAKIAAAAAAMDAwICAgP//////ADAwMAAAAAAA' \
'ACH5BAEAAAEALAAAAAAQAA0AAAM6GCrM+jCIQamIbw6ybXNSx3GVB' \
'YRiygnA534Eq5UlO8jUqLYsquuy0+SXap1CxBHr+HoBjoGndDpNAAA7')
shut_icon=PhotoImage(
data='R0lGODlhDwANAKIAAAAAAMDAwICAgP//////ADAwMAAAAAAA' \
'ACH5BAEAAAEALAAAAAAPAA0AAAMyGCHM+lAMMoeAT9Jtm5NDKI4Wo' \
'FXcJphhipanq7Kvu8b1dLc5tcuom2foAQQAyKRSmQAAOw==')
file_icon=PhotoImage(
data='R0lGODlhCwAOAJEAAAAAAICAgP///8DAwCH5BAEAAAMALAAA' \
'AAALAA4AAAIphA+jA+JuVgtUtMQePJlWCgSN9oSTV5lkKQpo2q5W+' \
'wbzuJrIHgw1WgAAOw==')
yes_icon=PhotoImage(
data='R0lGODlhCwAOAMIAAAAAAP////4AAHZ2dv///////////////' \
'yH5BAEKAAQALAAAAAALAA4AAAMuCLpATiBIqV6cITaI8+LCFGZ' \
'DNAYjUKIitY5CuqKhfLWkiatXfPKM4IAwKBqPCQA7')
no_icon=PhotoImage(
data='R0lGODlhCwAOAMIAAAAAAP///3Z2dik8/////////////////' \
'yH5BAEKAAQALAAAAAALAA4AAAMtCLpATiBIqV6cITaI8+IdJUR' \
'DMJQiiaLAaE6si76ZDKc03V7hzvwCgmBILCYAADs=')
# menus=[]
options=[]
cmlnode=node.id
for child in cmlnode.items:
if interactively_visible(child):
if child.type =="menu" and cmlnode.items :
# menus.append((child.prompt, child, shut_icon, open_icon))
options.append((child.prompt, child, shut_icon, open_icon))
else:
if child.type in ("trit","bool") and \
(child.eval() == cml.y or child.eval() ==cml.m):
options.append((child.prompt, child, yes_icon, None))
child.yes="yes"
elif child.type in ("trit","bool") and \
child.eval() == cml.n:
options.append((child.prompt, child, no_icon, None))
child.yes="no"
else:
options.append((child.prompt, child, file_icon, None))
# menus.sort()
# options.sort()
#return options+menus
return options
class ConfigMenu(Frame):
"Generic X front end for configurator."
def __init__(self, menu, config, mybanner):
Frame.__init__(self, master=None)
self.config = config
announce = configuration.banner + lang["VERSION"] % cml.version
if mybanner and announce.find("%s") > -1:
announce %= mybanner
self.master.title(announce)
self.master.iconname(announce)
self.master.resizable(FALSE, TRUE)
Pack.config(self, fill=BOTH, expand=YES)
self.keepalive = [] # Use this to anchor the PhotoImage object
if configuration.icon:
make_icon_window(self, configuration.icon)
## Test icon display with the following:
# icon_image = PhotoImage(data=configuration.icon)
# Label(self, image=icon_image).pack(side=TOP, pady=10)
# self.keepalive.append(icon_image)
self.header = Frame(self)
self.header.pack(side=TOP, fill=X)
self.menubar = Frame(self.header, relief=RAISED, bd=2)
self.menubar.pack(side=TOP, fill=X, expand=YES)
self.filemenu = self.makeMenu(lang["FILEBUTTON"],
(("LOADBUTTON", self.load),
("FREEZEBUTTON", self.freeze),
("SAVEBUTTON", self.save),
("SAVEAS", self.save_as),
("QUITBUTTON", self.leave),
))
self.navmenu = self.makeMenu(lang["NAVBUTTON"],
(("BACKBUTTON", self.pop),
("UPBUTTON", self.up),
("GOBUTTON", self.goto),
("SEARCHBUTTON", self.symbolsearch),
("HSEARCHBUTTON", self.helpsearch),
("UNSUPPRESSBUTTON",self.toggle_suppress),
("ANCESTORBUTTON",self.show_ancestors),
("DEPENDENTBUTTON",self.show_dependents),
))
self.helpmenu = self.makeMenu(lang["HELPBUTTON"],
(("HELPBUTTON", self.cmdhelp),))
self.menulabel=Label(self.menubar)
self.menulabel.pack(side=RIGHT)
self.toolbar = Frame(self.header, relief=RAISED, bd=2)
self.backbutton = Button(self.toolbar, text=lang["BACKBUTTON"],
command=self.pop)
self.backbutton.pack(side=LEFT)
self.helpbutton = Button(self.toolbar, text=lang["HELPBUTTON"],
command=lambda self=self: self.help(self.menustack[-1]))
self.helpbutton.pack(side=RIGHT)
self.workframe = None
def makeMenu(self, label, ops):
mbutton = Menubutton(self.menubar, text=label, underline=0)
mbutton.pack(side=LEFT)
dropdown = Menu(mbutton)
for (legend, function) in ops:
dropdown.add_command(label=lang[legend], command=function)
mbutton['menu'] = dropdown
return dropdown
def setchoice(self, symbol):
# Handle a choice-menu selection.
self.set_symbol(symbol, cml.y)
self.lastmenu = symbol
# File menu operations
def enable_file_ops(self, ok):
if ok:
self.filemenu.entryconfig(1, state=NORMAL)
self.filemenu.entryconfig(2, state=NORMAL)
self.filemenu.entryconfig(3, state=NORMAL)
self.filemenu.entryconfig(4, state=NORMAL)
#self.filemenu.entryconfig(5, state=NORMAL)
else:
self.filemenu.entryconfig(1, state=DISABLED)
self.filemenu.entryconfig(2, state=DISABLED)
self.filemenu.entryconfig(3, state=DISABLED)
self.filemenu.entryconfig(4, state=DISABLED)
#self.filemenu.entryconfig(5, state=DISABLED)
def load(self):
self.enable_file_ops(0)
PromptGo(self, "LOADFILE", self.load_internal)
def load_internal(self, file):
"Load a configuration file."
if file:
try:
(changes, errors) = configuration.load(file, freeze=0)
except IOError:
Dialog(self,
title = lang["PROBLEM"],
text = lang["LOADFAIL"] % file,
bitmap = 'error',
default = 0,
strings = (lang["DONE"],))
else:
if errors:
Dialog(self,
title = lang["PROBLEM"],
text = errors,
bitmap = 'error',
default = 0,
strings = (lang["DONE"],))
else:
# Note, we don't try to display side effects here.
# From a file include, there are very likely to
# be more of them than can fit in a popup.
Dialog(self,
title = lang["OK"],
text = lang["INCCHANGES"] % (changes,file),
bitmap = 'hourglass',
default = 0,
strings = (lang["DONE"],))
#self.tree.update_tree()
self.enable_file_ops(1)
def freeze(self):
ans = Dialog(self,
title = lang["CONFIRM"],
text = lang["FREEZE"],
bitmap = 'questhead',
default = 0,
strings = (lang["FREEZEBUTTON"], lang["CANCEL"]))
if ans.num == 0:
for key in configuration.dictionary.keys():
entry = configuration.dictionary[key]
if entry.eval():
entry.freeze()
def save_internal(self, config):
failure = configuration.save(config)
if not failure:
return 1
else:
ans = Dialog(self,
title = lang["PROBLEM"],
text = failure,
bitmap = 'error',
default = 0,
strings = (lang["CANCEL"], lang["DONE"]))
return ans.num
def save(self):
if self.save_internal(self.config):
self.quit()
def save_as(self):
# Disable everything but quit while this is going on
self.enable_file_ops(0)
PromptGo(self, "SAVEFILE", self.save_as_internal)
def save_as_internal(self, file):
if file:
self.save_internal(file)
self.enable_file_ops(1)
def leave(self):
if configuration.commits == 0:
self.quit()
else:
ans = Dialog(self,
title = lang["QUITCONFIRM"],
text = lang["REALLY"],
bitmap = 'questhead',
default = 0,
strings = (lang["EXIT"], lang["CANCEL"]))
if ans.num == 0:
self.quit()
raise SystemExit, 1
# Navigation menu options
def enable_nav_ops(self, ok):
if ok:
self.navmenu.entryconfig(1, state=NORMAL)
self.navmenu.entryconfig(2, state=NORMAL)
self.navmenu.entryconfig(3, state=NORMAL)
self.navmenu.entryconfig(4, state=NORMAL)
#self.navmenu.entryconfig(5, state=NORMAL)
self.navmenu.entryconfig(6, state=NORMAL)
self.navmenu.entryconfig(7, state=NORMAL)
else:
self.navmenu.entryconfig(1, state=DISABLED)
self.navmenu.entryconfig(2, state=DISABLED)
self.navmenu.entryconfig(3, state=DISABLED)
self.navmenu.entryconfig(4, state=DISABLED)
#self.navmenu.entryconfig(5, state=DISABLED)
self.navmenu.entryconfig(6, state=DISABLED)
self.navmenu.entryconfig(7, state=DISABLED)
def up(self):
here = self.menustack[-1]
if here.menu:
self.push(here.menu, here)
def goto(self):
self.enable_nav_ops(0)
PromptGo(self, "GOTOBYNAME", self.goto_internal)
def goto_internal(self, symname):
if symname:
if not configuration.dictionary.has_key(symname):
Dialog(self,
title = lang["PROBLEM"],
text = lang["NONEXIST"] % symname,
bitmap = 'error',
default = 0,
strings = (lang["DONE"],))
else:
symbol = configuration.dictionary[symname]
print symbol
# We can't go to a symbol in a choices menu directly;
# instead we must go to its parent.
if symbol.menu and symbol.menu.type == "choices":
symbol = symbol.menu
if not configuration.is_mutable(symbol):
Dialog(self,
title = lang["PROBLEM"],
text = lang["FROZEN"],
bitmap = 'hourglass',
default = 0,
strings = (lang["DONE"],))
elif not interactively_visible(symbol):
configuration.suppressions = 0
if symbol.type in ("menu", "choices"):
self.push(symbol)
elif symbol.menu:
self.push(symbol.menu, symbol)
else:
Dialog(self,
title = lang["PROBLEM"],
text = (lang["NOMENU"] % (symbol.name)),
bitmap = 'error',
default = 0,
strings = (lang["DONE"],))
self.enable_nav_ops(1)
def symbolsearch(self):
self.enable_nav_ops(0)
PromptGo(self, "SEARCHSYMBOLS", self.symbolsearch_internal)
def symbolsearch_internal(self, pattern):
if not pattern is None:
if pattern:
hits = configuration.symbolsearch(pattern)
hits.inspected = 0
if hits.items:
self.push(hits)
print hits
else:
Dialog(self,
title = lang["PROBLEM"],
text = lang["NOMATCHES"],
bitmap = 'error',
default = 0,
strings = (lang["DONE"],))
else:
Dialog(self,
title = lang["PROBLEM"],
text = lang["EMPTYSEARCH"],
bitmap = 'error',
default = 0,
strings = (lang["DONE"],))
self.enable_nav_ops(1)
def helpsearch(self):
self.enable_nav_ops(0)
PromptGo(self, "SEARCHHELP", self.helpsearch_internal)
def helpsearch_internal(self, pattern):
if not pattern is None:
if pattern:
hits = configuration.helpsearch(pattern)
hits.inspected = 0
if hits.items:
self.push(hits)
else:
Dialog(self,
title = lang["PROBLEM"],
text = lang["NOMATCHES"],
bitmap = 'error',
default = 0,
strings = (lang["DONE"],))
else:
Dialog(self,
title = lang["PROBLEM"],
text = lang["EMPTYSEARCH"],
bitmap = 'error',
default = 0,
strings = (lang["DONE"],))
self.enable_nav_ops(1)
def show_ancestors(self):
self.enable_nav_ops(0)
PromptGo(self, "SHOW_ANC", self.show_ancestors_internal)
def show_ancestors_internal(self, symname):
if symname:
entry = configuration.dictionary.get(symname)
if not entry:
Dialog(self,
title = lang["INFO"],
text = lang["NONEXIST"] % symname,
bitmap = 'error',
default = 0,
strings = (lang["DONE"],))
elif not entry.ancestors:
Dialog(self,
title = lang["PROBLEM"],
text = lang["NOANCEST"],
bitmap = 'info',
default = 0,
strings = (lang["DONE"],))
else:
hits = cml.ConfigSymbol("ancestors", "menu")
hits.items = entry.ancestors
# Give result a parent only if all members have same parent
hits.menu = None
hits.inspected = 0
for symbol in hits.items:
if not interactively_visible(symbol):
configuration.suppressions = 0
if hits.menu == None:
hits.menu = symbol.menu
elif symbol.menu != hits.menu:
hits.menu = None
break
self.push(hits)
self.enable_nav_ops(1)
def show_dependents(self):
self.enable_nav_ops(0)
PromptGo(self, "SHOW_ANC", self.show_dependents_internal)
def show_dependents_internal(self, symname):
if symname:
entry = configuration.dictionary.get(symname)
if not entry:
Dialog(self,
title = lang["INFO"],
text = lang["NONEXIST"] % symname,
bitmap = 'error',
default = 0,
strings = (lang["DONE"],))
elif not entry.dependents:
Dialog(self,
title = lang["PROBLEM"],
text = lang["NODEPS"],
bitmap = 'info',
default = 0,
strings = (lang["DONE"],))
else:
hits = cml.ConfigSymbol("dependents", "menu")
hits.items = entry.dependents
# Give result a parent only if all members have same parent
hits.menu = None
hits.inspected = 0
for symbol in hits.items:
if not interactively_visible(symbol):
configuration.suppressions = 0
if hits.menu == None:
hits.menu = symbol.menu
elif symbol.menu != hits.menu:
hits.menu = None
break
self.push(hits)
self.enable_nav_ops(1)
def toggle_suppress(self):
configuration.suppressions = not configuration.suppressions
if configuration.suppressions:
self.navmenu.entryconfig(6, label=lang["UNSUPPRESSBUTTON"])
else:
self.navmenu.entryconfig(6, label=lang["SUPPRESSBUTTON"])
self.build()
self.display()
# Help menu operations
def cmdhelp(self):
makehelpwin(title=lang["HELPBUTTON"],
mybanner=lang["HELPFOR"] % (configuration.banner,),
text=lang["TKCMDHELP"])
class ConfigTreeMenu(ConfigMenu):
"Top-level CML2 configurator object."
def __init__(self, menu, config, mybanner):
global helpwin
Frame.__init__(self, master=None)
ConfigMenu.__init__(self,menu,config,mybanner)
self.optionframe=None
self.tree=None
self.treewindow=Frame(self)
self.draw_tree()
self.treewindow.pack(expand=YES,fill=BOTH,side=LEFT)
#quitbutton=Button(self.master,text='Quit',command=parent.quit)
#quitbutton.pack(fill=X,side=BOTTOM)
self.navmenu.entryconfig(1, state=DISABLED)
self.navmenu.entryconfig(2, state=DISABLED)
self.navmenu.entryconfig(3, state=DISABLED)
self.navmenu.entryconfig(4, state=DISABLED)
self.navmenu.entryconfig(5, state=DISABLED)
self.navmenu.entryconfig(6, state=DISABLED)
self.navmenu.entryconfig(7, state=DISABLED)
self.navmenu.entryconfig(8, state=DISABLED)
helpwin=ScrolledText(self.master,text='',height=10)
helpwin.pack(fill=X,side=BOTTOM)
def push(self):
self.tree.ascend()
def pop(self):
self.tree.descend()
def load_internal(self,file):
ConfigMenu.load_internal(self,file)
self.tree.update_tree()
def toggle_init(self,node):
global current_node
current_node=node.id
if current_node.helptext is None:
helpwin.settext(current_node.prompt)
else:
helpwin.settext(current_node.helptext)
self.draw_optionframe()
def draw_optionframe(self):
global current_node,configuration
node=current_node
if self.optionframe:
Widget.destroy(self.optionframe)
if node:
id=node.name +": "+node.prompt
if configuration.is_new(node):
id += " " + "New"
self.ties={}
self.optionframe=Frame(self.master)
self.optionframe.pack(fill=X,side=TOP)
if node.type =="choices":
new= Menubutton(self.optionframe,relief=RAISED,
text=node.prompt)
cmenu=Menu(new,tearoff=0)
self.ties[node.name]=StringVar()
for alt in node.items:
cmenu.add_radiobutton(
label=alt.name+": "+alt.prompt,
variable=self.ties[node.name], value=alt.name,
command=lambda self=self, x=alt:self.setchoice(x))
new.config(menu=cmenu)
new.pack(side=LEFT,anchor=W,fill=X,expand=YES)
elif node.type in ("trit","bool"):
self.ties[node.name]=IntVar()
if configuration.trits_enabled:
w=Radiobutton(self.optionframe, text="y",
variable=self.ties[node.name],
command=lambda x=node, self=self:
self.set_symbol(x,cml.y),
relief=GROOVE,value=cml.y)
w.pack(anchor=W,side=LEFT)
w=Radiobutton(self.optionframe, text="m",
variable=self.ties[node.name],
command=lambda x=node, self=self:
self.set_symbol(x,cml.m),
relief=GROOVE,value=cml.m)
if node.type== "bool":
w.config(state=DISABLED,text="-")
w.pack(anchor=W,side=LEFT)
w=Radiobutton(self.optionframe, text="n",
variable=self.ties[node.name],
command=lambda x=node, self=self:
self.set_symbol(x,cml.n),
relief=GROOVE,value=cml.n)
w.pack(anchor=W,side=LEFT)
else:
w=Checkbutton(self.optionframe,relief=GROOVE,
variable=self.ties[node.name],
command=lambda x=node,self=self:self.set_symbol(x,(cml.n,cml.y)[self.ties[x.name].get()]))
w.pack(anchor=W,side=LEFT)
tw=Label(self.optionframe,text=id,\
relief=GROOVE,anchor=W)
tw.pack(anchor=E,side=LEFT,fill=X,expand=YES)
elif node.type == "string":
self.ties[node.name]=StringVar()
new=ValidatedField(self.optionframe,node,\
id,self.ties[node.name],
self.set_symbol_simple)
new.pack(side=LEFT,anchor=W,fill=X,expand=YES)
elif node.type =="decimal":
self.ties[node.name]=StringVar()
new=ValidatedField(self.optionframe,node,\
id,self.ties[node.name],
lambda n,v,s=self:s.set_symbol_simple(n,int(v)))
new.pack(side=LEFT,anchor=W,fill=X,expand=YES)
elif node.type =="hexadecimal":
self.ties[node.name]=StringVar()
new=ValidatedField(self.optionframe,node,\
id,self.ties[node.name],
lambda n,v,s=self:s.set_symbol_simple(n,int(v,16)))
new.pack(side=LEFT,anchor=W,fill=X,expand=YES)
else:
pass
#fill in the menu value
if self.ties.has_key(node.name):
if node.type =="choices":
self.ties[node.name].set(node.menuvalue.name)
elif node.type in ("string","decimal") or \
node.enum:
self.ties[node.name].set(str(node.eval()))
elif node.type =="hexadecimal":
self.ties[node.name].set("0x%x" % node.eval())
else:
enumval=node.eval()
if not configuration.trits_enabled and \
node.is_logical():
enumval= min(enumval.value, cml.m.value)
self.ties[node.name].set(enumval)
def draw_tree(self):
global configuration
self.tree=myTree(self.treewindow, rootname=configuration.start, rootlabel=configuration.start.name, width=298,getcontents=get_contents,toggle_init=self.toggle_init)
self.tree.pack(fill=BOTH,expand=YES,side=LEFT)
sb=Scrollbar(self.treewindow)
sb.configure(command=self.tree.yview)
sb.pack(side=RIGHT,fill=Y)
self.tree.configure(yscrollcommand=sb.set)
self.tree.focus_set()
def setchoice(self, symbol):
# Handle a choice-menu selection.
self.set_symbol(symbol, cml.y)
self.lastmenu = symbol
def set_symbol(self,symbol,value):
"Set symbol, checking validity"
global configuration
#print "set_symbol(%s,%s)" % (symbol.name,value)
if symbol.is_numeric() and symbol.range:
if not configuration.range_check(symbol,value):
Dialog(self,
title=lang["PROBLEM"],
text=lang["OUTOFBOUNDS"] % (value, symbol.range,),
bitmap='error',
default=0,
strings=(lang["DONE"],))
return
old_tritflag=configuration.trits_enabled
self.master.grab_set()
(ok, effects, violations)=configuration.set_symbol(symbol, value)
#print ok,effects,violation
self.master.grab_release()
if not ok:
explain =""
if effects:
explain = lang["EFFECTS"] + "\n" \
+ string.join(effects, "\n") + "\n"
explain += lang["ROLLBACK"] % (symbol.name, value) + \
"\n" + string.join(map(repr, violations), "\n") + "\n"
Dialog(self, \
title = lang["PROBLEM"], \
text = explain, \
bitmap = 'error', \
default = 0, \
strings = (lang["DONE"],))
else:
#wchkang
#self.tree.update_node()
self.tree.update_tree()
if old_tritflag != configuration.trits_enabled:
pass
# self.draw_optionframe()
if violations:
Dialog(self,
title = lang["SIDEEFFECTS"],
text = string.join(map(repr, violations), "\n"),
bitmap = 'info',
default = 0,
strings = (lang["DONE"],))
self.draw_optionframe()
def set_symbol_simple(self,symbol,value):
"Simple set-symbol without any screen update, validity checking"
#print "set_symbol_simple(%s,%s)" % (symbol.name,value)
self.master.grab_set()
(ok, effects, violations) = configuration.set_symbol(symbol, value)
self.master.grab_release()
class ConfigStackMenu(ConfigMenu):
"Top-level CML2 configurator object."
def __init__(self, menu, config, mybanner):
Frame.__init__(self, master=None)
ConfigMenu.__init__(self, menu, config, mybanner)
self.menuframe = ScrolledFrame(self)
self.menuframe.pack(side=BOTTOM, fill=BOTH, expand=YES)
self.menustack = []
self.locstack = []
# Time to set up the main menu
self.lastmenu = None
self.push(configuration.start)
# Repainting
def build(self):
"Build widgets for all symbols in a menu, but don't pack them."
if self.workframe:
Widget.destroy(self.workframe)
self.workframe = Frame(self.menuframe.inner)
self.visible = []
menu = self.menustack[-1]
w = Label(self.workframe, text=menu.prompt)
w.pack(side=TOP, fill=X, expand=YES)
self.menulabel.config(text="(" + menu.name +")")
self.symbol2widget = {}
self.ties = {}
self.textparts = {}
for node in menu.items:
id = node.name + ": " + node.prompt
if configuration.is_new(node):
id += " " + lang["NEW"]
myframe = Frame(self.workframe)
if node.type == "message":
new = Label(myframe, text=node.prompt)
self.textparts[node.name] = new
elif node.frozen():
value = str(node.eval(debug))
new = Label(myframe, text=node.name + ": " + \
node.prompt + " = " + value)
self.textparts[node.name] = new
new.config(fg='blue')
elif node.type == "menu":
new = Button(myframe, text=node.prompt,
command=lambda x=self,y=node:x.push(y))
self.textparts[node.name] = new
elif node.type == "choices":
new = Menubutton(myframe, relief=RAISED,
text=node.prompt)
self.textparts[node.name] = new
cmenu = Menu(new, tearoff=0)
self.ties[node.name] = StringVar(self.workframe)
for alt in node.items:
cmenu.add_radiobutton(
label=alt.name+": "+alt.prompt,
variable=self.ties[node.name], value=alt.name,
command=lambda self=self, x=alt:self.setchoice(x))
# This is inelegant, but it will get the job done...
self.symbol2widget[alt] = new
new.config(menu=cmenu)
elif node.type in ("trit", "bool"):
new = Frame(myframe)
self.ties[node.name] = IntVar(self.workframe)
if configuration.trits_enabled:
w = Radiobutton(new, text="y", relief=GROOVE,
variable=self.ties[node.name], value=cml.y,
command=lambda x=node, self=self: \
self.set_symbol(x, cml.y))
w.pack(anchor=W, side=LEFT)
w = Radiobutton(new, text="m", relief=GROOVE,
variable=self.ties[node.name], value=cml.m,
command=lambda x=node, self=self: \
self.set_symbol(x, cml.m))
if node.type == "bool":
w.config(state=DISABLED, text="-")
w.pack(anchor=W, side=LEFT)
w = Radiobutton(new, text="n", relief=GROOVE,
variable=self.ties[node.name], value=cml.n,
command=lambda x=node, self=self: \
self.set_symbol(x, cml.n))
w.pack(anchor=W, side=LEFT)
else:
w = Checkbutton(new, relief=GROOVE,
variable=self.ties[node.name],
command=lambda x=node, self=self: \
self.set_symbol(x, (cml.n, cml.y)[self.ties[x.name].get()]))
w.pack(anchor=W, side=LEFT)
tw = Label(new, text=id, relief=GROOVE, anchor=W)
tw.pack(anchor=E, side=LEFT, fill=X, expand=YES)
self.textparts[node.name] = tw
elif node.discrete:
new = Menubutton(myframe, relief=RAISED,
text=node.name+": "+node.prompt,
anchor=W)
self.textparts[node.name] = new
cmenu = Menu(new, tearoff=0)
self.ties[node.name] = StringVar(self.workframe)
for value in node.range:
if node.type == "decimal":
label=`value`
elif node.type == "hexadecimal":
label = "0x%x" % value
cmenu.add_radiobutton(label=label, value=label,
variable=self.ties[node.name],
command=lambda self=self, symbol=node, label=label:self.set_symbol(symbol, label))
new.config(menu=cmenu)
elif node.enum:
new = Menubutton(myframe, relief=RAISED,
text=node.name+": "+node.prompt,
anchor=W)
self.textparts[node.name] = new
cmenu = Menu(new, tearoff=0)
self.ties[node.name] = StringVar(self.workframe)
for (label, value) in node.range:
cmenu.add_radiobutton(label=label, value=value,
variable=self.ties[node.name],
command=lambda self=self, symbol=node, label=label, value=value:self.set_symbol(symbol, value))
new.config(menu=cmenu)
elif node.type == "decimal":
self.ties[node.name] = StringVar(self.workframe)
new = ValidatedField(myframe, node,
id, self.ties[node.name],
lambda n, v, s=self: s.set_symbol(n, int(v)))
self.textparts[node.name] = new.L
elif node.type == "hexadecimal":
self.ties[node.name] = StringVar(self.workframe)
new = ValidatedField(myframe, node,
id, self.ties[node.name],
lambda n, v, s=self: s.set_symbol(n, int(v, 16)))
self.textparts[node.name] = new.L
elif node.type == "string":
self.ties[node.name] = StringVar(self.workframe)
new = ValidatedField(myframe, node,
id, self.ties[node.name],
self.set_symbol)
self.textparts[node.name] = new.L
new.pack(side=LEFT, anchor=W, fill=X, expand=YES)
if node.type not in ("explanation", "message"):
help = Button(myframe, text=lang["HELPBUTTON"],
command=lambda symbol=node, self=self: self.help(symbol))
help.pack(side=RIGHT, anchor=E)
if not node.help():
help.config(state=DISABLED)
myframe.pack(side=TOP, fill=X, expand=YES)
self.symbol2widget[node] = myframe
# This isn't widget layout, it grays out the Back buttons
if len(self.menustack) <= 1:
self.backbutton.config(state=DISABLED)
self.navmenu.entryconfig(1, state=DISABLED)
else:
self.backbutton.config(state=NORMAL)
self.navmenu.entryconfig(1, state=NORMAL)
# Likewise, this grays out the help button when appropriate.
here = self.menustack[-1]
if isinstance(here, cml.ConfigSymbol) and here.help():
self.helpbutton.config(state=NORMAL)
else:
self.helpbutton.config(state=DISABLED)
# This grays out the "up" button
if not here.menu:
self.navmenu.entryconfig(2, state=DISABLED)
else:
self.navmenu.entryconfig(2, state=NORMAL)
# Pan canvas to the top of the widget list
self.menuframe.resetscroll()
def refresh(self):
self.workframe.update()
# Dynamic resizing. This code can flake out in some odd
# ways, notably by where it puts the resized window (this
# is probably tickling a window-manager bug). We want
# normal placement somewhere in an unused area of the root
# window. What we get too often (at least under
# Enlightenment) is the window placed where the top of
# frame isn't visible -- which is annoying, because it
# makes it hard to move the window to a better spot.
widgetheight = self.workframe.winfo_reqheight()
# Allow 50 vertical pixels for window frame cruft.
maxheight = self.winfo_screenheight() - 50
oversized = widgetheight > maxheight
self.menuframe.showscroll(oversized)
if oversized:
# This assumes the scrollbar widget will be < 25 pixels wide
newwidth = self.workframe.winfo_width() + 25
newheight = maxheight
else:
newwidth = self.workframe.winfo_width()
newheight = widgetheight + \
self.menubar.winfo_height()+self.menubar.winfo_height()
# Following four lines center the window.
#topx = (self.winfo_screenwidth() - newwidth) / 2
#topy = (self.winfo_screenheight() - newheight) / 2
#if topx < 0: topx = 0
#if topy < 0: topy = 0
#self.master.geometry("%dx%d+%d+%d"%(newwidth,newheight,topx,topy))
self.master.geometry("%dx%d" % (newwidth, newheight))
self.workframe.lift()
def display(self):
menu = self.menustack[-1]
newvisible = filter(lambda x, m=menu: hasattr(m, 'nosuppressions') or interactively_visible(x), menu.items)
# Insert all widgets that must newly become visible
for symbol in menu.items:
# Color the menu text
textpart = self.textparts[symbol.name]
if symbol.type in ("menu", "choices"):
if symbol.inspected:
textpart.config(fg='dark green')
elif not symbol.frozen():
textpart.config(fg='black')
elif symbol.type in ("trit", "bool"):
if symbol.setcount or symbol.included:
textpart.config(fg='dark green')
# Fill in the menu value
if self.ties.has_key(symbol.name):
if symbol.type == "choices":
self.ties[symbol.name].set(symbol.menuvalue.name)
elif symbol.type in ("string", "decimal") or symbol.enum:
self.ties[symbol.name].set(str(symbol.eval()))
elif symbol.type == "hexadecimal":
self.ties[symbol.name].set("0x%x" % symbol.eval())
else:
enumval = symbol.eval()
if not configuration.trits_enabled and symbol.is_logical():
enumval = min(enumval.value, cml.m.value)
self.ties[symbol.name].set(enumval)
# Now hack the widget visibilities
if symbol in newvisible and symbol not in self.visible:
argdict = {'anchor':W, 'side':TOP}
if self.menustack[-1].type != "choices":
argdict['expand'] = YES
argdict['fill'] = X
# Fiendishly clever hack alert: avoid excessive screen
# updating by repacking widgets in place as they pop
# in and out of visibility. Look for a first visible symbol
# after the current one. If you find one, use it to
# generate a "before" option for packing. Otherwise,
# generate an "after" option that packs after the last
# visible item.
if self.visible:
foundit = 0
for anchor in menu.items[menu.items.index(symbol):]:
if anchor in self.visible:
argdict['before'] = self.symbol2widget[anchor]
foundit = 1
break
if not foundit:
argdict['after'] = self.symbol2widget[self.visible[-1]]
self.visible.append(symbol)
self.symbol2widget[symbol].pack(argdict)
# We've used all the anchor points, clean up invisible ones
for symbol in menu.items:
if symbol not in newvisible:
self.symbol2widget[symbol].pack_forget()
elif symbol.type == "choices":
if symbol.menuvalue:
self.symbol2widget[symbol].winfo_children()[0].config(text="%s (%s)" % (symbol.prompt, symbol.menuvalue.name))
elif symbol.discrete:
self.symbol2widget[symbol].winfo_children()[0].config(text="%s: %s (%s)" % (symbol.name, symbol.prompt, str(symbol.eval())))
self.workframe.pack(side=BOTTOM)
self.toolbar.pack(side=BOTTOM, fill=X, expand=YES)
self.visible = newvisible
self.refresh()
# Operations on symbols and menus
def set_symbol(self, symbol, value):
"Set symbol, checking validity."
#print "set_symbol(%s, %s)" % (symbol.name, value)
if symbol.is_numeric() and symbol.range:
if not configuration.range_check(symbol, value):
Dialog(self,
title = lang["PROBLEM"],
text = lang["OUTOFBOUNDS"] % (value, symbol.range,),
bitmap = 'error',
default = 0,
strings = (lang["DONE"],))
return
old_tritflag = configuration.trits_enabled
# The set_grab() is an attempt to head off race conditions.
# We don't want the symbol widgets to accept new input
# events while the side-effects of a symbol set are still
# being computed.
self.master.grab_set()
(ok, effects, violations) = configuration.set_symbol(symbol,value)
self.master.grab_release()
if not ok:
explain = ""
if effects:
explain = lang["EFFECTS"] + "\n" \
+ string.join(effects, "\n") + "\n"
explain += lang["ROLLBACK"] % (symbol.name, value) + \
"\n" + string.join(map(repr, violations), "\n") + "\n"
Dialog(self,
title = lang["PROBLEM"],
text = explain,
bitmap = 'error',
default = 0,
strings = (lang["DONE"],))
else:
if old_tritflag != configuration.trits_enabled:
self.build()
self.display()
def help(self, symbol):
makehelpwin(title=lang["HELPBUTTON"],
mybanner=lang["HELPFOR"] % (symbol.name),
text = symbol.help())
def push(self, menu, highlight=None):
configuration.visit(menu)
self.menustack.append(menu)
self.locstack.append(self.menuframe.canvas.canvasy(0))
self.build()
self.lastmenu = highlight
self.display()
menu.inspected += 1
def pop(self):
if len(self.menustack) > 1:
from_menu = self.menustack[-1]
self.menustack = self.menustack[:-1]
self.build()
self.lastmenu = from_menu
self.display()
from_loc = self.locstack[-1]
self.locstack = self.locstack[:-1]
self.menuframe.resetscroll(from_loc)
def freeze(self):
"Call the base freeze, then update the display."
ConfigMenu.freeze(self)
self.build()
self.display()
except ImportError:
pass
def tkinter_style_menu(config, mybanner):
ConfigStackMenu(configuration.start, config, mybanner).mainloop()
def tkinter_qplus_style_menu(config, mybanner):
ConfigTreeMenu(configuration.start, config, mybanner).mainloop()
# Report generator
def menu_tree_list(node, indent):
"Print a map of a menu subtree."
totalindent = (indent + 4 * node.depth)
trailer = (" " * (40 - totalindent - len(node.name))) + `node.prompt`
print " " * totalindent, node.name, trailer
if configuration.debug:
if node.visibility:
print " " * 41, lang["VISIBILITY"], cml.display_expression(node.visibility)
if node.default:
print " " * 41, lang["DEFAULT"], cml.display_expression(node.default)
if node.items:
for child in node.items:
menu_tree_list(child, indent + 4)
# Environment probes
def is_under_X():
# It would be nice to just check WINDOWID, but some terminal
# emulators don't set it. One of those is kvt.
if os.environ.has_key("WINDOWID"):
return 1
else:
import commands
(status, output) = commands.getstatusoutput("xdpyinfo")
return status == 0
# Rulebase loading and option processing
def load_system(cmd_options, cmd_arguments):
"Read in the rulebase and handle command-line arguments."
global debug, config
debug = 0;
config = None
if not cmd_arguments:
rulebase = "rules.out"
else:
rulebase = cmd_arguments[0]
try:
open(rulebase, 'rb')
except IOError:
print lang["NOFILE"] % (rulebase,)
raise SystemExit
configuration = cmlsystem.CMLSystem(rulebase)
process_options(configuration, cmd_options)
configuration.debug_emit(1, lang["PARAMS"] % (config,configuration.prefix))
# Perhaps the user needs modules enabled initially
if configuration.trit_tie and cml.evaluate(configuration.trit_tie):
configuration.trits_enabled = 1
# Don't count all these automatically generated settings
# for purposes of figuring out whether we should confirm a quit.
configuration.commits = 0
return configuration
def process_include(configuration, file, freeze):
"Process a -i or -I inclusion option."
# Failure to find an include file is non-fatal
try:
(changes, errors) = configuration.load(file, freeze)
except IOError:
print lang["LOADFAIL"] % file
return
if errors:
print errors
elif configuration.side_effects:
print lang["SIDEFROM"] % file
sys.stdout.write(string.join(configuration.side_effects, "\n") + "\n")
def process_define(configuration, val, freeze):
"Process a -d=xxx or -D=xxx option."
parts = string.split(val, "=")
sym = parts[0]
if configuration.dictionary.has_key(sym):
sym = configuration.dictionary[sym]
else:
configuration.errout.write(lang["SYMUNKNOWN"] % (`sym`,))
sys.exit(1)
if sym.is_derived():
configuration.debug_emit(1, lang["DERIVED"] % (`sym`,))
sys.exit(1)
elif sym.is_logical():
if len(parts) == 1:
val = 'y'
elif parts[1] == 'y':
val = 'y'
elif parts[1] == 'm':
configuration.trits_enabled = 1
val = 'm'
elif parts[1] == 'n':
val = 'n'
else:
print lang["BADBOOL"]
sys.exit(1)
elif len(parts) == 1:
print lang["NOCMDLINE"] % (`sym`,)
sys.exit(1)
else:
val = parts[1]
(ok, effects, violations) = configuration.set_symbol(sym,
configuration.value_from_string(sym, val),
freeze)
if effects:
print lang["EFFECTS"]
sys.stdout.write(string.join(effects,"\n")+"\n")
if not ok:
print lang["ROLLBACK"] % (sym.name, val)
sys.stdout.write("\n".join(map(repr, violations))+"\n")
def process_options(configuration, options):
# Process command-line options second so they override
global list, config
global force_batch, force_x, force_q, force_tty, force_curses, debug
global readlog, banner
config = "config.out"
for (switch, val) in options:
if switch == '-b':
force_batch = 1
elif switch == '-B':
banner = val
elif switch == '-d':
process_define(configuration, val, freeze=0)
elif switch == '-D':
process_define(configuration, val, freeze=1)
elif switch == '-i':
process_include(configuration, val, freeze=0)
elif switch == '-I':
process_include(configuration, val, freeze=1)
elif switch == '-l':
list = 1
elif switch == '-o':
config = val
elif switch == '-v':
debug = debug + 1
configuration.debug = configuration.debug + 1
elif switch == '-S':
configuration.suppressions = 0
elif switch == '-R':
readlog = open(val, "r")
# Main sequence -- isolated here so we can profile it
def main(options, arguments):
global force_batch, force_x, force_q, force_curses, force_tty
global configuration
try:
configuration = load_system(options, arguments)
except KeyboardInterrupt:
raise SystemExit
if list:
try:
menu_tree_list(configuration.start, 0)
except EnvironmentError:
pass # Don't emit a traceback when we interrupt the listing
raise SystemExit
# Perhaps we're in batchmode. If so, only process options.
if force_batch:
# Have to realize all choices values first...
for entry in configuration.dictionary.values():
if entry.type == "choices":
configuration.visit(entry)
configuration.save(config)
return
# Next, try X
if force_x:
tkinter_style_menu(config, banner)
return
# Next, try Qplus style X
if force_q:
tkinter_qplus_style_menu(config, banner)
return
# Next, try curses
if force_curses and not force_tty:
try:
curses.wrapper(curses_style_menu, config, banner)
return
except "TERMTOOSMALL":
print lang["TERMTOOSMALL"]
force_tty = 1
# If both failed, go glass-tty
if force_debugger:
print lang["DEBUG"] % configuration.banner
debugger_style_menu(config, banner).cmdloop()
elif force_tty:
print lang["WELCOME"]%(configuration.banner,) + lang["VERSION"]%(cml.version,)
configuration.errout = sys.stdout
print lang["TTYQUERY"]
tty_style_menu(config, banner).cmdloop()
if __name__ == '__main__':
try:
runopts = "bB:cD:d:h:i:I:lo:P:qR:SstVvWx"
(options,arguments) = getopt.getopt(sys.argv[1:], runopts, "help")
if os.environ.has_key("CML2OPTIONS"):
(envopts, envargs) = getopt.getopt(
os.environ["CML2OPTIONS"].split(),
runopts)
options = envopts + options
except:
print lang["BADOPTION"]
print lang["CLIHELP"]
sys.exit(1)
for (switch, val) in options:
if switch == "-V":
print "cmlconfigure", cml.version
raise SystemExit
elif switch == '-P':
proflog = val
elif switch == '-x':
force_x = 1
elif switch == '-q':
force_q = 1
elif switch == '-t':
force_tty = 1
elif switch == '-c':
force_curses = 1
elif switch == '-s':
force_debugger = force_tty = 1
elif switch == '--help':
sys.stdout.write(lang["CLIHELP"])
raise SystemExit
# Probe the environment to see if we can use X for the front end.
if not force_tty and not force_debugger and not force_curses and not force_x and not force_q:
force_x = force_q = is_under_X()
# Do we see X capability?
if force_x or force_q:
try:
from Tkinter import *
from Dialog import *
except:
print lang["NOTKINTER"]
time.sleep(5)
force_curses = 1
force_x = force_q = 0
# Probe the environment to see if we can come up in ncurses mode
if not force_tty and not force_x and not force_q:
if not os.environ.has_key('TERM'):
print lang["TERMNOTSET"]
force_tty = 1
else:
import traceback
try:
import curses, curses.textpad, curses.wrapper
force_curses = 1
except:
ImportError
print lang["NOCURSES"]
force_tty = 1
if force_tty or force_debugger:
# It's been reported that this import fails under some 1.5.2s.
# No disaster; it's just a convenience to have command history
# in the line-oriented mode.
try:
import readline
except:
pass
try:
if proflog:
import profile, pstats
profile.run("main(options, arguments)", proflog)
else:
main(options, arguments)
except KeyboardInterrupt:
#if configuration.commits > 0:
# print lang["NOTSAVED"]
print lang["ABORTED"]
raise SystemExit, 2
except "UNSATISFIABLE":
#configuration.save("post.mortem")
print lang["POSTMORTEM"]
raise SystemExit, 3
# That's all, folks!