OpenCores
URL https://opencores.org/ocsvn/c0or1k/c0or1k/trunk

Subversion Repositories c0or1k

[/] [c0or1k/] [trunk/] [tools/] [cml2-tools/] [cmlconfigure.py] - Blame information for rev 2

Details | Compare with Previous | View Log

Line No. Rev Author Line
1 2 drasko
#!/usr/bin/env python
2
#
3
# cmlconfigure.py -- CML2 configurator front ends
4
# by Eric S. Raymond, <esr@thyrsus.com>
5
#
6
# Here is the actual code for the configurator front ends.
7
#
8
import sys
9
 
10
if sys.version[0] < '2':
11
    print "Python 2.0 or later is required for this program."
12
    raise SystemExit, 1
13
 
14
import os, string, getopt, cmd, re, time
15
import cml, cmlsystem, webbrowser
16
 
17
# Globals
18
debug = list = 0
19
force_batch = force_tty = force_curses = force_x = force_q = force_debugger = None
20
readlog = proflog = None
21
banner = ""
22
 
23
configuration = None
24
current_node = None
25
helpwin = None
26
 
27
# User-visible strings in the configurator.  Separated out in order to
28
# support internationalization.
29
_eng = {
30
    "ABORTED":"Configurator aborted.",
31
    "ANCESTORBUTTON":"Show ancestors of...",
32
    "BACKBUTTON":"Back",
33
    "BADBOOL":"Bad value for boolean or trit.",
34
    "BADOPTION":"cmlconfigure: unknown option on command line.\n",
35
    "BADREQUIRE":"Some requirements are violated: ",
36
    "BADVERIFY":"ERROR===>Expected <%s>=<%s>, instead of <%s>",
37
    "BOOLEAN":"`y' and `n' can only be applied to booleans or tristates",
38
    "CANCEL":"Cancel",
39
    "CANNOTSET":"    Can't assign this value for bool or trit symbol.",
40
    "CANTGO":"Cannot go to that symbol (it's probably derived).",
41
    "CCAPHELP":"C -- show constraints (all, or including specified symbol)",
42
    "CHARINVAL":"Invalid character in numeric field",
43
    "CHELP":"c -- clear the configuration",
44
    "CMDHELP":"Command summary",
45
    "COMPILEOK": "Compilation OK.",
46
    "COMPILEFAIL": "Compilation failed.",
47
    "CONFIRM":"Confirmation",
48
    "CONSTRAINTS":"Constraints:",
49
    "CURSQUERY":"Type '?' for help",
50
    "CURSESSET":"Curses mode set symbol %s to value %s",
51
    "DEBUG": "Debugging %s ruleset.",
52
    "DEFAULT":"Default: ",
53
    "DEPENDENTBUTTON":"Show dependents of...",
54
    "DERIVED":"Symbol %s is derived and cannot be set.",
55
    "DONE":"Done",
56
    "EDITING":"Editing %s",
57
    "EFFECTS":"Side effects:",
58
    "EMPTYSEARCH":"You must enter a regular expression to search for",
59
    "SUPPRESSBUTTON":"Suppress",
60
    "SUPPRESSOFF":"Suppression turned off",
61
    "SUPPRESSON":"Suppression turned on",
62
    "ECAPHELP": "E -- dump the state of the binding history",
63
    "EHELP": "e -- examine specified symbol",
64
    "EXIT":"Exit",
65
    "EXITCONFIRM":"[Press q to exit, any other key to continue]",
66
    "FHELP": "f -- freeze specified symbol",
67
    "FIELDEDIT":"Field Editor help",
68
    "FILEBUTTON":"File",
69
    "FREEZEBUTTON":"Freeze",
70
    "FREEZE":"Freeze all symbols with a user-supplied value?",
71
    "FREEZELABEL":"(FROZEN)",
72
    "FROZEN":"This symbol has been frozen and cannot be modified.",
73
    "GHELP":"g -- go to a named symbol or menu (follow g with the label)",
74
    "GO":"Go",
75
    "GOBUTTON":"Go to...",
76
    "GOTOBYNAME":"Go to symbol by name: ",
77
    "GPROMPT":"Symbol to set or edit: ",
78
    "HELPBANNER": "Press ? for help on current symbol, h for command help",
79
    "HELPBUTTON":"Help",
80
    "HELPFOR":"Help for %s",
81
    "HSEARCHBUTTON":"Search help text...",
82
    "ICAPHELP":"I -- read in and freeze a configuration (follow I with the filename)",
83
    "IHELP":"i -- read in a configuration (follow i with the filename)",
84
    "INCCHANGES":"%d change(s) from %s",
85
    "INFO":"Information",
86
    "INVISIBLE":"Symbol is invisible",
87
    "INVISILOCK":" and invisibility is locked.",
88
    "INVISINONE":"Symbol is invisible (no visibility predicate).",
89
    "INVISOUT":"Symbol %s is currently invisible and will not be saved out.",
90
    "LOADFILE":"Load configuration from: ",
91
    "LOADBUTTON":"Load...",
92
    "LOADFAIL":"Loading '%s' failed, continuing...",
93
    "LOADFREEZE":"Load frozen configuration from: ",
94
    "MDISABLED":"Module-valued symbols are not enabled",
95
    "MHELP":"m -- set the value of the selected symbol to m",
96
    "MNOTVALID":"   m is not a valid value for %s",
97
    "MORE":"(More lines omitted...)",
98
    "NAVBUTTON":"Navigation",
99
    "NEW":"(NEW)",
100
    "NHELP":"n -- set the value of the selected symbol to n",
101
    "NNOTVALID":"   n is not a valid value for %s",
102
    "NOANCEST":"No ancestors.",
103
    "NOCMDLINE":"%s is the wrong type to be set from the command line",
104
    "NOCURSES":"Your Python seems to be lacking curses support.",
105
    "NODEPS":"No dependents.",
106
    "NOFILE":"cmlconfigure: '%s' does not exist or is unreadable.",
107
    "NOHELP":"No help available for %s",
108
    "NOMATCHES":"No matches.",
109
    "NOMENU":"Symbol %s is not in a menu.",
110
    "NONEXIST":"No such symbol or menu as %s.",
111
    "NOPOP":"Can't pop back further",
112
    "NOSAVEP":"No save predicate",
113
    "NOSUCHAS":"No such symbol as",
114
    "NOSYMBOL":"No symbol is currently selected.",
115
    "NOTKINTER":"Can't find tkinter support, falling back to curses mode...",
116
    "NOTSAVED":"Configuration not saved",
117
    "NOVISIBLE":"No visible items on starting menu.",
118
    "OK":"Operation complete",
119
    "OUTOFBOUNDS":"%s no good; legal values are in %s",
120
    "PAGEPROMPT":"[Press Enter to continue] ",
121
    "PARAMS":"    Config = %s, prefix = %s",
122
    "PDHELP":"p -- print the configuration",
123
    "PHELP":"p -- back up to previous symbol",
124
    "POSTMORTEM":"The ruleset was inconsistent.",
125
    "PRESSANY":"[Press any key to continue]",
126
    "PROBLEM":"Problem",
127
    "QHELP":"q -- quit, discarding changes",
128
    "QUITBUTTON":"Quit",
129
    "QUITCONFIRM":"Really exit without saving?",
130
    "RADIOBAD":"    That's not a valid selection.",
131
    "DISCRETEVALS":"%s may have these values:\n",
132
    "REALLY":"Really exit without saving?",
133
    "ROLLBACK":"%s=%s would have violated these requirements:",
134
    "SAVEABLE":"Symbol is saveable.",
135
    "SAVEAS":"Save As...",
136
    "SAVEBUTTON":"Save & Exit",
137
    "SAVECONFIRM":"Save confirmation",
138
    "SAVEEND":"Done",
139
    "SAVEFILE":"Save configuration to: ",
140
    "SAVESTART":"Saving %s",
141
    "SAVING":"Saving...",
142
    "SEARCHBUTTON":"Search symbols...",
143
    "SEARCHFAIL":"No matches.",
144
    "SEARCHINVAL":"Invalid Regular Expression:",
145
    "SEARCHHELP":"Search help text for: ",
146
    "SEARCHSYMBOLS":"Search symbols for regular expression: ",
147
    "SETCOUNT":"Symbol has been set %d time(s)",
148
    "SHELP":"s -- save the configuration (follow with a filename)",
149
    "SHOW_ANC":"Show ancestors of symbol: ",
150
    "SHOW_DEP":"Show dependents of symbol: ",
151
    "SIDEEFFECTS":"Side Effects",
152
    "SIDEFROM":"Side effects from %s:",
153
    "SKIPCALLED":"    Skip-to-query called from %s",
154
    "SKIPEXIT":"    Skip-to-query arrived at %s",
155
    "SYMUNKNOWN":"cmlconfigure: unknown symbol %s\n",
156
    "TERMNOTSET":"TERM is not set.",
157
    "TERMTOOSMALL":"Your terminal is too small to support curses mode.",
158
    "TOOLONG":"String is too long to edit.  Use cmlconfigure -t.",
159
    "TRIT":"`m' can only be applied to tristates",
160
    "TTYQUERY":"Type '?' at any prompt for help, 'h' for command help.",
161
    "TTYSUMMARY":"                   Command summary:",
162
    "UHELP":"u -- toggle interactive flag.",
163
    "UNSUPPRESSBUTTON":"Unsuppress",
164
    "UNKNOWN":"Unknown command %s -- type `h' for a command summary",
165
    "UPBUTTON":"Up",
166
    "UNSAVEABLE":"Symbol is unsaveable.",
167
    "VCAPHELP":"V -- verify that a symbol has a given value",
168
    "VERBOSITY": "Verbosity level is %d",
169
    "VERSION":", version %s.",
170
    "VHELP": "v -- increase the verbosity level, or set to numeric argument",
171
    "VISIBLE":"Symbol is visible",
172
    "VISIBILITY":"Visibility: ",
173
    "WARNING":"Warning",
174
    "WELCOME":"Welcome to the %s",
175
    "XHELP":"x -- exit, saving the configuration",
176
    "YHELP":"y -- set the value of the selected symbol to y",
177
    "YNOTVALID":"   y is not a valid value for %s",
178
 
179
    "TTYHELP":"""
180
Type '?' to see help text associated with the current symbol.
181
Typing Return accepts the default for the query.
182
 
183
Each prompt consists of a label, followed by a colon, followed by prompt text,
184
followed by a value and a bracketed range indication.  The brackets indicate
185
whether the symbol is bool [] modular <> or integer/string ().  The current
186
value in the brackets may be blank (indicating bool or trit n), 'M' (indicating
187
trit m) or an integer or string literal. If `?' follows, it means help is
188
available for this symbol.
189
""",
190
    "CURSHELP":"""\
191
Use up- and down-arrows to change the current selection.  Use spacebar or Enter
192
to enter a selected sub-menu, or to toggle the value of a selected boolean
193
symbol, or to cycle through the possible y/m/n values of a selected tristate
194
symbol, or to begin editing the value of a symbol that has string or decimal
195
or hexadecimal type.  Use left-arrow to back out of a menu.
196
 
197
'y', 'm', and 'n' set boolean or trit symbols to the corresponding values.
198
 
199
When you are editing a symbol value, the highlight on its value field will be
200
switched off.  A subset of Emacs's default key bindings is available to edit
201
the field; to see details, enter such a field (by typing a space with the
202
symbol selected) and press your tab key.  Other characters will usually be
203
inserted at the cursor location.  Press up-arrow or down-arrow or Enter to
204
stop editing a field.
205
 
206
Type `x' to save configuration and exit, `q' to exit without saving, `s' to
207
save the configuration to a named file, and `i' to read in a configuration by
208
filename.  -I reads in a configuration and freezes the variables.
209
 
210
Type '?' to see any help text associated with the current symbol.  Type 'h'
211
to see this command summary again.  Some expert commands are documented
212
in separate help; press TAB from this help screen to see it.\
213
""",
214
    "EDITHELP":"""\
215
Numbers and short strings can be edited in their display fields near the
216
left edge of the screen.  To edit longer strings, enter a right arrow at the
217
left edge of the display field.  This will pop up a wide window in which you
218
can edit the value.  The field editor supports a subset of Emacs's default
219
key bindings.
220
 
221
    Ctrl-A      Go to left edge of window.
222
    Ctrl-B      Cursor left, wrapping to previous line if appropriate.
223
    Ctrl-D      Delete character under cursor.
224
    Ctrl-E      Go to right edge (nospaces off) or end of line (nospaces on).
225
    Ctrl-F      Cursor right, wrapping to next line when appropriate.
226
    Ctrl-H      Delete character backward.
227
    Ctrl-J      Terminate if the window is 1 line, otherwise insert newline.
228
    Ctrl-K      If line is blank, delete it, otherwise clear to end of line.
229
    Ctrl-L      Refresh screen
230
    Ctrl-N      Cursor down; move down one line.
231
    Ctrl-O      Insert a blank line at cursor location.
232
    Ctrl-P      Cursor up; move up one line.
233
    KEY_LEFT = Ctrl-B, KEY_RIGHT = Ctrl-F, KEY_UP = Ctrl-P, KEY_DOWN = Ctrl-N
234
    KEY_BACKSPACE = Ctrl-h
235
 
236
To leave the field editor, press Enter or carriage return, or use the up and
237
down arrows. Ctrl-g leaves the field editor ant revers to the unedited value.\
238
""",
239
    "EXPERTHELP":"""\
240
Here are the expert commands:
241
 
242
/ -- Search for symbols matching a given regular expression in either
243
     the symbol name or prompt text.
244
 
245
g -- Go to symbol. Go directly to symbol. Do not pass go, do not collect $200.
246
     If the target symbol is suppressed, clear the suppression flag.
247
 
248
i -- Load a configuration.  Set all the variables in the given config file.
249
 
250
I -- Load a configuration.  Set all the variables in the config file,
251
     then freeze them so they can't be overridden.
252
 
253
e -- Examine the value of the named symbol.
254
 
255
S -- Toggle the suppression flag (normally on).  When the suppression flag
256
     is off, invisible symbols are not skipped.
257
 
258
Type 'h' to see the help for ordinary commands.
259
""",
260
    "TKCMDHELP":"""\
261
The main window is a configuration menu browser. It presents you with a menu of
262
symbols and sub-menu buttons.  If there is help available for a symbol or menu,
263
there will be an active `Help' button off to the right.  In the help window,
264
URLS are live; clicking on them will launch a browser.
265
 
266
You can set boolean or tristate symbols simply by clicking on the appropriate
267
value.  You can change the values of numeric and string symbols by editing
268
their fill-in fields.
269
 
270
In the file menu, the `Load' command allows you to load a configuration file.
271
Values in the configuration file are set as though the user had selected them.
272
 
273
In the file menu, the `Freeze' command freezes all symbols that have
274
been set by user actions (including loading configuration files) so they
275
won't be queried again and cannot subsequently be overridden.
276
 
277
In the File menu, the `Save' command saves the configuration file to the
278
location specified by configurator command-line options).  The `Save As...'
279
command saves the defconfig file (only) to a named location.
280
 
281
The Back command in the Navigation menu (and the toolbar button) returns you to
282
the menu visited before this one.  When you back out of a sub-menu, the label
283
on its button is highlighted in blue.
284
 
285
The Go command in the Navigation menu moves you to a named menu, or to the
286
menu containing a named symbol.  After a `Go' command the target is
287
highlighted blue.
288
 
289
The Search command in the Navigation menu matches a given regular expression
290
against the names and prompt text of all configuration symbols. It generates
291
a menu that includes all hits, and turns off the elisions flag.
292
 
293
The Suppress/Unsuppress command in the Navigation toggles whether suppressed
294
symbols are visible or not.  Normally, suppressed symbols are invisible and the
295
menu entry reads `Unsuppress'.  The suppression flag may also be cleared by
296
using the `Go' command to visit a suppressed symbol, or by doing a Search.
297
""",
298
    "CLIHELP":"""\
299
 
300
Usage: clmlconfigure.py [-tcxqbs] [-[Dd] sym] [-[Ii] file]
301
                        [-B banner] [-o output] [-v]
302
 
303
-t            force tty (line-oriented) mode
304
-c            force curses (screen-oriented) mode
305
-x            force default X mode
306
-q            force expermintal tree-widget-based X interface
307
-b            batch mode (process command-line options only).
308
-s            debugger mode
309
 
310
-d sym[=val]  set a symbol
311
-D sym[=val]  set and freeze a symbol
312
-i            include a config file
313
-I            include a config file, frozen
314
 
315
-B banner     set banner string
316
-o file       write config to specified file
317
-v            increment debug level
318
 
319
""",
320
}
321
 
322
# Eventually, do more intelligent selection using LOCALE
323
lang = _eng
324
 
325
# Shared code
326
 
327
def cgenvalue(symbol):
328
    "Generate an appropriate prompt for the given symbol."
329
    value = symbol.eval()
330
    if symbol.type in ("bool", "trit"):
331
        if symbol.type == "trit" and configuration.trits_enabled:
332
            format = "<%s>"
333
        else:
334
            format = "[%s]"
335
        if value == cml.y:
336
            return format % "Y"
337
        elif value == cml.m:
338
            return format % "m"
339
        elif value == cml.n:
340
            return format % " "
341
    elif symbol.type == "choices":
342
        if symbol.menuvalue:
343
            return symbol.menuvalue.prompt
344
        elif symbol.default:
345
            return symbol.default.prompt
346
        else:
347
            return "??"
348
    elif symbol.type == "message":
349
        return ""
350
    elif symbol.type == "menu":
351
        return "-->"            # Effective only in menuconfig
352
    elif symbol.type in ("decimal", "string"):
353
        return str(value)
354
    elif symbol.type == "hexadecimal":
355
        return "0x%x" % value
356
    else:
357
        return "??"
358
 
359
def cgenprompt(symbol, novice=1):
360
    "Decorate a symbol prompt string according to its warndepend conditions."
361
    res = ""
362
    for warndepend in symbol.warnings:
363
        if novice:
364
            res += warndepend.name + ", "
365
        else:
366
            res += warndepend.name[0] + ", "
367
    if symbol.warnings:
368
        res = " (" + res[:-2] + ")"
369
    return symbol.prompt + res
370
 
371
def interactively_visible(symbol):
372
    "Should a symbol be visible interactively?"
373
    return configuration.is_visible(symbol) and not symbol.frozen()
374
 
375
# Line-oriented interface
376
 
377
class tty_style_base(cmd.Cmd):
378
    "A class for browsing a CML2 menu subtree with line-oriented commands."
379
 
380
    def set_symbol(self, symbol, value, freeze=0):
381
        "Set the value of a symbol -- line-oriented error messages."
382
        if symbol.is_numeric() and symbol.range:
383
            if not configuration.range_check(symbol, value):
384
                print lang["OUTOFBOUNDS"] % (value, symbol.range,)
385
                return
386
        (ok, effects, violations) = configuration.set_symbol(symbol, value, freeze)
387
        if effects:
388
            print lang["EFFECTS"]
389
            sys.stdout.write(string.join(effects, "\n") + "\n\n")
390
        if ok:
391
            if not interactively_visible(symbol):
392
                print lang["INVISOUT"] % symbol.name
393
        else:
394
            print lang["ROLLBACK"] % (symbol.name, value)
395
            sys.stdout.write(string.join(map(repr, violations), "\n") + "\n")
396
 
397
    def page(self, text):
398
        text = string.split(text, "\n")
399
        pagedepth = os.environ.get("LINES")
400
        if pagedepth:
401
            pagedepth = int(pagedepth)
402
        else:
403
            pagedepth = 24
404
        base = 0
405
        try:
406
            while base < len(text):
407
                for i in range(base, base+pagedepth):
408
                    if i >= len(text):
409
                        break;
410
                    print text[i]
411
                base = base + pagedepth
412
                raw_input(lang["PAGEPROMPT"])
413
        except KeyboardInterrupt:
414
            print ""
415
 
416
    def __init__(self, config, mybanner):
417
        cmd.Cmd.__init__(self)
418
        self.config = config
419
        if mybanner and configuration.banner.find("%s") > -1:
420
            self.banner = configuration.banner % mybanner
421
        elif mybanner:
422
            self.banner = mybanner
423
        else:
424
            self.banner = configuration.banner
425
        self.current = configuration.start;
426
 
427
    def do_g(self, line):
428
        if configuration.dictionary.has_key(line):
429
            self.current = configuration.dictionary[line]
430
            configuration.visit(self.current)
431
            if not interactively_visible(self.current) and not self.current.frozen():
432
                print lang["SUPPRESSOFF"]
433
                self.suppressions = 0
434
            if self.current.type in ("menu", "message"):
435
                self.skip_to_query(configuration.next_node, 1)
436
        else:
437
            print lang["NONEXIST"] % line
438
    def do_i(self, line):
439
        file = string.strip(line)
440
        try:
441
            (changes, errors) = configuration.load(file, freeze=0)
442
        except IOError:
443
            print lang["LOADFAIL"] % file
444
        else:
445
            if errors:
446
                print errors
447
            print lang["INCCHANGES"] % (changes,file)
448
            if configuration.side_effects:
449
                sys.stdout.write(string.join(configuration.side_effects, "\n") + "\n")
450
    def do_I(self, line):
451
        file = string.strip(line)
452
        try:
453
            (changes, errors) = configuration.load(file, freeze=1)
454
        except IOError:
455
            print lang["LOADFAIL"] % file
456
        else:
457
            if errors:
458
                print errors
459
            print lang["INCCHANGES"] % (changes,file)
460
            if configuration.side_effects:
461
                sys.stdout.write(string.join(configuration.side_effects, "\n") + "\n")
462
    def do_y(self, line):
463
        if not line:
464
            target = self.current
465
        else:
466
            # Undocumented feature -- "y FOO" sets FOO to y.
467
            line = string.strip(line)
468
            if not configuration.dictionary.has_key(line):
469
                print lang["NONEXIST"] % line
470
                return
471
            else:
472
                target = configuration.dictionary[line]
473
        if not target.type in ("trit", "bool"):
474
            print lang["YNOTVALID"] % (target.name)
475
        else:
476
            self.set_symbol(target, cml.y)
477
        return None
478
    def do_Y(self, line):
479
        self.do_y(line)
480
    def do_m(self, line):
481
        if not line:
482
            target = self.current
483
        else:
484
            # Undocumented feature -- "m FOO" sets FOO to m.
485
            line = string.strip(line)
486
            if not configuration.dictionary.has_key(line):
487
                print lang["NONEXIST"] % line
488
                return
489
            else:
490
                target = configuration.dictionary[line]
491
        if not target.type == "trit":
492
            print lang["MNOTVALID"] % (target.name)
493
        elif not configuration.trits_enabled:
494
            print lang["MNOTVALID"] % (target.name)
495
        else:
496
            self.set_symbol(target, cml.m)
497
        return None
498
    def do_M(self, line):
499
        self.do_m(line)
500
    def do_n(self, line):
501
        if not line:
502
            target = self.current
503
        else:
504
            # Undocumented feature -- "n FOO" sets FOO to n.
505
            line = string.strip(line)
506
            if not configuration.dictionary.has_key(line):
507
                print lang["NONEXIST"] % line
508
                return
509
            else:
510
                target = configuration.dictionary[line]
511
        if not target.type in ("trit", "bool"):
512
            print lang["NNOTVALID"] % (target.name)
513
        else:
514
            self.set_symbol(target, cml.n)
515
        return None
516
    def do_N(self, line):
517
        self.do_n(line)
518
    def do_s(self, line):
519
        file = string.strip(line)
520
        failure = configuration.save(file, cml.Baton(lang["SAVESTART"] % file, lang["SAVEEND"]))
521
        if failure:
522
            print failure
523
    def do_x(self, dummy):
524
        # Terminate this cmd instance, saving configuration
525
        self.do_s(config)
526
        return 1
527
    def do_C(self, line):
528
        # Show constraints (all, or all including specified symbol).
529
        filter = None
530
        if line:
531
            line = line.strip()
532
            if configuration.dictionary.has_key(line):
533
                filter = configuration.dictionary[line]
534
        for i in range(len(configuration.constraints)):
535
            constraint = configuration.constraints[i].predicate
536
            reduced = configuration.reduced[i]
537
            if reduced in (cml.n, cml.m, cml.y):
538
                continue
539
            if filter and filter not in cml.flatten_expr(constraint):
540
                continue
541
            if constraint == reduced:
542
                print cml.display_expression(reduced)[1:-1]
543
            else:
544
                print "%s -> %s" % (constraint,cml.display_expression(reduced)[1:-1])
545
        return 0
546
    def do_q(self, line):
547
        # Terminate this cmd instance, not saving configuration
548
        raise SystemExit, 1
549
 
550
    def do_v(self, line):
551
        # Set the debug flag
552
        if not line:
553
            configuration.debug += 1
554
        else:
555
            configuration.debug = int(line)
556
        print lang["VERBOSITY"] % configuration.debug
557
        return 0
558
    def do_e(self, line):
559
        # Examine the state of a given symbol
560
        symbol = string.strip(line)
561
        if configuration.dictionary.has_key(symbol):
562
            entry = configuration.dictionary[symbol]
563
            print entry
564
            if entry.constraints:
565
                print lang["CONSTRAINTS"]
566
                for wff in entry.constraints:
567
                    print cml.display_expression(wff)
568
            if interactively_visible(entry):
569
                print lang["VISIBLE"]
570
            elif entry.visibility is None:
571
                print lang["INVISINONE"]
572
            elif configuration.eval_frozen(entry.visibility):
573
                print lang["INVISIBLE"] + lang["INVISILOCK"]
574
            else:
575
                print lang["INVISIBLE"]
576
            if entry.saveability == None:
577
                print lang["NOSAVEP"]
578
            if configuration.saveable(entry):
579
                print lang["SAVEABLE"]
580
            else:
581
                print lang["UNSAVEABLE"]
582
            if entry.setcount:
583
                print lang["SETCOUNT"] % entry.setcount
584
        else:
585
            print lang["NOSUCHAS"], symbol
586
        return 0
587
    def do_E(self, dummy):
588
        # Dump the state of the bindings stack
589
        print configuration.binddump()
590
        return 0
591
    def do_S(self, dummy):
592
        # Toggle the suppressions flag
593
        configuration.suppressions = not configuration.suppressions
594
        if configuration.suppressions:
595
            print lang["SUPPRESSON"]
596
        else:
597
            print lang["SUPPRESSOFF"]
598
        return 0
599
    def help_e(self):
600
        print lang["EHELP"]
601
    def help_E(self):
602
        print lang["ECAPHELP"]
603
    def help_g(self):
604
        print lang["GHELP"]
605
    def help_i(self):
606
        print lang["IHELP"]
607
    def help_I(self):
608
        print lang["ICAPHELP"]
609
    def help_y(self):
610
        print lang["YHELP"]
611
    def help_m(self):
612
        print lang["MHELP"]
613
    def help_n(self):
614
        print lang["NHELP"]
615
    def help_s(self):
616
        print lang["SHELP"]
617
    def help_q(self):
618
        print lang["QHELP"]
619
    def help_v(self):
620
        print lang["VHELP"]
621
    def help_x(self):
622
        print lang["XHELP"]
623
    def do_help(self, line):
624
        line = line.strip()
625
        if configuration.dictionary.has_key(line):
626
            target = configuration.dictionary[line]
627
        else:
628
            target = self.current
629
        help = target.help()
630
        if help:
631
            self.page(help)
632
        else:
633
            print lang["NOHELP"] % (self.current.name,)
634
 
635
class tty_style_menu(tty_style_base):
636
    "Interface for configuring with line-oriented commands."
637
    def skip_to_query(self, function, showbase=0):
638
        configuration.debug_emit(2, lang["SKIPCALLED"] % (self.current.name,))
639
        if showbase:
640
            if self.current.type == "menu":
641
                self.menu_banner(self.current)
642
                configuration.visit(self.current)
643
        while 1:
644
            self.current = function(self.current)
645
            if self.current == configuration.start:
646
                break;
647
            elif self.current.is_symbol() and self.current.frozen():
648
                # sys.stdout.write(self.generate_prompt(self.current) + lang["FREEZELABEL"] + "\n")
649
                continue
650
            elif not interactively_visible(self.current):
651
                continue
652
            elif self.current.type in ("menu", "choices"):
653
                self.menu_banner(self.current)
654
                configuration.visit(self.current)
655
            if not self.current.type in ("message", "menu"):
656
                break;
657
        configuration.debug_emit(2, lang["SKIPEXIT"] % (self.current.name,))
658
        if self.current == configuration.start:
659
            self.do_s(config)
660
            raise SystemExit
661
 
662
    def menu_banner(self, menu):
663
        sys.stdout.write("*\n* %s: %s\n*\n" % (menu.name, menu.prompt))
664
 
665
    def generate_prompt(self, symbol):
666
        leader = "   " * symbol.depth
667
        genpart = cgenvalue(symbol)
668
        if symbol.help and not symbol.frozen():
669
            havehelp = "?"
670
        else:
671
            havehelp = ""
672
        if configuration.is_new(symbol):
673
            genpart += " " + lang["NEW"]
674
        if symbol.type in ("bool", "trit"):
675
            return leader+"%s: %s %s%s: " % (symbol.name, cgenprompt(symbol), genpart, havehelp)
676
        elif symbol.enum:
677
            dflt = cml.evaluate(symbol, debug)
678
            if symbol.frozen():
679
                p = ""
680
            else:
681
                p = leader + lang["DISCRETEVALS"]  % (cgenprompt(symbol),)
682
            selected = ""
683
            for (label, value) in symbol.range:
684
                if value == dflt:
685
                    selected = "(" + label + ")"
686
            if not symbol.frozen():
687
                p = p + leader + "%2d: %s\n" % (value, label)
688
            return p + leader + "%s: %s %s%s: " % (symbol.name, cgenprompt(symbol),selected, havehelp)
689
 
690
        elif symbol.type in ("decimal", "hexadecimal", "string"):
691
            dflt = cml.evaluate(symbol, debug)
692
            return leader + "%s: %s (%s)%s: "  % (symbol.name, cgenprompt(symbol), cgenvalue(symbol), havehelp)
693
        elif symbol.type == "choices":
694
            if symbol.frozen():
695
                p = ""
696
            else:
697
                p = leader + lang["DISCRETEVALS"]  % (cgenprompt(symbol))
698
            index = 0
699
            selected= ""
700
            for v in symbol.items:
701
                index = index + 1
702
                if not symbol.frozen():
703
                    p = p + leader + "%2d: %s%s%s\n" % (index, v.name, " " * (32 - len(v.name)), v.prompt)
704
                if v.eval():
705
                    selected = v.name
706
            return p + leader + "%s: %s (%s)%s: " % (symbol.name, cgenprompt(symbol),selected, havehelp)
707
 
708
    def __init__(self, config=None, mybanner=""):
709
        tty_style_base.__init__(self, config=config, mybanner=mybanner)
710
        self.skip_to_query(configuration.next_node, 1)
711
        # This handles the case that all variables were frozen.
712
        self.prompt = self.generate_prompt(self.current)
713
 
714
    def do_p(self, dummy):
715
        self.skip_to_query(configuration.previous_node)
716
        return None
717
 
718
    def do_y(self, line):
719
        tty_style_base.do_y(self, line)
720
        if not line:
721
            self.skip_to_query(configuration.next_node)
722
    def do_m(self, line):
723
        tty_style_base.do_m(self, line)
724
        if not line:
725
            self.skip_to_query(configuration.next_node)
726
    def do_n(self, line):
727
        tty_style_base.do_n(self, line)
728
        if not line:
729
            self.skip_to_query(configuration.next_node)
730
 
731
    def do_h(self, dummy):
732
        self.page(string.join(map(lambda x: lang[x],
733
                                  ("TTYSUMMARY",
734
                                   "GHELP", "IHELP", "ICAPHELP", "YHELP",
735
                                   "MHELP", "NHELP", "PHELP", "SHELP",
736
                                   "QHELP", "XHELP", "TTYHELP")), "\n"))
737
    def default(self, line):
738
        v = string.strip(line)
739
        if self.current.type == 'choices':
740
            try:
741
                ind = string.atoi(v)
742
            except ValueError:
743
                ind = -1
744
            if ind <= 0 or ind > len(self.current.items):
745
                print lang["RADIOBAD"]
746
            else:
747
                # print lang["TTYSETTING"] % (`self.current.items[ind - 1]`)
748
                self.set_symbol(self.current.items[ind - 1], cml.y)
749
                self.skip_to_query(configuration.next_node)
750
        elif self.current.type in ("bool", "trit"):
751
            print lang["CANNOTSET"]
752
        else:
753
            self.set_symbol(self.current, v)
754
            self.skip_to_query(configuration.next_node)
755
        return None
756
 
757
    def emptyline(self):
758
        if self.current and self.current.type == "choices":
759
            if self.current.default:
760
                # print lang["TTYSETTING"] % (`self.current.default`)
761
                self.set_symbol(self.current.default, cml.y)
762
        self.skip_to_query(configuration.next_node)
763
        return 0
764
 
765
    def help_p(self):
766
        print lang["PHELP"]
767
 
768
    def postcmd(self, stop, dummy):
769
        if stop:
770
            return stop
771
        self.prompt = self.generate_prompt(self.current)
772
        return None
773
 
774
class debugger_style_menu(tty_style_base):
775
    "Ruleset-debugger class."
776
    def __init__(self, config=None, mybanner=""):
777
        tty_style_base.__init__(self, config=config, mybanner=mybanner)
778
        configuration.debug += 1
779
        self.prompt = "> "
780
 
781
    def do_l(self, line):
782
        import cmlcompile
783
        newsystem = cmlcompile.compile(debug=0, arguments=None, profile=0, endtok=line)
784
        if newsystem:
785
            global configuration
786
            configuration = cmlsystem.CMLSystem(newsystem)
787
            print lang["COMPILEOK"]
788
        else:
789
            print lang["COMPILEFAIL"]
790
        return 0
791
 
792
    def do_V(self,line):
793
        print "V",line
794
        for setting in line.split():
795
            symbol,expected=setting.split('=')
796
            if not configuration.dictionary.has_key(symbol):
797
                sys.stderr.write((lang["NONEXIST"] % symbol) + "\n")
798
                print lang["NONEXIST"] % line
799
                continue
800
            dictsym = configuration.dictionary[symbol]
801
            dictval = cml.evaluate(dictsym)
802
            if dictval != \
803
               configuration.value_from_string(dictsym,expected):
804
                errstr = lang["BADVERIFY"] % (symbol,expected,dictval)
805
                print errstr
806
                sys.stderr.write(errstr + '\n')
807
        return 0
808
 
809
    def do_y(self, line):
810
        print line + "=y"
811
        if not line:
812
            print lang["NOSYMBOL"]
813
        else:
814
            tty_style_base.do_y(self, line)
815
            if configuration.debug:
816
                print configuration.binddump()
817
    def do_m(self, line):
818
        print line + "=m"
819
        if not line:
820
            print lang["NOSYMBOL"]
821
        else:
822
            tty_style_base.do_m(self, line)
823
            if configuration.debug:
824
                print configuration.binddump()
825
    def do_n(self, line):
826
        print line + "=n"
827
        if not line:
828
            print lang["NOSYMBOL"]
829
        else:
830
            tty_style_base.do_n(self, line)
831
            if configuration.debug:
832
                print configuration.binddump()
833
 
834
    def do_f(self, line):
835
        print "f", line
836
        line = line.strip()
837
        if not line:
838
            print lang["NOSYMBOL"]
839
        elif not configuration.dictionary.has_key(line):
840
            print lang["NONEXIST"] % line
841
        else:
842
            configuration.dictionary[line].freeze()
843
        return None
844
 
845
    def do_c(self, line):
846
        print "c", line
847
        configuration.clear()
848
        return None
849
 
850
    def do_p(self, line):
851
        print "p", line
852
        configuration.save(sys.stdout, baton=None, all=1)
853
        return None
854
 
855
    def do_u(self, line):
856
        print "u", line
857
        configuration.interactive = not configuration.interactive
858
        return None
859
 
860
    def do_h(self, line):
861
        print string.join(map(lambda x: lang[x],
862
                              ("TTYSUMMARY",
863
                               "YHELP", "MHELP", "NHELP", "PDHELP",
864
                               "FHELP", "CHELP",
865
                               "EHELP", "ECAPHELP", "CCAPHELP",
866
                               "IHELP", "ICAPHELP", "SHELP", "UHELP",
867
                               "QHELP", "XHELP", "VCAPHELP", "VHELP")), "\n")
868
 
869
    def default(self, line):
870
        if line.strip()[0] == "#":
871
            print line
872
        else:
873
            print "?"
874
        return 0
875
 
876
    def emptyline(self):
877
        print ""
878
        return 0
879
 
880
    def do_help(self, line):
881
        if not line:
882
            self.do_h(line)
883
        else:
884
            tty_style_base.do_help(self, line)
885
        return None
886
 
887
    def help_f(self):
888
        print lang["FHELP"]
889
 
890
    def help_c(self):
891
        print lang["CHELP"]
892
 
893
    def help_V(self):
894
        print lang["VCAPHELP"]
895
 
896
    def help_p(self):
897
        print lang["PDHELP"]
898
 
899
    def do_EOF(self, line):
900
        print ""
901
        self.do_q(line)
902
        return 1
903
 
904
# Curses interface
905
 
906
class MenuBrowser:
907
    "Support abstract browser operations on a stack of indexable objects."
908
    def __init__(self, mydebug=0, errout=sys.stderr):
909
        self.page_stack = []
910
        self.selection_stack = []
911
        self.viewbase_stack = []
912
        self.viewport_height = 0
913
        self.debug = mydebug
914
        self.errout = errout
915
 
916
    def match(self, a, b):
917
        "Browseable-object comparison."
918
        return a == b
919
 
920
    def push(self, browseable, selected=None):
921
        "Push a browseable object onto the location stack."
922
        if self.debug:
923
            self.errout.write("MenuBrowser.push(): pushing %s=@%d, selection=%s\n" % (browseable, id(browseable), `selected`))
924
        selnum = 0
925
        if selected == None:
926
            if self.debug:
927
                self.errout.write("MenuBrowser.push(): selection defaulted\n")
928
        else:
929
            for i in range(len(browseable)):
930
                selnum = len(browseable) - i - 1
931
                if self.match(browseable[selnum], selected):
932
                     break
933
            if self.debug:
934
                self.errout.write("MenuBrowser.push(): selection set to %d\n" % (selnum))
935
        self.page_stack.append(browseable)
936
        self.selection_stack.append(selnum)
937
        self.viewbase_stack.append(selnum - selnum % self.viewport_height)
938
        if self.debug:
939
            object = self.page_stack[-1]
940
            selection = self.selection_stack[-1]
941
            viewbase = self.viewbase_stack[-1]
942
            self.errout.write("MenuBrowser.push(): pushed %s=@%d->%d, selection=%d, viewbase=%d\n" % (object, id(object), len(self.page_stack), selection, viewbase))
943
 
944
    def pop(self):
945
        "Pop a browseable object off the location stack."
946
        if not self.page_stack:
947
            if self.debug:
948
                self.errout.write("MenuBrowser.pop(): stack empty\n")
949
            return None
950
        else:
951
            item = self.page_stack[-1]
952
            self.page_stack = self.page_stack[:-1]
953
            self.selection_stack = self.selection_stack[:-1]
954
            self.viewbase_stack = self.viewbase_stack[:-1]
955
            if self.debug:
956
                if len(self.page_stack) == 0:
957
                    self.errout.write("MenuBrowser.pop(): stack is empty.")
958
                else:
959
                    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]))
960
            return item
961
 
962
    def stackdepth(self):
963
        "Return the current stack depth."
964
        return len(self.page_stack)
965
 
966
    def list(self):
967
        "Return all elements of the current object that ought to be visible."
968
        if not self.page_stack:
969
            return None
970
        object = self.page_stack[-1]
971
        viewbase = self.viewbase_stack[-1]
972
 
973
        if self.debug:
974
            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]))
975
 
976
        # This requires a slice method
977
        return object[viewbase:viewbase+self.viewport_height]
978
 
979
    def top(self):
980
        "Return the top-of-stack menu"
981
        if self.debug >= 2:
982
            self.errout.write("MenuBrowser.top(): level=%d, @%d\n" % (len(self.page_stack)-1,id(self.page_stack[-1])))
983
        return self.page_stack[-1]
984
 
985
    def selected(self):
986
        "Return the currently selected element in the top menu."
987
        object = self.page_stack[-1]
988
        selection = self.selection_stack[-1]
989
        if self.debug:
990
            self.errout.write("MenuBrowser.selected(): at %d, object=@%d, %s\n" % (len(self.page_stack)-1, id(object), self.selection_stack[-1]))
991
        return object[selection]
992
 
993
    def viewbase(self):
994
        "Return the viewport base of the current menu."
995
        object = self.page_stack[-1]
996
        base = self.viewbase_stack[-1]
997
        if self.debug:
998
            self.errout.write("MenuBrowser.viewbase(): at level=%d, object=@%d, %d\n" % (len(self.page_stack)-1, id(object), base,))
999
        return base
1000
 
1001
    def thumb(self):
1002
        "Return top and bottom boundaries of a thumb scaled to the viewport."
1003
        object = self.page_stack[-1]
1004
        windowscale = float(self.viewport_height) / float(len(object))
1005
        thumb_top = self.viewbase() * windowscale
1006
        thumb_bottom = thumb_top + windowscale * self.viewport_height - 1
1007
        return (thumb_top, thumb_bottom)
1008
 
1009
    def move(self, delta=1, wrap=0):
1010
        "Move the selection on the current item downward."
1011
        if delta == 0:
1012
            return
1013
        object = self.page_stack[-1]
1014
        oldloc = self.selection_stack[-1]
1015
 
1016
        # Change the selection.  Requires a length method
1017
        if oldloc + delta in range(len(object)):
1018
            newloc = oldloc + delta
1019
        elif wrap:
1020
            newloc = (oldloc + delta) % len(object)
1021
        elif delta > 0:
1022
            newloc = len(object) - 1
1023
        else:
1024
            newloc = 0
1025
        return self.goto(newloc)
1026
 
1027
    def goto(self, newloc):
1028
        "Move the selection to the menu item with the given number."
1029
        oldloc = self.selection_stack[-1]
1030
        self.selection_stack[-1] = newloc
1031
        # When the selection is moved out of the viewport, move the viewbase
1032
        # just part enough to track it.
1033
        oldbase = self.viewbase_stack[-1]
1034
        if newloc in range(oldbase, oldbase + self.viewport_height):
1035
            pass
1036
        elif newloc < oldbase:
1037
            self.viewbase_stack[-1] = newloc
1038
        else:
1039
            self.scroll(newloc - (oldbase + self.viewport_height) + 1)
1040
        if self.debug:
1041
            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]))
1042
        return (oldloc != newloc)
1043
 
1044
    def scroll(self, delta=1, wrap=0):
1045
        "Scroll the viewport up or down in the current option."
1046
        object = self.page_stack[-1]
1047
        if not wrap:
1048
            oldbase = self.viewbase_stack[-1]
1049
            if delta > 0 and oldbase+delta > len(object)-self.viewport_height:
1050
                return
1051
            elif delta < 0 and oldbase + delta < 0:
1052
                return
1053
        self.viewbase_stack[-1] = (self.viewbase_stack[-1] + delta) % len(object)
1054
 
1055
    def dump(self):
1056
        "Dump the whole stack of objects."
1057
        self.errout.write("Viewport height: %d\n" % (self.viewport_height,))
1058
        for i in range(len(self.page_stack)):
1059
            self.errout.write("Page: %d\n" % (i,))
1060
            self.errout.write("Selection: %d\n" % (self.selection_stack[i],))
1061
            self.errout.write(`self.page_stack[i]` + "\n");
1062
 
1063
    def next(self, wrap=0):
1064
        return self.move(1, wrap)
1065
 
1066
    def previous(self, wrap=0):
1067
        return self.move(-1, wrap)
1068
 
1069
    def page_down(self):
1070
        return self.move(2*self.viewport_height-1)
1071
 
1072
    def page_up(self):
1073
        return self.move(-(2*self.viewport_height-1))
1074
 
1075
class PopupBaton:
1076
    "A popup window with a twirly-baton."
1077
    def __init__(self, startmsg, master):
1078
        self.subwin = master.window.subwin(3, len(startmsg)+3,
1079
                           (master.lines-3)/2,
1080
                           (master.columns-len(startmsg)-3)/2)
1081
        self.subwin.clear()
1082
        self.subwin.box()
1083
        self.subwin.addstr(1,1, startmsg)
1084
        self.subwin.refresh()
1085
        self.count = 0
1086
 
1087
    def twirl(self, ch=None):
1088
        if ch:
1089
            self.subwin.addch(ch)
1090
        else:
1091
            self.subwin.addch("-/|\\"[self.count % 4])
1092
            self.subwin.addch("\010")
1093
        self.subwin.refresh()
1094
        self.count = self.count + 1
1095
 
1096
    def end(self, msg=None):
1097
        pass
1098
 
1099
class WindowBaton:
1100
    "Put a twirly-baton at the upper right corner to indicate activity."
1101
    def __init__(self, master):
1102
        self.master = master
1103
        self.count = 0
1104
 
1105
    def twirl(self, ch=None):
1106
        if ch:
1107
            self.master.window.addch(0, self.master.columns-1, ch)
1108
        else:
1109
            self.master.window.addch(0, self.master.columns-1, "-/|\\"[self.count % 4])
1110
            self.master.window.addch("\010")
1111
        self.master.window.refresh()
1112
        self.count = self.count + 1
1113
 
1114
    def end(self, dummy=None):
1115
        self.master.window.addch(0, self.master.columns-1, " ")
1116
        self.master.window.refresh()
1117
        pass
1118
 
1119
class curses_style_menu:
1120
    "Command interpreter for line-oriented configurator."
1121
    input_nmatch = re.compile(r">>>.*\(([0-9]+)\)$")
1122
    valwidth = 32       # This is a constant
1123
 
1124
    def __init__(self, stdscr, config, mybanner):
1125
        if mybanner and configuration.banner.find("%s") > -1:
1126
            self.banner = configuration.banner % mybanner
1127
        elif mybanner:
1128
            self.banner = mybanner
1129
        else:
1130
            self.banner = configuration.banner
1131
        self.input_queue = []
1132
        self.menus = self.values = self.textbox = None
1133
        self.window = stdscr
1134
        self.msgbuf = ""
1135
        self.lastmenu = None
1136
 
1137
        menudebug = 0
1138
        if configuration.debug > 1:
1139
            menudebug = configuration.debug - 2
1140
        self.menus = MenuBrowser(menudebug,configuration.errout)
1141
 
1142
        (self.lines, self.columns) = self.window.getmaxyx()
1143
        if self.lines < 9 or self.columns < 60:
1144
            raise "TERMTOOSMALL"
1145
        self.menus.viewport_height = self.lines-2 + (not configuration.expert_tie or cml.evaluate(configuration.expert_tie) != cml.n)
1146
        if curses.has_colors():
1147
            #curses.init_pair(curses.COLOR_CYAN, curses.COLOR_WHITE, curses.COLOR_BLACK)
1148
            #curses.init_pair(curses.COLOR_GREEN, curses.COLOR_WHITE, curses.COLOR_BLACK)
1149
            curses.init_pair(curses.COLOR_CYAN, curses.COLOR_BLACK, curses.COLOR_WHITE)
1150
            curses.init_pair(curses.COLOR_GREEN, curses.COLOR_BLACK, curses.COLOR_WHITE)
1151
        self.window.clear()
1152
        self.window.scrollok(0)
1153
        self.window.idlok(1)
1154
        stdscr.bkgd(' ', curses.color_pair(curses.COLOR_CYAN))
1155
        # Most of the work gets done here
1156
        self.interact(config)
1157
 
1158
    # Input (with logging support)
1159
 
1160
    def getch(self, win):
1161
        if not readlog:
1162
            try:
1163
                ch = win.getch()
1164
            except KeyboardInterrupt:
1165
                curses.endwin()
1166
                raise
1167
        else:
1168
            time.sleep(1)
1169
            if self.input_queue:
1170
                ch = self.input_queue[0]
1171
                self.input_queue = self.input_queue[1:]
1172
            while 1:
1173
                line = readlog.readline()
1174
                if line == "":
1175
                    ch = -1
1176
                    break
1177
                m =  curses_style_menu.input_nmatch.match(line)
1178
                if m:
1179
                    ch = string.atoi(m.group(1))
1180
                    break
1181
        if configuration.debug:
1182
            configuration.debug_emit(1, ">>> '%s' (%d)"% (curses.keyname(ch), ch))
1183
        return ch
1184
 
1185
    def ungetch(self, c):
1186
        if readlog:
1187
            self.input_queue = c + self.input_queue
1188
        else:
1189
            curses.ungetch(c)
1190
 
1191
    # Notification
1192
 
1193
    def help_popup(self, instructions, msglist, beep=1):
1194
        "Pop up a help message."
1195
        if configuration.debug:
1196
            configuration.errout.write("***" + lang[instructions] + "\n")
1197
            configuration.errout.write(string.join(msglist, "\n"))
1198
        msgwidth = 0
1199
        pad = 2         # constant, must be >= 1
1200
        msgparts = []
1201
        for line in msglist:
1202
            unemitted = line
1203
            ww = self.columns - pad
1204
            while unemitted:
1205
                msgparts.append(unemitted[:ww])
1206
                unemitted = unemitted[ww:]
1207
        if len(msgparts) > self.lines - pad*2 - 1:
1208
            msgparts = msgparts[:self.lines - pad*2 - 2] + [lang["MORE"]]
1209
        for msg in msgparts:
1210
            if len(msg) > msgwidth:
1211
                msgwidth = len(msg)
1212
        msgwidth = min(self.columns - pad*2, msgwidth)
1213
        start_x = (self.columns - msgwidth) / 2
1214
        start_y = (self.lines - len(msgparts)) / 2
1215
        leave = lang[instructions]
1216
        msgwidth = max(msgwidth, len(leave))
1217
        subwin = self.window.subwin(len(msgparts)+1+pad*2, msgwidth+pad*2,
1218
                               start_y-pad, start_x-pad)
1219
        subwin.clear()
1220
        for i in range(len(msgparts)):
1221
            subwin.addstr(pad+i, pad + int((msgwidth-len(msgparts[i]))/2),
1222
                          msgparts[i], curses.A_BOLD)
1223
        subwin.addstr(pad*2+len(msgparts)-1, pad+int((msgwidth-len(leave))/2),
1224
                      leave)
1225
        subwin.box()
1226
        if beep:
1227
            curses.beep()
1228
        self.window.noutrefresh()
1229
        subwin.noutrefresh()
1230
        curses.doupdate()
1231
        value = self.getch(self.window)
1232
        subwin.clear()
1233
        subwin.noutrefresh()
1234
        self.window.noutrefresh()
1235
        curses.doupdate()
1236
        return value
1237
 
1238
    def query_popup(self, prompt, initval=None):
1239
        "Pop up a window to accept a string."
1240
        maxsymwidth = self.columns - len(prompt) - 10
1241
        if initval and len(initval) > maxsymwidth:
1242
            self.help_popup("PRESSANY", (lang["TOOLONG"],), beep=1)
1243
            return initval
1244
        gwinwidth = (len(prompt) + maxsymwidth)
1245
        start_y = self.lines/2-3
1246
        start_x = (self.columns - gwinwidth)/2
1247
        subwin = self.window.subwin(3, 2+gwinwidth, start_y-1, start_x-1)
1248
        subwin.clear()
1249
        subwin.box()
1250
        self.window.addstr(start_y, start_x, prompt, curses.A_BOLD)
1251
        self.window.refresh()
1252
        subwin.refresh()
1253
        subsubwin = subwin.subwin(1,maxsymwidth,start_y,start_x+len(prompt))
1254
        if initval:
1255
            subsubwin.addstr(0, 0, initval[:maxsymwidth-1])
1256
            subsubwin.touchwin()
1257
        configuration.debug_emit(1, "+++ %s"% (prompt,))
1258
        textbox = curses.textpad.Textbox(subsubwin)
1259
        popupval = textbox.edit()
1260
        self.window.touchwin()
1261
        if initval and textbox.lastcmd == curses.ascii.BEL:
1262
            return initval
1263
        else:
1264
            return popupval
1265
 
1266
    # Symbol state changes
1267
 
1268
    def set_symbol(self, sym, val, freeze=0):
1269
        "Try to set a symbol, display constraints in a popup if it fails."
1270
        configuration.debug_emit(1, lang["CURSESSET"] % (sym.name, val))
1271
        (ok, effects, violations) = configuration.set_symbol(sym, val, freeze)
1272
        if ok:
1273
            if not interactively_visible(sym):
1274
                self.help_popup("PRESSANY", [lang["INVISOUT"] % sym.name], beep=1)
1275
        else:
1276
            effects.append("\n")
1277
            self.help_popup("PRESSANY",
1278
                       effects + [lang["BADREQUIRE"]] + map(repr, violations), beep=1)
1279
 
1280
    # User interaction
1281
 
1282
    def in_menu(self):
1283
        "Return 1 if we're in a symbol menu, 0 otherwise"
1284
        return isinstance(self.menus.selected(), cml.ConfigSymbol)
1285
 
1286
    def recompute(self, here):
1287
        "Recompute the visible-members set for the given menu."
1288
        # First, make sure any choices menus immediately
1289
        # below this one get their defaults asserted.  Has
1290
        # to be done here because the visibility of stuff
1291
        # in a menu may depend on a choice submenu before
1292
        # it, so we need the default value to be hardened,
1293
        map(configuration.visit, here.items)
1294
        # Now compute visibilities.
1295
        visible = filter(lambda x, m=here: hasattr(m, 'nosuppressions') or interactively_visible(x), here.items)
1296
        lookingat = self.menus.selected()
1297
        if lookingat in visible:
1298
            selected = self.menus.selected()
1299
            self.menus.pop()
1300
            self.menus.push(visible, selected)
1301
            self.seek_mutable(1)
1302
        else:
1303
            if configuration.suppressions:
1304
                configuration.debug_emit(1, lang["SUPPRESSOFF"])
1305
                configuration.suppressions = 0
1306
                self.help_popup("PRESSANY", (lang["SUPPRESSOFF"],), beep=1)
1307
            selected = self.menus.selected()
1308
            self.menus.pop()
1309
            self.menus.push(here.items, selected)
1310
        # We've recomputed the top-of-stack item,
1311
        # so we must regenerate all associated prompts.
1312
        self.values = map(cgenvalue, self.menus.top())
1313
 
1314
    def redisplay(self, repaint):
1315
        "Repaint the screen."
1316
        sel_symbol = current_line = None
1317
        if self.banner and self.in_menu():
1318
            title = self.msgbuf + (" " * (self.columns - len(self.msgbuf) - len(self.banner) -1)) + self.banner
1319
        else:
1320
            title = (" " * ((self.columns-len(self.msgbuf)) / 2)) + self.msgbuf
1321
        self.menus.viewport_height = self.lines-2 + (not configuration.expert_tie or cml.evaluate(configuration.expert_tie) != cml.n)
1322
        self.window.move(0, 0)
1323
        self.window.clrtoeol()
1324
        self.window.addstr(title, curses.A_BOLD)
1325
 
1326
        (thumb_top, thumb_bottom) = self.menus.thumb()
1327
 
1328
        # Display the current band of entries 
1329
        screenlines = self.menus.list()
1330
        if self.in_menu():
1331
            screenvals = self.values[self.menus.viewbase():self.menus.viewbase()+self.menus.viewport_height]
1332
            configuration.debug_emit(1, "screenvals: " + `screenvals`)
1333
        else:
1334
            current_prompt = None
1335
 
1336
        # To change the number of lines on the screen that this paints,
1337
        # change the initialization of the viewport_height member.
1338
        for i in range(self.menus.viewport_height):
1339
            self.window.move(i+1, 0)
1340
            self.window.clrtoeol()
1341
            if len(self.menus.top()) <= self.menus.viewport_height:
1342
                thumb = None
1343
            elif i <= thumb_bottom and i >= thumb_top:
1344
                thumb = curses.ACS_CKBOARD
1345
            else:
1346
                thumb = curses.ACS_VLINE
1347
            if i < len(screenlines):
1348
                child = screenlines[i]
1349
                if type(child) is type(""):
1350
                    self.window.addstr(i+1, 0, child)
1351
                elif child.type == "message":
1352
                    self.window.addstr(i+1, 0, child.prompt + " ")
1353
                    self.window.hline(i+1, len(child.prompt) + 2,
1354
                                 curses.ACS_HLINE, self.columns-len(child.prompt)-3)
1355
                else:
1356
                    if child == self.menus.selected():
1357
                        lpointer = ">"
1358
                        rpointer = "<"
1359
                        highlight = curses.A_REVERSE
1360
                        current_line = i
1361
                        current_prompt = screenvals[i]
1362
                        sel_symbol = child
1363
                    else:
1364
                        lpointer = rpointer = " "
1365
                        highlight = curses.A_NORMAL
1366
                        if curses.has_colors():
1367
                            if child.frozen():
1368
                                highlight=curses.color_pair(curses.COLOR_CYAN)
1369
                            #elif child.inspected:
1370
                            #    highlight=curses.color_pair(curses.COLOR_GREEN)
1371
                            elif child.setcount or child.included:
1372
                                highlight=curses.color_pair(curses.COLOR_GREEN)
1373
                    # OK, now assemble the rest of the line
1374
                    leftpart = ("  " * child.depth) + cgenprompt(child, not configuration.expert_tie or not cml.evaluate(configuration.expert_tie))
1375
                    if configuration.is_new(child):
1376
                        leftpart = leftpart + " " + lang["NEW"]
1377
                    if child.frozen():
1378
                        leftpart = leftpart + " " + lang["FREEZELABEL"]
1379
                    if child.help():
1380
                        helpflag = "?"
1381
                    else:
1382
                        helpflag = ""
1383
                    rightpart = "=" + child.name + helpflag
1384
                    # Now make sure the information will fit in the line
1385
                    fixedlen = 1+curses_style_menu.valwidth+1+len(rightpart)+(thumb!=None) + 1
1386
                    leftpart = leftpart[:self.columns-fixedlen]
1387
                    filler = " " * (self.columns - len(leftpart) - fixedlen)
1388
                    line = leftpart + filler + rightpart
1389
                    # Write it
1390
                    self.window.move(i+1, 0)
1391
                    self.window.addstr(lpointer)
1392
                    if "edit" in repaint and child == self.menus.selected():
1393
                        self.window.move(i+1, curses_style_menu.valwidth+2)
1394
                        self.window.attron(highlight)
1395
                    else:
1396
                        self.window.attron(highlight)
1397
                        valstring = screenvals[i][:curses_style_menu.valwidth]
1398
                        self.window.addstr(valstring + (" " * (curses_style_menu.valwidth - len(valstring))) + " ")
1399
                    self.window.addstr(line)
1400
                    self.window.attroff(highlight)
1401
 
1402
                    # Ignore error from writing to last cell of
1403
                    # last line; the old curses module in 1.5.2
1404
                    # doesn't like this.  The try/catch around the
1405
                    # thumb write does the same thing.
1406
                    try:
1407
                        self.window.addstr(rpointer)
1408
                    except:
1409
                        pass
1410
            if thumb:
1411
                try:
1412
                    self.window.addch(i+1, self.columns-1, thumb)
1413
                except:
1414
                    pass
1415
        if not configuration.expert_tie or not cml.evaluate(configuration.expert_tie):
1416
            self.window.move(self.lines-1, 0)
1417
            self.window.clrtoeol()
1418
            helpbanner = lang["HELPBANNER"]
1419
            title = " " * ((self.columns - len(helpbanner))/2) + helpbanner
1420
            self.window.addstr(title, curses.A_BOLD)
1421
 
1422
        if type(self.menus.selected()) is not type(""):
1423
            self.window.move(current_line + 1, 0)
1424
        if "main" in repaint or "edithelp" in repaint:
1425
            self.window.noutrefresh()
1426
        if "edit" in repaint:
1427
            self.textbox.win.touchwin()
1428
            self.textbox.win.noutrefresh()
1429
        curses.doupdate()
1430
        return (current_line, current_prompt, sel_symbol)
1431
 
1432
    def accept_field(self, selected, value, oldval):
1433
        "Process the contents of a field edit."
1434
        base = 0
1435
        if selected.type == "hexadecimal":
1436
            base = 16
1437
            if oldval[:2] != "0x":
1438
                value = "0x" + value
1439
        value = string.strip(value)
1440
        if selected.is_numeric():
1441
            value = int(value, base)
1442
            if not configuration.range_check(selected, value):
1443
                self.help_popup("PRESSANY",
1444
                       (lang["OUTOFBOUNDS"] % (value, selected.range,),))
1445
                return
1446
        self.set_symbol(selected, value)
1447
 
1448
    def symbol_menu_command(self, cmd, operand):
1449
        "Handle commands that don't directly hack the screen or exit."
1450
        recompute = 0
1451
        if cmd == curses.KEY_LEFT:
1452
            if self.menus.stackdepth() <= 1:
1453
                self.msgbuf = lang["NOPOP"]
1454
            else:
1455
                self.menus.pop()
1456
                self.lastmenu = self.menus.selected()
1457
                recompute = 1
1458
        elif cmd == ord('y'):
1459
            if not self.in_menu():
1460
                self.help_popup("PRESSANY", (lang["NOSYMBOL"],))
1461
            elif operand.type in ("bool", "trit"):
1462
                self.set_symbol(operand, cml.y)
1463
            else:
1464
                self.help_popup("PRESSANY", (lang["BOOLEAN"],))
1465
            recompute = 1
1466
            if operand.menu.type != "choices":
1467
                self.ungetch(curses.KEY_DOWN)
1468
        elif cmd == ord('m'):
1469
            if not self.in_menu():
1470
                self.help_popup("PRESSANY", (lang["NOSYMBOL"],))
1471
            elif not configuration.trits_enabled:
1472
                self.help_popup("PRESSANY", (lang["MDISABLED"],))
1473
            elif operand.type == "trit":
1474
                self.set_symbol(operand, cml.m)
1475
            elif operand.type == "bool":
1476
                self.set_symbol(operand, cml.y) # Shortcut from old menuconfig
1477
            else:
1478
                self.help_popup("PRESSANY", (lang["TRIT"],))
1479
            recompute = 1
1480
            if operand.menu.type != "choices":
1481
                self.ungetch(curses.KEY_DOWN)
1482
        elif cmd == ord('n'):
1483
            if not self.in_menu():
1484
                self.help_popup("PRESSANY", (lang["NOSYMBOL"],))
1485
            elif operand.type in ("bool", "trit") and \
1486
                                        operand.menu.type != "choices":
1487
                self.set_symbol(operand, cml.n)
1488
            else:
1489
                self.help_popup("PRESSANY", (lang["BOOLEAN"],))
1490
            recompute = 1
1491
            if operand.menu.type != "choices":
1492
                self.ungetch(curses.KEY_DOWN)
1493
        elif cmd == ord('i'):
1494
            file = self.query_popup(lang["LOADFILE"])
1495
            try:
1496
                (changes, errors) = configuration.load(file, freeze=0)
1497
            except:
1498
                self.help_popup("PRESSANY", [lang["LOADFAIL"] % file])
1499
            else:
1500
                if errors:
1501
                    self.help_popup("PRESSANY", (errors,))
1502
                else:
1503
                    # Note, we don't try to display side effects here.
1504
                    # From a file include, there are very likely to
1505
                    # be more of them than can fit in a popup. 
1506
                    self.help_popup("PRESSANY",
1507
                               [lang["INCCHANGES"]%(changes,file)], beep=0)
1508
                recompute = 1
1509
        elif cmd == ord('I'):
1510
            file = self.query_popup(lang["LOADFILE"])
1511
            try:
1512
                (changes, errors) = configuration.load(file, freeze=0)
1513
            except:
1514
                self.help_popup("PRESSANY", [lang["LOADFAIL"] % file])
1515
            else:
1516
                if errors:
1517
                    self.help_popup("PRESSANY", (errors,))
1518
                else:
1519
                    # Note, we don't try to display side effects here.
1520
                    # From a file include, there are very likely to
1521
                    # be more of them than can fit in a popup. 
1522
                    self.help_popup("PRESSANY",
1523
                               [lang["INCCHANGES"]%(changes,file)], beep=0)
1524
                recompute = 1
1525
        elif cmd == ord('S'):
1526
            configuration.suppressions = not configuration.suppressions
1527
            recompute = 1
1528
        elif cmd == ord('/'):
1529
            pattern = self.query_popup(lang["SEARCHSYMBOLS"])
1530
            if pattern:
1531
                try:
1532
                    hits = configuration.symbolsearch(pattern)
1533
                except re.error, detail:
1534
                    self.help_popup("PRESSANY",
1535
                                    (lang["SEARCHINVAL"], str(detail)))
1536
                else:
1537
                    configuration.debug_emit(1, "hits: " + str(hits))
1538
                    if len(hits.items):
1539
                        self.menus.push(hits.items)
1540
                    else:
1541
                        self.help_popup("PRESSANY", (lang["SEARCHFAIL"],))
1542
            recompute = 1
1543
        elif cmd == ord('s'):
1544
            failure = configuration.save(config,
1545
                                         PopupBaton(lang["SAVING"], self))
1546
            if failure:
1547
                self.help_popup("PRESSANY", [failure])
1548
        else:
1549
            self.help_popup("PRESSANY",
1550
                            (lang["UNKNOWN"]%(curses.keyname(cmd)),))
1551
        return recompute
1552
 
1553
    def seek_mutable(self, direction, movefirst=0):
1554
        if movefirst:
1555
            self.menus.move(delta=direction, wrap=1)
1556
        while self.menus.selected().type =="message" \
1557
              or self.menus.selected().frozen():
1558
            self.menus.move(delta=direction, wrap=1)
1559
 
1560
    def interact(self, config):
1561
        "Configuration through a curses-based UI"
1562
        self.menus.push(configuration.start.items)
1563
        while not interactively_visible(self.menus.selected()):
1564
            if not self.menus.move(1):
1565
                self.help_popup("PRESSANY", (lang["NOVISIBLE"],), beep=1)
1566
                raise SystemExit, 1
1567
        recompute = 1
1568
        repaint = ["main"]
1569
        #curses.ungetch(curses.ascii.TAB)               # Get to a help screen.
1570
        while 1:
1571
            if isinstance(self.menus.selected(), cml.ConfigSymbol):
1572
                # In theory we could optimize this by only computing
1573
                # visibilities for children we have space to display,
1574
                # but never mind.  We'll settle for recomputing only
1575
                # when a variable changes value.
1576
                here = self.menus.selected().menu
1577
                configuration.visit(here)
1578
                if recompute:
1579
                    self.recompute(here)
1580
                    recompute = 0
1581
                # Clear the decks, issue the current menu title 
1582
                self.msgbuf = here.prompt
1583
                sel_symbol = None
1584
 
1585
            # Repaint the screen
1586
            (current_line,current_prompt,sel_symbol) = self.redisplay(repaint)
1587
            newval = current_prompt
1588
 
1589
            # OK, here is the command interpretation
1590
            if "edit" in repaint:
1591
                cmd = self.getch(self.textbox.win)
1592
            else:
1593
                cmd = self.getch(self.window)
1594
 
1595
            if "edithelp" in repaint:
1596
                repaint = ["main", "edit"]
1597
                self.textbox.win.move(oldy, oldx)
1598
                self.menus.pop()
1599
                continue
1600
            elif "edit" in repaint:
1601
                if cmd in (curses.KEY_DOWN, curses.KEY_UP, curses.KEY_ENTER,
1602
                           curses.ascii.NL, curses.ascii.CR, curses.ascii.BEL,
1603
                           ord(curses.ascii.ctrl('p')), ord(curses.ascii.ctrl('n'))):
1604
                    if cmd != curses.ascii.BEL:
1605
                        newval = self.textbox.gather()
1606
                        self.accept_field(sel_symbol,
1607
                                      newval,
1608
                                      current_prompt)
1609
                    # allow window to be deallocated
1610
                    self.textbox = None
1611
                    recompute = 1
1612
                    repaint = ["main"]
1613
                    self.msgbuf = ""
1614
                    if cmd in (curses.KEY_DOWN, curses.KEY_UP):
1615
                        self.ungetch(cmd)
1616
                elif curses.ascii.isprint(cmd):
1617
                    if sel_symbol.type == "decimal" and not curses.ascii.isdigit(cmd):
1618
                        curses.beep()
1619
                    elif sel_symbol.type == "hexadecimal" and not curses.ascii.isxdigit(cmd):
1620
                        curses.beep()
1621
                    else:
1622
                        self.textbox.do_command(cmd)
1623
                elif cmd == curses.ascii.TAB:
1624
                    self.msgbuf = lang["FIELDEDIT"]
1625
                    self.menus.push(string.split(lang["EDITHELP"], "\n"))
1626
                    (oldy, oldx) = self.textbox.win.getyx()
1627
                    repaint = ["edithelp"]
1628
                    continue
1629
                elif cmd == curses.KEY_RIGHT and self.textbox.win.getyx()[1]>=curses_style_menu.valwidth:
1630
                    oldval = newval
1631
                    newval = self.query_popup(sel_symbol.name+": ", oldval)
1632
                    if newval:
1633
                        self.accept_field(sel_symbol, newval, oldval)
1634
                        self.textbox.win.clear()
1635
                        self.textbox.win.addstr(0, 0, newval[0:curses_style_menu.valwidth])
1636
                        recompute = 1
1637
                    self.textbox = None
1638
                    repaint = ["main"]
1639
                else:
1640
                    self.textbox.do_command(cmd)
1641
            else:
1642
                if cmd == curses.ascii.FF:
1643
                    self.window.touchwin()
1644
                    self.window.refresh()
1645
                elif cmd == curses.KEY_RESIZE or cmd == -1:
1646
                    # Second test works around a bug in the old curses module
1647
                    # it gives back a -1 on resizes instead of KEY_RESIZE.
1648
                    (self.lines, self.columns) = self.window.getmaxyx()
1649
                    self.menus.viewport_height = self.lines-1
1650
                    recompute = 1
1651
                elif cmd in (curses.ascii.TAB, ord('h')):
1652
                    if self.in_menu():
1653
                        self.menus.push(string.split(lang["CURSHELP"], "\n"))
1654
                        self.msgbuf = lang["WELCOME"] % (configuration.banner) \
1655
                            + lang["VERSION"] % (cml.version,) \
1656
                            + " " + lang["CURSQUERY"]
1657
                        self.helpmode = 1
1658
                    elif self.helpmode == 1:
1659
                        self.menus.pop()
1660
                        self.menus.push(string.split(lang["EXPERTHELP"], "\n"))
1661
                        self.msgbuf = lang["CMDHELP"]
1662
                        self.helpmode = 2
1663
                    else:
1664
                        self.menus.pop()
1665
                        recompute = 1
1666
                elif cmd == ord('e'):
1667
                    if not self.in_menu():
1668
                        self.help_popup("PRESSANY", (lang["NOSYMBOL"],))
1669
                    else:
1670
                        self.help_popup("PRESSANY", (str(sel_symbol),), beep=0)
1671
                elif cmd == ord('g'):
1672
                    symname = self.query_popup(lang["GPROMPT"])
1673
                    if not configuration.dictionary.has_key(symname):
1674
                        self.help_popup("PRESSANY", (lang["NONEXIST"] % symname,))
1675
                    else:
1676
                        entry = configuration.dictionary[symname]
1677
                        if entry.type in ("menu", "choices"):
1678
                            self.menus.push(entry.items)
1679
                            recompute = 1
1680
                        elif entry.type == "message" or not entry.menu:
1681
                            self.help_popup("PRESSANY", (lang["CANTGO"],))
1682
                        else:
1683
                            self.menus.push(entry.menu.items, entry)
1684
                            recompute = 1
1685
                elif cmd == ord('?'):
1686
                    if not self.in_menu():
1687
                        self.help_popup("PRESSANY", (lang["NOSYMBOL"],))
1688
                    else:
1689
                        help = sel_symbol.help()
1690
                        if help:
1691
                            self.msgbuf = lang["HELPFOR"] % (sel_symbol.name,)
1692
                            self.menus.push(string.split(help, "\n"))
1693
                        else:
1694
                            self.help_popup("PRESSANY",
1695
                                       (lang["NOHELP"] % (sel_symbol.name,),))
1696
                elif cmd in (curses.KEY_DOWN, curses.ascii.ctrl('n')):
1697
                    if not self.in_menu():
1698
                        self.menus.scroll(1)
1699
                    else:
1700
                        self.seek_mutable(1, 1)
1701
                elif cmd == curses.KEY_UP:
1702
                    if not self.in_menu():
1703
                        self.menus.scroll(-1)
1704
                    else:
1705
                        self.seek_mutable(-1, 1)
1706
                elif cmd in (curses.KEY_NPAGE, curses.ascii.ctrl('p')):
1707
                    if self.in_menu():
1708
                        self.menus.page_down()
1709
                        notlast = (self.menus.selected() != self.menus.list()[-1])
1710
                        self.seek_mutable(notlast)
1711
                elif cmd == curses.KEY_PPAGE:
1712
                    if self.in_menu():
1713
                        self.menus.page_up()
1714
                        notlast = (self.menus.selected() != self.menus.list()[-1])
1715
                        self.seek_mutable(notlast)
1716
                elif cmd == curses.KEY_HOME:
1717
                    if self.in_menu():
1718
                        self.menus.goto(0)
1719
                        self.seek_mutable(0)
1720
                elif cmd == curses.KEY_END:
1721
                    if self.in_menu():
1722
                        self.menus.goto(len(self.menus.list())-1)
1723
                        self.seek_mutable(0)
1724
                # This guard intercepts all other commands in helpmode
1725
                elif not self.in_menu():
1726
                    if self.menus.stackdepth() == 1:
1727
                        here = configuration.start.items[0]
1728
                        while not interactively_visible(here):
1729
                            here = configuration.next_node(here)
1730
                        self.menus.push(here.menu.items, here)
1731
                    else:
1732
                        self.menus.pop()
1733
                # Following commands are not executed in helpmode
1734
                elif cmd == ord('x'):
1735
                    failure = configuration.save(config,
1736
                                  PopupBaton(lang["SAVING"], self))
1737
                    if failure:
1738
                        self.help_popup("PRESSANY", [failure])
1739
                    break
1740
                elif cmd == ord('q'):
1741
                    if configuration.commits == 0:
1742
                        break
1743
                    cmd = self.help_popup("EXITCONFIRM", (lang["REALLY"],), beep=0)
1744
                    if cmd == ord('q'):
1745
                        raise SystemExit, 1
1746
                elif cmd in (curses.KEY_ENTER,ord(' '),ord('\r'),ord('\n'),curses.KEY_RIGHT) :
1747
                    # Operate on the current object
1748
                    if sel_symbol.type == "message":
1749
                        curses.beep()
1750
                    elif sel_symbol.type in ("menu", "choices"):
1751
                        self.menus.push(sel_symbol.items)
1752
                        sel_symbol.inspected += 1
1753
                        while not interactively_visible(self.menus.selected()) or self.menus.selected().type == "message":
1754
                            if not self.menus.move(1, 0):
1755
                                break
1756
                    elif here.type == "choices" and sel_symbol.eval():
1757
                        pass
1758
                    elif cmd == curses.KEY_RIGHT:
1759
                        pass
1760
                    elif sel_symbol.type == "bool" or (sel_symbol.type == "trit" and not configuration.trits_enabled):
1761
                        if sel_symbol.eval() == cml.y:
1762
                            toggled = cml.n
1763
                        else:
1764
                            toggled = cml.y
1765
                        self.set_symbol(sel_symbol, toggled)
1766
                    elif sel_symbol.type == "trit":
1767
                        if sel_symbol.eval() == cml.y:
1768
                            toggled = cml.n
1769
                        elif sel_symbol.eval() == cml.m:
1770
                            toggled = cml.y
1771
                        else:
1772
                            toggled = cml.m
1773
                        self.set_symbol(sel_symbol, toggled)
1774
                    else:
1775
                        win =  curses.newwin(1, curses_style_menu.valwidth+1, current_line+1, 1)
1776
                        self.textbox = curses.textpad.Textbox(win)
1777
                        self.textbox.win.addstr(0, 0, current_prompt[:curses_style_menu.valwidth])
1778
                        newval = current_prompt
1779
                        self.textbox.win.move(0, 0)
1780
                        self.msgbuf = lang["EDITING"] % (sel_symbol.name[:self.columns-1],)
1781
                        repaint = ["main", "edit"]
1782
                    recompute = 1
1783
                else:
1784
                    recompute = self.symbol_menu_command(cmd, sel_symbol)
1785
 
1786
# Tkinter interface
1787
 
1788
# This is wrapped in try/expect in case the Tkinter import fails.
1789
# We need the import here because these classes have Frame as a parent.
1790
try:
1791
    from Tkinter import *
1792
    from tree import *
1793
 
1794
    class ValidatedField(Frame):
1795
        "Accept a string, decimal or hex value in a labeled field."
1796
        def __init__(self, master, symbol, prompt, variable, hook):
1797
            Frame.__init__(self, master)
1798
            self.symbol = symbol
1799
            self.hook = hook
1800
            self.fieldval = variable
1801
            self.L = Label(self, text=prompt, anchor=W)
1802
            self.L.pack(side=LEFT)
1803
            self.E = Entry(self, textvar=self.fieldval)
1804
            self.E.pack({'side':'left', 'expand':YES, 'fill':X})
1805
            self.E.bind('<Return>', self.handlePost)
1806
            self.E.bind('<Enter>', self.handleEnter)
1807
            self.fieldval.set(str(cml.evaluate(symbol)))
1808
            self.errorwin = None
1809
        def handleEnter(self, dummy):
1810
            self.E.bind('<Leave>', self.handlePost)
1811
        def handlePost(self, event):
1812
            if self.errorwin:
1813
                return
1814
            self.E.bind('<Leave>', lambda e: None)
1815
            result = string.strip(self.fieldval.get())
1816
            if self.symbol.type == "decimal":
1817
                if not re.compile("[" + string.digits +"]+$").match(result):
1818
                    self.error_popup(title=lang["PROBLEM"],
1819
                            banner=self.symbol.name,
1820
                            text=lang["CHARINVAL"])
1821
                    return
1822
            elif self.symbol.type == "hexadecimal":
1823
                if not re.compile("(0x)?["+string.hexdigits+"]+$").match(result):
1824
                    self.error_popup(title=lang["PROBLEM"],
1825
                            banner=self.symbol.name,
1826
                            text=lang["CHARINVAL"])
1827
                    return
1828
            apply(self.hook, (self.symbol, result))
1829
        def error_popup(self, title, mybanner, text):
1830
            self.errorwin = Toplevel()
1831
            self.errorwin.title(title)
1832
            self.errorwin.iconname(title)
1833
            Label(self.errorwin, text=mybanner).pack()
1834
            Label(self.errorwin, text=text).pack()
1835
            Button(self.errorwin, text=lang["DONE"],
1836
                   command=lambda x=self.errorwin: Widget.destroy(x), bd=2).pack()
1837
 
1838
 
1839
    class PromptGo(Frame):
1840
        "Accept a string value in a browser-like prompt window."
1841
        def __init__(self, master, label, command):
1842
            Frame.__init__(self, master)
1843
            # We really want to do this to make the window appear
1844
            # within the workframe:
1845
            #self.promptframe = Frame(master)
1846
            # Unfortunately, the scroll function in the canvas seems
1847
            # to get confused when we try this
1848
            self.promptframe = Frame(Toplevel())
1849
            self.promptframe.master.bind('<Destroy>', self.handleDestroy);
1850
            self.fieldval = StringVar(self.promptframe)
1851
            self.promptframe.L = Label(self.promptframe,
1852
                                   text=lang[label], anchor=W)
1853
            self.promptframe.L.pack(side=LEFT)
1854
            self.promptframe.E = Entry(self.promptframe, textvar=self.fieldval)
1855
            self.promptframe.E.pack({'side':'left', 'expand':YES, 'fill':X})
1856
            self.promptframe.E.bind('<Return>', self.dispatch)
1857
            self.promptframe.E.focus_set()
1858
            self.command = command
1859
            Button(self.promptframe, text=lang["GO"],
1860
                   command=self.dispatch, bd=2).pack()
1861
            self.promptframe.pack()
1862
            # Scroll to top of canvas and refresh/resize
1863
            self.master.menuframe.resetscroll()
1864
            self.master.refresh()
1865
        def dispatch(self, dummy=None):
1866
            apply(self.command, (self.fieldval.get(),))
1867
            # if PromptGo is implemented as top level widget this is not
1868
            # sufficient:
1869
            #self.promptframe.destroy()
1870
            # instead the top level widget must be destroyed
1871
            self.promptframe.master.destroy()
1872
        def handleDestroy(self, dummy=None):
1873
            apply(self.command, (None,))
1874
 
1875
 
1876
    class ScrolledFrame(Frame):
1877
        "A Frame object with a scrollbar on the right."
1878
        def __init__(self, master, **kw):
1879
            apply(Frame.__init__, (self, master), kw)
1880
 
1881
            self.scrollbar = Scrollbar(self, orient=VERTICAL)
1882
            self.canvas = Canvas(self, yscrollcommand=self.scrollbar.set)
1883
            self.scrollbar.config(command=self.canvas.yview)
1884
            self.scrollbar.pack(fill=Y, side=RIGHT)
1885
            self.canvas.pack(side=LEFT, fill=BOTH, expand=YES)
1886
 
1887
            # create the inner frame
1888
            self.inner = Frame(self.canvas)
1889
 
1890
            # track changes to its size
1891
            self.inner.bind('<Configure>', self.__configure)
1892
 
1893
            # place the frame inside the canvas
1894
            # (this also runs the __configure method)
1895
            self.canvas.create_window(0, 0, window=self.inner, anchor=NW)
1896
 
1897
        def showscroll(self, flag):
1898
            if flag:
1899
                self.canvas.pack_forget()
1900
                self.scrollbar.pack(fill=Y, side=RIGHT)
1901
                self.canvas.pack(side=LEFT, fill=BOTH, expand=YES)
1902
            else:
1903
                self.scrollbar.pack_forget()
1904
 
1905
        def resetscroll(self, loc=0.0):
1906
            self.canvas.yview("moveto", loc)
1907
 
1908
        def __configure(self, dummy):
1909
            # update the scrollbars to match the size of the inner frame
1910
            size = self.inner.winfo_reqwidth(), self.inner.winfo_reqheight()
1911
            self.canvas.config(scrollregion="0 0 %s %s" % size)
1912
 
1913
    class ScrolledText(Frame):
1914
        def __init__(self,parent=None,text=None,file=None,height=10,**kw):
1915
            apply(Frame.__init__,(self,parent),kw)
1916
            self.makewidgets(height)
1917
            self.settext(text,file)
1918
        def makewidgets(self,ht):
1919
            sbar=Scrollbar(self)
1920
            text=Text(self,relief=SUNKEN,height=ht)
1921
            sbar.config(command=text.yview)
1922
            text.config(yscrollcommand=sbar.set)
1923
            sbar.pack(side=RIGHT,fill=Y)
1924
            text.pack(side=LEFT,expand=YES,fill=BOTH)
1925
            self.text=text
1926
        def settext(self, text=None,file=None):
1927
            if file:
1928
                text=open(file,'r').read()
1929
            elif text:
1930
                self.text.delete('1.0',END)
1931
                self.text.insert('1.0',text)
1932
            else:
1933
                text='None'
1934
                self.text.delete('1.0',END)
1935
 
1936
    # Routine to get contents of subtree.  Supply this for a different
1937
    # type of app argument is the node object being expanded should return
1938
    # list of 4-tuples in the form: (label, unique identifier, closed
1939
    # icon, open icon) where:
1940
    #    label             - the name to be displayed
1941
    #    unique identifier - an internal fully unique name
1942
    #    closed icon       - PhotoImage of closed item
1943
    #    open icon         - PhotoImage of open item, or None if not openable
1944
    def my_get_contents(node):
1945
        menus=[]
1946
        options=[]
1947
        cmlnode=node.id
1948
        for child in cmlnode.items:
1949
            if interactively_visible(child):
1950
                if child.type =="menu" and cmlnode.items :
1951
                    menus.append((child.prompt, child, shut_icon, open_icon))
1952
                else:
1953
                    options.append((child.prompt, child, file_icon, None))
1954
        menus.sort()
1955
        options.sort()
1956
        return options+menus
1957
 
1958
    class myTree(Tree):
1959
        def __init__(self,master,**kw):
1960
            apply(Tree.__init__,(self,master),kw)
1961
 
1962
        def update_node(self,node=None):
1963
            if node==None:
1964
                node=self.pos
1965
            if node.id.type in ("trit","bool"):
1966
                if node.id.yes=="yes" and node.id.eval()==cml.n:
1967
                    node.id.yes="no"
1968
                    x1,y1=self.coords(node.symbol)
1969
                    self.delete(node.symbol)
1970
                    node.symbol=self.create_image(x1,y1,image=no_icon)
1971
                elif node.id.yes=="no" and \
1972
                    (node.id.eval()==cml.y or node.id.eval()==cml.m):
1973
                    node.id.yes="yes"
1974
                    x1,y1=self.coords(node.symbol)
1975
                    self.delete(node.symbol)
1976
                    node.symbol=self.create_image(x1,y1,image=yes_icon)
1977
        def update_tree(self):
1978
            #old cursor position
1979
            oldpos=self.pos.full_id()
1980
            #get expanded node list    
1981
            n=self.root.expanded()
1982
            #redraw whole tree again
1983
            self.root.toggle_state(0)
1984
            for j in n:
1985
                self.root.expand(j)
1986
            self.move_cursor(self.root.expand(oldpos[1:]))
1987
 
1988
    def makehelpwin(title, mybanner, text):
1989
        # help message window with a self-destruct button
1990
        makehelpwin = Toplevel()
1991
        makehelpwin.title(title)
1992
        makehelpwin.iconname(title)
1993
        if mybanner:
1994
            Label(makehelpwin, text=mybanner).pack()
1995
        textframe = Frame(makehelpwin)
1996
        scroll = Scrollbar(textframe)
1997
        makehelpwin.textwidget = Text(textframe, setgrid=TRUE)
1998
        textframe.pack(side=TOP, expand=YES, fill=BOTH)
1999
        makehelpwin.textwidget.config(yscrollcommand=scroll.set)
2000
        makehelpwin.textwidget.pack(side=LEFT, expand=YES, fill=BOTH)
2001
        scroll.config(command=makehelpwin.textwidget.yview)
2002
        scroll.pack(side=RIGHT, fill=BOTH)
2003
        Button(makehelpwin, text=lang["DONE"],
2004
               command=lambda x=makehelpwin: x.destroy(), bd=2).pack()
2005
        makehelpwin.textwidget.tag_config('url', foreground='blue', underline=YES)
2006
        makehelpwin.textwidget.tag_bind('url', '<Button-1>', launch_browser)
2007
        makehelpwin.textwidget.tag_bind('url', '<Enter>', lambda event, x=makehelpwin.textwidget: x.config(cursor='hand2'))
2008
        makehelpwin.textwidget.tag_bind('url', '<Leave>', lambda event, x=makehelpwin.textwidget: x.config(cursor='xterm'))
2009
        tag_urls(makehelpwin.textwidget, text)
2010
        makehelpwin.textwidget.config(state=DISABLED)   # prevent editing
2011
        makehelpwin.lift()
2012
 
2013
    def tag_urls(textwidget, text):
2014
        getURL = re.compile('((?:http|ftp|mailto|file)://[-.~/_?=#%\w]+\w)')
2015
        textlist = getURL.split(text)
2016
        for n in range(len(textlist)):
2017
            if n % 2 == 1:
2018
                textwidget.insert(END, textlist[n], ('url', textlist[n]))
2019
            else:
2020
                textwidget.insert(END, textlist[n])
2021
 
2022
    def launch_browser(event):
2023
        url = event.widget.tag_names(CURRENT)[1]
2024
        webbrowser.open(url)
2025
 
2026
    def make_icon_window(base, image):
2027
        try:
2028
            # Some older pythons will error out on this
2029
            icon_image = PhotoImage(data=image)
2030
            icon_window = Toplevel()
2031
            Label(icon_window, image=icon_image, bg='black').pack()
2032
            base.master.iconwindow(icon_window)
2033
            # Avoid TkInter brain death. PhotoImage objects go out of
2034
            # scope when the enclosing function returns.  Therefore
2035
            # we have to explicitly link them to something.
2036
            base.keepalive.append(icon_image)
2037
        except:
2038
            pass
2039
 
2040
 
2041
    def get_contents(node):
2042
        global open_icon, shut_icon, file_icon, yes_icon, no_icon
2043
        open_icon=PhotoImage(
2044
            data='R0lGODlhEAANAKIAAAAAAMDAwICAgP//////ADAwMAAAAAAA' \
2045
                'ACH5BAEAAAEALAAAAAAQAA0AAAM6GCrM+jCIQamIbw6ybXNSx3GVB' \
2046
                'YRiygnA534Eq5UlO8jUqLYsquuy0+SXap1CxBHr+HoBjoGndDpNAAA7')
2047
        shut_icon=PhotoImage(
2048
            data='R0lGODlhDwANAKIAAAAAAMDAwICAgP//////ADAwMAAAAAAA' \
2049
                'ACH5BAEAAAEALAAAAAAPAA0AAAMyGCHM+lAMMoeAT9Jtm5NDKI4Wo' \
2050
                'FXcJphhipanq7Kvu8b1dLc5tcuom2foAQQAyKRSmQAAOw==')
2051
        file_icon=PhotoImage(
2052
            data='R0lGODlhCwAOAJEAAAAAAICAgP///8DAwCH5BAEAAAMALAAA' \
2053
                'AAALAA4AAAIphA+jA+JuVgtUtMQePJlWCgSN9oSTV5lkKQpo2q5W+' \
2054
                'wbzuJrIHgw1WgAAOw==')
2055
        yes_icon=PhotoImage(
2056
            data='R0lGODlhCwAOAMIAAAAAAP////4AAHZ2dv///////////////' \
2057
                'yH5BAEKAAQALAAAAAALAA4AAAMuCLpATiBIqV6cITaI8+LCFGZ' \
2058
                'DNAYjUKIitY5CuqKhfLWkiatXfPKM4IAwKBqPCQA7')
2059
        no_icon=PhotoImage(
2060
            data='R0lGODlhCwAOAMIAAAAAAP///3Z2dik8/////////////////' \
2061
                'yH5BAEKAAQALAAAAAALAA4AAAMtCLpATiBIqV6cITaI8+IdJUR' \
2062
                'DMJQiiaLAaE6si76ZDKc03V7hzvwCgmBILCYAADs=')
2063
 
2064
        # menus=[]
2065
        options=[]
2066
        cmlnode=node.id
2067
        for child in cmlnode.items:
2068
           if interactively_visible(child):
2069
               if child.type =="menu" and cmlnode.items :
2070
                   # menus.append((child.prompt, child, shut_icon, open_icon))
2071
                   options.append((child.prompt, child, shut_icon, open_icon))
2072
               else:
2073
                   if child.type in ("trit","bool") and \
2074
                       (child.eval() == cml.y or child.eval() ==cml.m):
2075
                       options.append((child.prompt, child, yes_icon, None))
2076
                       child.yes="yes"
2077
                   elif child.type in ("trit","bool") and \
2078
                       child.eval() == cml.n:
2079
                       options.append((child.prompt, child, no_icon, None))
2080
                       child.yes="no"
2081
                   else:
2082
                       options.append((child.prompt, child, file_icon, None))
2083
 
2084
    #    menus.sort()
2085
    #    options.sort()
2086
 
2087
        #return options+menus
2088
        return options
2089
 
2090
    class ConfigMenu(Frame):
2091
        "Generic X front end for configurator."
2092
        def __init__(self, menu, config, mybanner):
2093
            Frame.__init__(self, master=None)
2094
            self.config = config
2095
            announce = configuration.banner + lang["VERSION"] % cml.version
2096
            if mybanner and announce.find("%s") > -1:
2097
                announce %= mybanner
2098
            self.master.title(announce)
2099
            self.master.iconname(announce)
2100
            self.master.resizable(FALSE, TRUE)
2101
            Pack.config(self, fill=BOTH, expand=YES)
2102
            self.keepalive = [] # Use this to anchor the PhotoImage object
2103
            if configuration.icon:
2104
                make_icon_window(self, configuration.icon)
2105
            ## Test icon display with the following:
2106
            # icon_image = PhotoImage(data=configuration.icon)
2107
            # Label(self, image=icon_image).pack(side=TOP, pady=10)
2108
            # self.keepalive.append(icon_image)
2109
            self.header = Frame(self)
2110
            self.header.pack(side=TOP, fill=X)
2111
 
2112
            self.menubar = Frame(self.header, relief=RAISED, bd=2)
2113
            self.menubar.pack(side=TOP, fill=X, expand=YES)
2114
            self.filemenu = self.makeMenu(lang["FILEBUTTON"],
2115
                                          (("LOADBUTTON", self.load),
2116
                                           ("FREEZEBUTTON", self.freeze),
2117
                                           ("SAVEBUTTON", self.save),
2118
                                           ("SAVEAS", self.save_as),
2119
                                           ("QUITBUTTON", self.leave),
2120
                                           ))
2121
            self.navmenu = self.makeMenu(lang["NAVBUTTON"],
2122
                                          (("BACKBUTTON", self.pop),
2123
                                           ("UPBUTTON", self.up),
2124
                                           ("GOBUTTON", self.goto),
2125
                                           ("SEARCHBUTTON", self.symbolsearch),
2126
                                           ("HSEARCHBUTTON", self.helpsearch),
2127
                                           ("UNSUPPRESSBUTTON",self.toggle_suppress),
2128
                                           ("ANCESTORBUTTON",self.show_ancestors),
2129
                                           ("DEPENDENTBUTTON",self.show_dependents),
2130
                                           ))
2131
            self.helpmenu = self.makeMenu(lang["HELPBUTTON"],
2132
                                          (("HELPBUTTON", self.cmdhelp),))
2133
            self.menulabel=Label(self.menubar)
2134
            self.menulabel.pack(side=RIGHT)
2135
 
2136
            self.toolbar = Frame(self.header, relief=RAISED, bd=2)
2137
            self.backbutton = Button(self.toolbar, text=lang["BACKBUTTON"],
2138
                          command=self.pop)
2139
            self.backbutton.pack(side=LEFT)
2140
            self.helpbutton = Button(self.toolbar, text=lang["HELPBUTTON"],
2141
                          command=lambda self=self: self.help(self.menustack[-1]))
2142
            self.helpbutton.pack(side=RIGHT)
2143
 
2144
            self.workframe = None
2145
 
2146
        def makeMenu(self, label, ops):
2147
            mbutton = Menubutton(self.menubar, text=label, underline=0)
2148
            mbutton.pack(side=LEFT)
2149
            dropdown = Menu(mbutton)
2150
            for (legend, function) in ops:
2151
                dropdown.add_command(label=lang[legend], command=function)
2152
            mbutton['menu'] = dropdown
2153
            return dropdown
2154
 
2155
        def setchoice(self, symbol):
2156
            # Handle a choice-menu selection.
2157
            self.set_symbol(symbol, cml.y)
2158
            self.lastmenu = symbol
2159
 
2160
        # File menu operations
2161
 
2162
        def enable_file_ops(self, ok):
2163
            if ok:
2164
                self.filemenu.entryconfig(1, state=NORMAL)
2165
                self.filemenu.entryconfig(2, state=NORMAL)
2166
                self.filemenu.entryconfig(3, state=NORMAL)
2167
                self.filemenu.entryconfig(4, state=NORMAL)
2168
                #self.filemenu.entryconfig(5, state=NORMAL)
2169
            else:
2170
                self.filemenu.entryconfig(1, state=DISABLED)
2171
                self.filemenu.entryconfig(2, state=DISABLED)
2172
                self.filemenu.entryconfig(3, state=DISABLED)
2173
                self.filemenu.entryconfig(4, state=DISABLED)
2174
                #self.filemenu.entryconfig(5, state=DISABLED)
2175
 
2176
        def load(self):
2177
            self.enable_file_ops(0)
2178
            PromptGo(self, "LOADFILE", self.load_internal)
2179
        def load_internal(self, file):
2180
            "Load a configuration file."
2181
            if file:
2182
                try:
2183
                    (changes, errors) = configuration.load(file, freeze=0)
2184
                except IOError:
2185
                    Dialog(self,
2186
                           title = lang["PROBLEM"],
2187
                           text = lang["LOADFAIL"] % file,
2188
                           bitmap = 'error',
2189
                           default = 0,
2190
                           strings = (lang["DONE"],))
2191
                else:
2192
                    if errors:
2193
                        Dialog(self,
2194
                                 title = lang["PROBLEM"],
2195
                                 text = errors,
2196
                                 bitmap = 'error',
2197
                                 default = 0,
2198
                                 strings = (lang["DONE"],))
2199
                    else:
2200
                        # Note, we don't try to display side effects here.
2201
                        # From a file include, there are very likely to
2202
                        # be more of them than can fit in a popup. 
2203
                        Dialog(self,
2204
                                 title = lang["OK"],
2205
                                 text = lang["INCCHANGES"] % (changes,file),
2206
                                 bitmap = 'hourglass',
2207
                                 default = 0,
2208
                                 strings = (lang["DONE"],))
2209
                        #self.tree.update_tree()
2210
            self.enable_file_ops(1)
2211
        def freeze(self):
2212
            ans = Dialog(self,
2213
                         title = lang["CONFIRM"],
2214
                         text = lang["FREEZE"],
2215
                         bitmap = 'questhead',
2216
                         default = 0,
2217
                         strings = (lang["FREEZEBUTTON"], lang["CANCEL"]))
2218
            if ans.num == 0:
2219
                for key in configuration.dictionary.keys():
2220
                    entry = configuration.dictionary[key]
2221
                    if entry.eval():
2222
                        entry.freeze()
2223
 
2224
        def save_internal(self, config):
2225
            failure = configuration.save(config)
2226
            if not failure:
2227
                return 1
2228
            else:
2229
                ans = Dialog(self,
2230
                             title = lang["PROBLEM"],
2231
                             text = failure,
2232
                             bitmap = 'error',
2233
                             default = 0,
2234
                             strings = (lang["CANCEL"], lang["DONE"]))
2235
                return ans.num
2236
 
2237
        def save(self):
2238
            if self.save_internal(self.config):
2239
                self.quit()
2240
 
2241
        def save_as(self):
2242
            # Disable everything but quit while this is going on
2243
            self.enable_file_ops(0)
2244
            PromptGo(self, "SAVEFILE", self.save_as_internal)
2245
        def save_as_internal(self, file):
2246
            if file:
2247
                self.save_internal(file)
2248
            self.enable_file_ops(1)
2249
 
2250
        def leave(self):
2251
            if configuration.commits == 0:
2252
                self.quit()
2253
            else:
2254
                ans = Dialog(self,
2255
                         title = lang["QUITCONFIRM"],
2256
                         text = lang["REALLY"],
2257
                         bitmap = 'questhead',
2258
                         default = 0,
2259
                         strings = (lang["EXIT"], lang["CANCEL"]))
2260
                if ans.num == 0:
2261
                    self.quit()
2262
                    raise SystemExit, 1
2263
 
2264
        # Navigation menu options
2265
 
2266
        def enable_nav_ops(self, ok):
2267
            if ok:
2268
                self.navmenu.entryconfig(1, state=NORMAL)
2269
                self.navmenu.entryconfig(2, state=NORMAL)
2270
                self.navmenu.entryconfig(3, state=NORMAL)
2271
                self.navmenu.entryconfig(4, state=NORMAL)
2272
                #self.navmenu.entryconfig(5, state=NORMAL)
2273
                self.navmenu.entryconfig(6, state=NORMAL)
2274
                self.navmenu.entryconfig(7, state=NORMAL)
2275
            else:
2276
                self.navmenu.entryconfig(1, state=DISABLED)
2277
                self.navmenu.entryconfig(2, state=DISABLED)
2278
                self.navmenu.entryconfig(3, state=DISABLED)
2279
                self.navmenu.entryconfig(4, state=DISABLED)
2280
                #self.navmenu.entryconfig(5, state=DISABLED)
2281
                self.navmenu.entryconfig(6, state=DISABLED)
2282
                self.navmenu.entryconfig(7, state=DISABLED)
2283
 
2284
        def up(self):
2285
            here = self.menustack[-1]
2286
            if here.menu:
2287
                self.push(here.menu, here)
2288
 
2289
        def goto(self):
2290
            self.enable_nav_ops(0)
2291
            PromptGo(self, "GOTOBYNAME", self.goto_internal)
2292
        def goto_internal(self, symname):
2293
            if symname:
2294
                if not configuration.dictionary.has_key(symname):
2295
                    Dialog(self,
2296
                             title = lang["PROBLEM"],
2297
                             text = lang["NONEXIST"] % symname,
2298
                             bitmap = 'error',
2299
                             default = 0,
2300
                             strings = (lang["DONE"],))
2301
                else:
2302
                    symbol = configuration.dictionary[symname]
2303
                    print symbol
2304
                    # We can't go to a symbol in a choices menu directly;
2305
                    # instead we must go to its parent. 
2306
                    if symbol.menu and symbol.menu.type == "choices":
2307
                        symbol = symbol.menu
2308
                    if not configuration.is_mutable(symbol):
2309
                        Dialog(self,
2310
                             title = lang["PROBLEM"],
2311
                             text = lang["FROZEN"],
2312
                             bitmap = 'hourglass',
2313
                             default = 0,
2314
                             strings = (lang["DONE"],))
2315
                    elif not interactively_visible(symbol):
2316
                        configuration.suppressions = 0
2317
                    if symbol.type in ("menu", "choices"):
2318
                        self.push(symbol)
2319
                    elif symbol.menu:
2320
                        self.push(symbol.menu, symbol)
2321
                    else:
2322
                        Dialog(self,
2323
                               title = lang["PROBLEM"],
2324
                               text = (lang["NOMENU"] % (symbol.name)),
2325
                               bitmap = 'error',
2326
                               default = 0,
2327
                               strings = (lang["DONE"],))
2328
            self.enable_nav_ops(1)
2329
 
2330
        def symbolsearch(self):
2331
            self.enable_nav_ops(0)
2332
            PromptGo(self, "SEARCHSYMBOLS", self.symbolsearch_internal)
2333
        def symbolsearch_internal(self, pattern):
2334
            if not pattern is None:
2335
                if pattern:
2336
                    hits = configuration.symbolsearch(pattern)
2337
                    hits.inspected = 0
2338
                    if hits.items:
2339
                        self.push(hits)
2340
                        print hits
2341
                    else:
2342
                        Dialog(self,
2343
                               title = lang["PROBLEM"],
2344
                               text = lang["NOMATCHES"],
2345
                               bitmap = 'error',
2346
                               default = 0,
2347
                               strings = (lang["DONE"],))
2348
                else:
2349
                    Dialog(self,
2350
                           title = lang["PROBLEM"],
2351
                           text = lang["EMPTYSEARCH"],
2352
                           bitmap = 'error',
2353
                           default = 0,
2354
                           strings = (lang["DONE"],))
2355
            self.enable_nav_ops(1)
2356
 
2357
        def helpsearch(self):
2358
            self.enable_nav_ops(0)
2359
            PromptGo(self, "SEARCHHELP", self.helpsearch_internal)
2360
        def helpsearch_internal(self, pattern):
2361
            if not pattern is None:
2362
                if pattern:
2363
                    hits = configuration.helpsearch(pattern)
2364
                    hits.inspected = 0
2365
                    if hits.items:
2366
                        self.push(hits)
2367
                    else:
2368
                        Dialog(self,
2369
                               title = lang["PROBLEM"],
2370
                               text = lang["NOMATCHES"],
2371
                               bitmap = 'error',
2372
                               default = 0,
2373
                               strings = (lang["DONE"],))
2374
                else:
2375
                    Dialog(self,
2376
                           title = lang["PROBLEM"],
2377
                           text = lang["EMPTYSEARCH"],
2378
                           bitmap = 'error',
2379
                           default = 0,
2380
                           strings = (lang["DONE"],))
2381
            self.enable_nav_ops(1)
2382
 
2383
        def show_ancestors(self):
2384
            self.enable_nav_ops(0)
2385
            PromptGo(self, "SHOW_ANC", self.show_ancestors_internal)
2386
        def show_ancestors_internal(self, symname):
2387
            if symname:
2388
                entry = configuration.dictionary.get(symname)
2389
                if not entry:
2390
                    Dialog(self,
2391
                           title = lang["INFO"],
2392
                           text = lang["NONEXIST"] % symname,
2393
                           bitmap = 'error',
2394
                           default = 0,
2395
                           strings = (lang["DONE"],))
2396
                elif not entry.ancestors:
2397
                    Dialog(self,
2398
                           title = lang["PROBLEM"],
2399
                           text = lang["NOANCEST"],
2400
                           bitmap = 'info',
2401
                           default = 0,
2402
                           strings = (lang["DONE"],))
2403
                else:
2404
                    hits = cml.ConfigSymbol("ancestors", "menu")
2405
                    hits.items = entry.ancestors
2406
                    # Give result a parent only if all members have same parent
2407
                    hits.menu = None
2408
                    hits.inspected = 0
2409
                    for symbol in hits.items:
2410
                        if not interactively_visible(symbol):
2411
                            configuration.suppressions = 0
2412
                        if hits.menu == None:
2413
                            hits.menu = symbol.menu
2414
                        elif symbol.menu != hits.menu:
2415
                            hits.menu = None
2416
                            break
2417
                    self.push(hits)
2418
            self.enable_nav_ops(1)
2419
 
2420
        def show_dependents(self):
2421
            self.enable_nav_ops(0)
2422
            PromptGo(self, "SHOW_ANC", self.show_dependents_internal)
2423
        def show_dependents_internal(self, symname):
2424
            if symname:
2425
                entry = configuration.dictionary.get(symname)
2426
                if not entry:
2427
                    Dialog(self,
2428
                           title = lang["INFO"],
2429
                           text = lang["NONEXIST"] % symname,
2430
                           bitmap = 'error',
2431
                           default = 0,
2432
                           strings = (lang["DONE"],))
2433
                elif not entry.dependents:
2434
                    Dialog(self,
2435
                           title = lang["PROBLEM"],
2436
                           text = lang["NODEPS"],
2437
                           bitmap = 'info',
2438
                           default = 0,
2439
                           strings = (lang["DONE"],))
2440
                else:
2441
                    hits = cml.ConfigSymbol("dependents", "menu")
2442
                    hits.items = entry.dependents
2443
                    # Give result a parent only if all members have same parent
2444
                    hits.menu = None
2445
                    hits.inspected = 0
2446
                    for symbol in hits.items:
2447
                        if not interactively_visible(symbol):
2448
                            configuration.suppressions = 0
2449
                        if hits.menu == None:
2450
                            hits.menu = symbol.menu
2451
                        elif symbol.menu != hits.menu:
2452
                            hits.menu = None
2453
                            break
2454
                    self.push(hits)
2455
            self.enable_nav_ops(1)
2456
 
2457
        def toggle_suppress(self):
2458
            configuration.suppressions = not configuration.suppressions
2459
            if configuration.suppressions:
2460
                self.navmenu.entryconfig(6, label=lang["UNSUPPRESSBUTTON"])
2461
            else:
2462
                self.navmenu.entryconfig(6, label=lang["SUPPRESSBUTTON"])
2463
            self.build()
2464
            self.display()
2465
 
2466
        # Help menu operations
2467
 
2468
        def cmdhelp(self):
2469
            makehelpwin(title=lang["HELPBUTTON"],
2470
                    mybanner=lang["HELPFOR"] % (configuration.banner,),
2471
                    text=lang["TKCMDHELP"])
2472
 
2473
    class ConfigTreeMenu(ConfigMenu):
2474
        "Top-level CML2 configurator object."
2475
        def __init__(self, menu, config, mybanner):
2476
            global helpwin
2477
            Frame.__init__(self, master=None)
2478
            ConfigMenu.__init__(self,menu,config,mybanner)
2479
            self.optionframe=None
2480
            self.tree=None
2481
            self.treewindow=Frame(self)
2482
            self.draw_tree()
2483
            self.treewindow.pack(expand=YES,fill=BOTH,side=LEFT)
2484
 
2485
            #quitbutton=Button(self.master,text='Quit',command=parent.quit)
2486
            #quitbutton.pack(fill=X,side=BOTTOM)
2487
 
2488
            self.navmenu.entryconfig(1, state=DISABLED)
2489
            self.navmenu.entryconfig(2, state=DISABLED)
2490
            self.navmenu.entryconfig(3, state=DISABLED)
2491
            self.navmenu.entryconfig(4, state=DISABLED)
2492
            self.navmenu.entryconfig(5, state=DISABLED)
2493
            self.navmenu.entryconfig(6, state=DISABLED)
2494
            self.navmenu.entryconfig(7, state=DISABLED)
2495
            self.navmenu.entryconfig(8, state=DISABLED)
2496
 
2497
            helpwin=ScrolledText(self.master,text='',height=10)
2498
            helpwin.pack(fill=X,side=BOTTOM)
2499
        def push(self):
2500
            self.tree.ascend()
2501
        def pop(self):
2502
            self.tree.descend()
2503
        def load_internal(self,file):
2504
            ConfigMenu.load_internal(self,file)
2505
            self.tree.update_tree()
2506
        def toggle_init(self,node):
2507
            global current_node
2508
            current_node=node.id
2509
            if current_node.helptext is None:
2510
                helpwin.settext(current_node.prompt)
2511
            else:
2512
                helpwin.settext(current_node.helptext)
2513
            self.draw_optionframe()
2514
 
2515
        def draw_optionframe(self):
2516
            global current_node,configuration
2517
            node=current_node
2518
            if self.optionframe:
2519
                Widget.destroy(self.optionframe)
2520
            if node:
2521
                id=node.name +": "+node.prompt
2522
                if configuration.is_new(node):
2523
                    id += " " + "New"
2524
                self.ties={}
2525
                self.optionframe=Frame(self.master)
2526
                self.optionframe.pack(fill=X,side=TOP)
2527
                if node.type =="choices":
2528
                    new= Menubutton(self.optionframe,relief=RAISED,
2529
                                    text=node.prompt)
2530
                    cmenu=Menu(new,tearoff=0)
2531
                    self.ties[node.name]=StringVar()
2532
                    for alt in node.items:
2533
                        cmenu.add_radiobutton(
2534
                            label=alt.name+": "+alt.prompt,
2535
                            variable=self.ties[node.name], value=alt.name,
2536
                            command=lambda self=self, x=alt:self.setchoice(x))
2537
                    new.config(menu=cmenu)
2538
                    new.pack(side=LEFT,anchor=W,fill=X,expand=YES)
2539
                elif node.type in ("trit","bool"):
2540
                    self.ties[node.name]=IntVar()
2541
                    if configuration.trits_enabled:
2542
                        w=Radiobutton(self.optionframe, text="y",
2543
                                    variable=self.ties[node.name],
2544
                                    command=lambda x=node, self=self:
2545
                                        self.set_symbol(x,cml.y),
2546
                                    relief=GROOVE,value=cml.y)
2547
                        w.pack(anchor=W,side=LEFT)
2548
                        w=Radiobutton(self.optionframe, text="m",
2549
                                    variable=self.ties[node.name],
2550
                                    command=lambda x=node, self=self:
2551
                                        self.set_symbol(x,cml.m),
2552
                                    relief=GROOVE,value=cml.m)
2553
                        if node.type== "bool":
2554
                            w.config(state=DISABLED,text="-")
2555
                        w.pack(anchor=W,side=LEFT)
2556
                        w=Radiobutton(self.optionframe, text="n",
2557
                                    variable=self.ties[node.name],
2558
                                    command=lambda x=node, self=self:
2559
                                        self.set_symbol(x,cml.n),
2560
                                    relief=GROOVE,value=cml.n)
2561
                        w.pack(anchor=W,side=LEFT)
2562
                    else:
2563
                        w=Checkbutton(self.optionframe,relief=GROOVE,
2564
                                    variable=self.ties[node.name],
2565
                                    command=lambda x=node,self=self:self.set_symbol(x,(cml.n,cml.y)[self.ties[x.name].get()]))
2566
                        w.pack(anchor=W,side=LEFT)
2567
                    tw=Label(self.optionframe,text=id,\
2568
                                relief=GROOVE,anchor=W)
2569
                    tw.pack(anchor=E,side=LEFT,fill=X,expand=YES)
2570
                elif node.type == "string":
2571
                    self.ties[node.name]=StringVar()
2572
                    new=ValidatedField(self.optionframe,node,\
2573
                                id,self.ties[node.name],
2574
                                self.set_symbol_simple)
2575
                    new.pack(side=LEFT,anchor=W,fill=X,expand=YES)
2576
                elif node.type =="decimal":
2577
                    self.ties[node.name]=StringVar()
2578
                    new=ValidatedField(self.optionframe,node,\
2579
                                id,self.ties[node.name],
2580
                                lambda n,v,s=self:s.set_symbol_simple(n,int(v)))
2581
                    new.pack(side=LEFT,anchor=W,fill=X,expand=YES)
2582
                elif node.type =="hexadecimal":
2583
                    self.ties[node.name]=StringVar()
2584
                    new=ValidatedField(self.optionframe,node,\
2585
                                id,self.ties[node.name],
2586
                                lambda n,v,s=self:s.set_symbol_simple(n,int(v,16)))
2587
                    new.pack(side=LEFT,anchor=W,fill=X,expand=YES)
2588
                else:
2589
                    pass
2590
 
2591
                #fill in the menu value    
2592
                if self.ties.has_key(node.name):
2593
                    if node.type =="choices":
2594
                        self.ties[node.name].set(node.menuvalue.name)
2595
                    elif node.type in ("string","decimal") or \
2596
                            node.enum:
2597
                        self.ties[node.name].set(str(node.eval()))
2598
                    elif node.type =="hexadecimal":
2599
                        self.ties[node.name].set("0x%x" % node.eval())
2600
                    else:
2601
                        enumval=node.eval()
2602
                        if not configuration.trits_enabled and \
2603
                            node.is_logical():
2604
                            enumval= min(enumval.value, cml.m.value)
2605
                        self.ties[node.name].set(enumval)
2606
 
2607
        def draw_tree(self):
2608
            global configuration
2609
            self.tree=myTree(self.treewindow, rootname=configuration.start, rootlabel=configuration.start.name, width=298,getcontents=get_contents,toggle_init=self.toggle_init)
2610
            self.tree.pack(fill=BOTH,expand=YES,side=LEFT)
2611
 
2612
 
2613
            sb=Scrollbar(self.treewindow)
2614
            sb.configure(command=self.tree.yview)
2615
            sb.pack(side=RIGHT,fill=Y)
2616
            self.tree.configure(yscrollcommand=sb.set)
2617
 
2618
            self.tree.focus_set()
2619
 
2620
        def setchoice(self, symbol):
2621
            # Handle a choice-menu selection.
2622
            self.set_symbol(symbol, cml.y)
2623
            self.lastmenu = symbol
2624
 
2625
        def set_symbol(self,symbol,value):
2626
            "Set symbol, checking validity"
2627
            global configuration
2628
            #print "set_symbol(%s,%s)" % (symbol.name,value)
2629
            if symbol.is_numeric() and symbol.range:
2630
                if not configuration.range_check(symbol,value):
2631
                    Dialog(self,
2632
                        title=lang["PROBLEM"],
2633
                        text=lang["OUTOFBOUNDS"] % (value, symbol.range,),
2634
                        bitmap='error',
2635
                        default=0,
2636
                        strings=(lang["DONE"],))
2637
                    return
2638
            old_tritflag=configuration.trits_enabled
2639
            self.master.grab_set()
2640
            (ok, effects, violations)=configuration.set_symbol(symbol, value)
2641
            #print ok,effects,violation
2642
            self.master.grab_release()
2643
            if not ok:
2644
                explain =""
2645
                if effects:
2646
                    explain = lang["EFFECTS"] + "\n" \
2647
                            + string.join(effects, "\n") + "\n"
2648
                explain += lang["ROLLBACK"] % (symbol.name, value) + \
2649
                    "\n" + string.join(map(repr, violations), "\n") + "\n"
2650
                Dialog(self, \
2651
                    title = lang["PROBLEM"], \
2652
                    text = explain, \
2653
                    bitmap = 'error', \
2654
                    default = 0, \
2655
                    strings = (lang["DONE"],))
2656
            else:
2657
                #wchkang
2658
                #self.tree.update_node()    
2659
                self.tree.update_tree()
2660
 
2661
                if old_tritflag != configuration.trits_enabled:
2662
                    pass
2663
                #    self.draw_optionframe()
2664
                if violations:
2665
                    Dialog(self,
2666
                        title = lang["SIDEEFFECTS"],
2667
                        text = string.join(map(repr, violations), "\n"),
2668
                        bitmap = 'info',
2669
                        default = 0,
2670
                        strings = (lang["DONE"],))
2671
            self.draw_optionframe()
2672
        def set_symbol_simple(self,symbol,value):
2673
            "Simple set-symbol without any screen update, validity checking"
2674
            #print "set_symbol_simple(%s,%s)" % (symbol.name,value)
2675
            self.master.grab_set()
2676
            (ok, effects, violations) = configuration.set_symbol(symbol, value)
2677
            self.master.grab_release()
2678
 
2679
 
2680
    class ConfigStackMenu(ConfigMenu):
2681
        "Top-level CML2 configurator object."
2682
        def __init__(self, menu, config, mybanner):
2683
            Frame.__init__(self, master=None)
2684
            ConfigMenu.__init__(self, menu, config, mybanner)
2685
 
2686
            self.menuframe = ScrolledFrame(self)
2687
            self.menuframe.pack(side=BOTTOM, fill=BOTH, expand=YES)
2688
 
2689
            self.menustack = []
2690
            self.locstack = []
2691
 
2692
            # Time to set up the main menu
2693
            self.lastmenu = None
2694
            self.push(configuration.start)
2695
 
2696
        # Repainting
2697
 
2698
        def build(self):
2699
            "Build widgets for all symbols in a menu, but don't pack them."
2700
            if self.workframe:
2701
                Widget.destroy(self.workframe)
2702
            self.workframe = Frame(self.menuframe.inner)
2703
            self.visible = []
2704
 
2705
            menu = self.menustack[-1]
2706
            w = Label(self.workframe,  text=menu.prompt)
2707
            w.pack(side=TOP, fill=X, expand=YES)
2708
            self.menulabel.config(text="(" + menu.name +")")
2709
 
2710
            self.symbol2widget = {}
2711
            self.ties = {}
2712
            self.textparts = {}
2713
            for node in menu.items:
2714
                id = node.name + ": " + node.prompt
2715
                if configuration.is_new(node):
2716
                    id += " " + lang["NEW"]
2717
                myframe = Frame(self.workframe)
2718
                if node.type == "message":
2719
                    new = Label(myframe, text=node.prompt)
2720
                    self.textparts[node.name] = new
2721
                elif node.frozen():
2722
                    value =  str(node.eval(debug))
2723
                    new = Label(myframe, text=node.name + ": " + \
2724
                                    node.prompt + " = " + value)
2725
                    self.textparts[node.name] = new
2726
                    new.config(fg='blue')
2727
                elif node.type == "menu":
2728
                    new = Button(myframe, text=node.prompt,
2729
                                 command=lambda x=self,y=node:x.push(y))
2730
                    self.textparts[node.name] = new
2731
                elif node.type == "choices":
2732
                    new = Menubutton(myframe, relief=RAISED,
2733
                                     text=node.prompt)
2734
                    self.textparts[node.name] = new
2735
                    cmenu = Menu(new, tearoff=0)
2736
                    self.ties[node.name] = StringVar(self.workframe)
2737
                    for alt in node.items:
2738
                        cmenu.add_radiobutton(
2739
                                        label=alt.name+": "+alt.prompt,
2740
                                        variable=self.ties[node.name], value=alt.name,
2741
                                        command=lambda self=self, x=alt:self.setchoice(x))
2742
                        # This is inelegant, but it will get the job done...
2743
                        self.symbol2widget[alt] = new
2744
                    new.config(menu=cmenu)
2745
                elif node.type in  ("trit", "bool"):
2746
                    new = Frame(myframe)
2747
                    self.ties[node.name] = IntVar(self.workframe)
2748
                    if configuration.trits_enabled:
2749
                        w = Radiobutton(new, text="y", relief=GROOVE,
2750
                                    variable=self.ties[node.name], value=cml.y,
2751
                                    command=lambda x=node, self=self: \
2752
                                        self.set_symbol(x, cml.y))
2753
                        w.pack(anchor=W, side=LEFT)
2754
                        w = Radiobutton(new, text="m", relief=GROOVE,
2755
                                        variable=self.ties[node.name], value=cml.m,
2756
                                        command=lambda x=node, self=self: \
2757
                                        self.set_symbol(x, cml.m))
2758
                        if node.type == "bool":
2759
                            w.config(state=DISABLED, text="-")
2760
                        w.pack(anchor=W, side=LEFT)
2761
                        w = Radiobutton(new, text="n", relief=GROOVE,
2762
                                    variable=self.ties[node.name], value=cml.n,
2763
                                    command=lambda x=node, self=self: \
2764
                                        self.set_symbol(x, cml.n))
2765
                        w.pack(anchor=W, side=LEFT)
2766
                    else:
2767
                        w = Checkbutton(new, relief=GROOVE,
2768
                                    variable=self.ties[node.name],
2769
                                    command=lambda x=node, self=self: \
2770
                                        self.set_symbol(x, (cml.n, cml.y)[self.ties[x.name].get()]))
2771
                        w.pack(anchor=W, side=LEFT)
2772
                    tw = Label(new, text=id, relief=GROOVE, anchor=W)
2773
                    tw.pack(anchor=E, side=LEFT, fill=X, expand=YES)
2774
                    self.textparts[node.name] = tw
2775
                elif node.discrete:
2776
                    new = Menubutton(myframe, relief=RAISED,
2777
                                     text=node.name+": "+node.prompt,
2778
                                     anchor=W)
2779
                    self.textparts[node.name] = new
2780
                    cmenu = Menu(new, tearoff=0)
2781
                    self.ties[node.name] = StringVar(self.workframe)
2782
                    for value in node.range:
2783
                        if node.type == "decimal":
2784
                            label=`value`
2785
                        elif node.type == "hexadecimal":
2786
                            label = "0x%x" % value
2787
                        cmenu.add_radiobutton(label=label, value=label,
2788
                                              variable=self.ties[node.name],
2789
                                              command=lambda self=self, symbol=node, label=label:self.set_symbol(symbol, label))
2790
                    new.config(menu=cmenu)
2791
                elif node.enum:
2792
                    new = Menubutton(myframe, relief=RAISED,
2793
                                     text=node.name+": "+node.prompt,
2794
                                     anchor=W)
2795
                    self.textparts[node.name] = new
2796
                    cmenu = Menu(new, tearoff=0)
2797
                    self.ties[node.name] = StringVar(self.workframe)
2798
                    for (label, value) in node.range:
2799
                        cmenu.add_radiobutton(label=label, value=value,
2800
                                              variable=self.ties[node.name],
2801
                                              command=lambda self=self, symbol=node, label=label, value=value:self.set_symbol(symbol, value))
2802
                    new.config(menu=cmenu)
2803
                elif node.type == "decimal":
2804
                    self.ties[node.name] = StringVar(self.workframe)
2805
                    new = ValidatedField(myframe, node,
2806
                                         id, self.ties[node.name],
2807
                                         lambda n, v, s=self: s.set_symbol(n, int(v)))
2808
                    self.textparts[node.name] = new.L
2809
                elif node.type == "hexadecimal":
2810
                    self.ties[node.name] = StringVar(self.workframe)
2811
                    new = ValidatedField(myframe, node,
2812
                                         id, self.ties[node.name],
2813
                                         lambda n, v, s=self: s.set_symbol(n, int(v, 16)))
2814
                    self.textparts[node.name] = new.L
2815
                elif node.type == "string":
2816
                    self.ties[node.name] = StringVar(self.workframe)
2817
                    new = ValidatedField(myframe, node,
2818
                                         id, self.ties[node.name],
2819
                                         self.set_symbol)
2820
                    self.textparts[node.name] = new.L
2821
                new.pack(side=LEFT, anchor=W, fill=X, expand=YES)
2822
                if node.type not in ("explanation", "message"):
2823
                    help = Button(myframe, text=lang["HELPBUTTON"],
2824
                                 command=lambda symbol=node, self=self: self.help(symbol))
2825
                    help.pack(side=RIGHT, anchor=E)
2826
                    if not node.help():
2827
                        help.config(state=DISABLED)
2828
                myframe.pack(side=TOP, fill=X, expand=YES)
2829
                self.symbol2widget[node] = myframe
2830
 
2831
            # This isn't widget layout, it grays out the Back buttons
2832
            if len(self.menustack) <= 1:
2833
                self.backbutton.config(state=DISABLED)
2834
                self.navmenu.entryconfig(1, state=DISABLED)
2835
            else:
2836
                self.backbutton.config(state=NORMAL)
2837
                self.navmenu.entryconfig(1, state=NORMAL)
2838
 
2839
            # Likewise, this grays out the help button when appropriate.
2840
            here = self.menustack[-1]
2841
            if isinstance(here, cml.ConfigSymbol) and here.help():
2842
                self.helpbutton.config(state=NORMAL)
2843
            else:
2844
                self.helpbutton.config(state=DISABLED)
2845
 
2846
            # This grays out the "up" button
2847
            if not here.menu:
2848
                self.navmenu.entryconfig(2, state=DISABLED)
2849
            else:
2850
                self.navmenu.entryconfig(2, state=NORMAL)
2851
 
2852
            # Pan canvas to the top of the widget list
2853
            self.menuframe.resetscroll()
2854
 
2855
        def refresh(self):
2856
            self.workframe.update()
2857
 
2858
            # Dynamic resizing.  This code can flake out in some odd
2859
            # ways, notably by where it puts the resized window (this
2860
            # is probably tickling a window-manager bug). We want
2861
            # normal placement somewhere in an unused area of the root
2862
            # window.  What we get too often (at least under
2863
            # Enlightenment) is the window placed where the top of
2864
            # frame isn't visible -- which is annoying, because it
2865
            # makes it hard to move the window to a better spot.
2866
            widgetheight = self.workframe.winfo_reqheight()
2867
            # Allow 50 vertical pixels for window frame cruft.
2868
            maxheight = self.winfo_screenheight() - 50
2869
            oversized = widgetheight > maxheight
2870
            self.menuframe.showscroll(oversized)
2871
            if oversized:
2872
                # This assumes the scrollbar widget will be < 25 pixels wide
2873
                newwidth = self.workframe.winfo_width() + 25
2874
                newheight = maxheight
2875
            else:
2876
                newwidth = self.workframe.winfo_width()
2877
                newheight = widgetheight + \
2878
                        self.menubar.winfo_height()+self.menubar.winfo_height()
2879
            # Following four lines center the window.
2880
            #topx = (self.winfo_screenwidth() - newwidth) / 2
2881
            #topy = (self.winfo_screenheight() - newheight) / 2
2882
            #if topx < 0: topx = 0
2883
            #if topy < 0: topy = 0
2884
            #self.master.geometry("%dx%d+%d+%d"%(newwidth,newheight,topx,topy))
2885
            self.master.geometry("%dx%d" % (newwidth, newheight))
2886
            self.workframe.lift()
2887
 
2888
        def display(self):
2889
            menu = self.menustack[-1]
2890
            newvisible = filter(lambda x, m=menu: hasattr(m, 'nosuppressions') or interactively_visible(x), menu.items)
2891
            # Insert all widgets that must newly become visible 
2892
            for symbol in menu.items:
2893
                # Color the menu text
2894
                textpart = self.textparts[symbol.name]
2895
                if symbol.type in ("menu", "choices"):
2896
                    if symbol.inspected:
2897
                        textpart.config(fg='dark green')
2898
                    elif not symbol.frozen():
2899
                        textpart.config(fg='black')
2900
                elif symbol.type in ("trit", "bool"):
2901
                    if symbol.setcount or symbol.included:
2902
                        textpart.config(fg='dark green')
2903
                # Fill in the menu value
2904
                if self.ties.has_key(symbol.name):
2905
                    if symbol.type == "choices":
2906
                        self.ties[symbol.name].set(symbol.menuvalue.name)
2907
                    elif symbol.type in ("string", "decimal") or symbol.enum:
2908
                        self.ties[symbol.name].set(str(symbol.eval()))
2909
                    elif symbol.type == "hexadecimal":
2910
                        self.ties[symbol.name].set("0x%x" % symbol.eval())
2911
                    else:
2912
                        enumval = symbol.eval()
2913
                        if not configuration.trits_enabled and symbol.is_logical():
2914
                            enumval = min(enumval.value, cml.m.value)
2915
                        self.ties[symbol.name].set(enumval)
2916
                # Now hack the widget visibilities
2917
                if symbol in newvisible and symbol not in self.visible:
2918
                    argdict = {'anchor':W, 'side':TOP}
2919
                    if self.menustack[-1].type != "choices":
2920
                         argdict['expand'] = YES
2921
                         argdict['fill'] = X
2922
                    # Fiendishly clever hack alert: avoid excessive screen
2923
                    # updating by repacking widgets in place as they pop
2924
                    # in and out of visibility. Look for a first visible symbol
2925
                    # after the current one.  If you find one, use it to
2926
                    # generate a "before" option for packing.  Otherwise,
2927
                    # generate an "after" option that packs after the last
2928
                    # visible item.
2929
                    if self.visible:
2930
                        foundit = 0
2931
                        for anchor in menu.items[menu.items.index(symbol):]:
2932
                            if anchor in self.visible:
2933
                                argdict['before'] = self.symbol2widget[anchor]
2934
                                foundit = 1
2935
                                break
2936
                        if not foundit:
2937
                            argdict['after'] = self.symbol2widget[self.visible[-1]]
2938
                            self.visible.append(symbol)
2939
                    self.symbol2widget[symbol].pack(argdict)
2940
            # We've used all the anchor points, clean up invisible ones
2941
            for symbol in menu.items:
2942
                if symbol not in newvisible:
2943
                    self.symbol2widget[symbol].pack_forget()
2944
                elif symbol.type == "choices":
2945
                    if symbol.menuvalue:
2946
                        self.symbol2widget[symbol].winfo_children()[0].config(text="%s (%s)" % (symbol.prompt, symbol.menuvalue.name))
2947
                elif symbol.discrete:
2948
                    self.symbol2widget[symbol].winfo_children()[0].config(text="%s: %s (%s)" % (symbol.name, symbol.prompt, str(symbol.eval())))
2949
            self.workframe.pack(side=BOTTOM)
2950
            self.toolbar.pack(side=BOTTOM, fill=X, expand=YES)
2951
            self.visible = newvisible
2952
 
2953
            self.refresh()
2954
 
2955
        # Operations on symbols and menus
2956
 
2957
        def set_symbol(self, symbol, value):
2958
            "Set symbol, checking validity."
2959
            #print "set_symbol(%s, %s)" % (symbol.name, value)
2960
            if symbol.is_numeric() and symbol.range:
2961
                if not configuration.range_check(symbol, value):
2962
                    Dialog(self,
2963
                         title = lang["PROBLEM"],
2964
                         text = lang["OUTOFBOUNDS"] % (value, symbol.range,),
2965
                         bitmap = 'error',
2966
                         default = 0,
2967
                         strings = (lang["DONE"],))
2968
                    return
2969
            old_tritflag = configuration.trits_enabled
2970
            # The set_grab() is an attempt to head off race conditions.
2971
            # We don't want the symbol widgets to accept new input
2972
            # events while the side-effects of a symbol set are still
2973
            # being computed.
2974
            self.master.grab_set()
2975
            (ok, effects, violations) = configuration.set_symbol(symbol,value)
2976
            self.master.grab_release()
2977
            if not ok:
2978
                explain = ""
2979
                if effects:
2980
                    explain = lang["EFFECTS"] + "\n" \
2981
                              + string.join(effects, "\n") + "\n"
2982
                explain += lang["ROLLBACK"] % (symbol.name, value) + \
2983
                           "\n" + string.join(map(repr, violations), "\n") + "\n"
2984
                Dialog(self,
2985
                         title = lang["PROBLEM"],
2986
                         text = explain,
2987
                         bitmap = 'error',
2988
                         default = 0,
2989
                         strings = (lang["DONE"],))
2990
            else:
2991
                if old_tritflag != configuration.trits_enabled:
2992
                    self.build()
2993
            self.display()
2994
 
2995
        def help(self, symbol):
2996
            makehelpwin(title=lang["HELPBUTTON"],
2997
                    mybanner=lang["HELPFOR"] % (symbol.name),
2998
                    text = symbol.help())
2999
 
3000
        def push(self, menu, highlight=None):
3001
            configuration.visit(menu)
3002
            self.menustack.append(menu)
3003
            self.locstack.append(self.menuframe.canvas.canvasy(0))
3004
            self.build()
3005
            self.lastmenu = highlight
3006
            self.display()
3007
            menu.inspected += 1
3008
 
3009
        def pop(self):
3010
            if len(self.menustack) > 1:
3011
                from_menu = self.menustack[-1]
3012
                self.menustack = self.menustack[:-1]
3013
                self.build()
3014
                self.lastmenu = from_menu
3015
                self.display()
3016
                from_loc = self.locstack[-1]
3017
                self.locstack = self.locstack[:-1]
3018
                self.menuframe.resetscroll(from_loc)
3019
 
3020
        def freeze(self):
3021
            "Call the base freeze, then update the display."
3022
            ConfigMenu.freeze(self)
3023
            self.build()
3024
            self.display()
3025
 
3026
except ImportError:
3027
    pass
3028
 
3029
def tkinter_style_menu(config, mybanner):
3030
    ConfigStackMenu(configuration.start, config, mybanner).mainloop()
3031
 
3032
def tkinter_qplus_style_menu(config, mybanner):
3033
    ConfigTreeMenu(configuration.start, config, mybanner).mainloop()
3034
 
3035
# Report generator
3036
 
3037
def menu_tree_list(node, indent):
3038
    "Print a map of a menu subtree."
3039
    totalindent = (indent + 4 * node.depth)
3040
    trailer = (" " * (40 - totalindent - len(node.name))) + `node.prompt`
3041
    print " " * totalindent, node.name, trailer
3042
    if configuration.debug:
3043
        if node.visibility:
3044
            print " " * 41, lang["VISIBILITY"], cml.display_expression(node.visibility)
3045
        if node.default:
3046
            print " " * 41, lang["DEFAULT"], cml.display_expression(node.default)
3047
    if node.items:
3048
        for child in node.items:
3049
            menu_tree_list(child, indent + 4)
3050
 
3051
# Environment probes
3052
 
3053
def is_under_X():
3054
    # It would be nice to just check WINDOWID, but some terminal
3055
    # emulators don't set it. One of those is kvt.
3056
    if os.environ.has_key("WINDOWID"):
3057
        return 1
3058
    else:
3059
        import commands
3060
        (status, output) = commands.getstatusoutput("xdpyinfo")
3061
        return status == 0
3062
 
3063
# Rulebase loading and option processing
3064
 
3065
def load_system(cmd_options, cmd_arguments):
3066
    "Read in the rulebase and handle command-line arguments."
3067
    global debug, config
3068
    debug = 0;
3069
    config = None
3070
 
3071
    if not cmd_arguments:
3072
        rulebase = "rules.out"
3073
    else:
3074
        rulebase = cmd_arguments[0]
3075
    try:
3076
        open(rulebase, 'rb')
3077
    except IOError:
3078
        print lang["NOFILE"] % (rulebase,)
3079
        raise SystemExit
3080
    configuration = cmlsystem.CMLSystem(rulebase)
3081
 
3082
    process_options(configuration, cmd_options)
3083
 
3084
    configuration.debug_emit(1, lang["PARAMS"] % (config,configuration.prefix))
3085
 
3086
    # Perhaps the user needs modules enabled initially
3087
    if configuration.trit_tie and cml.evaluate(configuration.trit_tie):
3088
        configuration.trits_enabled = 1
3089
 
3090
    # Don't count all these automatically generated settings
3091
    # for purposes of figuring out whether we should confirm a quit.
3092
    configuration.commits = 0
3093
 
3094
    return configuration
3095
 
3096
def process_include(configuration, file, freeze):
3097
    "Process a -i or -I inclusion option."
3098
    # Failure to find an include file is non-fatal
3099
    try:
3100
        (changes, errors) = configuration.load(file, freeze)
3101
    except IOError:
3102
        print lang["LOADFAIL"] % file
3103
        return
3104
    if errors:
3105
        print errors
3106
    elif configuration.side_effects:
3107
        print lang["SIDEFROM"] % file
3108
        sys.stdout.write(string.join(configuration.side_effects, "\n") + "\n")
3109
 
3110
def process_define(configuration, val, freeze):
3111
    "Process a -d=xxx or -D=xxx option."
3112
    parts = string.split(val, "=")
3113
    sym = parts[0]
3114
    if configuration.dictionary.has_key(sym):
3115
        sym = configuration.dictionary[sym]
3116
    else:
3117
        configuration.errout.write(lang["SYMUNKNOWN"] % (`sym`,))
3118
        sys.exit(1)
3119
    if sym.is_derived():
3120
        configuration.debug_emit(1, lang["DERIVED"] % (`sym`,))
3121
        sys.exit(1)
3122
    elif sym.is_logical():
3123
        if len(parts) == 1:
3124
            val = 'y'
3125
        elif parts[1] == 'y':
3126
            val = 'y'
3127
        elif parts[1] == 'm':
3128
            configuration.trits_enabled = 1
3129
            val = 'm'
3130
        elif parts[1] == 'n':
3131
            val = 'n'
3132
        else:
3133
            print lang["BADBOOL"]
3134
            sys.exit(1)
3135
    elif len(parts) == 1:
3136
        print lang["NOCMDLINE"] % (`sym`,)
3137
        sys.exit(1)
3138
    else:
3139
        val = parts[1]
3140
    (ok, effects, violations) = configuration.set_symbol(sym,
3141
                                 configuration.value_from_string(sym, val),
3142
                                 freeze)
3143
    if effects:
3144
        print lang["EFFECTS"]
3145
        sys.stdout.write(string.join(effects,"\n")+"\n")
3146
    if not ok:
3147
        print lang["ROLLBACK"] % (sym.name, val)
3148
        sys.stdout.write("\n".join(map(repr, violations))+"\n")
3149
 
3150
def process_options(configuration, options):
3151
    # Process command-line options second so they override
3152
    global list, config
3153
    global force_batch, force_x, force_q, force_tty, force_curses, debug
3154
    global readlog, banner
3155
    config = "config.out"
3156
    for (switch, val) in options:
3157
        if switch == '-b':
3158
            force_batch = 1
3159
        elif switch == '-B':
3160
            banner = val
3161
        elif switch == '-d':
3162
            process_define(configuration, val, freeze=0)
3163
        elif switch == '-D':
3164
            process_define(configuration, val, freeze=1)
3165
        elif switch == '-i':
3166
            process_include(configuration, val, freeze=0)
3167
        elif switch == '-I':
3168
            process_include(configuration, val, freeze=1)
3169
        elif switch == '-l':
3170
            list = 1
3171
        elif switch == '-o':
3172
            config = val
3173
        elif switch == '-v':
3174
            debug = debug + 1
3175
            configuration.debug = configuration.debug + 1
3176
        elif switch == '-S':
3177
            configuration.suppressions = 0
3178
        elif switch == '-R':
3179
            readlog = open(val, "r")
3180
 
3181
# Main sequence -- isolated here so we can profile it
3182
 
3183
def main(options, arguments):
3184
    global force_batch, force_x, force_q, force_curses, force_tty
3185
    global configuration
3186
 
3187
    try:
3188
        configuration = load_system(options, arguments)
3189
    except KeyboardInterrupt:
3190
        raise SystemExit
3191
 
3192
    if list:
3193
        try:
3194
            menu_tree_list(configuration.start, 0)
3195
        except EnvironmentError:
3196
            pass        # Don't emit a traceback when we interrupt the listing
3197
        raise SystemExit
3198
    # Perhaps we're in batchmode.  If so, only process options. 
3199
    if force_batch:
3200
        # Have to realize all choices values first...
3201
        for entry in configuration.dictionary.values():
3202
            if entry.type == "choices":
3203
                configuration.visit(entry)
3204
        configuration.save(config)
3205
        return
3206
 
3207
    # Next, try X
3208
    if force_x:
3209
        tkinter_style_menu(config, banner)
3210
        return
3211
 
3212
    # Next, try Qplus style X
3213
    if force_q:
3214
        tkinter_qplus_style_menu(config, banner)
3215
        return
3216
 
3217
    # Next, try curses
3218
    if force_curses and not force_tty:
3219
        try:
3220
            curses.wrapper(curses_style_menu, config, banner)
3221
            return
3222
        except "TERMTOOSMALL":
3223
            print lang["TERMTOOSMALL"]
3224
            force_tty = 1
3225
 
3226
    # If both failed, go glass-tty
3227
    if force_debugger:
3228
        print lang["DEBUG"] % configuration.banner
3229
        debugger_style_menu(config, banner).cmdloop()
3230
    elif force_tty:
3231
        print lang["WELCOME"]%(configuration.banner,) + lang["VERSION"]%(cml.version,)
3232
        configuration.errout = sys.stdout
3233
        print lang["TTYQUERY"]
3234
        tty_style_menu(config, banner).cmdloop()
3235
 
3236
if __name__ == '__main__':
3237
    try:
3238
        runopts = "bB:cD:d:h:i:I:lo:P:qR:SstVvWx"
3239
        (options,arguments) = getopt.getopt(sys.argv[1:], runopts, "help")
3240
        if os.environ.has_key("CML2OPTIONS"):
3241
            (envopts, envargs) = getopt.getopt(
3242
                os.environ["CML2OPTIONS"].split(),
3243
                runopts)
3244
            options = envopts + options
3245
    except:
3246
        print lang["BADOPTION"]
3247
        print lang["CLIHELP"]
3248
        sys.exit(1)
3249
 
3250
    for (switch, val) in options:
3251
        if switch == "-b":
3252
            force_batch = 1
3253
        if switch == "-V":
3254
            print "cmlconfigure", cml.version
3255
            raise SystemExit
3256
        elif switch == '-P':
3257
            proflog = val
3258
        elif switch == '-x':
3259
            force_x = 1
3260
        elif switch == '-q':
3261
            force_q = 1
3262
        elif switch == '-t':
3263
            force_tty = 1
3264
        elif switch == '-c':
3265
            force_curses = 1
3266
        elif switch == '-s':
3267
            force_debugger = force_tty = 1
3268
        elif switch == '--help':
3269
            sys.stdout.write(lang["CLIHELP"])
3270
            raise SystemExit
3271
 
3272
    # Probe the environment to see if we can use X for the front end.
3273
    if not force_tty and not force_debugger and not force_curses and not force_x and not force_q and not force_batch:
3274
        force_x = force_q = is_under_X()
3275
 
3276
    # Do we see X capability?
3277
    if force_x or force_q:
3278
        try:
3279
            from Tkinter import *
3280
            from Dialog import *
3281
        except:
3282
            print lang["NOTKINTER"]
3283
            if not force_batch:
3284
                time.sleep(5)
3285
            force_curses = 1
3286
            force_x = force_q = 0
3287
 
3288
    # Probe the environment to see if we can come up in ncurses mode
3289
    if not force_tty and not force_x and not force_q:
3290
        if not os.environ.has_key('TERM'):
3291
            print lang["TERMNOTSET"]
3292
            force_tty = 1
3293
        else:
3294
            import traceback
3295
            try:
3296
                import curses, curses.textpad, curses.wrapper
3297
                force_curses = 1
3298
            except:
3299
                ImportError
3300
                print lang["NOCURSES"]
3301
                force_tty = 1
3302
 
3303
    if force_tty or force_debugger:
3304
        # It's been reported that this import fails under some 1.5.2s.
3305
        # No disaster; it's just a convenience to have command history
3306
        # in the line-oriented mode.
3307
        try:
3308
            import readline
3309
        except:
3310
            pass
3311
 
3312
    try:
3313
        if proflog:
3314
            import profile, pstats
3315
            profile.run("main(options, arguments)", proflog)
3316
        else:
3317
            main(options, arguments)
3318
    except KeyboardInterrupt:
3319
        #if configuration.commits > 0:
3320
        #    print lang["NOTSAVED"]
3321
        print lang["ABORTED"]
3322
        raise SystemExit, 2
3323
    except "UNSATISFIABLE":
3324
        #configuration.save("post.mortem")
3325
        print lang["POSTMORTEM"]
3326
        raise SystemExit, 3
3327
 
3328
# That's all, folks!

powered by: WebSVN 2.1.0

© copyright 1999-2024 OpenCores.org, equivalent to Oliscience, all rights reserved. OpenCores®, registered trademark.