diff options
author | Guido van Rossum <guido@python.org> | 2003-04-29 10:23:27 (GMT) |
---|---|---|
committer | Guido van Rossum <guido@python.org> | 2003-04-29 10:23:27 (GMT) |
commit | 57cd21fde285d25021ee978cd09ed58159166bf8 (patch) | |
tree | 57936b5a729c1219ae080d518832b9e17aa219ec /Tools/idle | |
parent | 19691360c7a8c203d1a94f6074036e810a6f0527 (diff) | |
download | cpython-57cd21fde285d25021ee978cd09ed58159166bf8.zip cpython-57cd21fde285d25021ee978cd09ed58159166bf8.tar.gz cpython-57cd21fde285d25021ee978cd09ed58159166bf8.tar.bz2 |
Checking in IDLEFORK exactly as it appears in the idlefork CVS.
On a branch, for now.
Diffstat (limited to 'Tools/idle')
40 files changed, 4632 insertions, 1865 deletions
diff --git a/Tools/idle/AutoIndent.py b/Tools/idle/AutoIndent.py deleted file mode 100644 index 7bc195b..0000000 --- a/Tools/idle/AutoIndent.py +++ /dev/null @@ -1,551 +0,0 @@ -#from Tkinter import TclError -#import tkMessageBox -#import tkSimpleDialog - -###$ event <<newline-and-indent>> -###$ win <Key-Return> -###$ win <KP_Enter> -###$ unix <Key-Return> -###$ unix <KP_Enter> - -###$ event <<indent-region>> -###$ win <Control-bracketright> -###$ unix <Alt-bracketright> -###$ unix <Control-bracketright> - -###$ event <<dedent-region>> -###$ win <Control-bracketleft> -###$ unix <Alt-bracketleft> -###$ unix <Control-bracketleft> - -###$ event <<comment-region>> -###$ win <Alt-Key-3> -###$ unix <Alt-Key-3> - -###$ event <<uncomment-region>> -###$ win <Alt-Key-4> -###$ unix <Alt-Key-4> - -###$ event <<tabify-region>> -###$ win <Alt-Key-5> -###$ unix <Alt-Key-5> - -###$ event <<untabify-region>> -###$ win <Alt-Key-6> -###$ unix <Alt-Key-6> - -import PyParse - -class AutoIndent: - - menudefs = [ - ('edit', [ - None, - ('_Indent region', '<<indent-region>>'), - ('_Dedent region', '<<dedent-region>>'), - ('Comment _out region', '<<comment-region>>'), - ('U_ncomment region', '<<uncomment-region>>'), - ('Tabify region', '<<tabify-region>>'), - ('Untabify region', '<<untabify-region>>'), - ('Toggle tabs', '<<toggle-tabs>>'), - ('New indent width', '<<change-indentwidth>>'), - ]), - ] - - keydefs = { - '<<smart-backspace>>': ['<Key-BackSpace>'], - '<<newline-and-indent>>': ['<Key-Return>', '<KP_Enter>'], - '<<smart-indent>>': ['<Key-Tab>'] - } - - windows_keydefs = { - '<<indent-region>>': ['<Control-bracketright>'], - '<<dedent-region>>': ['<Control-bracketleft>'], - '<<comment-region>>': ['<Alt-Key-3>'], - '<<uncomment-region>>': ['<Alt-Key-4>'], - '<<tabify-region>>': ['<Alt-Key-5>'], - '<<untabify-region>>': ['<Alt-Key-6>'], - '<<toggle-tabs>>': ['<Alt-Key-t>'], - '<<change-indentwidth>>': ['<Alt-Key-u>'], - } - - unix_keydefs = { - '<<indent-region>>': ['<Alt-bracketright>', - '<Meta-bracketright>', - '<Control-bracketright>'], - '<<dedent-region>>': ['<Alt-bracketleft>', - '<Meta-bracketleft>', - '<Control-bracketleft>'], - '<<comment-region>>': ['<Alt-Key-3>', '<Meta-Key-3>'], - '<<uncomment-region>>': ['<Alt-Key-4>', '<Meta-Key-4>'], - '<<tabify-region>>': ['<Alt-Key-5>', '<Meta-Key-5>'], - '<<untabify-region>>': ['<Alt-Key-6>', '<Meta-Key-6>'], - '<<toggle-tabs>>': ['<Alt-Key-t>'], - '<<change-indentwidth>>': ['<Alt-Key-u>'], - } - - # usetabs true -> literal tab characters are used by indent and - # dedent cmds, possibly mixed with spaces if - # indentwidth is not a multiple of tabwidth - # false -> tab characters are converted to spaces by indent - # and dedent cmds, and ditto TAB keystrokes - # indentwidth is the number of characters per logical indent level. - # tabwidth is the display width of a literal tab character. - # CAUTION: telling Tk to use anything other than its default - # tab setting causes it to use an entirely different tabbing algorithm, - # treating tab stops as fixed distances from the left margin. - # Nobody expects this, so for now tabwidth should never be changed. - usetabs = 1 - indentwidth = 4 - tabwidth = 8 # for IDLE use, must remain 8 until Tk is fixed - - # If context_use_ps1 is true, parsing searches back for a ps1 line; - # else searches for a popular (if, def, ...) Python stmt. - context_use_ps1 = 0 - - # When searching backwards for a reliable place to begin parsing, - # first start num_context_lines[0] lines back, then - # num_context_lines[1] lines back if that didn't work, and so on. - # The last value should be huge (larger than the # of lines in a - # conceivable file). - # Making the initial values larger slows things down more often. - num_context_lines = 50, 500, 5000000 - - def __init__(self, editwin): - self.editwin = editwin - self.text = editwin.text - - def config(self, **options): - for key, value in options.items(): - if key == 'usetabs': - self.usetabs = value - elif key == 'indentwidth': - self.indentwidth = value - elif key == 'tabwidth': - self.tabwidth = value - elif key == 'context_use_ps1': - self.context_use_ps1 = value - else: - raise KeyError, "bad option name: %s" % `key` - - # If ispythonsource and guess are true, guess a good value for - # indentwidth based on file content (if possible), and if - # indentwidth != tabwidth set usetabs false. - # In any case, adjust the Text widget's view of what a tab - # character means. - - def set_indentation_params(self, ispythonsource, guess=1): - if guess and ispythonsource: - i = self.guess_indent() - if 2 <= i <= 8: - self.indentwidth = i - if self.indentwidth != self.tabwidth: - self.usetabs = 0 - - self.editwin.set_tabwidth(self.tabwidth) - - def smart_backspace_event(self, event): - text = self.text - first, last = self.editwin.get_selection_indices() - if first and last: - text.delete(first, last) - text.mark_set("insert", first) - return "break" - # Delete whitespace left, until hitting a real char or closest - # preceding virtual tab stop. - chars = text.get("insert linestart", "insert") - if chars == '': - if text.compare("insert", ">", "1.0"): - # easy: delete preceding newline - text.delete("insert-1c") - else: - text.bell() # at start of buffer - return "break" - if chars[-1] not in " \t": - # easy: delete preceding real char - text.delete("insert-1c") - return "break" - # Ick. It may require *inserting* spaces if we back up over a - # tab character! This is written to be clear, not fast. - tabwidth = self.tabwidth - have = len(chars.expandtabs(tabwidth)) - assert have > 0 - want = ((have - 1) // self.indentwidth) * self.indentwidth - ncharsdeleted = 0 - while 1: - chars = chars[:-1] - ncharsdeleted = ncharsdeleted + 1 - have = len(chars.expandtabs(tabwidth)) - if have <= want or chars[-1] not in " \t": - break - text.undo_block_start() - text.delete("insert-%dc" % ncharsdeleted, "insert") - if have < want: - text.insert("insert", ' ' * (want - have)) - text.undo_block_stop() - return "break" - - def smart_indent_event(self, event): - # if intraline selection: - # delete it - # elif multiline selection: - # do indent-region & return - # indent one level - text = self.text - first, last = self.editwin.get_selection_indices() - text.undo_block_start() - try: - if first and last: - if index2line(first) != index2line(last): - return self.indent_region_event(event) - text.delete(first, last) - text.mark_set("insert", first) - prefix = text.get("insert linestart", "insert") - raw, effective = classifyws(prefix, self.tabwidth) - if raw == len(prefix): - # only whitespace to the left - self.reindent_to(effective + self.indentwidth) - else: - if self.usetabs: - pad = '\t' - else: - effective = len(prefix.expandtabs(self.tabwidth)) - n = self.indentwidth - pad = ' ' * (n - effective % n) - text.insert("insert", pad) - text.see("insert") - return "break" - finally: - text.undo_block_stop() - - def newline_and_indent_event(self, event): - text = self.text - first, last = self.editwin.get_selection_indices() - text.undo_block_start() - try: - if first and last: - text.delete(first, last) - text.mark_set("insert", first) - line = text.get("insert linestart", "insert") - i, n = 0, len(line) - while i < n and line[i] in " \t": - i = i+1 - if i == n: - # the cursor is in or at leading indentation; just inject - # an empty line at the start - text.insert("insert linestart", '\n') - return "break" - indent = line[:i] - # strip whitespace before insert point - i = 0 - while line and line[-1] in " \t": - line = line[:-1] - i = i+1 - if i: - text.delete("insert - %d chars" % i, "insert") - # strip whitespace after insert point - while text.get("insert") in " \t": - text.delete("insert") - # start new line - text.insert("insert", '\n') - - # adjust indentation for continuations and block - # open/close first need to find the last stmt - lno = index2line(text.index('insert')) - y = PyParse.Parser(self.indentwidth, self.tabwidth) - for context in self.num_context_lines: - startat = max(lno - context, 1) - startatindex = `startat` + ".0" - rawtext = text.get(startatindex, "insert") - y.set_str(rawtext) - bod = y.find_good_parse_start( - self.context_use_ps1, - self._build_char_in_string_func(startatindex)) - if bod is not None or startat == 1: - break - y.set_lo(bod or 0) - c = y.get_continuation_type() - if c != PyParse.C_NONE: - # The current stmt hasn't ended yet. - if c == PyParse.C_STRING: - # inside a string; just mimic the current indent - text.insert("insert", indent) - elif c == PyParse.C_BRACKET: - # line up with the first (if any) element of the - # last open bracket structure; else indent one - # level beyond the indent of the line with the - # last open bracket - self.reindent_to(y.compute_bracket_indent()) - elif c == PyParse.C_BACKSLASH: - # if more than one line in this stmt already, just - # mimic the current indent; else if initial line - # has a start on an assignment stmt, indent to - # beyond leftmost =; else to beyond first chunk of - # non-whitespace on initial line - if y.get_num_lines_in_stmt() > 1: - text.insert("insert", indent) - else: - self.reindent_to(y.compute_backslash_indent()) - else: - assert 0, "bogus continuation type " + `c` - return "break" - - # This line starts a brand new stmt; indent relative to - # indentation of initial line of closest preceding - # interesting stmt. - indent = y.get_base_indent_string() - text.insert("insert", indent) - if y.is_block_opener(): - self.smart_indent_event(event) - elif indent and y.is_block_closer(): - self.smart_backspace_event(event) - return "break" - finally: - text.see("insert") - text.undo_block_stop() - - auto_indent = newline_and_indent_event - - # Our editwin provides a is_char_in_string function that works - # with a Tk text index, but PyParse only knows about offsets into - # a string. This builds a function for PyParse that accepts an - # offset. - - def _build_char_in_string_func(self, startindex): - def inner(offset, _startindex=startindex, - _icis=self.editwin.is_char_in_string): - return _icis(_startindex + "+%dc" % offset) - return inner - - def indent_region_event(self, event): - head, tail, chars, lines = self.get_region() - for pos in range(len(lines)): - line = lines[pos] - if line: - raw, effective = classifyws(line, self.tabwidth) - effective = effective + self.indentwidth - lines[pos] = self._make_blanks(effective) + line[raw:] - self.set_region(head, tail, chars, lines) - return "break" - - def dedent_region_event(self, event): - head, tail, chars, lines = self.get_region() - for pos in range(len(lines)): - line = lines[pos] - if line: - raw, effective = classifyws(line, self.tabwidth) - effective = max(effective - self.indentwidth, 0) - lines[pos] = self._make_blanks(effective) + line[raw:] - self.set_region(head, tail, chars, lines) - return "break" - - def comment_region_event(self, event): - head, tail, chars, lines = self.get_region() - for pos in range(len(lines) - 1): - line = lines[pos] - lines[pos] = '##' + line - self.set_region(head, tail, chars, lines) - - def uncomment_region_event(self, event): - head, tail, chars, lines = self.get_region() - for pos in range(len(lines)): - line = lines[pos] - if not line: - continue - if line[:2] == '##': - line = line[2:] - elif line[:1] == '#': - line = line[1:] - lines[pos] = line - self.set_region(head, tail, chars, lines) - - def tabify_region_event(self, event): - head, tail, chars, lines = self.get_region() - tabwidth = self._asktabwidth() - for pos in range(len(lines)): - line = lines[pos] - if line: - raw, effective = classifyws(line, tabwidth) - ntabs, nspaces = divmod(effective, tabwidth) - lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:] - self.set_region(head, tail, chars, lines) - - def untabify_region_event(self, event): - head, tail, chars, lines = self.get_region() - tabwidth = self._asktabwidth() - for pos in range(len(lines)): - lines[pos] = lines[pos].expandtabs(tabwidth) - self.set_region(head, tail, chars, lines) - - def toggle_tabs_event(self, event): - if self.editwin.askyesno( - "Toggle tabs", - "Turn tabs " + ("on", "off")[self.usetabs] + "?", - parent=self.text): - self.usetabs = not self.usetabs - return "break" - - # XXX this isn't bound to anything -- see class tabwidth comments - def change_tabwidth_event(self, event): - new = self._asktabwidth() - if new != self.tabwidth: - self.tabwidth = new - self.set_indentation_params(0, guess=0) - return "break" - - def change_indentwidth_event(self, event): - new = self.editwin.askinteger( - "Indent width", - "New indent width (1-16)", - parent=self.text, - initialvalue=self.indentwidth, - minvalue=1, - maxvalue=16) - if new and new != self.indentwidth: - self.indentwidth = new - return "break" - - def get_region(self): - text = self.text - first, last = self.editwin.get_selection_indices() - if first and last: - head = text.index(first + " linestart") - tail = text.index(last + "-1c lineend +1c") - else: - head = text.index("insert linestart") - tail = text.index("insert lineend +1c") - chars = text.get(head, tail) - lines = chars.split("\n") - return head, tail, chars, lines - - def set_region(self, head, tail, chars, lines): - text = self.text - newchars = "\n".join(lines) - if newchars == chars: - text.bell() - return - text.tag_remove("sel", "1.0", "end") - text.mark_set("insert", head) - text.undo_block_start() - text.delete(head, tail) - text.insert(head, newchars) - text.undo_block_stop() - text.tag_add("sel", head, "insert") - - # Make string that displays as n leading blanks. - - def _make_blanks(self, n): - if self.usetabs: - ntabs, nspaces = divmod(n, self.tabwidth) - return '\t' * ntabs + ' ' * nspaces - else: - return ' ' * n - - # Delete from beginning of line to insert point, then reinsert - # column logical (meaning use tabs if appropriate) spaces. - - def reindent_to(self, column): - text = self.text - text.undo_block_start() - if text.compare("insert linestart", "!=", "insert"): - text.delete("insert linestart", "insert") - if column: - text.insert("insert", self._make_blanks(column)) - text.undo_block_stop() - - def _asktabwidth(self): - return self.editwin.askinteger( - "Tab width", - "Spaces per tab?", - parent=self.text, - initialvalue=self.tabwidth, - minvalue=1, - maxvalue=16) or self.tabwidth - - # Guess indentwidth from text content. - # Return guessed indentwidth. This should not be believed unless - # it's in a reasonable range (e.g., it will be 0 if no indented - # blocks are found). - - def guess_indent(self): - opener, indented = IndentSearcher(self.text, self.tabwidth).run() - if opener and indented: - raw, indentsmall = classifyws(opener, self.tabwidth) - raw, indentlarge = classifyws(indented, self.tabwidth) - else: - indentsmall = indentlarge = 0 - return indentlarge - indentsmall - -# "line.col" -> line, as an int -def index2line(index): - return int(float(index)) - -# Look at the leading whitespace in s. -# Return pair (# of leading ws characters, -# effective # of leading blanks after expanding -# tabs to width tabwidth) - -def classifyws(s, tabwidth): - raw = effective = 0 - for ch in s: - if ch == ' ': - raw = raw + 1 - effective = effective + 1 - elif ch == '\t': - raw = raw + 1 - effective = (effective // tabwidth + 1) * tabwidth - else: - break - return raw, effective - -import tokenize -_tokenize = tokenize -del tokenize - -class IndentSearcher: - - # .run() chews over the Text widget, looking for a block opener - # and the stmt following it. Returns a pair, - # (line containing block opener, line containing stmt) - # Either or both may be None. - - def __init__(self, text, tabwidth): - self.text = text - self.tabwidth = tabwidth - self.i = self.finished = 0 - self.blkopenline = self.indentedline = None - - def readline(self): - if self.finished: - return "" - i = self.i = self.i + 1 - mark = `i` + ".0" - if self.text.compare(mark, ">=", "end"): - return "" - return self.text.get(mark, mark + " lineend+1c") - - def tokeneater(self, type, token, start, end, line, - INDENT=_tokenize.INDENT, - NAME=_tokenize.NAME, - OPENERS=('class', 'def', 'for', 'if', 'try', 'while')): - if self.finished: - pass - elif type == NAME and token in OPENERS: - self.blkopenline = line - elif type == INDENT and self.blkopenline: - self.indentedline = line - self.finished = 1 - - def run(self): - save_tabsize = _tokenize.tabsize - _tokenize.tabsize = self.tabwidth - try: - try: - _tokenize.tokenize(self.readline, self.tokeneater) - except _tokenize.TokenError: - # since we cut off the tokenizer early, we can trigger - # spurious errors - pass - finally: - _tokenize.tabsize = save_tabsize - return self.blkopenline, self.indentedline diff --git a/Tools/idle/CREDITS.txt b/Tools/idle/CREDITS.txt new file mode 100644 index 0000000..fd7af95 --- /dev/null +++ b/Tools/idle/CREDITS.txt @@ -0,0 +1,36 @@ +IDLEfork Credits +================== + +Guido van Rossum, as well as being the creator of the Python language, is +the original creator of IDLE. He also developed the RPC code and Remote +Debugger extension used in IDLEfork. + +The IDLEfork project was initiated and brought up to version 0.7.1 primarily +by David Scherer, with help from Peter Schneider-Kamp and Nicholas Riley. +Bruce Sherwood has contributed considerable time testing and suggesting +improvements. + +Besides Guido, the main developers who have been active on IDLEfork version +0.8.1 and later are Stephen M. Gava, who implemented the Configuration GUI, the +new configuration system, and the new About menu, and Kurt B. Kaiser, who +completed the integration of the RPC and remote debugger, and made a number of +usability enhancements. + +Other contributors include Raymond Hettinger, Tony Lownds (Mac integration), +Neal Norwitz (code check and clean-up), and Chui Tey (RPC integration, debugger +integration and persistent breakpoints). + +Hernan Foffani, Christos Georgiou, Jason Orendorff, Josh Robb, and Bruce +Sherwood have submitted useful patches. Thanks, guys! + +There are others who should be included here, especially those who contributed +to IDLE versions prior to 0.8, principally Mark Hammond, Jeremy Hylton, +Tim Peters, and Moshe Zadka. For additional details refer to NEWS.txt and +Changelog. + +Please contact the IDLEfork maintainer to have yourself included here if you +are one of those we missed! + +Contact details at http://idlefork.sourceforge.net + + diff --git a/Tools/idle/FrameViewer.py b/Tools/idle/FrameViewer.py deleted file mode 100644 index 2ce0935..0000000 --- a/Tools/idle/FrameViewer.py +++ /dev/null @@ -1,38 +0,0 @@ -from repr import Repr -from Tkinter import * - -class FrameViewer: - - def __init__(self, root, frame): - self.root = root - self.frame = frame - self.top = Toplevel(self.root) - self.repr = Repr() - self.repr.maxstring = 60 - self.load_variables() - - def load_variables(self): - row = 0 - if self.frame.f_locals is not self.frame.f_globals: - l = Label(self.top, text="Local Variables", - borderwidth=2, relief="raised") - l.grid(row=row, column=0, columnspan=2, sticky="ew") - row = self.load_names(self.frame.f_locals, row+1) - l = Label(self.top, text="Global Variables", - borderwidth=2, relief="raised") - l.grid(row=row, column=0, columnspan=2, sticky="ew") - row = self.load_names(self.frame.f_globals, row+1) - - def load_names(self, dict, row): - names = dict.keys() - names.sort() - for name in names: - value = dict[name] - svalue = self.repr.repr(value) - l = Label(self.top, text=name) - l.grid(row=row, column=0, sticky="w") - l = Entry(self.top, width=60, borderwidth=0) - l.insert(0, svalue) - l.grid(row=row, column=1, sticky="w") - row = row+1 - return row diff --git a/Tools/idle/HISTORY.txt b/Tools/idle/HISTORY.txt new file mode 100644 index 0000000..9312f32 --- /dev/null +++ b/Tools/idle/HISTORY.txt @@ -0,0 +1,180 @@ +IDLE History +============ + +This file contains the release messages for previous IDLE releases. +As you read on you go back to the dark ages of IDLE's history. + + +IDLE 0.5 - February 2000 - Release Notes +---------------------------------------- + +This is an early release of IDLE, my own attempt at a Tkinter-based +IDE for Python. + +(For a more detailed change log, see the file ChangeLog.) + +FEATURES + +IDLE has the following features: + +- coded in 100% pure Python, using the Tkinter GUI toolkit (i.e. Tcl/Tk) + +- cross-platform: works on Windows and Unix (on the Mac, there are +currently problems with Tcl/Tk) + +- multi-window text editor with multiple undo, Python colorizing +and many other features, e.g. smart indent and call tips + +- Python shell window (a.k.a. interactive interpreter) + +- debugger (not complete, but you can set breakpoints, view and step) + +USAGE + +The main program is in the file "idle.py"; on Unix, you should be able +to run it by typing "./idle.py" to your shell. On Windows, you can +run it by double-clicking it; you can use idle.pyw to avoid popping up +a DOS console. If you want to pass command line arguments on Windows, +use the batch file idle.bat. + +Command line arguments: files passed on the command line are executed, +not opened for editing, unless you give the -e command line option. +Try "./idle.py -h" to see other command line options. + +IDLE requires Python 1.5.2, so it is currently only usable with a +Python 1.5.2 distribution. (An older version of IDLE is distributed +with Python 1.5.2; you can drop this version on top of it.) + +COPYRIGHT + +IDLE is covered by the standard Python copyright notice +(http://www.python.org/doc/Copyright.html). + + +New in IDLE 0.5 (2/15/2000) +--------------------------- + +Tons of stuff, much of it contributed by Tim Peters and Mark Hammond: + +- Status bar, displaying current line/column (Moshe Zadka). + +- Better stack viewer, using tree widget. (XXX Only used by Stack +Viewer menu, not by the debugger.) + +- Format paragraph now recognizes Python block comments and reformats +them correctly (MH) + +- New version of pyclbr.py parses top-level functions and understands +much more of Python's syntax; this is reflected in the class and path +browsers (TP) + +- Much better auto-indent; knows how to indent the insides of +multi-line statements (TP) + +- Call tip window pops up when you type the name of a known function +followed by an open parenthesis. Hit ESC or click elsewhere in the +window to close the tip window (MH) + +- Comment out region now inserts ## to make it stand out more (TP) + +- New path and class browsers based on a tree widget that looks +familiar to Windows users + +- Reworked script running commands to be more intuitive: I/O now +always goes to the *Python Shell* window, and raw_input() works +correctly. You use F5 to import/reload a module: this adds the module +name to the __main__ namespace. You use Control-F5 to run a script: +this runs the script *in* the __main__ namespace. The latter also +sets sys.argv[] to the script name + + +New in IDLE 0.4 (4/7/99) +------------------------ + +Most important change: a new menu entry "File -> Path browser", shows +a 4-column hierarchical browser which lets you browse sys.path, +directories, modules, and classes. Yes, it's a superset of the Class +browser menu entry. There's also a new internal module, +MultiScrolledLists.py, which provides the framework for this dialog. + + +New in IDLE 0.3 (2/17/99) +------------------------- + +Most important changes: + +- Enabled support for running a module, with or without the debugger. +Output goes to a new window. Pressing F5 in a module is effectively a +reload of that module; Control-F5 loads it under the debugger. + +- Re-enable tearing off the Windows menu, and make a torn-off Windows +menu update itself whenever a window is opened or closed. + +- Menu items can now be have a checkbox (when the menu label starts +with "!"); use this for the Debugger and "Auto-open stack viewer" +(was: JIT stack viewer) menu items. + +- Added a Quit button to the Debugger API. + +- The current directory is explicitly inserted into sys.path. + +- Fix the debugger (when using Python 1.5.2b2) to use canonical +filenames for breakpoints, so these actually work. (There's still a +lot of work to be done to the management of breakpoints in the +debugger though.) + +- Closing a window that is still colorizing now actually works. + +- Allow dragging of the separator between the two list boxes in the +class browser. + +- Bind ESC to "close window" of the debugger, stack viewer and class +browser. It removes the selection highlighting in regular text +windows. (These are standard Windows conventions.) + + +New in IDLE 0.2 (1/8/99) +------------------------ + +Lots of changes; here are the highlights: + +General: + +- You can now write and configure your own IDLE extension modules; see +extend.txt. + + +File menu: + +The command to open the Python shell window is now in the File menu. + + +Edit menu: + +New Find dialog with more options; replace dialog; find in files dialog. + +Commands to tabify or untabify a region. + +Command to format a paragraph. + + +Debug menu: + +JIT (Just-In-Time) stack viewer toggle -- if set, the stack viewer +automaticall pops up when you get a traceback. + +Windows menu: + +Zoom height -- make the window full height. + + +Help menu: + +The help text now show up in a regular window so you can search and +even edit it if you like. + + + +IDLE 0.1 was distributed with the Python 1.5.2b1 release on 12/22/98. + +====================================================================== diff --git a/Tools/idle/INSTALL.txt b/Tools/idle/INSTALL.txt new file mode 100644 index 0000000..ed7ac69 --- /dev/null +++ b/Tools/idle/INSTALL.txt @@ -0,0 +1,51 @@ +IDLEfork Installation Notes +=========================== + +IDLEfork requires Python Version 2.2 or later. + +There are several distribution files (where xx is the subversion): + +IDLEfork-0.9xx.win32.exe + This is a Windows installer which will install IDLEfork in + ..../site-packages/idleforklib/ and place the idefork startup script + at ..../scripts/idlefork. Rename this to idlefork.pyw and + point your launcher icons at it. Installation is as idlefork + to avoid conflict with the original Python IDLE. + +IDLEfork-0.9xx-1.noarch.rpm + This is an rpm which is designed to install as idleforklib in an + existing /usr/lib/python2.2 tree. It installs as idlefork to avoid + conflict with Python IDLE. + + Python rpms are available at http://www.python.org/2.2.2/rpms.html and + http://www.python.org/2.2.1/rpms.html. + +IDLEfork-0.9xx.tar.gz + This is a distutils sdist (source) tarfile which can be used to make + installations on platforms not supported by the above files. + ** It remains configured to install as idlelib, not idleforklib. ** + + Unpack in ..../Tools/, cd to the IDLEfork directory created, and + "python setup.py install" to install in ....site-packages/idlelib. + This will overwrite the Python IDLE installation. + + If you don't want to overwrite Python IDLE, it is also possible to + simply call "python idle.py" to run from the IDLEfork source directory + without making an installation. In this case, IDLE will not be on + your PATH unless you are in the source directory. Also, it is then + advisable to remove any Python IDLE installation by removing + ..../site-packages/idlelib so the two identically named packages don't + conflict. + + On Redhat Linux systems prior to 8.0, /usr/bin/python may be pointing + at python1.5. If so, change the first line in the /usr/bin/idle + script to read: + !# /usr/bin/python2.2 + +See README.txt for more details on this version of IDLEfork. + + + + + + diff --git a/Tools/idle/IdleConf.py b/Tools/idle/IdleConf.py deleted file mode 100644 index 8eaa8e0..0000000 --- a/Tools/idle/IdleConf.py +++ /dev/null @@ -1,113 +0,0 @@ -"""Provides access to configuration information""" - -import os -import sys -from ConfigParser import ConfigParser, NoOptionError, NoSectionError - -class IdleConfParser(ConfigParser): - - # these conf sections do not define extensions! - builtin_sections = {} - for section in ('EditorWindow', 'Colors'): - builtin_sections[section] = section - - def getcolor(self, sec, name): - """Return a dictionary with foreground and background colors - - The return value is appropriate for passing to Tkinter in, e.g., - a tag_config call. - """ - fore = self.getdef(sec, name + "-foreground") - back = self.getdef(sec, name + "-background") - return {"foreground": fore, - "background": back} - - def getdef(self, sec, options, raw=0, vars=None, default=None): - """Get an option value for given section or return default""" - try: - return self.get(sec, options, raw, vars) - except (NoSectionError, NoOptionError): - return default - - def getsection(self, section): - """Return a SectionConfigParser object""" - return SectionConfigParser(section, self) - - def getextensions(self): - exts = [] - for sec in self.sections(): - if self.builtin_sections.has_key(sec): - continue - # enable is a bool, but it may not be defined - if self.getdef(sec, 'enable') != '0': - exts.append(sec) - return exts - - def reload(self): - global idleconf - idleconf = IdleConfParser() - load(_dir) # _dir is a global holding the last directory loaded - -class SectionConfigParser: - """A ConfigParser object specialized for one section - - This class has all the get methods that a regular ConfigParser does, - but without requiring a section argument. - """ - def __init__(self, section, config): - self.section = section - self.config = config - - def options(self): - return self.config.options(self.section) - - def get(self, options, raw=0, vars=None): - return self.config.get(self.section, options, raw, vars) - - def getdef(self, options, raw=0, vars=None, default=None): - return self.config.getdef(self.section, options, raw, vars, default) - - def getint(self, option): - return self.config.getint(self.section, option) - - def getfloat(self, option): - return self.config.getint(self.section, option) - - def getboolean(self, option): - return self.config.getint(self.section, option) - - def getcolor(self, option): - return self.config.getcolor(self.section, option) - -def load(dir): - """Load IDLE configuration files based on IDLE install in dir - - Attempts to load two config files: - dir/config.txt - dir/config-[win/mac/unix].txt - dir/config-%(sys.platform)s.txt - ~/.idle - """ - global _dir - _dir = dir - - if sys.platform[:3] == 'win': - genplatfile = os.path.join(dir, "config-win.txt") - # XXX don't know what the platform string is on a Mac - elif sys.platform[:3] == 'mac': - genplatfile = os.path.join(dir, "config-mac.txt") - else: - genplatfile = os.path.join(dir, "config-unix.txt") - - platfile = os.path.join(dir, "config-%s.txt" % sys.platform) - - try: - homedir = os.environ['HOME'] - except KeyError: - homedir = os.getcwd() - - idleconf.read((os.path.join(dir, "config.txt"), genplatfile, platfile, - os.path.join(homedir, ".idle"))) - -idleconf = IdleConfParser() -load(os.path.dirname(__file__)) diff --git a/Tools/idle/LICENSE.txt b/Tools/idle/LICENSE.txt new file mode 100644 index 0000000..f7a8395 --- /dev/null +++ b/Tools/idle/LICENSE.txt @@ -0,0 +1,50 @@ +To apply this license to IDLE or IDLEfork, read 'IDLE' or 'IDLEfork' +for every occurence of 'Python 2.1.1' in the text below. + +PSF LICENSE AGREEMENT +--------------------- + +1. This LICENSE AGREEMENT is between the Python Software Foundation +("PSF"), and the Individual or Organization ("Licensee") accessing and +otherwise using Python 2.1.1 software in source or binary form and its +associated documentation. + +2. Subject to the terms and conditions of this License Agreement, PSF +hereby grants Licensee a nonexclusive, royalty-free, world-wide +license to reproduce, analyze, test, perform and/or display publicly, +prepare derivative works, distribute, and otherwise use Python 2.1.1 +alone or in any derivative version, provided, however, that PSF's +License Agreement and PSF's notice of copyright, i.e., "Copyright (c) +2001 Python Software Foundation; All Rights Reserved" are retained in +Python 2.1.1 alone or in any derivative version prepared by Licensee. + +3. In the event Licensee prepares a derivative work that is based on +or incorporates Python 2.1.1 or any part thereof, and wants to make +the derivative work available to others as provided herein, then +Licensee hereby agrees to include in any such work a brief summary of +the changes made to Python 2.1.1. + +4. PSF is making Python 2.1.1 available to Licensee on an "AS IS" +basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 2.1.1 WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON +2.1.1 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS +A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 2.1.1, +OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +6. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +7. Nothing in this License Agreement shall be deemed to create any +relationship of agency, partnership, or joint venture between PSF and +Licensee. This License Agreement does not grant permission to use PSF +trademarks or trade name in a trademark sense to endorse or promote +products or services of Licensee, or any third party. + +8. By copying, installing or otherwise using Python 2.1.1, Licensee +agrees to be bound by the terms and conditions of this License +Agreement. diff --git a/Tools/idle/Makefile b/Tools/idle/Makefile new file mode 100644 index 0000000..f118ebf --- /dev/null +++ b/Tools/idle/Makefile @@ -0,0 +1,8 @@ +# Makefile to build the interrupt module, which is a C extension + +PYTHON=python2.3 + +all: interrupt.so + +interrupt.so: interruptmodule.c + $(PYTHON) setup.py build_ext -i diff --git a/Tools/idle/MultiScrolledLists.py b/Tools/idle/MultiScrolledLists.py deleted file mode 100644 index 6398b86..0000000 --- a/Tools/idle/MultiScrolledLists.py +++ /dev/null @@ -1,137 +0,0 @@ -# One or more ScrolledLists with HSeparators between them. -# There is a hierarchical relationship between them: -# the right list displays the substructure of the selected item -# in the left list. - -from Tkinter import * -from WindowList import ListedToplevel -from Separator import HSeparator -from ScrolledList import ScrolledList - -class MultiScrolledLists: - - def __init__(self, root, nlists=2): - assert nlists >= 1 - self.root = root - self.nlists = nlists - self.path = [] - # create top - self.top = top = ListedToplevel(root) - top.protocol("WM_DELETE_WINDOW", self.close) - top.bind("<Escape>", self.close) - self.settitle() - # create frames and separators in between - self.frames = [] - self.separators = [] - last = top - for i in range(nlists-1): - sepa = HSeparator(last) - self.separators.append(sepa) - frame, last = sepa.parts() - self.frames.append(frame) - self.frames.append(last) - # create labels and lists - self.labels = [] - self.lists = [] - for i in range(nlists): - frame = self.frames[i] - label = Label(frame, text=self.subtitle(i), - relief="groove", borderwidth=2) - label.pack(fill="x") - self.labels.append(label) - list = ScrolledList(frame, width=self.width(i), - height=self.height(i)) - self.lists.append(list) - list.on_select = \ - lambda index, i=i, self=self: self.on_select(index, i) - list.on_double = \ - lambda index, i=i, self=self: self.on_double(index, i) - # fill leftmost list (rest get filled on demand) - self.fill(0) - # XXX one after_idle isn't enough; two are... - top.after_idle(self.call_pack_propagate_1) - - def call_pack_propagate_1(self): - self.top.after_idle(self.call_pack_propagate) - - def call_pack_propagate(self): - for frame in self.frames: - frame.pack_propagate(0) - - def close(self, event=None): - self.top.destroy() - - def settitle(self): - short = self.shorttitle() - long = self.longtitle() - if short and long: - title = short + " - " + long - elif short: - title = short - elif long: - title = long - else: - title = "Untitled" - icon = short or long or title - self.top.wm_title(title) - self.top.wm_iconname(icon) - - def longtitle(self): - # override this - return "Multi Scrolled Lists" - - def shorttitle(self): - # override this - return None - - def width(self, i): - # override this - return 20 - - def height(self, i): - # override this - return 10 - - def subtitle(self, i): - # override this - return "Column %d" % i - - def fill(self, i): - for k in range(i, self.nlists): - self.lists[k].clear() - self.labels[k].configure(text=self.subtitle(k)) - list = self.lists[i] - l = self.items(i) - for s in l: - list.append(s) - - def on_select(self, index, i): - item = self.lists[i].get(index) - del self.path[i:] - self.path.append(item) - if i+1 < self.nlists: - self.fill(i+1) - - def items(self, i): - # override this - l = [] - for k in range(10): - s = str(k) - if i > 0: - s = self.path[i-1] + "." + s - l.append(s) - return l - - def on_double(self, index, i): - pass - - -def main(): - root = Tk() - quit = Button(root, text="Exit", command=root.destroy) - quit.pack() - MultiScrolledLists(root, 4) - root.mainloop() - -if __name__ == "__main__": - main() diff --git a/Tools/idle/OldStackViewer.py b/Tools/idle/OldStackViewer.py deleted file mode 100644 index 4f295e8..0000000 --- a/Tools/idle/OldStackViewer.py +++ /dev/null @@ -1,275 +0,0 @@ -import sys -import os -from Tkinter import * -import linecache -from repr import Repr -from WindowList import ListedToplevel - -from ScrolledList import ScrolledList - - -class StackBrowser: - - def __init__(self, root, flist, stack=None): - self.top = top = ListedToplevel(root) - top.protocol("WM_DELETE_WINDOW", self.close) - top.bind("<Key-Escape>", self.close) - top.wm_title("Stack viewer") - top.wm_iconname("Stack") - # Create help label - self.helplabel = Label(top, - text="Click once to view variables; twice for source", - borderwidth=2, relief="groove") - self.helplabel.pack(fill="x") - # - self.sv = StackViewer(top, flist, self) - if stack is None: - stack = get_stack() - self.sv.load_stack(stack) - - def close(self, event=None): - self.top.destroy() - - localsframe = None - localsviewer = None - localsdict = None - globalsframe = None - globalsviewer = None - globalsdict = None - curframe = None - - def show_frame(self, (frame, lineno)): - if frame is self.curframe: - return - self.curframe = None - if frame.f_globals is not self.globalsdict: - self.show_globals(frame) - self.show_locals(frame) - self.curframe = frame - - def show_globals(self, frame): - title = "Global Variables" - if frame.f_globals.has_key("__name__"): - try: - name = str(frame.f_globals["__name__"]) + "" - except: - name = "" - if name: - title = title + " in module " + name - self.globalsdict = None - if self.globalsviewer: - self.globalsviewer.close() - self.globalsviewer = None - if not self.globalsframe: - self.globalsframe = Frame(self.top) - self.globalsdict = frame.f_globals - self.globalsviewer = NamespaceViewer( - self.globalsframe, - title, - self.globalsdict) - self.globalsframe.pack(fill="both", side="bottom") - - def show_locals(self, frame): - self.localsdict = None - if self.localsviewer: - self.localsviewer.close() - self.localsviewer = None - if frame.f_locals is not frame.f_globals: - title = "Local Variables" - code = frame.f_code - funcname = code.co_name - if funcname not in ("?", "", None): - title = title + " in " + funcname - if not self.localsframe: - self.localsframe = Frame(self.top) - self.localsdict = frame.f_locals - self.localsviewer = NamespaceViewer( - self.localsframe, - title, - self.localsdict) - self.localsframe.pack(fill="both", side="top") - else: - if self.localsframe: - self.localsframe.forget() - - -class StackViewer(ScrolledList): - - def __init__(self, master, flist, browser): - ScrolledList.__init__(self, master, width=80) - self.flist = flist - self.browser = browser - self.stack = [] - - def load_stack(self, stack, index=None): - self.stack = stack - self.clear() -## if len(stack) > 10: -## l["height"] = 10 -## self.topframe.pack(expand=1) -## else: -## l["height"] = len(stack) -## self.topframe.pack(expand=0) - for i in range(len(stack)): - frame, lineno = stack[i] - try: - modname = frame.f_globals["__name__"] - except: - modname = "?" - code = frame.f_code - filename = code.co_filename - funcname = code.co_name - sourceline = linecache.getline(filename, lineno) - sourceline = sourceline.strip() - if funcname in ("?", "", None): - item = "%s, line %d: %s" % (modname, lineno, sourceline) - else: - item = "%s.%s(), line %d: %s" % (modname, funcname, - lineno, sourceline) - if i == index: - item = "> " + item - self.append(item) - if index is not None: - self.select(index) - - def popup_event(self, event): - if self.stack: - return ScrolledList.popup_event(self, event) - - def fill_menu(self): - menu = self.menu - menu.add_command(label="Go to source line", - command=self.goto_source_line) - menu.add_command(label="Show stack frame", - command=self.show_stack_frame) - - def on_select(self, index): - if 0 <= index < len(self.stack): - self.browser.show_frame(self.stack[index]) - - def on_double(self, index): - self.show_source(index) - - def goto_source_line(self): - index = self.listbox.index("active") - self.show_source(index) - - def show_stack_frame(self): - index = self.listbox.index("active") - if 0 <= index < len(self.stack): - self.browser.show_frame(self.stack[index]) - - def show_source(self, index): - if not (0 <= index < len(self.stack)): - return - frame, lineno = self.stack[index] - code = frame.f_code - filename = code.co_filename - if os.path.isfile(filename): - edit = self.flist.open(filename) - if edit: - edit.gotoline(lineno) - - -def get_stack(t=None, f=None): - if t is None: - t = sys.last_traceback - stack = [] - if t and t.tb_frame is f: - t = t.tb_next - while f is not None: - stack.append((f, f.f_lineno)) - if f is self.botframe: - break - f = f.f_back - stack.reverse() - while t is not None: - stack.append((t.tb_frame, t.tb_lineno)) - t = t.tb_next - return stack - - -def getexception(type=None, value=None): - if type is None: - type = sys.last_type - value = sys.last_value - if hasattr(type, "__name__"): - type = type.__name__ - s = str(type) - if value is not None: - s = s + ": " + str(value) - return s - - -class NamespaceViewer: - - def __init__(self, master, title, dict=None): - width = 0 - height = 40 - if dict: - height = 20*len(dict) # XXX 20 == observed height of Entry widget - self.master = master - self.title = title - self.repr = Repr() - self.repr.maxstring = 60 - self.repr.maxother = 60 - self.frame = frame = Frame(master) - self.frame.pack(expand=1, fill="both") - self.label = Label(frame, text=title, borderwidth=2, relief="groove") - self.label.pack(fill="x") - self.vbar = vbar = Scrollbar(frame, name="vbar") - vbar.pack(side="right", fill="y") - self.canvas = canvas = Canvas(frame, - height=min(300, max(40, height)), - scrollregion=(0, 0, width, height)) - canvas.pack(side="left", fill="both", expand=1) - vbar["command"] = canvas.yview - canvas["yscrollcommand"] = vbar.set - self.subframe = subframe = Frame(canvas) - self.sfid = canvas.create_window(0, 0, window=subframe, anchor="nw") - self.load_dict(dict) - - dict = -1 - - def load_dict(self, dict, force=0): - if dict is self.dict and not force: - return - subframe = self.subframe - frame = self.frame - for c in subframe.children.values(): - c.destroy() - self.dict = None - if not dict: - l = Label(subframe, text="None") - l.grid(row=0, column=0) - else: - names = dict.keys() - names.sort() - row = 0 - for name in names: - value = dict[name] - svalue = self.repr.repr(value) # repr(value) - l = Label(subframe, text=name) - l.grid(row=row, column=0, sticky="nw") - ## l = Label(subframe, text=svalue, justify="l", wraplength=300) - l = Entry(subframe, width=0, borderwidth=0) - l.insert(0, svalue) - ## l["state"] = "disabled" - l.grid(row=row, column=1, sticky="nw") - row = row+1 - self.dict = dict - # XXX Could we use a <Configure> callback for the following? - subframe.update_idletasks() # Alas! - width = subframe.winfo_reqwidth() - height = subframe.winfo_reqheight() - canvas = self.canvas - self.canvas["scrollregion"] = (0, 0, width, height) - if height > 300: - canvas["height"] = 300 - frame.pack(expand=1) - else: - canvas["height"] = height - frame.pack(expand=0) - - def close(self): - self.frame.destroy() diff --git a/Tools/idle/RemoteDebugger.py b/Tools/idle/RemoteDebugger.py new file mode 100644 index 0000000..41f910f --- /dev/null +++ b/Tools/idle/RemoteDebugger.py @@ -0,0 +1,381 @@ +"""Support for remote Python debugging. + +Some ASCII art to describe the structure: + + IN PYTHON SUBPROCESS # IN IDLE PROCESS + # + # oid='gui_adapter' + +----------+ # +------------+ +-----+ + | GUIProxy |--remote#call-->| GUIAdapter |--calls-->| GUI | ++-----+--calls-->+----------+ # +------------+ +-----+ +| Idb | # / ++-----+<-calls--+------------+ # +----------+<--calls-/ + | IdbAdapter |<--remote#call--| IdbProxy | + +------------+ # +----------+ + oid='idb_adapter' # + +The purpose of the Proxy and Adapter classes is to translate certain +arguments and return values that cannot be transported through the RPC +barrier, in particular frame and traceback objects. + +""" + +import sys +import types +import rpc +import Debugger + +debugging = 0 + +idb_adap_oid = "idb_adapter" +gui_adap_oid = "gui_adapter" + +#======================================= +# +# In the PYTHON subprocess: + +frametable = {} +dicttable = {} +codetable = {} +tracebacktable = {} + +def wrap_frame(frame): + fid = id(frame) + frametable[fid] = frame + return fid + +def wrap_info(info): + "replace info[2], a traceback instance, by its ID" + if info is None: + return None + else: + traceback = info[2] + assert isinstance(traceback, types.TracebackType) + traceback_id = id(traceback) + tracebacktable[traceback_id] = traceback + modified_info = (info[0], info[1], traceback_id) + return modified_info + +class GUIProxy: + + def __init__(self, conn, gui_adap_oid): + self.conn = conn + self.oid = gui_adap_oid + + def interaction(self, message, frame, info=None): + # calls rpc.SocketIO.remotecall() via run.MyHandler instance + # pass frame and traceback object IDs instead of the objects themselves + self.conn.remotecall(self.oid, "interaction", + (message, wrap_frame(frame), wrap_info(info)), + {}) + +class IdbAdapter: + + def __init__(self, idb): + self.idb = idb + + #----------called by an IdbProxy---------- + + def set_step(self): + self.idb.set_step() + + def set_quit(self): + self.idb.set_quit() + + def set_continue(self): + self.idb.set_continue() + + def set_next(self, fid): + frame = frametable[fid] + self.idb.set_next(frame) + + def set_return(self, fid): + frame = frametable[fid] + self.idb.set_return(frame) + + def get_stack(self, fid, tbid): + ##print >>sys.__stderr__, "get_stack(%s, %s)" % (`fid`, `tbid`) + frame = frametable[fid] + if tbid is None: + tb = None + else: + tb = tracebacktable[tbid] + stack, i = self.idb.get_stack(frame, tb) + ##print >>sys.__stderr__, "get_stack() ->", stack + stack = [(wrap_frame(frame), k) for frame, k in stack] + ##print >>sys.__stderr__, "get_stack() ->", stack + return stack, i + + def run(self, cmd): + import __main__ + self.idb.run(cmd, __main__.__dict__) + + def set_break(self, filename, lineno): + msg = self.idb.set_break(filename, lineno) + return msg + + def clear_break(self, filename, lineno): + msg = self.idb.clear_break(filename, lineno) + return msg + + def clear_all_file_breaks(self, filename): + msg = self.idb.clear_all_file_breaks(filename) + return msg + + #----------called by a FrameProxy---------- + + def frame_attr(self, fid, name): + frame = frametable[fid] + return getattr(frame, name) + + def frame_globals(self, fid): + frame = frametable[fid] + dict = frame.f_globals + did = id(dict) + dicttable[did] = dict + return did + + def frame_locals(self, fid): + frame = frametable[fid] + dict = frame.f_locals + did = id(dict) + dicttable[did] = dict + return did + + def frame_code(self, fid): + frame = frametable[fid] + code = frame.f_code + cid = id(code) + codetable[cid] = code + return cid + + #----------called by a CodeProxy---------- + + def code_name(self, cid): + code = codetable[cid] + return code.co_name + + def code_filename(self, cid): + code = codetable[cid] + return code.co_filename + + #----------called by a DictProxy---------- + + def dict_keys(self, did): + dict = dicttable[did] + return dict.keys() + + def dict_item(self, did, key): + dict = dicttable[did] + value = dict[key] + value = repr(value) + return value + +#----------end class IdbAdapter---------- + + +def start_debugger(rpchandler, gui_adap_oid): + """Start the debugger and its RPC link in the Python subprocess + + Start the subprocess side of the split debugger and set up that side of the + RPC link by instantiating the GUIProxy, Idb debugger, and IdbAdapter + objects and linking them together. Register the IdbAdapter with the + RPCServer to handle RPC requests from the split debugger GUI via the + IdbProxy. + + """ + gui_proxy = GUIProxy(rpchandler, gui_adap_oid) + idb = Debugger.Idb(gui_proxy) + idb_adap = IdbAdapter(idb) + rpchandler.register(idb_adap_oid, idb_adap) + return idb_adap_oid + + +#======================================= +# +# In the IDLE process: + + +class FrameProxy: + + def __init__(self, conn, fid): + self._conn = conn + self._fid = fid + self._oid = "idb_adapter" + self._dictcache = {} + + def __getattr__(self, name): + if name[:1] == "_": + raise AttributeError, name + if name == "f_code": + return self._get_f_code() + if name == "f_globals": + return self._get_f_globals() + if name == "f_locals": + return self._get_f_locals() + return self._conn.remotecall(self._oid, "frame_attr", + (self._fid, name), {}) + + def _get_f_code(self): + cid = self._conn.remotecall(self._oid, "frame_code", (self._fid,), {}) + return CodeProxy(self._conn, self._oid, cid) + + def _get_f_globals(self): + did = self._conn.remotecall(self._oid, "frame_globals", + (self._fid,), {}) + return self._get_dict_proxy(did) + + def _get_f_locals(self): + did = self._conn.remotecall(self._oid, "frame_locals", + (self._fid,), {}) + return self._get_dict_proxy(did) + + def _get_dict_proxy(self, did): + if self._dictcache.has_key(did): + return self._dictcache[did] + dp = DictProxy(self._conn, self._oid, did) + self._dictcache[did] = dp + return dp + + +class CodeProxy: + + def __init__(self, conn, oid, cid): + self._conn = conn + self._oid = oid + self._cid = cid + + def __getattr__(self, name): + if name == "co_name": + return self._conn.remotecall(self._oid, "code_name", + (self._cid,), {}) + if name == "co_filename": + return self._conn.remotecall(self._oid, "code_filename", + (self._cid,), {}) + + +class DictProxy: + + def __init__(self, conn, oid, did): + self._conn = conn + self._oid = oid + self._did = did + + def keys(self): + return self._conn.remotecall(self._oid, "dict_keys", (self._did,), {}) + + def __getitem__(self, key): + return self._conn.remotecall(self._oid, "dict_item", + (self._did, key), {}) + + def __getattr__(self, name): + ##print >>sys.__stderr__, "failed DictProxy.__getattr__:", name + raise AttributeError, name + + +class GUIAdapter: + + def __init__(self, conn, gui): + self.conn = conn + self.gui = gui + + def interaction(self, message, fid, modified_info): + ##print "interaction: (%s, %s, %s)" % (message, fid, modified_info) + frame = FrameProxy(self.conn, fid) + self.gui.interaction(message, frame, modified_info) + + +class IdbProxy: + + def __init__(self, conn, shell, oid): + self.oid = oid + self.conn = conn + self.shell = shell + + def call(self, methodname, *args, **kwargs): + ##print "**IdbProxy.call %s %s %s" % (methodname, args, kwargs) + value = self.conn.remotecall(self.oid, methodname, args, kwargs) + ##print "**IdbProxy.call %s returns %s" % (methodname, `value`) + return value + + def run(self, cmd, locals): + # Ignores locals on purpose! + seq = self.conn.asynccall(self.oid, "run", (cmd,), {}) + self.shell.interp.active_seq = seq + + def get_stack(self, frame, tbid): + # passing frame and traceback IDs, not the objects themselves + stack, i = self.call("get_stack", frame._fid, tbid) + stack = [(FrameProxy(self.conn, fid), k) for fid, k in stack] + return stack, i + + def set_continue(self): + self.call("set_continue") + + def set_step(self): + self.call("set_step") + + def set_next(self, frame): + self.call("set_next", frame._fid) + + def set_return(self, frame): + self.call("set_return", frame._fid) + + def set_quit(self): + self.call("set_quit") + + def set_break(self, filename, lineno): + msg = self.call("set_break", filename, lineno) + return msg + + def clear_break(self, filename, lineno): + msg = self.call("clear_break", filename, lineno) + return msg + + def clear_all_file_breaks(self, filename): + msg = self.call("clear_all_file_breaks", filename) + return msg + +def start_remote_debugger(rpcclt, pyshell): + """Start the subprocess debugger, initialize the debugger GUI and RPC link + + Request the RPCServer start the Python subprocess debugger and link. Set + up the Idle side of the split debugger by instantiating the IdbProxy, + debugger GUI, and debugger GUIAdapter objects and linking them together. + + Register the GUIAdapter with the RPCClient to handle debugger GUI + interaction requests coming from the subprocess debugger via the GUIProxy. + + The IdbAdapter will pass execution and environment requests coming from the + Idle debugger GUI to the subprocess debugger via the IdbProxy. + + """ + global idb_adap_oid + + idb_adap_oid = rpcclt.remotecall("exec", "start_the_debugger",\ + (gui_adap_oid,), {}) + idb_proxy = IdbProxy(rpcclt, pyshell, idb_adap_oid) + gui = Debugger.Debugger(pyshell, idb_proxy) + gui_adap = GUIAdapter(rpcclt, gui) + rpcclt.register(gui_adap_oid, gui_adap) + return gui + +def close_remote_debugger(rpcclt): + """Shut down subprocess debugger and Idle side of debugger RPC link + + Request that the RPCServer shut down the subprocess debugger and link. + Unregister the GUIAdapter, which will cause a GC on the Idle process + debugger and RPC link objects. (The second reference to the debugger GUI + is deleted in PyShell.close_remote_debugger().) + + """ + close_subprocess_debugger(rpcclt) + rpcclt.unregister(gui_adap_oid) + +def close_subprocess_debugger(rpcclt): + rpcclt.remotecall("exec", "stop_the_debugger", (idb_adap_oid,), {}) + +def restart_subprocess_debugger(rpcclt): + idb_adap_oid_ret = rpcclt.remotecall("exec", "start_the_debugger",\ + (gui_adap_oid,), {}) + assert idb_adap_oid_ret == idb_adap_oid, 'Idb restarted with different oid' diff --git a/Tools/idle/RemoteInterp.py b/Tools/idle/RemoteInterp.py deleted file mode 100644 index e6f7671..0000000 --- a/Tools/idle/RemoteInterp.py +++ /dev/null @@ -1,341 +0,0 @@ -import select -import socket -import struct -import sys -import types - -VERBOSE = None - -class SocketProtocol: - """A simple protocol for sending strings across a socket""" - BUF_SIZE = 8192 - - def __init__(self, sock): - self.sock = sock - self._buffer = '' - self._closed = 0 - - def close(self): - self._closed = 1 - self.sock.close() - - def send(self, buf): - """Encode buf and write it on the socket""" - if VERBOSE: - VERBOSE.write('send %d:%s\n' % (len(buf), `buf`)) - self.sock.send('%d:%s' % (len(buf), buf)) - - def receive(self, timeout=0): - """Get next complete string from socket or return None - - Raise EOFError on EOF - """ - buf = self._read_from_buffer() - if buf is not None: - return buf - recvbuf = self._read_from_socket(timeout) - if recvbuf is None: - return None - if recvbuf == '' and self._buffer == '': - raise EOFError - if VERBOSE: - VERBOSE.write('recv %s\n' % `recvbuf`) - self._buffer = self._buffer + recvbuf - r = self._read_from_buffer() - return r - - def _read_from_socket(self, timeout): - """Does not block""" - if self._closed: - return '' - if timeout is not None: - r, w, x = select.select([self.sock], [], [], timeout) - if timeout is None or r: - return self.sock.recv(self.BUF_SIZE) - else: - return None - - def _read_from_buffer(self): - buf = self._buffer - i = buf.find(':') - if i == -1: - return None - buflen = int(buf[:i]) - enclen = i + 1 + buflen - if len(buf) >= enclen: - s = buf[i+1:enclen] - self._buffer = buf[enclen:] - return s - else: - self._buffer = buf - return None - -# helpers for registerHandler method below - -def get_methods(obj): - methods = [] - for name in dir(obj): - attr = getattr(obj, name) - if callable(attr): - methods.append(name) - if type(obj) == types.InstanceType: - methods = methods + get_methods(obj.__class__) - if type(obj) == types.ClassType: - for super in obj.__bases__: - methods = methods + get_methods(super) - return methods - -class CommandProtocol: - def __init__(self, sockp): - self.sockp = sockp - self.seqno = 0 - self.handlers = {} - - def close(self): - self.sockp.close() - self.handlers.clear() - - def registerHandler(self, handler): - """A Handler is an object with handle_XXX methods""" - for methname in get_methods(handler): - if methname[:7] == "handle_": - name = methname[7:] - self.handlers[name] = getattr(handler, methname) - - def send(self, cmd, arg='', seqno=None): - if arg: - msg = "%s %s" % (cmd, arg) - else: - msg = cmd - if seqno is None: - seqno = self.get_seqno() - msgbuf = self.encode_seqno(seqno) + msg - self.sockp.send(msgbuf) - if cmd == "reply": - return - reply = self.sockp.receive(timeout=None) - r_cmd, r_arg, r_seqno = self._decode_msg(reply) - assert r_seqno == seqno and r_cmd == "reply", "bad reply" - return r_arg - - def _decode_msg(self, msg): - seqno = self.decode_seqno(msg[:self.SEQNO_ENC_LEN]) - msg = msg[self.SEQNO_ENC_LEN:] - parts = msg.split(" ", 2) - if len(parts) == 1: - cmd = msg - arg = '' - else: - cmd = parts[0] - arg = parts[1] - return cmd, arg, seqno - - def dispatch(self): - msg = self.sockp.receive() - if msg is None: - return - cmd, arg, seqno = self._decode_msg(msg) - self._current_reply = seqno - h = self.handlers.get(cmd, self.default_handler) - try: - r = h(arg) - except TypeError, msg: - raise TypeError, "handle_%s: %s" % (cmd, msg) - if self._current_reply is None: - if r is not None: - sys.stderr.write("ignoring %s return value type %s\n" % \ - (cmd, type(r).__name__)) - return - if r is None: - r = '' - if type(r) != types.StringType: - raise ValueError, "invalid return type for %s" % cmd - self.send("reply", r, seqno=seqno) - - def reply(self, arg=''): - """Send a reply immediately - - otherwise reply will be sent when handler returns - """ - self.send("reply", arg, self._current_reply) - self._current_reply = None - - def default_handler(self, arg): - sys.stderr.write("WARNING: unhandled message %s\n" % arg) - return '' - - SEQNO_ENC_LEN = 4 - - def get_seqno(self): - seqno = self.seqno - self.seqno = seqno + 1 - return seqno - - def encode_seqno(self, seqno): - return struct.pack("I", seqno) - - def decode_seqno(self, buf): - return struct.unpack("I", buf)[0] - - -class StdioRedirector: - """Redirect sys.std{in,out,err} to a set of file-like objects""" - - def __init__(self, stdin, stdout, stderr): - self.stdin = stdin - self.stdout = stdout - self.stderr = stderr - - def redirect(self): - self.save() - sys.stdin = self.stdin - sys.stdout = self.stdout - sys.stderr = self.stderr - - def save(self): - self._stdin = sys.stdin - self._stdout = sys.stdout - self._stderr = sys.stderr - - def restore(self): - sys.stdin = self._stdin - sys.stdout = self._stdout - sys.stderr = self._stderr - -class IOWrapper: - """Send output from a file-like object across a SocketProtocol - - XXX Should this be more tightly integrated with the CommandProtocol? - """ - - def __init__(self, name, cmdp): - self.name = name - self.cmdp = cmdp - self.buffer = [] - -class InputWrapper(IOWrapper): - def write(self, buf): - # XXX what should this do on Windows? - raise IOError, (9, '[Errno 9] Bad file descriptor') - - def read(self, arg=None): - if arg is not None: - if arg <= 0: - return '' - else: - arg = 0 - return self.cmdp.send(self.name, "read,%s" % arg) - - def readline(self): - return self.cmdp.send(self.name, "readline") - -class OutputWrapper(IOWrapper): - def write(self, buf): - self.cmdp.send(self.name, buf) - - def read(self, arg=None): - return '' - -class RemoteInterp: - def __init__(self, sock): - self._sock = SocketProtocol(sock) - self._cmd = CommandProtocol(self._sock) - self._cmd.registerHandler(self) - - def run(self): - try: - while 1: - self._cmd.dispatch() - except EOFError: - pass - - def handle_execfile(self, arg): - self._cmd.reply() - io = StdioRedirector(InputWrapper("stdin", self._cmd), - OutputWrapper("stdout", self._cmd), - OutputWrapper("stderr", self._cmd)) - io.redirect() - execfile(arg, {'__name__':'__main__'}) - io.restore() - self._cmd.send("terminated") - - def handle_quit(self, arg): - self._cmd.reply() - self._cmd.close() - -def startRemoteInterp(id): - import os - # UNIX domain sockets are simpler for starters - sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) - sock.bind("/var/tmp/ri.%s" % id) - try: - sock.listen(1) - cli, addr = sock.accept() - rinterp = RemoteInterp(cli) - rinterp.run() - finally: - os.unlink("/var/tmp/ri.%s" % id) - -class RIClient: - """Client of the remote interpreter""" - def __init__(self, sock): - self._sock = SocketProtocol(sock) - self._cmd = CommandProtocol(self._sock) - self._cmd.registerHandler(self) - - def execfile(self, file): - self._cmd.send("execfile", file) - - def run(self): - try: - while 1: - self._cmd.dispatch() - except EOFError: - pass - - def handle_stdout(self, buf): - sys.stdout.write(buf) -## sys.stdout.flush() - - def handle_stderr(self, buf): - sys.stderr.write(buf) - - def handle_stdin(self, arg): - if arg == "readline": - return sys.stdin.readline() - i = arg.find(",") + 1 - bytes = int(arg[i:]) - if bytes == 0: - return sys.stdin.read() - else: - return sys.stdin.read(bytes) - - def handle_terminated(self, arg): - self._cmd.reply() - self._cmd.send("quit") - self._cmd.close() - -def riExec(id, file): - sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) - sock.connect("/var/tmp/ri.%s" % id) - cli = RIClient(sock) - cli.execfile(file) - cli.run() - -if __name__ == "__main__": - import getopt - - SERVER = 1 - opts, args = getopt.getopt(sys.argv[1:], 'cv') - for o, v in opts: - if o == '-c': - SERVER = 0 - elif o == '-v': - VERBOSE = sys.stderr - id = args[0] - - if SERVER: - startRemoteInterp(id) - else: - file = args[1] - riExec(id, file) diff --git a/Tools/idle/RemoteObjectBrowser.py b/Tools/idle/RemoteObjectBrowser.py new file mode 100644 index 0000000..6ba3391 --- /dev/null +++ b/Tools/idle/RemoteObjectBrowser.py @@ -0,0 +1,36 @@ +import rpc + +def remote_object_tree_item(item): + wrapper = WrappedObjectTreeItem(item) + oid = id(wrapper) + rpc.objecttable[oid] = wrapper + return oid + +class WrappedObjectTreeItem: + # Lives in PYTHON subprocess + + def __init__(self, item): + self.__item = item + + def __getattr__(self, name): + value = getattr(self.__item, name) + return value + + def _GetSubList(self): + list = self.__item._GetSubList() + return map(remote_object_tree_item, list) + +class StubObjectTreeItem: + # Lives in IDLE process + + def __init__(self, sockio, oid): + self.sockio = sockio + self.oid = oid + + def __getattr__(self, name): + value = rpc.MethodProxy(self.sockio, self.oid, name) + return value + + def _GetSubList(self): + list = self.sockio.remotecall(self.oid, "_GetSubList", (), {}) + return [StubObjectTreeItem(self.sockio, oid) for oid in list] diff --git a/Tools/idle/SearchBinding.py b/Tools/idle/SearchBinding.py deleted file mode 100644 index 5943e3b..0000000 --- a/Tools/idle/SearchBinding.py +++ /dev/null @@ -1,97 +0,0 @@ -import tkSimpleDialog - -###$ event <<find>> -###$ win <Control-f> -###$ unix <Control-u><Control-u><Control-s> - -###$ event <<find-again>> -###$ win <Control-g> -###$ win <F3> -###$ unix <Control-u><Control-s> - -###$ event <<find-selection>> -###$ win <Control-F3> -###$ unix <Control-s> - -###$ event <<find-in-files>> -###$ win <Alt-F3> - -###$ event <<replace>> -###$ win <Control-h> - -###$ event <<goto-line>> -###$ win <Alt-g> -###$ unix <Alt-g> - -class SearchBinding: - - windows_keydefs = { - '<<find-again>>': ['<Control-g>', '<F3>'], - '<<find-in-files>>': ['<Alt-F3>'], - '<<find-selection>>': ['<Control-F3>'], - '<<find>>': ['<Control-f>'], - '<<replace>>': ['<Control-h>'], - '<<goto-line>>': ['<Alt-g>'], - } - - unix_keydefs = { - '<<find-again>>': ['<Control-u><Control-s>'], - '<<find-in-files>>': ['<Alt-s>', '<Meta-s>'], - '<<find-selection>>': ['<Control-s>'], - '<<find>>': ['<Control-u><Control-u><Control-s>'], - '<<replace>>': ['<Control-r>'], - '<<goto-line>>': ['<Alt-g>', '<Meta-g>'], - } - - menudefs = [ - ('edit', [ - None, - ('_Find...', '<<find>>'), - ('Find a_gain', '<<find-again>>'), - ('Find _selection', '<<find-selection>>'), - ('Find in Files...', '<<find-in-files>>'), - ('R_eplace...', '<<replace>>'), - ('Go to _line', '<<goto-line>>'), - ]), - ] - - def __init__(self, editwin): - self.editwin = editwin - - def find_event(self, event): - import SearchDialog - SearchDialog.find(self.editwin.text) - return "break" - - def find_again_event(self, event): - import SearchDialog - SearchDialog.find_again(self.editwin.text) - return "break" - - def find_selection_event(self, event): - import SearchDialog - SearchDialog.find_selection(self.editwin.text) - return "break" - - def find_in_files_event(self, event): - import GrepDialog - GrepDialog.grep(self.editwin.text, self.editwin.io, self.editwin.flist) - return "break" - - def replace_event(self, event): - import ReplaceDialog - ReplaceDialog.replace(self.editwin.text) - return "break" - - def goto_line_event(self, event): - text = self.editwin.text - lineno = tkSimpleDialog.askinteger("Goto", - "Go to line number:", - parent=text) - if lineno is None: - return "break" - if lineno <= 0: - text.bell() - return "break" - text.mark_set("insert", "%d.0" % lineno) - text.see("insert") diff --git a/Tools/idle/Separator.py b/Tools/idle/Separator.py deleted file mode 100644 index 7145559..0000000 --- a/Tools/idle/Separator.py +++ /dev/null @@ -1,92 +0,0 @@ -from Tkinter import * - -class Separator: - - def __init__(self, master, orient, min=10, thickness=5, bg=None): - self.min = max(1, min) - self.thickness = max(1, thickness) - if orient in ("h", "horizontal"): - self.side = "left" - self.dim = "width" - self.dir = "x" - self.cursor = "sb_h_double_arrow" - elif orient in ("v", "vertical"): - self.side = "top" - self.dim = "height" - self.dir = "y" - self.cursor = "sb_v_double_arrow" - else: - raise ValueError, "Separator: orient should be h or v" - self.winfo_dim = "winfo_" + self.dim - self.master = master = Frame(master) - master.pack(expand=1, fill="both") - self.f1 = Frame(master) - self.f1.pack(expand=1, fill="both", side=self.side) - self.div = Frame(master, cursor=self.cursor) - self.div[self.dim] = self.thickness - self.div.pack(fill="both", side=self.side) - self.f2 = Frame(master) - self.f2.pack(expand=1, fill="both", side=self.side) - self.div.bind("<ButtonPress-1>", self.divider_press) - if bg: - ##self.f1["bg"] = bg - ##self.f2["bg"] = bg - self.div["bg"] = bg - - def parts(self): - return self.f1, self.f2 - - def divider_press(self, event): - self.press_event = event - self.f1.pack_propagate(0) - self.f2.pack_propagate(0) - for f in self.f1, self.f2: - for dim in "width", "height": - f[dim] = getattr(f, "winfo_"+dim)() - self.div.bind("<Motion>", self.div_motion) - self.div.bind("<ButtonRelease-1>", self.div_release) - self.div.grab_set() - - def div_motion(self, event): - delta = getattr(event, self.dir) - getattr(self.press_event, self.dir) - if delta: - dim1 = getattr(self.f1, self.winfo_dim)() - dim2 = getattr(self.f2, self.winfo_dim)() - delta = max(delta, self.min-dim1) - delta = min(delta, dim2-self.min) - dim1 = dim1 + delta - dim2 = dim2 - delta - self.f1[self.dim] = dim1 - self.f2[self.dim] = dim2 - - def div_release(self, event): - self.div_motion(event) - self.div.unbind("<Motion>") - self.div.grab_release() - -class VSeparator(Separator): - - def __init__(self, master, min=10, thickness=5, bg=None): - Separator.__init__(self, master, "v", min, thickness, bg) - -class HSeparator(Separator): - - def __init__(self, master, min=10, thickness=5, bg=None): - Separator.__init__(self, master, "h", min, thickness, bg) - -def main(): - root = Tk() - tlist = [] - outer = HSeparator(root, bg="red") - for part in outer.parts(): - inner = VSeparator(part, bg="blue") - for f in inner.parts(): - t = Text(f, width=40, height=10, borderwidth=0) - t.pack(fill="both", expand=1) - tlist.append(t) - tlist[0].insert("1.0", "Make your own Mondrian!") - tlist[1].insert("1.0", "Move the colored dividers...") - root.mainloop() - -if __name__ == '__main__': - main() diff --git a/Tools/idle/aboutDialog.py b/Tools/idle/aboutDialog.py new file mode 100644 index 0000000..2fc4a42 --- /dev/null +++ b/Tools/idle/aboutDialog.py @@ -0,0 +1,126 @@ +""" +about box for idle +""" + +from Tkinter import * +import string, os +import textView +import idlever + +class AboutDialog(Toplevel): + """ + modal about dialog for idle + """ + def __init__(self,parent,title): + Toplevel.__init__(self, parent) + self.configure(borderwidth=5) + self.geometry("+%d+%d" % (parent.winfo_rootx()+30, + parent.winfo_rooty()+30)) + self.bg="#707070" + self.fg="#ffffff" + + self.CreateWidgets() + self.resizable(height=FALSE,width=FALSE) + self.title(title) + self.transient(parent) + self.grab_set() + self.protocol("WM_DELETE_WINDOW", self.Ok) + self.parent = parent + self.buttonOk.focus_set() + #key bindings for this dialog + self.bind('<Alt-c>',self.CreditsButtonBinding) #credits button + self.bind('<Alt-l>',self.LicenseButtonBinding) #license button + self.bind('<Return>',self.Ok) #dismiss dialog + self.bind('<Escape>',self.Ok) #dismiss dialog + self.wait_window() + + def CreateWidgets(self): + frameMain = Frame(self,borderwidth=2,relief=SUNKEN) + frameButtons = Frame(self) + frameButtons.pack(side=BOTTOM,fill=X) + frameMain.pack(side=TOP,expand=TRUE,fill=BOTH) + self.buttonOk = Button(frameButtons,text='Ok', + command=self.Ok)#,default=ACTIVE + self.buttonOk.pack(padx=5,pady=5) + #self.picture = Image('photo',data=self.pictureData) + frameBg = Frame(frameMain,bg=self.bg) + frameBg.pack(expand=TRUE,fill=BOTH) + labelTitle = Label(frameBg,text='IDLEfork',fg=self.fg,bg=self.bg, + font=('courier', 24, 'bold')) + labelTitle.grid(row=0,column=0,sticky=W,padx=10,pady=10) + #labelPicture = Label(frameBg,text='[picture]') + #image=self.picture,bg=self.bg) + #labelPicture.grid(row=0,column=1,sticky=W,rowspan=2,padx=0,pady=3) + labelVersion = Label(frameBg,text='version '+idlever.IDLE_VERSION, + fg=self.fg,bg=self.bg) + labelVersion.grid(row=1,column=0,sticky=W,padx=10,pady=5) + labelDesc = Label(frameBg, + text="A development version of Python's lightweight\n"+ + 'Integrated DeveLopment Environment, IDLE.', + justify=LEFT,fg=self.fg,bg=self.bg) + labelDesc.grid(row=2,column=0,sticky=W,columnspan=3,padx=10,pady=5) + labelCopyright = Label(frameBg, + text="Copyright (c) 2001 Python Software Foundation;\nAll Rights Reserved", + justify=LEFT,fg=self.fg,bg=self.bg) + labelCopyright.grid(row=3,column=0,sticky=W,columnspan=3,padx=10,pady=5) + labelLicense = Label(frameBg, + text='Released under the Python 2.1.1 PSF Licence', + justify=LEFT,fg=self.fg,bg=self.bg) + labelLicense.grid(row=4,column=0,sticky=W,columnspan=3,padx=10,pady=5) + Frame(frameBg,height=5,bg=self.bg).grid(row=5,column=0) + labelEmail = Label(frameBg,text='email: idle-dev@python.org', + justify=LEFT,fg=self.fg,bg=self.bg) + labelEmail.grid(row=6,column=0,columnspan=2,sticky=W,padx=10,pady=0) + labelWWW = Label(frameBg,text='www: http://idlefork.sourceforge.net', + justify=LEFT,fg=self.fg,bg=self.bg) + labelWWW.grid(row=7,column=0,columnspan=2,sticky=W,padx=10,pady=0) + Frame(frameBg,borderwidth=1,relief=SUNKEN, + height=2,bg=self.bg).grid(row=8,column=0,sticky=EW, + columnspan=3, padx=5, pady=5) + labelPythonVer = Label(frameBg,text='Python version: '+ + sys.version.split()[0],fg=self.fg,bg=self.bg) + labelPythonVer.grid(row=9,column=0,sticky=W,padx=10,pady=0) + #handle weird tk version num in windoze python >= 1.6 (?!?) + tkVer = `TkVersion`.split('.') + tkVer[len(tkVer)-1] = str('%.3g' % (float('.'+tkVer[len(tkVer)-1])))[2:] + if tkVer[len(tkVer)-1] == '': + tkVer[len(tkVer)-1] = '0' + tkVer = string.join(tkVer,'.') + labelTkVer = Label(frameBg,text='Tk version: '+ + tkVer,fg=self.fg,bg=self.bg) + labelTkVer.grid(row=9,column=1,sticky=W,padx=2,pady=0) + + self.buttonLicense = Button(frameBg,text='View License',underline=5, + width=14,highlightbackground=self.bg,command=self.ShowLicense)#takefocus=FALSE + self.buttonLicense.grid(row=10,column=0,sticky=W,padx=10,pady=10) + self.buttonCredits = Button(frameBg,text='View Credits',underline=5, + width=14,highlightbackground=self.bg,command=self.ShowCredits)#takefocus=FALSE + self.buttonCredits.grid(row=10,column=1,columnspan=2,sticky=E,padx=10,pady=10) + + def CreditsButtonBinding(self,event): + self.buttonCredits.invoke() + + def LicenseButtonBinding(self,event): + self.buttonLicense.invoke() + + def ShowLicense(self): + self.ViewFile('About - License','LICENSE.txt') + + def ShowCredits(self): + self.ViewFile('About - Credits','CREDITS.txt') + + def ViewFile(self,viewTitle,viewFile): + fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),viewFile) + textView.TextViewer(self,viewTitle,fn) + + def Ok(self, event=None): + self.destroy() + +if __name__ == '__main__': + #test the dialog + root=Tk() + def run(): + import aboutDialog + aboutDialog.AboutDialog(root,'About') + Button(root,text='Dialog',command=run).pack() + root.mainloop() diff --git a/Tools/idle/boolcheck.py b/Tools/idle/boolcheck.py new file mode 100644 index 0000000..f682232 --- /dev/null +++ b/Tools/idle/boolcheck.py @@ -0,0 +1,9 @@ +"boolcheck - import this module to ensure True, False, bool() builtins exist." +try: + True +except NameError: + import __builtin__ + __builtin__.True = 1 + __builtin__.False = 0 + from operator import truth + __builtin__.bool = truth diff --git a/Tools/idle/config-extensions.def b/Tools/idle/config-extensions.def new file mode 100644 index 0000000..d4905e8 --- /dev/null +++ b/Tools/idle/config-extensions.def @@ -0,0 +1,54 @@ +# IDLE reads several config files to determine user preferences. This +# file is the default config file for idle extensions settings. +# +# Each extension must have at least one section, named after the extension +# module. This section must contain an 'enable' item (=1 to enable the +# extension, =0 to disable it) and also contains any other general +# configuration items for the extension. Each extension may also define up to +# two optional sections named ExtensionName_bindings and +# ExtensionName_cfgBindings. If present, ExtensionName_bindings defines virtual +# event bindings for the extension that are not sensibly re-configurable. If +# present, ExtensionName_cfgBindings defines virtual event bindings for the +# extension that may be sensibly re-configured. + +# See config-keys.def for notes on specifying keys. + +[FormatParagraph] +enable=1 +[FormatParagraph_cfgBindings] +format-paragraph=<Alt-Key-q> + +[AutoExpand] +enable=1 +[AutoExpand_cfgBindings] +expand-word=<Alt-Key-slash> + +[ZoomHeight] +enable=1 +[ZoomHeight_cfgBindings] +zoom-height=<Alt-Key-2> + +[ScriptBinding] +enable=1 +[ScriptBinding_cfgBindings] +run-module=<Key-F5> +check-module=<Alt-Key-x> + +[CallTips] +enable=1 +[CallTips_bindings] +paren-open=<Key-parenleft> +paren-close=<Key-parenright> +check-calltip-cancel=<KeyRelease> +calltip-cancel=<ButtonPress> <Key-Escape> + +[ParenMatch] +enable=0 +style= expression +flash-delay= 500 +bell= 1 +hilite-foreground= black +hilite-background= #43cd80 +[ParenMatch_bindings] +flash-open-paren=<KeyRelease-parenright> <KeyRelease-bracketright> <KeyRelease-braceright> +check-restore=<KeyPress> diff --git a/Tools/idle/config-highlight.def b/Tools/idle/config-highlight.def new file mode 100644 index 0000000..2b7deab --- /dev/null +++ b/Tools/idle/config-highlight.def @@ -0,0 +1,60 @@ +# IDLE reads several config files to determine user preferences. This +# file is the default config file for idle highlight theme settings. + +[IDLE Classic] +normal-foreground= #000000 +normal-background= #ffffff +keyword-foreground= #ff7700 +keyword-background= #ffffff +comment-foreground= #dd0000 +comment-background= #ffffff +string-foreground= #00aa00 +string-background= #ffffff +definition-foreground= #0000ff +definition-background= #ffffff +hilite-foreground= #000000 +hilite-background= gray +break-foreground= black +break-background= #ffff55 +hit-foreground= #ffffff +hit-background= #000000 +error-foreground= #000000 +error-background= #ff7777 +#cursor (only foreground can be set) +cursor-foreground= black +#shell window +stdout-foreground= blue +stdout-background= #ffffff +stderr-foreground= red +stderr-background= #ffffff +console-foreground= #770000 +console-background= #ffffff + +[IDLE New] +normal-foreground= #000000 +normal-background= #ffffff +keyword-foreground= #ff7700 +keyword-background= #ffffff +comment-foreground= #dd0000 +comment-background= #ffffff +string-foreground= #00aa00 +string-background= #ffffff +definition-foreground= #0000ff +definition-background= #ffffff +hilite-foreground= #000000 +hilite-background= gray +break-foreground= black +break-background= #ffff55 +hit-foreground= #ffffff +hit-background= #000000 +error-foreground= #000000 +error-background= #ff7777 +#cursor (only foreground can be set) +cursor-foreground= black +#shell window +stdout-foreground= blue +stdout-background= #ffffff +stderr-foreground= red +stderr-background= #ffffff +console-foreground= #770000 +console-background= #ffffff diff --git a/Tools/idle/config-keys.def b/Tools/idle/config-keys.def new file mode 100644 index 0000000..4f2be59 --- /dev/null +++ b/Tools/idle/config-keys.def @@ -0,0 +1,155 @@ +# IDLE reads several config files to determine user preferences. This +# file is the default config file for idle key binding settings. +# Where multiple keys are specified for an action: if they are separated +# by a space (eg. action=<key1> <key2>) then the keys are altenatives, if +# there is no space (eg. action=<key1><key2>) then the keys comprise a +# single 'emacs style' multi-keystoke binding. The tk event specifier 'Key' +# is used in all cases, for consistency in auto key conflict checking in the +# configuration gui. + +[IDLE Classic Windows] +copy=<Control-Key-c> +cut=<Control-Key-x> +paste=<Control-Key-v> +beginning-of-line= <Key-Home> +center-insert=<Control-Key-l> +close-all-windows=<Control-Key-q> +close-window=<Alt-Key-F4> <Meta-Key-F4> +do-nothing=<Control-Key-F12> +end-of-file=<Control-Key-d> +python-docs=<Key-F1> +python-context-help=<Shift-Key-F1> +history-next=<Alt-Key-n> <Meta-Key-n> +history-previous=<Alt-Key-p> <Meta-Key-p> +interrupt-execution=<Control-Key-c> +view-restart=<Key-F6> +restart-shell=<Control-Key-F6> +open-class-browser=<Alt-Key-c> <Meta-Key-c> +open-module=<Alt-Key-m> <Meta-Key-m> +open-new-window=<Control-Key-n> +open-window-from-file=<Control-Key-o> +plain-newline-and-indent=<Control-Key-j> +print-window=<Control-Key-p> +redo=<Control-Shift-Key-z> +remove-selection=<Key-Escape> +save-copy-of-window-as-file=<Alt-Shift-Key-s> +save-window-as-file=<Control-Shift-Key-s> +save-window=<Control-Key-s> +select-all=<Control-Key-a> +toggle-auto-coloring=<Control-Key-slash> +undo=<Control-Key-z> +find=<Control-Key-f> +find-again=<Control-Key-g> <Key-F3> +find-in-files=<Alt-Key-F3> <Meta-Key-F3> +find-selection=<Control-Key-F3> +replace=<Control-Key-h> +goto-line=<Alt-Key-g> <Meta-Key-g> +smart-backspace=<Key-BackSpace> +newline-and-indent=<Key-Return> <Key-KP_Enter> +smart-indent=<Key-Tab> +indent-region=<Control-Key-bracketright> +dedent-region=<Control-Key-bracketleft> +comment-region=<Alt-Key-3> <Meta-Key-3> +uncomment-region=<Alt-Key-4> <Meta-Key-4> +tabify-region=<Alt-Key-5> <Meta-Key-5> +untabify-region=<Alt-Key-6> <Meta-Key-6> +toggle-tabs=<Alt-Key-t> <Meta-Key-t> +change-indentwidth=<Alt-Key-u> <Meta-Key-u> + +[IDLE Classic Unix] +copy=<Alt-Key-w> <Meta-Key-w> +cut=<Control-Key-w> +paste=<Control-Key-y> +beginning-of-line=<Control-Key-a> <Key-Home> +center-insert=<Control-Key-l> +close-all-windows=<Control-Key-x><Control-Key-c> +close-window=<Control-Key-x><Control-Key-0> +do-nothing=<Control-Key-x> +end-of-file=<Control-Key-d> +history-next=<Alt-Key-n> <Meta-Key-n> +history-previous=<Alt-Key-p> <Meta-Key-p> +interrupt-execution=<Control-Key-c> +view-restart=<Key-F6> +restart-shell=<Control-Key-F6> +open-class-browser=<Control-Key-x><Control-Key-b> +open-module=<Control-Key-x><Control-Key-m> +open-new-window=<Control-Key-x><Control-Key-n> +open-window-from-file=<Control-Key-x><Control-Key-f> +plain-newline-and-indent=<Control-Key-j> +print-window=<Control-x><Control-Key-p> +python-docs=<Control-Key-h> +python-context-help=<Control-Shift-Key-h> +redo=<Alt-Key-z> <Meta-Key-z> +remove-selection=<Key-Escape> +save-copy-of-window-as-file=<Control-Key-x><Control-Key-y> +save-window-as-file=<Control-Key-x><Control-Key-w> +save-window=<Control-Key-x><Control-Key-s> +select-all=<Alt-Key-a> <Meta-Key-a> +toggle-auto-coloring=<Control-Key-slash> +undo=<Control-Key-z> +find=<Control-Key-u><Control-Key-u><Control-Key-s> +find-again=<Control-Key-u><Control-Key-s> +find-in-files=<Alt-Key-s> <Meta-Key-s> +find-selection=<Control-Key-s> +replace=<Control-Key-r> +goto-line=<Alt-Key-g> <Meta-Key-g> +smart-backspace=<Key-BackSpace> +newline-and-indent=<Key-Return> <Key-KP_Enter> +smart-indent=<Key-Tab> +indent-region=<Control-Key-bracketright> +dedent-region=<Control-Key-bracketleft> +comment-region=<Alt-Key-3> +uncomment-region=<Alt-Key-4> +tabify-region=<Alt-Key-5> +untabify-region=<Alt-Key-6> +toggle-tabs=<Alt-Key-t> +change-indentwidth=<Alt-Key-u> + +[IDLE Classic Mac] +copy=<Command-Key-c> +cut=<Command-Key-x> +paste=<Command-Key-v> +beginning-of-line= <Key-Home> +center-insert=<Control-Key-l> +close-all-windows=<Command-Key-q> +close-window=<Command-Key-w> +do-nothing=<Control-Key-F12> +end-of-file=<Control-Key-d> +python-docs=<Key-F1> +python-context-help=<Shift-Key-F1> +history-next=<Control-Key-n> +history-previous=<Control-Key-p> +interrupt-execution=<Control-Key-c> +view-restart=<Key-F6> +restart-shell=<Control-Key-F6> +open-class-browser=<Command-Key-b> +open-module=<Command-Key-m> +open-new-window=<Command-Key-n> +open-window-from-file=<Command-Key-o> +plain-newline-and-indent=<Control-Key-j> +print-window=<Command-Key-p> +redo=<Shift-Command-Key-z> +remove-selection=<Key-Escape> +save-window-as-file=<Shift-Command-Key-s> +save-window=<Command-Key-s> +save-copy-of-window-as-file=<Option-Command-Key-s> +select-all=<Command-Key-a> +toggle-auto-coloring=<Control-Key-slash> +undo=<Command-Key-z> +find=<Command-Key-f> +find-again=<Command-Key-g> <Key-F3> +find-in-files=<Command-Key-F3> +find-selection=<Shift-Command-Key-F3> +replace=<Command-Key-r> +goto-line=<Command-Key-j> +smart-backspace=<Key-BackSpace> +newline-and-indent=<Key-Return> <Key-KP_Enter> +smart-indent=<Key-Tab> +indent-region=<Command-Key-bracketright> +dedent-region=<Command-Key-bracketleft> +comment-region=<Control-Key-3> +uncomment-region=<Control-Key-4> +tabify-region=<Control-Key-5> +untabify-region=<Control-Key-6> +toggle-tabs=<Control-Key-t> +change-indentwidth=<Control-Key-u> diff --git a/Tools/idle/config-mac.txt b/Tools/idle/config-mac.txt deleted file mode 100644 index ee36e13..0000000 --- a/Tools/idle/config-mac.txt +++ /dev/null @@ -1,3 +0,0 @@ -[EditorWindow] -font-name= monaco -font-size= 9 diff --git a/Tools/idle/config-main.def b/Tools/idle/config-main.def new file mode 100644 index 0000000..9d520c1 --- /dev/null +++ b/Tools/idle/config-main.def @@ -0,0 +1,65 @@ +# IDLE reads several config files to determine user preferences. This +# file is the default config file for general idle settings. +# +# When IDLE starts, it will look in +# the following two sets of files, in order: +# +# default configuration +# --------------------- +# config-main.def the default general config file +# config-extensions.def the default extension config file +# config-highlight.def the default highlighting config file +# config-keys.def the default keybinding config file +# +# user configuration +# ------------------- +# ~/.idlerc/idle-main.cfg the user general config file +# ~/.idlerc/idle-extensions.cfg the user extension config file +# ~/.idlerc/idle-highlight.cfg the user highlighting config file +# ~/.idlerc/idle-keys.cfg the user keybinding config file +# +# Any options the user saves through the config dialog will be saved to +# the relevant user config file. Reverting any general setting to the +# default causes that entry to be wiped from the user file and re-read +# from the default file. User highlighting themes or keybinding sets are +# retained unless specifically deleted within the config dialog. Choosing +# one of the default themes or keysets just applies the relevant settings +# from the default file. +# +# Additional help sources are listed in the [HelpFiles] section and must be +# viewable by a web browser (or the Windows Help viewer in the case of .chm +# files). These sources will be listed on the Help menu. The pattern is +# <sequence_number = menu item;/path/to/help/source> +# You can't use a semi-colon in a menu item or path. The path will be platform +# specific because of path separators, drive specs etc. +# +# It is best to use the Configuration GUI to set up additional help sources! +# Example: +#1 = My Extra Help Source;/usr/share/doc/foo/index.html +#2 = Another Help Source;/path/to/another.pdf + +[General] +editor-on-startup= 0 +print-command-posix=lpr %s +print-command-win=start /min notepad /p %s + +[EditorWindow] +width= 80 +height= 30 +font= courier +font-size= 12 +font-bold= 0 + +[Indent] +use-spaces= 1 +num-spaces= 4 + +[Theme] +default= 1 +name= IDLE Classic + +[Keys] +default= 1 +name= IDLE Classic Windows + +[HelpFiles] diff --git a/Tools/idle/config-unix.txt b/Tools/idle/config-unix.txt deleted file mode 100644 index 782965f..0000000 --- a/Tools/idle/config-unix.txt +++ /dev/null @@ -1,4 +0,0 @@ -[EditorWindow] -font-name= courier -font-size= 10 -print-command=lpr %s diff --git a/Tools/idle/config-win.txt b/Tools/idle/config-win.txt deleted file mode 100644 index aeb6ab9..0000000 --- a/Tools/idle/config-win.txt +++ /dev/null @@ -1,4 +0,0 @@ -[EditorWindow] -font-name: courier new -font-size: 10 -print-command=start /min notepad /p %s diff --git a/Tools/idle/config.txt b/Tools/idle/config.txt deleted file mode 100644 index 6f98a3e..0000000 --- a/Tools/idle/config.txt +++ /dev/null @@ -1,64 +0,0 @@ -# IDLE reads several config files to determine user preferences. This -# file is the default config file. When IDLE starts, it will look in -# the following four files in order: -# config.txt the default config file -# config-[win/unix/mac].txt the generic platform config file -# config-[sys.platform].txt the specific platform config file -# ~/.idle the user config file -# XXX what about Windows? -# -# The last definition of each option is used. For example, you can -# override the default window size (80x24) by defining width and -# height options in the EditorWindow section of your ~/.idle file -# -# IDLE extensions can be enabled and disabled by adding them to one of -# the config files. To enable an extension, create a section with the -# same name as the extension, e.g. the [ParenMatch] section below. To -# disable an extension, either remove the section or add the 'enable' -# option with the value 0. - -[EditorWindow] -width= 80 -height= 24 -# fonts defined in config-[win/unix].txt - -[Colors] -normal-foreground= black -normal-background= white -# These color types are not explicitly defined= sync, todo, stdin -keyword-foreground= #ff7700 -comment-foreground= #dd0000 -string-foreground= #00aa00 -definition-foreground= #0000ff -hilite-foreground= #000068 -hilite-background= #006868 -break-foreground= #ff7777 -hit-foreground= #ffffff -hit-background= #000000 -stdout-foreground= blue -stderr-foreground= red -console-foreground= #770000 -error-background= #ff7777 -cursor-background= black - -[SearchBinding] - -[AutoIndent] - -[AutoExpand] - -[FormatParagraph] - -[ZoomHeight] - -[ScriptBinding] - -[CallTips] - -[ParenMatch] -enable= 0 -style= expression -flash-delay= 500 -bell= 1 -hilite-foreground= black -hilite-background= #43cd80 diff --git a/Tools/idle/configDialog.py b/Tools/idle/configDialog.py new file mode 100644 index 0000000..814689c --- /dev/null +++ b/Tools/idle/configDialog.py @@ -0,0 +1,1134 @@ +"""IDLE Configuration Dialog: support user customization of IDLE by GUI + +Customize font faces, sizes, and colorization attributes. Set indentation +defaults. Customize keybindings. Colorization and keybindings can be +saved as user defined sets. Select startup options including shell/editor +and default window size. Define additional help sources. + +Note that tab width in IDLE is currently fixed at eight due to Tk issues. +Refer to comment in EditorWindow autoindent code for details. + +""" +from Tkinter import * +import tkMessageBox, tkColorChooser, tkFont +import string, copy + +from configHandler import idleConf +from dynOptionMenuWidget import DynOptionMenu +from tabpage import TabPageSet +from keybindingDialog import GetKeysDialog +from configSectionNameDialog import GetCfgSectionNameDialog +from configHelpSourceEdit import GetHelpSourceDialog + +class ConfigDialog(Toplevel): + """ + configuration dialog for idle + """ + def __init__(self,parent,title): + Toplevel.__init__(self, parent) + self.configure(borderwidth=5) + self.geometry("+%d+%d" % (parent.winfo_rootx()+20, + parent.winfo_rooty()+30)) + #Theme Elements. Each theme element key is it's display name. + #The first value of the tuple is the sample area tag name. + #The second value is the display name list sort index. + self.themeElements={'Normal Text':('normal','00'), + 'Python Keywords':('keyword','01'), + 'Python Definitions':('definition','02'), + 'Python Comments':('comment','03'), + 'Python Strings':('string','04'), + 'Selected Text':('hilite','05'), + 'Found Text':('hit','06'), + 'Cursor':('cursor','07'), + 'Error Text':('error','08'), + 'Shell Normal Text':('console','09'), + 'Shell Stdout Text':('stdout','10'), + 'Shell Stderr Text':('stderr','11')} + self.ResetChangedItems() #load initial values in changed items dict + self.CreateWidgets() + self.resizable(height=FALSE,width=FALSE) + self.transient(parent) + self.grab_set() + self.protocol("WM_DELETE_WINDOW", self.Cancel) + self.parent = parent + self.tabPages.focus_set() + #key bindings for this dialog + #self.bind('<Escape>',self.Cancel) #dismiss dialog, no save + #self.bind('<Alt-a>',self.Apply) #apply changes, save + #self.bind('<F1>',self.Help) #context help + self.LoadConfigs() + self.AttachVarCallbacks() #avoid callbacks during LoadConfigs + self.wait_window() + + def CreateWidgets(self): + self.tabPages = TabPageSet(self, + pageNames=['Fonts/Tabs','Highlighting','Keys','General']) + self.tabPages.ChangePage()#activates default (first) page + frameActionButtons = Frame(self) + #action buttons + self.buttonHelp = Button(frameActionButtons,text='Help', + command=self.Help,takefocus=FALSE) + self.buttonOk = Button(frameActionButtons,text='Ok', + command=self.Ok,takefocus=FALSE) + self.buttonApply = Button(frameActionButtons,text='Apply', + command=self.Apply,takefocus=FALSE) + self.buttonCancel = Button(frameActionButtons,text='Cancel', + command=self.Cancel,takefocus=FALSE) + self.CreatePageFontTab() + self.CreatePageHighlight() + self.CreatePageKeys() + self.CreatePageGeneral() + self.buttonHelp.pack(side=RIGHT,padx=5,pady=5) + self.buttonOk.pack(side=LEFT,padx=5,pady=5) + self.buttonApply.pack(side=LEFT,padx=5,pady=5) + self.buttonCancel.pack(side=LEFT,padx=5,pady=5) + frameActionButtons.pack(side=BOTTOM) + self.tabPages.pack(side=TOP,expand=TRUE,fill=BOTH) + + def CreatePageFontTab(self): + #tkVars + self.fontSize=StringVar(self) + self.fontBold=BooleanVar(self) + self.fontName=StringVar(self) + self.spaceNum=IntVar(self) + #self.tabCols=IntVar(self) + self.indentBySpaces=BooleanVar(self) + self.editFont=tkFont.Font(self,('courier',12,'normal')) + ##widget creation + #body frame + frame=self.tabPages.pages['Fonts/Tabs']['page'] + #body section frames + frameFont=Frame(frame,borderwidth=2,relief=GROOVE) + frameIndent=Frame(frame,borderwidth=2,relief=GROOVE) + #frameFont + labelFontTitle=Label(frameFont,text='Set Base Editor Font') + frameFontName=Frame(frameFont) + frameFontParam=Frame(frameFont) + labelFontNameTitle=Label(frameFontName,justify=LEFT, + text='Font :') + self.listFontName=Listbox(frameFontName,height=5,takefocus=FALSE, + exportselection=FALSE) + self.listFontName.bind('<ButtonRelease-1>',self.OnListFontButtonRelease) + scrollFont=Scrollbar(frameFontName) + scrollFont.config(command=self.listFontName.yview) + self.listFontName.config(yscrollcommand=scrollFont.set) + labelFontSizeTitle=Label(frameFontParam,text='Size :') + self.optMenuFontSize=DynOptionMenu(frameFontParam,self.fontSize,None, + command=self.SetFontSample) + checkFontBold=Checkbutton(frameFontParam,variable=self.fontBold, + onvalue=1,offvalue=0,text='Bold',command=self.SetFontSample) + frameFontSample=Frame(frameFont,relief=SOLID,borderwidth=1) + self.labelFontSample=Label(frameFontSample, + text='AaBbCcDdEe\nFfGgHhIiJjK\n1234567890\n#:+=(){}[]', + justify=LEFT,font=self.editFont) + #frameIndent + labelIndentTitle=Label(frameIndent,text='Set Indentation Defaults') + frameIndentType=Frame(frameIndent) + frameIndentSize=Frame(frameIndent) + labelIndentTypeTitle=Label(frameIndentType, + text='Choose indentation type :') + radioUseSpaces=Radiobutton(frameIndentType,variable=self.indentBySpaces, + value=1,text='Tab key inserts spaces') + radioUseTabs=Radiobutton(frameIndentType,variable=self.indentBySpaces, + value=0,text='Tab key inserts tabs') + labelIndentSizeTitle=Label(frameIndentSize, + text='Choose indentation size :') + labelSpaceNumTitle=Label(frameIndentSize,justify=LEFT, + text='indent width') + self.scaleSpaceNum=Scale(frameIndentSize,variable=self.spaceNum, + orient='horizontal',tickinterval=2,from_=2,to=16) + #labeltabColsTitle=Label(frameIndentSize,justify=LEFT, + # text='when tab key inserts tabs,\ncolumns per tab') + #self.scaleTabCols=Scale(frameIndentSize,variable=self.tabCols, + # orient='horizontal',tickinterval=2,from_=2,to=8) + #widget packing + #body + frameFont.pack(side=LEFT,padx=5,pady=10,expand=TRUE,fill=BOTH) + frameIndent.pack(side=LEFT,padx=5,pady=10,fill=Y) + #frameFont + labelFontTitle.pack(side=TOP,anchor=W,padx=5,pady=5) + frameFontName.pack(side=TOP,padx=5,pady=5,fill=X) + frameFontParam.pack(side=TOP,padx=5,pady=5,fill=X) + labelFontNameTitle.pack(side=TOP,anchor=W) + self.listFontName.pack(side=LEFT,expand=TRUE,fill=X) + scrollFont.pack(side=LEFT,fill=Y) + labelFontSizeTitle.pack(side=LEFT,anchor=W) + self.optMenuFontSize.pack(side=LEFT,anchor=W) + checkFontBold.pack(side=LEFT,anchor=W,padx=20) + frameFontSample.pack(side=TOP,padx=5,pady=5,expand=TRUE,fill=BOTH) + self.labelFontSample.pack(expand=TRUE,fill=BOTH) + #frameIndent + labelIndentTitle.pack(side=TOP,anchor=W,padx=5,pady=5) + frameIndentType.pack(side=TOP,padx=5,fill=X) + frameIndentSize.pack(side=TOP,padx=5,pady=5,fill=BOTH) + labelIndentTypeTitle.pack(side=TOP,anchor=W,padx=5,pady=5) + radioUseSpaces.pack(side=TOP,anchor=W,padx=5) + radioUseTabs.pack(side=TOP,anchor=W,padx=5) + labelIndentSizeTitle.pack(side=TOP,anchor=W,padx=5,pady=5) + labelSpaceNumTitle.pack(side=TOP,anchor=W,padx=5) + self.scaleSpaceNum.pack(side=TOP,padx=5,fill=X) + #labeltabColsTitle.pack(side=TOP,anchor=W,padx=5) + #self.scaleTabCols.pack(side=TOP,padx=5,fill=X) + return frame + + def CreatePageHighlight(self): + self.builtinTheme=StringVar(self) + self.customTheme=StringVar(self) + self.fgHilite=BooleanVar(self) + self.colour=StringVar(self) + self.fontName=StringVar(self) + self.themeIsBuiltin=BooleanVar(self) + self.highlightTarget=StringVar(self) + ##widget creation + #body frame + frame=self.tabPages.pages['Highlighting']['page'] + #body section frames + frameCustom=Frame(frame,borderwidth=2,relief=GROOVE) + frameTheme=Frame(frame,borderwidth=2,relief=GROOVE) + #frameCustom + self.textHighlightSample=Text(frameCustom,relief=SOLID,borderwidth=1, + font=('courier',12,''),cursor='hand2',width=21,height=10, + takefocus=FALSE,highlightthickness=0,wrap=NONE) + text=self.textHighlightSample + text.bind('<Double-Button-1>',lambda e: 'break') + text.bind('<B1-Motion>',lambda e: 'break') + textAndTags=(('#you can click here','comment'),('\n','normal'), + ('#to choose items','comment'),('\n','normal'),('def','keyword'), + (' ','normal'),('func','definition'),('(param):','normal'), + ('\n ','normal'),('"""string"""','string'),('\n var0 = ','normal'), + ("'string'",'string'),('\n var1 = ','normal'),("'selected'",'hilite'), + ('\n var2 = ','normal'),("'found'",'hit'),('\n\n','normal'), + (' error ','error'),(' ','normal'),('cursor |','cursor'), + ('\n ','normal'),('shell','console'),(' ','normal'),('stdout','stdout'), + (' ','normal'),('stderr','stderr'),('\n','normal')) + for txTa in textAndTags: + text.insert(END,txTa[0],txTa[1]) + for element in self.themeElements.keys(): + text.tag_bind(self.themeElements[element][0],'<ButtonPress-1>', + lambda event,elem=element: event.widget.winfo_toplevel() + .highlightTarget.set(elem)) + text.config(state=DISABLED) + self.frameColourSet=Frame(frameCustom,relief=SOLID,borderwidth=1) + frameFgBg=Frame(frameCustom) + labelCustomTitle=Label(frameCustom,text='Set Custom Highlighting') + buttonSetColour=Button(self.frameColourSet,text='Choose Colour for :', + command=self.GetColour,highlightthickness=0) + self.optMenuHighlightTarget=DynOptionMenu(self.frameColourSet, + self.highlightTarget,None,highlightthickness=0)#,command=self.SetHighlightTargetBinding + self.radioFg=Radiobutton(frameFgBg,variable=self.fgHilite, + value=1,text='Foreground',command=self.SetColourSampleBinding) + self.radioBg=Radiobutton(frameFgBg,variable=self.fgHilite, + value=0,text='Background',command=self.SetColourSampleBinding) + self.fgHilite.set(1) + buttonSaveCustomTheme=Button(frameCustom, + text='Save as New Custom Theme',command=self.SaveAsNewTheme) + #frameTheme + labelThemeTitle=Label(frameTheme,text='Select a Highlighting Theme') + labelTypeTitle=Label(frameTheme,text='Select : ') + self.radioThemeBuiltin=Radiobutton(frameTheme,variable=self.themeIsBuiltin, + value=1,command=self.SetThemeType,text='a Built-in Theme') + self.radioThemeCustom=Radiobutton(frameTheme,variable=self.themeIsBuiltin, + value=0,command=self.SetThemeType,text='a Custom Theme') + self.optMenuThemeBuiltin=DynOptionMenu(frameTheme, + self.builtinTheme,None,command=None) + self.optMenuThemeCustom=DynOptionMenu(frameTheme, + self.customTheme,None,command=None) + self.buttonDeleteCustomTheme=Button(frameTheme,text='Delete Custom Theme', + command=self.DeleteCustomTheme) + ##widget packing + #body + frameCustom.pack(side=LEFT,padx=5,pady=10,expand=TRUE,fill=BOTH) + frameTheme.pack(side=LEFT,padx=5,pady=10,fill=Y) + #frameCustom + labelCustomTitle.pack(side=TOP,anchor=W,padx=5,pady=5) + self.frameColourSet.pack(side=TOP,padx=5,pady=5,expand=TRUE,fill=X) + frameFgBg.pack(side=TOP,padx=5,pady=0) + self.textHighlightSample.pack(side=TOP,padx=5,pady=5,expand=TRUE, + fill=BOTH) + buttonSetColour.pack(side=TOP,expand=TRUE,fill=X,padx=8,pady=4) + self.optMenuHighlightTarget.pack(side=TOP,expand=TRUE,fill=X,padx=8,pady=3) + self.radioFg.pack(side=LEFT,anchor=E) + self.radioBg.pack(side=RIGHT,anchor=W) + buttonSaveCustomTheme.pack(side=BOTTOM,fill=X,padx=5,pady=5) + #frameTheme + labelThemeTitle.pack(side=TOP,anchor=W,padx=5,pady=5) + labelTypeTitle.pack(side=TOP,anchor=W,padx=5,pady=5) + self.radioThemeBuiltin.pack(side=TOP,anchor=W,padx=5) + self.radioThemeCustom.pack(side=TOP,anchor=W,padx=5,pady=2) + self.optMenuThemeBuiltin.pack(side=TOP,fill=X,padx=5,pady=5) + self.optMenuThemeCustom.pack(side=TOP,fill=X,anchor=W,padx=5,pady=5) + self.buttonDeleteCustomTheme.pack(side=TOP,fill=X,padx=5,pady=5) + return frame + + def CreatePageKeys(self): + #tkVars + self.bindingTarget=StringVar(self) + self.builtinKeys=StringVar(self) + self.customKeys=StringVar(self) + self.keysAreBuiltin=BooleanVar(self) + self.keyBinding=StringVar(self) + ##widget creation + #body frame + frame=self.tabPages.pages['Keys']['page'] + #body section frames + frameCustom=Frame(frame,borderwidth=2,relief=GROOVE) + frameKeySets=Frame(frame,borderwidth=2,relief=GROOVE) + #frameCustom + frameTarget=Frame(frameCustom) + labelCustomTitle=Label(frameCustom,text='Set Custom Key Bindings') + labelTargetTitle=Label(frameTarget,text='Action - Key(s)') + scrollTargetY=Scrollbar(frameTarget) + scrollTargetX=Scrollbar(frameTarget,orient=HORIZONTAL) + self.listBindings=Listbox(frameTarget,takefocus=FALSE, + exportselection=FALSE) + self.listBindings.bind('<ButtonRelease-1>',self.KeyBindingSelected) + scrollTargetY.config(command=self.listBindings.yview) + scrollTargetX.config(command=self.listBindings.xview) + self.listBindings.config(yscrollcommand=scrollTargetY.set) + self.listBindings.config(xscrollcommand=scrollTargetX.set) + self.buttonNewKeys=Button(frameCustom,text='Get New Keys for Selection', + command=self.GetNewKeys,state=DISABLED) + buttonSaveCustomKeys=Button(frameCustom, + text='Save as New Custom Key Set',command=self.SaveAsNewKeySet) + #frameKeySets + labelKeysTitle=Label(frameKeySets,text='Select a Key Set') + labelTypeTitle=Label(frameKeySets,text='Select : ') + self.radioKeysBuiltin=Radiobutton(frameKeySets,variable=self.keysAreBuiltin, + value=1,command=self.SetKeysType,text='a Built-in Key Set') + self.radioKeysCustom=Radiobutton(frameKeySets,variable=self.keysAreBuiltin, + value=0,command=self.SetKeysType,text='a Custom Key Set') + self.optMenuKeysBuiltin=DynOptionMenu(frameKeySets, + self.builtinKeys,None,command=None) + self.optMenuKeysCustom=DynOptionMenu(frameKeySets, + self.customKeys,None,command=None) + self.buttonDeleteCustomKeys=Button(frameKeySets,text='Delete Custom Key Set', + command=self.DeleteCustomKeys) + ##widget packing + #body + frameCustom.pack(side=LEFT,padx=5,pady=5,expand=TRUE,fill=BOTH) + frameKeySets.pack(side=LEFT,padx=5,pady=5,fill=Y) + #frameCustom + labelCustomTitle.pack(side=TOP,anchor=W,padx=5,pady=5) + buttonSaveCustomKeys.pack(side=BOTTOM,fill=X,padx=5,pady=5) + self.buttonNewKeys.pack(side=BOTTOM,fill=X,padx=5,pady=5) + frameTarget.pack(side=LEFT,padx=5,pady=5,expand=TRUE,fill=BOTH) + #frame target + frameTarget.columnconfigure(0,weight=1) + frameTarget.rowconfigure(1,weight=1) + labelTargetTitle.grid(row=0,column=0,columnspan=2,sticky=W) + self.listBindings.grid(row=1,column=0,sticky=NSEW) + scrollTargetY.grid(row=1,column=1,sticky=NS) + scrollTargetX.grid(row=2,column=0,sticky=EW) + #frameKeySets + labelKeysTitle.pack(side=TOP,anchor=W,padx=5,pady=5) + labelTypeTitle.pack(side=TOP,anchor=W,padx=5,pady=5) + self.radioKeysBuiltin.pack(side=TOP,anchor=W,padx=5) + self.radioKeysCustom.pack(side=TOP,anchor=W,padx=5,pady=2) + self.optMenuKeysBuiltin.pack(side=TOP,fill=X,padx=5,pady=5) + self.optMenuKeysCustom.pack(side=TOP,fill=X,anchor=W,padx=5,pady=5) + self.buttonDeleteCustomKeys.pack(side=TOP,fill=X,padx=5,pady=5) + return frame + + def CreatePageGeneral(self): + #tkVars + self.winWidth=StringVar(self) + self.winHeight=StringVar(self) + self.startupEdit=IntVar(self) + self.userHelpBrowser=BooleanVar(self) + self.helpBrowser=StringVar(self) + #widget creation + #body + frame=self.tabPages.pages['General']['page'] + #body section frames + frameRun=Frame(frame,borderwidth=2,relief=GROOVE) + frameWinSize=Frame(frame,borderwidth=2,relief=GROOVE) + frameHelp=Frame(frame,borderwidth=2,relief=GROOVE) + #frameRun + labelRunTitle=Label(frameRun,text='Startup Preferences') + labelRunChoiceTitle=Label(frameRun,text='On Startup : ') + radioStartupEdit=Radiobutton(frameRun,variable=self.startupEdit, + value=1,command=self.SetKeysType,text="Open Edit Window") + radioStartupShell=Radiobutton(frameRun,variable=self.startupEdit, + value=0,command=self.SetKeysType,text='Open Shell Window') + #frameWinSize + labelWinSizeTitle=Label(frameWinSize,text='Initial Window Size'+ + ' (in characters)') + labelWinWidthTitle=Label(frameWinSize,text='Width') + entryWinWidth=Entry(frameWinSize,textvariable=self.winWidth, + width=3) + labelWinHeightTitle=Label(frameWinSize,text='Height') + entryWinHeight=Entry(frameWinSize,textvariable=self.winHeight, + width=3) + #frameHelp + labelHelpTitle=Label(frameHelp,text='Help Options') + frameHelpList=Frame(frameHelp) + frameHelpListButtons=Frame(frameHelpList) + labelHelpListTitle=Label(frameHelpList,text='Additional Help Sources:') + scrollHelpList=Scrollbar(frameHelpList) + self.listHelp=Listbox(frameHelpList,height=5,takefocus=FALSE, + exportselection=FALSE) + scrollHelpList.config(command=self.listHelp.yview) + self.listHelp.config(yscrollcommand=scrollHelpList.set) + self.listHelp.bind('<ButtonRelease-1>',self.HelpSourceSelected) + self.buttonHelpListEdit=Button(frameHelpListButtons,text='Edit', + state=DISABLED,width=8,command=self.HelpListItemEdit) + self.buttonHelpListAdd=Button(frameHelpListButtons,text='Add', + width=8,command=self.HelpListItemAdd) + self.buttonHelpListRemove=Button(frameHelpListButtons,text='Remove', + state=DISABLED,width=8,command=self.HelpListItemRemove) + # the following is better handled by the BROWSER environment + # variable under unix/linux + #checkHelpBrowser=Checkbutton(frameHelp,variable=self.userHelpBrowser, + # onvalue=1,offvalue=0,text='user specified (html) help browser:', + # command=self.OnCheckUserHelpBrowser) + #self.entryHelpBrowser=Entry(frameHelp,textvariable=self.helpBrowser, + # width=40) + #widget packing + #body + frameRun.pack(side=TOP,padx=5,pady=5,fill=X) + frameWinSize.pack(side=TOP,padx=5,pady=5,fill=X) + frameHelp.pack(side=TOP,padx=5,pady=5,expand=TRUE,fill=BOTH) + #frameRun + labelRunTitle.pack(side=TOP,anchor=W,padx=5,pady=5) + labelRunChoiceTitle.pack(side=LEFT,anchor=W,padx=5,pady=5) + radioStartupEdit.pack(side=LEFT,anchor=W,padx=5,pady=5) + radioStartupShell.pack(side=LEFT,anchor=W,padx=5,pady=5) + #frameWinSize + labelWinSizeTitle.pack(side=LEFT,anchor=W,padx=5,pady=5) + entryWinHeight.pack(side=RIGHT,anchor=E,padx=10,pady=5) + labelWinHeightTitle.pack(side=RIGHT,anchor=E,pady=5) + entryWinWidth.pack(side=RIGHT,anchor=E,padx=10,pady=5) + labelWinWidthTitle.pack(side=RIGHT,anchor=E,pady=5) + #frameHelp + labelHelpTitle.pack(side=TOP,anchor=W,padx=5,pady=5) + frameHelpListButtons.pack(side=RIGHT,padx=5,pady=5,fill=Y) + frameHelpList.pack(side=TOP,padx=5,pady=5,expand=TRUE,fill=BOTH) + labelHelpListTitle.pack(side=TOP,anchor=W) + scrollHelpList.pack(side=RIGHT,anchor=W,fill=Y) + self.listHelp.pack(side=LEFT,anchor=E,expand=TRUE,fill=BOTH) + self.buttonHelpListEdit.pack(side=TOP,anchor=W,pady=5) + self.buttonHelpListAdd.pack(side=TOP,anchor=W) + self.buttonHelpListRemove.pack(side=TOP,anchor=W,pady=5) + #checkHelpBrowser.pack(side=TOP,anchor=W,padx=5) + #self.entryHelpBrowser.pack(side=TOP,anchor=W,padx=5,pady=5) + return frame + + def AttachVarCallbacks(self): + self.fontSize.trace_variable('w',self.VarChanged_fontSize) + self.fontName.trace_variable('w',self.VarChanged_fontName) + self.fontBold.trace_variable('w',self.VarChanged_fontBold) + self.spaceNum.trace_variable('w',self.VarChanged_spaceNum) + #self.tabCols.trace_variable('w',self.VarChanged_tabCols) + self.indentBySpaces.trace_variable('w',self.VarChanged_indentBySpaces) + self.colour.trace_variable('w',self.VarChanged_colour) + self.builtinTheme.trace_variable('w',self.VarChanged_builtinTheme) + self.customTheme.trace_variable('w',self.VarChanged_customTheme) + self.themeIsBuiltin.trace_variable('w',self.VarChanged_themeIsBuiltin) + self.highlightTarget.trace_variable('w',self.VarChanged_highlightTarget) + self.keyBinding.trace_variable('w',self.VarChanged_keyBinding) + self.builtinKeys.trace_variable('w',self.VarChanged_builtinKeys) + self.customKeys.trace_variable('w',self.VarChanged_customKeys) + self.keysAreBuiltin.trace_variable('w',self.VarChanged_keysAreBuiltin) + self.winWidth.trace_variable('w',self.VarChanged_winWidth) + self.winHeight.trace_variable('w',self.VarChanged_winHeight) + self.startupEdit.trace_variable('w',self.VarChanged_startupEdit) + + def VarChanged_fontSize(self,*params): + value=self.fontSize.get() + self.AddChangedItem('main','EditorWindow','font-size',value) + + def VarChanged_fontName(self,*params): + value=self.fontName.get() + self.AddChangedItem('main','EditorWindow','font',value) + + def VarChanged_fontBold(self,*params): + value=self.fontBold.get() + self.AddChangedItem('main','EditorWindow','font-bold',value) + + def VarChanged_indentBySpaces(self,*params): + value=self.indentBySpaces.get() + self.AddChangedItem('main','Indent','use-spaces',value) + + def VarChanged_spaceNum(self,*params): + value=self.spaceNum.get() + self.AddChangedItem('main','Indent','num-spaces',value) + + #def VarChanged_tabCols(self,*params): + # value=self.tabCols.get() + # self.AddChangedItem('main','Indent','tab-cols',value) + + def VarChanged_colour(self,*params): + self.OnNewColourSet() + + def VarChanged_builtinTheme(self,*params): + value=self.builtinTheme.get() + self.AddChangedItem('main','Theme','name',value) + self.PaintThemeSample() + + def VarChanged_customTheme(self,*params): + value=self.customTheme.get() + if value != '- no custom themes -': + self.AddChangedItem('main','Theme','name',value) + self.PaintThemeSample() + + def VarChanged_themeIsBuiltin(self,*params): + value=self.themeIsBuiltin.get() + self.AddChangedItem('main','Theme','default',value) + if value: + self.VarChanged_builtinTheme() + else: + self.VarChanged_customTheme() + + def VarChanged_highlightTarget(self,*params): + self.SetHighlightTarget() + + def VarChanged_keyBinding(self,*params): + value=self.keyBinding.get() + keySet=self.customKeys.get() + event=self.listBindings.get(ANCHOR).split()[0] + if idleConf.IsCoreBinding(event): + #this is a core keybinding + self.AddChangedItem('keys',keySet,event,value) + else: #this is an extension key binding + extName=idleConf.GetExtnNameForEvent(event) + extKeybindSection=extName+'_cfgBindings' + self.AddChangedItem('extensions',extKeybindSection,event,value) + + def VarChanged_builtinKeys(self,*params): + value=self.builtinKeys.get() + self.AddChangedItem('main','Keys','name',value) + self.LoadKeysList(value) + + def VarChanged_customKeys(self,*params): + value=self.customKeys.get() + if value != '- no custom keys -': + self.AddChangedItem('main','Keys','name',value) + self.LoadKeysList(value) + + def VarChanged_keysAreBuiltin(self,*params): + value=self.keysAreBuiltin.get() + self.AddChangedItem('main','Keys','default',value) + if value: + self.VarChanged_builtinKeys() + else: + self.VarChanged_customKeys() + + def VarChanged_winWidth(self,*params): + value=self.winWidth.get() + self.AddChangedItem('main','EditorWindow','width',value) + + def VarChanged_winHeight(self,*params): + value=self.winHeight.get() + self.AddChangedItem('main','EditorWindow','height',value) + + def VarChanged_startupEdit(self,*params): + value=self.startupEdit.get() + self.AddChangedItem('main','General','editor-on-startup',value) + + def ResetChangedItems(self): + #When any config item is changed in this dialog, an entry + #should be made in the relevant section (config type) of this + #dictionary. The key should be the config file section name and the + #value a dictionary, whose key:value pairs are item=value pairs for + #that config file section. + self.changedItems={'main':{},'highlight':{},'keys':{},'extensions':{}} + + def AddChangedItem(self,type,section,item,value): + value=str(value) #make sure we use a string + if not self.changedItems[type].has_key(section): + self.changedItems[type][section]={} + self.changedItems[type][section][item]=value + + def GetDefaultItems(self): + dItems={'main':{},'highlight':{},'keys':{},'extensions':{}} + for configType in dItems.keys(): + sections=idleConf.GetSectionList('default',configType) + for section in sections: + dItems[configType][section]={} + options=idleConf.defaultCfg[configType].GetOptionList(section) + for option in options: + dItems[configType][section][option]=( + idleConf.defaultCfg[configType].Get(section,option)) + return dItems + + def SetThemeType(self): + if self.themeIsBuiltin.get(): + self.optMenuThemeBuiltin.config(state=NORMAL) + self.optMenuThemeCustom.config(state=DISABLED) + self.buttonDeleteCustomTheme.config(state=DISABLED) + else: + self.optMenuThemeBuiltin.config(state=DISABLED) + self.radioThemeCustom.config(state=NORMAL) + self.optMenuThemeCustom.config(state=NORMAL) + self.buttonDeleteCustomTheme.config(state=NORMAL) + + def SetKeysType(self): + if self.keysAreBuiltin.get(): + self.optMenuKeysBuiltin.config(state=NORMAL) + self.optMenuKeysCustom.config(state=DISABLED) + self.buttonDeleteCustomKeys.config(state=DISABLED) + else: + self.optMenuKeysBuiltin.config(state=DISABLED) + self.radioKeysCustom.config(state=NORMAL) + self.optMenuKeysCustom.config(state=NORMAL) + self.buttonDeleteCustomKeys.config(state=NORMAL) + + def GetNewKeys(self): + listIndex=self.listBindings.index(ANCHOR) + binding=self.listBindings.get(listIndex) + bindName=binding.split()[0] #first part, up to first space + if self.keysAreBuiltin.get(): + currentKeySetName=self.builtinKeys.get() + else: + currentKeySetName=self.customKeys.get() + currentBindings=idleConf.GetCurrentKeySet() + if currentKeySetName in self.changedItems['keys'].keys(): #unsaved changes + keySetChanges=self.changedItems['keys'][currentKeySetName] + for event in keySetChanges.keys(): + currentBindings[event]=keySetChanges[event].split() + currentKeySequences=currentBindings.values() + newKeys=GetKeysDialog(self,'Get New Keys',bindName, + currentKeySequences).result + if newKeys: #new keys were specified + if self.keysAreBuiltin.get(): #current key set is a built-in + message=('Your changes will be saved as a new Custom Key Set. '+ + 'Enter a name for your new Custom Key Set below.') + newKeySet=self.GetNewKeysName(message) + if not newKeySet: #user cancelled custom key set creation + self.listBindings.select_set(listIndex) + self.listBindings.select_anchor(listIndex) + return + else: #create new custom key set based on previously active key set + self.CreateNewKeySet(newKeySet) + self.listBindings.delete(listIndex) + self.listBindings.insert(listIndex,bindName+' - '+newKeys) + self.listBindings.select_set(listIndex) + self.listBindings.select_anchor(listIndex) + self.keyBinding.set(newKeys) + else: + self.listBindings.select_set(listIndex) + self.listBindings.select_anchor(listIndex) + + def GetNewKeysName(self,message): + usedNames=(idleConf.GetSectionList('user','keys')+ + idleConf.GetSectionList('default','keys')) + newKeySet=GetCfgSectionNameDialog(self,'New Custom Key Set', + message,usedNames).result + return newKeySet + + def SaveAsNewKeySet(self): + newKeysName=self.GetNewKeysName('New Key Set Name:') + if newKeysName: + self.CreateNewKeySet(newKeysName) + + def KeyBindingSelected(self,event): + self.buttonNewKeys.config(state=NORMAL) + + def CreateNewKeySet(self,newKeySetName): + #creates new custom key set based on the previously active key set, + #and makes the new key set active + if self.keysAreBuiltin.get(): + prevKeySetName=self.builtinKeys.get() + else: + prevKeySetName=self.customKeys.get() + prevKeys=idleConf.GetCoreKeys(prevKeySetName) + newKeys={} + for event in prevKeys.keys(): #add key set to changed items + eventName=event[2:-2] #trim off the angle brackets + binding=string.join(prevKeys[event]) + newKeys[eventName]=binding + #handle any unsaved changes to prev key set + if prevKeySetName in self.changedItems['keys'].keys(): + keySetChanges=self.changedItems['keys'][prevKeySetName] + for event in keySetChanges.keys(): + newKeys[event]=keySetChanges[event] + #save the new theme + self.SaveNewKeySet(newKeySetName,newKeys) + #change gui over to the new key set + customKeyList=idleConf.GetSectionList('user','keys') + customKeyList.sort() + self.optMenuKeysCustom.SetMenu(customKeyList,newKeySetName) + self.keysAreBuiltin.set(0) + self.SetKeysType() + + def LoadKeysList(self,keySetName): + reselect=0 + newKeySet=0 + if self.listBindings.curselection(): + reselect=1 + listIndex=self.listBindings.index(ANCHOR) + keySet=idleConf.GetKeySet(keySetName) + bindNames=keySet.keys() + bindNames.sort() + self.listBindings.delete(0,END) + for bindName in bindNames: + key=string.join(keySet[bindName]) #make key(s) into a string + bindName=bindName[2:-2] #trim off the angle brackets + if keySetName in self.changedItems['keys'].keys(): + #handle any unsaved changes to this key set + if bindName in self.changedItems['keys'][keySetName].keys(): + key=self.changedItems['keys'][keySetName][bindName] + self.listBindings.insert(END, bindName+' - '+key) + if reselect: + self.listBindings.see(listIndex) + self.listBindings.select_set(listIndex) + self.listBindings.select_anchor(listIndex) + + def DeleteCustomKeys(self): + keySetName=self.customKeys.get() + if not tkMessageBox.askyesno('Delete Key Set','Are you sure you wish '+ + 'to delete the key set '+`keySetName`+' ?', + parent=self): + return + #remove key set from config + idleConf.userCfg['keys'].remove_section(keySetName) + if self.changedItems['keys'].has_key(keySetName): + del(self.changedItems['keys'][keySetName]) + #write changes + idleConf.userCfg['keys'].Save() + #reload user key set list + itemList=idleConf.GetSectionList('user','keys') + itemList.sort() + if not itemList: + self.radioKeysCustom.config(state=DISABLED) + self.optMenuKeysCustom.SetMenu(itemList,'- no custom keys -') + else: + self.optMenuKeysCustom.SetMenu(itemList,itemList[0]) + #revert to default key set + self.keysAreBuiltin.set(idleConf.defaultCfg['main'].Get('Keys','default')) + self.builtinKeys.set(idleConf.defaultCfg['main'].Get('Keys','name')) + #user can't back out of these changes, they must be applied now + self.Apply() + self.SetKeysType() + + def DeleteCustomTheme(self): + themeName=self.customTheme.get() + if not tkMessageBox.askyesno('Delete Theme','Are you sure you wish '+ + 'to delete the theme '+`themeName`+' ?', + parent=self): + return + #remove theme from config + idleConf.userCfg['highlight'].remove_section(themeName) + if self.changedItems['highlight'].has_key(themeName): + del(self.changedItems['highlight'][themeName]) + #write changes + idleConf.userCfg['highlight'].Save() + #reload user theme list + itemList=idleConf.GetSectionList('user','highlight') + itemList.sort() + if not itemList: + self.radioThemeCustom.config(state=DISABLED) + self.optMenuThemeCustom.SetMenu(itemList,'- no custom themes -') + else: + self.optMenuThemeCustom.SetMenu(itemList,itemList[0]) + #revert to default theme + self.themeIsBuiltin.set(idleConf.defaultCfg['main'].Get('Theme','default')) + self.builtinTheme.set(idleConf.defaultCfg['main'].Get('Theme','name')) + #user can't back out of these changes, they must be applied now + self.Apply() + self.SetThemeType() + + def GetColour(self): + target=self.highlightTarget.get() + prevColour=self.frameColourSet.cget('bg') + rgbTuplet, colourString = tkColorChooser.askcolor(parent=self, + title='Pick new colour for : '+target,initialcolor=prevColour) + if colourString and (colourString!=prevColour): + #user didn't cancel, and they chose a new colour + if self.themeIsBuiltin.get(): #current theme is a built-in + message=('Your changes will be saved as a new Custom Theme. '+ + 'Enter a name for your new Custom Theme below.') + newTheme=self.GetNewThemeName(message) + if not newTheme: #user cancelled custom theme creation + return + else: #create new custom theme based on previously active theme + self.CreateNewTheme(newTheme) + self.colour.set(colourString) + else: #current theme is user defined + self.colour.set(colourString) + + def OnNewColourSet(self): + newColour=self.colour.get() + self.frameColourSet.config(bg=newColour)#set sample + if self.fgHilite.get(): plane='foreground' + else: plane='background' + sampleElement=self.themeElements[self.highlightTarget.get()][0] + apply(self.textHighlightSample.tag_config, + (sampleElement,),{plane:newColour}) + theme=self.customTheme.get() + themeElement=sampleElement+'-'+plane + self.AddChangedItem('highlight',theme,themeElement,newColour) + + def GetNewThemeName(self,message): + usedNames=(idleConf.GetSectionList('user','highlight')+ + idleConf.GetSectionList('default','highlight')) + newTheme=GetCfgSectionNameDialog(self,'New Custom Theme', + message,usedNames).result + return newTheme + + def SaveAsNewTheme(self): + newThemeName=self.GetNewThemeName('New Theme Name:') + if newThemeName: + self.CreateNewTheme(newThemeName) + + def CreateNewTheme(self,newThemeName): + #creates new custom theme based on the previously active theme, + #and makes the new theme active + if self.themeIsBuiltin.get(): + themeType='default' + themeName=self.builtinTheme.get() + else: + themeType='user' + themeName=self.customTheme.get() + newTheme=idleConf.GetThemeDict(themeType,themeName) + #apply any of the old theme's unsaved changes to the new theme + if themeName in self.changedItems['highlight'].keys(): + themeChanges=self.changedItems['highlight'][themeName] + for element in themeChanges.keys(): + newTheme[element]=themeChanges[element] + #save the new theme + self.SaveNewTheme(newThemeName,newTheme) + #change gui over to the new theme + customThemeList=idleConf.GetSectionList('user','highlight') + customThemeList.sort() + self.optMenuThemeCustom.SetMenu(customThemeList,newThemeName) + self.themeIsBuiltin.set(0) + self.SetThemeType() + + def OnListFontButtonRelease(self,event): + self.fontName.set(self.listFontName.get(ANCHOR)) + self.SetFontSample() + + def SetFontSample(self,event=None): + fontName=self.fontName.get() + if self.fontBold.get(): + fontWeight=tkFont.BOLD + else: + fontWeight=tkFont.NORMAL + self.editFont.config(size=self.fontSize.get(), + weight=fontWeight,family=fontName) + + def SetHighlightTarget(self): + if self.highlightTarget.get()=='Cursor': #bg not possible + self.radioFg.config(state=DISABLED) + self.radioBg.config(state=DISABLED) + self.fgHilite.set(1) + else: #both fg and bg can be set + self.radioFg.config(state=NORMAL) + self.radioBg.config(state=NORMAL) + self.fgHilite.set(1) + self.SetColourSample() + + def SetColourSampleBinding(self,*args): + self.SetColourSample() + + def SetColourSample(self): + #set the colour smaple area + tag=self.themeElements[self.highlightTarget.get()][0] + if self.fgHilite.get(): plane='foreground' + else: plane='background' + colour=self.textHighlightSample.tag_cget(tag,plane) + self.frameColourSet.config(bg=colour) + + def PaintThemeSample(self): + if self.themeIsBuiltin.get(): #a default theme + theme=self.builtinTheme.get() + else: #a user theme + theme=self.customTheme.get() + for elementTitle in self.themeElements.keys(): + element=self.themeElements[elementTitle][0] + colours=idleConf.GetHighlight(theme,element) + if element=='cursor': #cursor sample needs special painting + colours['background']=idleConf.GetHighlight(theme, + 'normal', fgBg='bg') + #handle any unsaved changes to this theme + if theme in self.changedItems['highlight'].keys(): + themeDict=self.changedItems['highlight'][theme] + if themeDict.has_key(element+'-foreground'): + colours['foreground']=themeDict[element+'-foreground'] + if themeDict.has_key(element+'-background'): + colours['background']=themeDict[element+'-background'] + apply(self.textHighlightSample.tag_config,(element,),colours) + self.SetColourSample() + +## def OnCheckUserHelpBrowser(self): +## if self.userHelpBrowser.get(): +## self.entryHelpBrowser.config(state=NORMAL) +## else: +## self.entryHelpBrowser.config(state=DISABLED) + + def HelpSourceSelected(self,event): + self.SetHelpListButtonStates() + + def SetHelpListButtonStates(self): + if self.listHelp.size()<1: #no entries in list + self.buttonHelpListEdit.config(state=DISABLED) + self.buttonHelpListRemove.config(state=DISABLED) + else: #there are some entries + if self.listHelp.curselection(): #there currently is a selection + self.buttonHelpListEdit.config(state=NORMAL) + self.buttonHelpListRemove.config(state=NORMAL) + else: #there currently is not a selection + self.buttonHelpListEdit.config(state=DISABLED) + self.buttonHelpListRemove.config(state=DISABLED) + + def HelpListItemAdd(self): + helpSource=GetHelpSourceDialog(self,'New Help Source').result + if helpSource: + self.userHelpList.append( (helpSource[0],helpSource[1]) ) + self.listHelp.insert(END,helpSource[0]) + self.UpdateUserHelpChangedItems() + self.SetHelpListButtonStates() + + def HelpListItemEdit(self): + itemIndex=self.listHelp.index(ANCHOR) + helpSource=self.userHelpList[itemIndex] + newHelpSource=GetHelpSourceDialog(self,'Edit Help Source', + menuItem=helpSource[0],filePath=helpSource[1]).result + if (not newHelpSource) or (newHelpSource==helpSource): + return #no changes + self.userHelpList[itemIndex]=newHelpSource + self.listHelp.delete(itemIndex) + self.listHelp.insert(itemIndex,newHelpSource[0]) + self.UpdateUserHelpChangedItems() + self.SetHelpListButtonStates() + + def HelpListItemRemove(self): + itemIndex=self.listHelp.index(ANCHOR) + del(self.userHelpList[itemIndex]) + self.listHelp.delete(itemIndex) + self.UpdateUserHelpChangedItems() + self.SetHelpListButtonStates() + + def UpdateUserHelpChangedItems(self): + "Clear and rebuild the HelpFiles section in self.changedItems" + self.changedItems['main']['HelpFiles'] = {} + for num in range(1,len(self.userHelpList)+1): + self.AddChangedItem('main','HelpFiles',str(num), + string.join(self.userHelpList[num-1][:2],';')) + + def LoadFontCfg(self): + ##base editor font selection list + fonts=list(tkFont.families(self)) + fonts.sort() + for font in fonts: + self.listFontName.insert(END,font) + configuredFont=idleConf.GetOption('main','EditorWindow','font', + default='courier') + self.fontName.set(configuredFont) + if configuredFont in fonts: + currentFontIndex=fonts.index(configuredFont) + self.listFontName.see(currentFontIndex) + self.listFontName.select_set(currentFontIndex) + self.listFontName.select_anchor(currentFontIndex) + ##font size dropdown + fontSize=idleConf.GetOption('main','EditorWindow','font-size', + default='12') + self.optMenuFontSize.SetMenu(('7','8','9','10','11','12','13','14', + '16','18','20','22'),fontSize ) + ##fontWeight + self.fontBold.set(idleConf.GetOption('main','EditorWindow', + 'font-bold',default=0,type='bool')) + ##font sample + self.SetFontSample() + + def LoadTabCfg(self): + ##indent type radiobuttons + spaceIndent=idleConf.GetOption('main','Indent','use-spaces', + default=1,type='bool') + self.indentBySpaces.set(spaceIndent) + ##indent sizes + spaceNum=idleConf.GetOption('main','Indent','num-spaces', + default=4,type='int') + #tabCols=idleConf.GetOption('main','Indent','tab-cols', + # default=4,type='int') + self.spaceNum.set(spaceNum) + #self.tabCols.set(tabCols) + + def LoadThemeCfg(self): + ##current theme type radiobutton + self.themeIsBuiltin.set(idleConf.GetOption('main','Theme','default', + type='bool',default=1)) + ##currently set theme + currentOption=idleConf.CurrentTheme() + ##load available theme option menus + if self.themeIsBuiltin.get(): #default theme selected + itemList=idleConf.GetSectionList('default','highlight') + itemList.sort() + self.optMenuThemeBuiltin.SetMenu(itemList,currentOption) + itemList=idleConf.GetSectionList('user','highlight') + itemList.sort() + if not itemList: + self.radioThemeCustom.config(state=DISABLED) + self.customTheme.set('- no custom themes -') + else: + self.optMenuThemeCustom.SetMenu(itemList,itemList[0]) + else: #user theme selected + itemList=idleConf.GetSectionList('user','highlight') + itemList.sort() + self.optMenuThemeCustom.SetMenu(itemList,currentOption) + itemList=idleConf.GetSectionList('default','highlight') + itemList.sort() + self.optMenuThemeBuiltin.SetMenu(itemList,itemList[0]) + self.SetThemeType() + ##load theme element option menu + themeNames=self.themeElements.keys() + themeNames.sort(self.__ThemeNameIndexCompare) + self.optMenuHighlightTarget.SetMenu(themeNames,themeNames[0]) + self.PaintThemeSample() + self.SetHighlightTarget() + + def __ThemeNameIndexCompare(self,a,b): + if self.themeElements[a][1]<self.themeElements[b][1]: return -1 + elif self.themeElements[a][1]==self.themeElements[b][1]: return 0 + else: return 1 + + def LoadKeyCfg(self): + ##current keys type radiobutton + self.keysAreBuiltin.set(idleConf.GetOption('main','Keys','default', + type='bool',default=1)) + ##currently set keys + currentOption=idleConf.CurrentKeys() + ##load available keyset option menus + if self.keysAreBuiltin.get(): #default theme selected + itemList=idleConf.GetSectionList('default','keys') + itemList.sort() + self.optMenuKeysBuiltin.SetMenu(itemList,currentOption) + itemList=idleConf.GetSectionList('user','keys') + itemList.sort() + if not itemList: + self.radioKeysCustom.config(state=DISABLED) + self.customKeys.set('- no custom keys -') + else: + self.optMenuKeysCustom.SetMenu(itemList,itemList[0]) + else: #user key set selected + itemList=idleConf.GetSectionList('user','keys') + itemList.sort() + self.optMenuKeysCustom.SetMenu(itemList,currentOption) + itemList=idleConf.GetSectionList('default','keys') + itemList.sort() + self.optMenuKeysBuiltin.SetMenu(itemList,itemList[0]) + self.SetKeysType() + ##load keyset element list + keySetName=idleConf.CurrentKeys() + self.LoadKeysList(keySetName) + + def LoadGeneralCfg(self): + #startup state + self.startupEdit.set(idleConf.GetOption('main','General', + 'editor-on-startup',default=1,type='bool')) + #initial window size + self.winWidth.set(idleConf.GetOption('main','EditorWindow','width')) + self.winHeight.set(idleConf.GetOption('main','EditorWindow','height')) + # additional help sources + self.userHelpList = idleConf.GetAllExtraHelpSourcesList() + for helpItem in self.userHelpList: + self.listHelp.insert(END,helpItem[0]) + self.SetHelpListButtonStates() + #self.userHelpBrowser.set(idleConf.GetOption('main','General', + # 'user-help-browser',default=0,type='bool')) + #self.helpBrowser.set(idleConf.GetOption('main','General', + # 'user-help-browser-command',default='')) + #self.OnCheckUserHelpBrowser() + + def LoadConfigs(self): + """ + load configuration from default and user config files and populate + the widgets on the config dialog pages. + """ + ### fonts / tabs page + self.LoadFontCfg() + self.LoadTabCfg() + ### highlighting page + self.LoadThemeCfg() + ### keys page + self.LoadKeyCfg() + ### general page + self.LoadGeneralCfg() + + def SaveNewKeySet(self,keySetName,keySet): + """ + save a newly created core key set. + keySetName - string, the name of the new key set + keySet - dictionary containing the new key set + """ + if not idleConf.userCfg['keys'].has_section(keySetName): + idleConf.userCfg['keys'].add_section(keySetName) + for event in keySet.keys(): + value=keySet[event] + idleConf.userCfg['keys'].SetOption(keySetName,event,value) + + def SaveNewTheme(self,themeName,theme): + """ + save a newly created theme. + themeName - string, the name of the new theme + theme - dictionary containing the new theme + """ + if not idleConf.userCfg['highlight'].has_section(themeName): + idleConf.userCfg['highlight'].add_section(themeName) + for element in theme.keys(): + value=theme[element] + idleConf.userCfg['highlight'].SetOption(themeName,element,value) + + def SetUserValue(self,configType,section,item,value): + if idleConf.defaultCfg[configType].has_option(section,item): + if idleConf.defaultCfg[configType].Get(section,item)==value: + #the setting equals a default setting, remove it from user cfg + return idleConf.userCfg[configType].RemoveOption(section,item) + #if we got here set the option + return idleConf.userCfg[configType].SetOption(section,item,value) + + def SaveAllChangedConfigs(self): + "Save configuration changes to the user config file." + idleConf.userCfg['main'].Save() + for configType in self.changedItems.keys(): + cfgTypeHasChanges = False + for section in self.changedItems[configType].keys(): + if section == 'HelpFiles': + #this section gets completely replaced + idleConf.userCfg['main'].remove_section('HelpFiles') + cfgTypeHasChanges = True + for item in self.changedItems[configType][section].keys(): + value = self.changedItems[configType][section][item] + if self.SetUserValue(configType,section,item,value): + cfgTypeHasChanges = True + if cfgTypeHasChanges: + idleConf.userCfg[configType].Save() + self.ResetChangedItems() #clear the changed items dict + + def ActivateConfigChanges(self): + #things that need to be done to make + #applied config changes dynamic: + #update editor/shell font and repaint + #dynamically update indentation setttings + #update theme and repaint + #update keybindings and re-bind + #update user help sources menu + winInstances=self.parent.instanceDict.keys() + for instance in winInstances: + instance.ResetColorizer() + instance.ResetFont() + instance.ResetKeybindings() + instance.reset_help_menu_entries() + + def Cancel(self): + self.destroy() + + def Ok(self): + self.Apply() + self.destroy() + + def Apply(self): + self.SaveAllChangedConfigs() + self.ActivateConfigChanges() + + def Help(self): + pass + +if __name__ == '__main__': + #test the dialog + root=Tk() + Button(root,text='Dialog', + command=lambda:ConfigDialog(root,'Settings')).pack() + root.instanceDict={} + root.mainloop() diff --git a/Tools/idle/configHandler.py b/Tools/idle/configHandler.py new file mode 100644 index 0000000..fd9cbc4 --- /dev/null +++ b/Tools/idle/configHandler.py @@ -0,0 +1,655 @@ +"""Provides access to stored IDLE configuration information. + +Refer to the comments at the beginning of config-main.def for a description of +the available configuration files and the design implemented to update user +configuration information. In particular, user configuration choices which +duplicate the defaults will be removed from the user's configuration files, +and if a file becomes empty, it will be deleted. + +The contents of the user files may be altered using the Options/Configure IDLE +menu to access the configuration GUI (configDialog.py), or manually. + +Throughout this module there is an emphasis on returning useable defaults +when a problem occurs in returning a requested configuration value back to +idle. This is to allow IDLE to continue to function in spite of errors in +the retrieval of config information. When a default is returned instead of +a requested config value, a message is printed to stderr to aid in +configuration problem notification and resolution. + +""" +import os +import sys +import string +from ConfigParser import ConfigParser, NoOptionError, NoSectionError + +class InvalidConfigType(Exception): pass +class InvalidConfigSet(Exception): pass +class InvalidFgBg(Exception): pass +class InvalidTheme(Exception): pass + +class IdleConfParser(ConfigParser): + """ + A ConfigParser specialised for idle configuration file handling + """ + def __init__(self, cfgFile, cfgDefaults=None): + """ + cfgFile - string, fully specified configuration file name + """ + self.file=cfgFile + ConfigParser.__init__(self,defaults=cfgDefaults) + + def Get(self, section, option, type=None, default=None): + """ + Get an option value for given section/option or return default. + If type is specified, return as type. + """ + if type=='bool': + getVal=self.getboolean + elif type=='int': + getVal=self.getint + else: + getVal=self.get + if self.has_option(section,option): + #return getVal(section, option, raw, vars, default) + return getVal(section, option) + else: + return default + + def GetOptionList(self,section): + """ + Get an option list for given section + """ + if self.has_section(section): + return self.options(section) + else: #return a default value + return [] + + def Load(self): + """ + Load the configuration file from disk + """ + self.read(self.file) + +class IdleUserConfParser(IdleConfParser): + """ + IdleConfigParser specialised for user configuration handling. + """ + + def AddSection(self,section): + """ + if section doesn't exist, add it + """ + if not self.has_section(section): + self.add_section(section) + + def RemoveEmptySections(self): + """ + remove any sections that have no options + """ + for section in self.sections(): + if not self.GetOptionList(section): + self.remove_section(section) + + def IsEmpty(self): + """ + Remove empty sections and then return 1 if parser has no sections + left, else return 0. + """ + self.RemoveEmptySections() + if self.sections(): + return 0 + else: + return 1 + + def RemoveOption(self,section,option): + """ + If section/option exists, remove it. + Returns 1 if option was removed, 0 otherwise. + """ + if self.has_section(section): + return self.remove_option(section,option) + + def SetOption(self,section,option,value): + """ + Sets option to value, adding section if required. + Returns 1 if option was added or changed, otherwise 0. + """ + if self.has_option(section,option): + if self.get(section,option)==value: + return 0 + else: + self.set(section,option,value) + return 1 + else: + if not self.has_section(section): + self.add_section(section) + self.set(section,option,value) + return 1 + + def RemoveFile(self): + """ + Removes the user config file from disk if it exists. + """ + if os.path.exists(self.file): + os.remove(self.file) + + def Save(self): + """Update user configuration file. + + Remove empty sections. If resulting config isn't empty, write the file + to disk. If config is empty, remove the file from disk if it exists. + + """ + if not self.IsEmpty(): + cfgFile=open(self.file,'w') + self.write(cfgFile) + else: + self.RemoveFile() + +class IdleConf: + """ + holds config parsers for all idle config files: + default config files + (idle install dir)/config-main.def + (idle install dir)/config-extensions.def + (idle install dir)/config-highlight.def + (idle install dir)/config-keys.def + user config files + (user home dir)/.idlerc/config-main.cfg + (user home dir)/.idlerc/config-extensions.cfg + (user home dir)/.idlerc/config-highlight.cfg + (user home dir)/.idlerc/config-keys.cfg + """ + def __init__(self): + self.defaultCfg={} + self.userCfg={} + self.cfg={} + self.CreateConfigHandlers() + self.LoadCfgFiles() + #self.LoadCfg() + + def CreateConfigHandlers(self): + """ + set up a dictionary of config parsers for default and user + configurations respectively + """ + #build idle install path + if __name__ != '__main__': # we were imported + idleDir=os.path.dirname(__file__) + else: # we were exec'ed (for testing only) + idleDir=os.path.abspath(sys.path[0]) + userDir=self.GetUserCfgDir() + configTypes=('main','extensions','highlight','keys') + defCfgFiles={} + usrCfgFiles={} + for cfgType in configTypes: #build config file names + defCfgFiles[cfgType]=os.path.join(idleDir,'config-'+cfgType+'.def') + usrCfgFiles[cfgType]=os.path.join(userDir,'config-'+cfgType+'.cfg') + for cfgType in configTypes: #create config parsers + self.defaultCfg[cfgType]=IdleConfParser(defCfgFiles[cfgType]) + self.userCfg[cfgType]=IdleUserConfParser(usrCfgFiles[cfgType]) + + def GetUserCfgDir(self): + """ + Creates (if required) and returns a filesystem directory for storing + user config files. + """ + cfgDir='.idlerc' + userDir=os.path.expanduser('~') + if userDir != '~': #'HOME' exists as a key in os.environ + if not os.path.exists(userDir): + warn=('\n Warning: HOME environment variable points to\n '+ + userDir+'\n but the path does not exist.\n') + sys.stderr.write(warn) + userDir='~' + if userDir=='~': #we still don't have a home directory + #traditionally idle has defaulted to os.getcwd(), is this adeqate? + userDir = os.getcwd() #hack for no real homedir + userDir=os.path.join(userDir,cfgDir) + if not os.path.exists(userDir): + try: #make the config dir if it doesn't exist yet + os.mkdir(userDir) + except IOError: + warn=('\n Warning: unable to create user config directory\n '+ + userDir+'\n') + sys.stderr.write(warn) + return userDir + + def GetOption(self, configType, section, option, default=None, type=None): + """ + Get an option value for given config type and given general + configuration section/option or return a default. If type is specified, + return as type. Firstly the user configuration is checked, with a + fallback to the default configuration, and a final 'catch all' + fallback to a useable passed-in default if the option isn't present in + either the user or the default configuration. + configType must be one of ('main','extensions','highlight','keys') + If a default is returned a warning is printed to stderr. + """ + if self.userCfg[configType].has_option(section,option): + return self.userCfg[configType].Get(section, option, type=type) + elif self.defaultCfg[configType].has_option(section,option): + return self.defaultCfg[configType].Get(section, option, type=type) + else: #returning default, print warning + warning=('\n Warning: configHandler.py - IdleConf.GetOption -\n'+ + ' problem retrieving configration option '+`option`+'\n'+ + ' from section '+`section`+'.\n'+ + ' returning default value: '+`default`+'\n') + sys.stderr.write(warning) + return default + + def GetSectionList(self, configSet, configType): + """ + Get a list of sections from either the user or default config for + the given config type. + configSet must be either 'user' or 'default' + configType must be one of ('main','extensions','highlight','keys') + """ + if not (configType in ('main','extensions','highlight','keys')): + raise InvalidConfigType, 'Invalid configType specified' + if configSet == 'user': + cfgParser=self.userCfg[configType] + elif configSet == 'default': + cfgParser=self.defaultCfg[configType] + else: + raise InvalidConfigSet, 'Invalid configSet specified' + return cfgParser.sections() + + def GetHighlight(self, theme, element, fgBg=None): + """ + return individual highlighting theme elements. + fgBg - string ('fg'or'bg') or None, if None return a dictionary + containing fg and bg colours (appropriate for passing to Tkinter in, + e.g., a tag_config call), otherwise fg or bg colour only as specified. + """ + if self.defaultCfg['highlight'].has_section(theme): + themeDict=self.GetThemeDict('default',theme) + else: + themeDict=self.GetThemeDict('user',theme) + fore=themeDict[element+'-foreground'] + if element=='cursor': #there is no config value for cursor bg + back=themeDict['normal-background'] + else: + back=themeDict[element+'-background'] + highlight={"foreground": fore,"background": back} + if not fgBg: #return dict of both colours + return highlight + else: #return specified colour only + if fgBg == 'fg': + return highlight["foreground"] + if fgBg == 'bg': + return highlight["background"] + else: + raise InvalidFgBg, 'Invalid fgBg specified' + + def GetThemeDict(self,type,themeName): + """ + type - string, 'default' or 'user' theme type + themeName - string, theme name + Returns a dictionary which holds {option:value} for each element + in the specified theme. Values are loaded over a set of ultimate last + fallback defaults to guarantee that all theme elements are present in + a newly created theme. + """ + if type == 'user': + cfgParser=self.userCfg['highlight'] + elif type == 'default': + cfgParser=self.defaultCfg['highlight'] + else: + raise InvalidTheme, 'Invalid theme type specified' + #foreground and background values are provded for each theme element + #(apart from cursor) even though all these values are not yet used + #by idle, to allow for their use in the future. Default values are + #generally black and white. + theme={ 'normal-foreground':'#000000', + 'normal-background':'#ffffff', + 'keyword-foreground':'#000000', + 'keyword-background':'#ffffff', + 'comment-foreground':'#000000', + 'comment-background':'#ffffff', + 'string-foreground':'#000000', + 'string-background':'#ffffff', + 'definition-foreground':'#000000', + 'definition-background':'#ffffff', + 'hilite-foreground':'#000000', + 'hilite-background':'gray', + 'break-foreground':'#ffffff', + 'break-background':'#000000', + 'hit-foreground':'#ffffff', + 'hit-background':'#000000', + 'error-foreground':'#ffffff', + 'error-background':'#000000', + #cursor (only foreground can be set) + 'cursor-foreground':'#000000', + #shell window + 'stdout-foreground':'#000000', + 'stdout-background':'#ffffff', + 'stderr-foreground':'#000000', + 'stderr-background':'#ffffff', + 'console-foreground':'#000000', + 'console-background':'#ffffff' } + for element in theme.keys(): + if not cfgParser.has_option(themeName,element): + #we are going to return a default, print warning + warning=('\n Warning: configHandler.py - IdleConf.GetThemeDict'+ + ' -\n problem retrieving theme element '+`element`+ + '\n from theme '+`themeName`+'.\n'+ + ' returning default value: '+`theme[element]`+'\n') + sys.stderr.write(warning) + colour=cfgParser.Get(themeName,element,default=theme[element]) + theme[element]=colour + return theme + + def CurrentTheme(self): + """ + Returns the name of the currently active theme + """ + return self.GetOption('main','Theme','name',default='') + + def CurrentKeys(self): + """ + Returns the name of the currently active key set + """ + return self.GetOption('main','Keys','name',default='') + + def GetExtensions(self, activeOnly=1): + """ + Gets a list of all idle extensions declared in the config files. + activeOnly - boolean, if true only return active (enabled) extensions + """ + extns=self.RemoveKeyBindNames( + self.GetSectionList('default','extensions')) + userExtns=self.RemoveKeyBindNames( + self.GetSectionList('user','extensions')) + for extn in userExtns: + if extn not in extns: #user has added own extension + extns.append(extn) + if activeOnly: + activeExtns=[] + for extn in extns: + if self.GetOption('extensions',extn,'enable',default=1, + type='bool'): + #the extension is enabled + activeExtns.append(extn) + return activeExtns + else: + return extns + + def RemoveKeyBindNames(self,extnNameList): + #get rid of keybinding section names + names=extnNameList + kbNameIndicies=[] + for name in names: + if name.endswith('_bindings') or name.endswith('_cfgBindings'): + kbNameIndicies.append(names.index(name)) + kbNameIndicies.sort() + kbNameIndicies.reverse() + for index in kbNameIndicies: #delete each keybinding section name + del(names[index]) + return names + + def GetExtnNameForEvent(self,virtualEvent): + """ + Returns the name of the extension that virtualEvent is bound in, or + None if not bound in any extension. + virtualEvent - string, name of the virtual event to test for, without + the enclosing '<< >>' + """ + extName=None + vEvent='<<'+virtualEvent+'>>' + for extn in self.GetExtensions(activeOnly=0): + for event in self.GetExtensionKeys(extn).keys(): + if event == vEvent: + extName=extn + return extName + + def GetExtensionKeys(self,extensionName): + """ + returns a dictionary of the configurable keybindings for a particular + extension,as they exist in the dictionary returned by GetCurrentKeySet; + that is, where previously used bindings are disabled. + """ + keysName=extensionName+'_cfgBindings' + activeKeys=self.GetCurrentKeySet() + extKeys={} + if self.defaultCfg['extensions'].has_section(keysName): + eventNames=self.defaultCfg['extensions'].GetOptionList(keysName) + for eventName in eventNames: + event='<<'+eventName+'>>' + binding=activeKeys[event] + extKeys[event]=binding + return extKeys + + def __GetRawExtensionKeys(self,extensionName): + """ + returns a dictionary of the configurable keybindings for a particular + extension, as defined in the configuration files, or an empty dictionary + if no bindings are found + """ + keysName=extensionName+'_cfgBindings' + extKeys={} + if self.defaultCfg['extensions'].has_section(keysName): + eventNames=self.defaultCfg['extensions'].GetOptionList(keysName) + for eventName in eventNames: + binding=self.GetOption('extensions',keysName, + eventName,default='').split() + event='<<'+eventName+'>>' + extKeys[event]=binding + return extKeys + + def GetExtensionBindings(self,extensionName): + """ + Returns a dictionary of all the event bindings for a particular + extension. The configurable keybindings are returned as they exist in + the dictionary returned by GetCurrentKeySet; that is, where re-used + keybindings are disabled. + """ + bindsName=extensionName+'_bindings' + extBinds=self.GetExtensionKeys(extensionName) + #add the non-configurable bindings + if self.defaultCfg['extensions'].has_section(bindsName): + eventNames=self.defaultCfg['extensions'].GetOptionList(bindsName) + for eventName in eventNames: + binding=self.GetOption('extensions',bindsName, + eventName,default='').split() + event='<<'+eventName+'>>' + extBinds[event]=binding + + return extBinds + + def GetKeyBinding(self, keySetName, eventStr): + """ + returns the keybinding for a specific event. + keySetName - string, name of key binding set + eventStr - string, the virtual event we want the binding for, + represented as a string, eg. '<<event>>' + """ + eventName=eventStr[2:-2] #trim off the angle brackets + binding=self.GetOption('keys',keySetName,eventName,default='').split() + return binding + + def GetCurrentKeySet(self): + return self.GetKeySet(self.CurrentKeys()) + + def GetKeySet(self,keySetName): + """ + Returns a dictionary of: all requested core keybindings, plus the + keybindings for all currently active extensions. If a binding defined + in an extension is already in use, that binding is disabled. + """ + keySet=self.GetCoreKeys(keySetName) + activeExtns=self.GetExtensions(activeOnly=1) + for extn in activeExtns: + extKeys=self.__GetRawExtensionKeys(extn) + if extKeys: #the extension defines keybindings + for event in extKeys.keys(): + if extKeys[event] in keySet.values(): + #the binding is already in use + extKeys[event]='' #disable this binding + keySet[event]=extKeys[event] #add binding + return keySet + + def IsCoreBinding(self,virtualEvent): + """ + returns true if the virtual event is bound in the core idle keybindings. + virtualEvent - string, name of the virtual event to test for, without + the enclosing '<< >>' + """ + return ('<<'+virtualEvent+'>>') in self.GetCoreKeys().keys() + + def GetCoreKeys(self, keySetName=None): + """ + returns the requested set of core keybindings, with fallbacks if + required. + Keybindings loaded from the config file(s) are loaded _over_ these + defaults, so if there is a problem getting any core binding there will + be an 'ultimate last resort fallback' to the CUA-ish bindings + defined here. + """ + keyBindings={ + '<<copy>>': ['<Control-c>', '<Control-C>'], + '<<cut>>': ['<Control-x>', '<Control-X>'], + '<<paste>>': ['<Control-v>', '<Control-V>'], + '<<beginning-of-line>>': ['<Control-a>', '<Home>'], + '<<center-insert>>': ['<Control-l>'], + '<<close-all-windows>>': ['<Control-q>'], + '<<close-window>>': ['<Alt-F4>'], + '<<do-nothing>>': ['<Control-x>'], + '<<end-of-file>>': ['<Control-d>'], + '<<python-docs>>': ['<F1>'], + '<<python-context-help>>': ['<Shift-F1>'], + '<<history-next>>': ['<Alt-n>'], + '<<history-previous>>': ['<Alt-p>'], + '<<interrupt-execution>>': ['<Control-c>'], + '<<view-restart>>': ['<F6>'], + '<<restart-shell>>': ['<Control-F6>'], + '<<open-class-browser>>': ['<Alt-c>'], + '<<open-module>>': ['<Alt-m>'], + '<<open-new-window>>': ['<Control-n>'], + '<<open-window-from-file>>': ['<Control-o>'], + '<<plain-newline-and-indent>>': ['<Control-j>'], + '<<print-window>>': ['<Control-p>'], + '<<redo>>': ['<Control-y>'], + '<<remove-selection>>': ['<Escape>'], + '<<save-copy-of-window-as-file>>': ['<Alt-Shift-s>'], + '<<save-window-as-file>>': ['<Alt-s>'], + '<<save-window>>': ['<Control-s>'], + '<<select-all>>': ['<Alt-a>'], + '<<toggle-auto-coloring>>': ['<Control-slash>'], + '<<undo>>': ['<Control-z>'], + '<<find-again>>': ['<Control-g>', '<F3>'], + '<<find-in-files>>': ['<Alt-F3>'], + '<<find-selection>>': ['<Control-F3>'], + '<<find>>': ['<Control-f>'], + '<<replace>>': ['<Control-h>'], + '<<goto-line>>': ['<Alt-g>'], + '<<smart-backspace>>': ['<Key-BackSpace>'], + '<<newline-and-indent>>': ['<Key-Return> <Key-KP_Enter>'], + '<<smart-indent>>': ['<Key-Tab>'], + '<<indent-region>>': ['<Control-Key-bracketright>'], + '<<dedent-region>>': ['<Control-Key-bracketleft>'], + '<<comment-region>>': ['<Alt-Key-3>'], + '<<uncomment-region>>': ['<Alt-Key-4>'], + '<<tabify-region>>': ['<Alt-Key-5>'], + '<<untabify-region>>': ['<Alt-Key-6>'], + '<<toggle-tabs>>': ['<Alt-Key-t>'], + '<<change-indentwidth>>': ['<Alt-Key-u>'] + } + if keySetName: + for event in keyBindings.keys(): + binding=self.GetKeyBinding(keySetName,event) + if binding: + keyBindings[event]=binding + else: #we are going to return a default, print warning + warning=('\n Warning: configHandler.py - IdleConf.GetCoreKeys'+ + ' -\n problem retrieving key binding for event '+ + `event`+'\n from key set '+`keySetName`+'.\n'+ + ' returning default value: '+`keyBindings[event]`+'\n') + sys.stderr.write(warning) + return keyBindings + + def GetExtraHelpSourceList(self,configSet): + """Fetch list of extra help sources from a given configSet. + + Valid configSets are 'user' or 'default'. Return a list of tuples of + the form (menu_item , path_to_help_file , option), or return the empty + list. 'option' is the sequence number of the help resource. 'option' + values determine the position of the menu items on the Help menu, + therefore the returned list must be sorted by 'option'. + + """ + helpSources=[] + if configSet=='user': + cfgParser=self.userCfg['main'] + elif configSet=='default': + cfgParser=self.defaultCfg['main'] + else: + raise InvalidConfigSet, 'Invalid configSet specified' + options=cfgParser.GetOptionList('HelpFiles') + for option in options: + value=cfgParser.Get('HelpFiles',option,default=';') + if value.find(';')==-1: #malformed config entry with no ';' + menuItem='' #make these empty + helpPath='' #so value won't be added to list + else: #config entry contains ';' as expected + value=string.split(value,';') + menuItem=value[0].strip() + helpPath=value[1].strip() + if menuItem and helpPath: #neither are empty strings + helpSources.append( (menuItem,helpPath,option) ) + helpSources.sort(self.__helpsort) + return helpSources + + def __helpsort(self, h1, h2): + if int(h1[2]) < int(h2[2]): + return -1 + elif int(h1[2]) > int(h2[2]): + return 1 + else: + return 0 + + def GetAllExtraHelpSourcesList(self): + """ + Returns a list of tuples containing the details of all additional help + sources configured, or an empty list if there are none. Tuples are of + the format returned by GetExtraHelpSourceList. + """ + allHelpSources=( self.GetExtraHelpSourceList('default')+ + self.GetExtraHelpSourceList('user') ) + return allHelpSources + + def LoadCfgFiles(self): + """ + load all configuration files. + """ + for key in self.defaultCfg.keys(): + self.defaultCfg[key].Load() + self.userCfg[key].Load() #same keys + + def SaveUserCfgFiles(self): + """ + write all loaded user configuration files back to disk + """ + for key in self.userCfg.keys(): + self.userCfg[key].Save() + +idleConf=IdleConf() + +### module test +if __name__ == '__main__': + def dumpCfg(cfg): + print '\n',cfg,'\n' + for key in cfg.keys(): + sections=cfg[key].sections() + print key + print sections + for section in sections: + options=cfg[key].options(section) + print section + print options + for option in options: + print option, '=', cfg[key].Get(section,option) + dumpCfg(idleConf.defaultCfg) + dumpCfg(idleConf.userCfg) + print idleConf.userCfg['main'].Get('Theme','name') + #print idleConf.userCfg['highlight'].GetDefHighlight('Foo','normal') diff --git a/Tools/idle/configHelpSourceEdit.py b/Tools/idle/configHelpSourceEdit.py new file mode 100644 index 0000000..b781884 --- /dev/null +++ b/Tools/idle/configHelpSourceEdit.py @@ -0,0 +1,157 @@ +"Dialog to specify or edit the parameters for a user configured help source." + +import os + +from Tkinter import * +import tkMessageBox +import tkFileDialog + +class GetHelpSourceDialog(Toplevel): + def __init__(self, parent, title, menuItem='', filePath=''): + """Get menu entry and url/ local file location for Additional Help + + User selects a name for the Help resource and provides a web url + or a local file as its source. The user can enter a url or browse + for the file. + + """ + Toplevel.__init__(self, parent) + self.configure(borderwidth=5) + self.resizable(height=FALSE, width=FALSE) + self.title(title) + self.transient(parent) + self.grab_set() + self.protocol("WM_DELETE_WINDOW", self.Cancel) + self.parent = parent + self.result = None + self.CreateWidgets() + self.menu.set(menuItem) + self.path.set(filePath) + self.withdraw() #hide while setting geometry + #needs to be done here so that the winfo_reqwidth is valid + self.update_idletasks() + #centre dialog over parent: + self.geometry("+%d+%d" % + ((parent.winfo_rootx() + ((parent.winfo_width()/2) + -(self.winfo_reqwidth()/2)), + parent.winfo_rooty() + ((parent.winfo_height()/2) + -(self.winfo_reqheight()/2))))) + self.deiconify() #geometry set, unhide + self.bind('<Return>', self.Ok) + self.wait_window() + + def CreateWidgets(self): + self.menu = StringVar(self) + self.path = StringVar(self) + self.fontSize = StringVar(self) + self.frameMain = Frame(self, borderwidth=2, relief=GROOVE) + self.frameMain.pack(side=TOP, expand=TRUE, fill=BOTH) + labelMenu = Label(self.frameMain, anchor=W, justify=LEFT, + text='Menu Item:') + self.entryMenu = Entry(self.frameMain, textvariable=self.menu, + width=30) + self.entryMenu.focus_set() + labelPath = Label(self.frameMain, anchor=W, justify=LEFT, + text='Help File Path: Enter URL or browse for file') + self.entryPath = Entry(self.frameMain, textvariable=self.path, + width=40) + self.entryMenu.focus_set() + labelMenu.pack(anchor=W, padx=5, pady=3) + self.entryMenu.pack(anchor=W, padx=5, pady=3) + labelPath.pack(anchor=W, padx=5, pady=3) + self.entryPath.pack(anchor=W, padx=5, pady=3) + browseButton = Button(self.frameMain, text='Browse', width=8, + command=self.browseFile) + browseButton.pack(pady=3) + frameButtons = Frame(self) + frameButtons.pack(side=BOTTOM, fill=X) + self.buttonOk = Button(frameButtons, text='OK', + width=8, default=ACTIVE, command=self.Ok) + self.buttonOk.grid(row=0, column=0, padx=5,pady=5) + self.buttonCancel = Button(frameButtons, text='Cancel', + width=8, command=self.Cancel) + self.buttonCancel.grid(row=0, column=1, padx=5, pady=5) + + def browseFile(self): + filetypes = [ + ("HTML Files", "*.htm *.html", "TEXT"), + ("PDF Files", "*.pdf", "TEXT"), + ("Windows Help Files", "*.chm"), + ("Text Files", "*.txt", "TEXT"), + ("All Files", "*")] + path = self.path.get() + if path: + dir, base = os.path.split(path) + else: + base = None + if sys.platform.count('win') or sys.platform.count('nt'): + dir = os.path.join(os.path.dirname(sys.executable), 'Doc') + if not os.path.isdir(dir): + dir = os.getcwd() + else: + dir = os.getcwd() + opendialog = tkFileDialog.Open(parent=self, filetypes=filetypes) + file = opendialog.show(initialdir=dir, initialfile=base) + if file: + self.path.set(file) + + def MenuOk(self): + "Simple validity check for a sensible menu item name" + menuOk = True + menu = self.menu.get() + menu.strip() + if not menu: + tkMessageBox.showerror(title='Menu Item Error', + message='No menu item specified', + parent=self) + self.entryMenu.focus_set() + menuOk = False + elif len(menu) > 30: + tkMessageBox.showerror(title='Menu Item Error', + message='Menu item too long:' + '\nLimit 30 characters.', + parent=self) + self.entryMenu.focus_set() + menuOk = False + return menuOk + + def PathOk(self): + "Simple validity check for menu file path" + pathOk = True + path = self.path.get() + path.strip() + if not path: #no path specified + tkMessageBox.showerror(title='File Path Error', + message='No help file path specified.', + parent=self) + self.entryPath.focus_set() + pathOk = False + elif path.startswith('www.') or path.startswith('http'): + pathOk = True + elif not os.path.exists(path): + tkMessageBox.showerror(title='File Path Error', + message='Help file path does not exist.', + parent=self) + self.entryPath.focus_set() + pathOk = False + return pathOk + + def Ok(self, event=None): + if self.MenuOk() and self.PathOk(): + self.result = (self.menu.get().strip(), + self.path.get().strip()) + self.destroy() + + def Cancel(self, event=None): + self.result = None + self.destroy() + +if __name__ == '__main__': + #test the dialog + root = Tk() + def run(): + keySeq = '' + dlg = GetHelpSourceDialog(root, 'Get Help Source') + print dlg.result + Button(root,text='Dialog', command=run).pack() + root.mainloop() diff --git a/Tools/idle/configSectionNameDialog.py b/Tools/idle/configSectionNameDialog.py new file mode 100644 index 0000000..4f1b002 --- /dev/null +++ b/Tools/idle/configSectionNameDialog.py @@ -0,0 +1,97 @@ +""" +Dialog that allows user to specify a new config file section name. +Used to get new highlight theme and keybinding set names. +""" +from Tkinter import * +import tkMessageBox + +class GetCfgSectionNameDialog(Toplevel): + def __init__(self,parent,title,message,usedNames): + """ + message - string, informational message to display + usedNames - list, list of names already in use for validity check + """ + Toplevel.__init__(self, parent) + self.configure(borderwidth=5) + self.resizable(height=FALSE,width=FALSE) + self.title(title) + self.transient(parent) + self.grab_set() + self.protocol("WM_DELETE_WINDOW", self.Cancel) + self.parent = parent + self.message=message + self.usedNames=usedNames + self.result='' + self.CreateWidgets() + self.withdraw() #hide while setting geometry + self.update_idletasks() + #needs to be done here so that the winfo_reqwidth is valid + self.messageInfo.config(width=self.frameMain.winfo_reqwidth()) + self.geometry("+%d+%d" % + ((parent.winfo_rootx()+((parent.winfo_width()/2) + -(self.winfo_reqwidth()/2)), + parent.winfo_rooty()+((parent.winfo_height()/2) + -(self.winfo_reqheight()/2)) )) ) #centre dialog over parent + self.deiconify() #geometry set, unhide + self.wait_window() + + def CreateWidgets(self): + self.name=StringVar(self) + self.fontSize=StringVar(self) + self.frameMain = Frame(self,borderwidth=2,relief=SUNKEN) + self.frameMain.pack(side=TOP,expand=TRUE,fill=BOTH) + self.messageInfo=Message(self.frameMain,anchor=W,justify=LEFT,padx=5,pady=5, + text=self.message)#,aspect=200) + entryName=Entry(self.frameMain,textvariable=self.name,width=30) + entryName.focus_set() + self.messageInfo.pack(padx=5,pady=5)#,expand=TRUE,fill=BOTH) + entryName.pack(padx=5,pady=5) + frameButtons=Frame(self) + frameButtons.pack(side=BOTTOM,fill=X) + self.buttonOk = Button(frameButtons,text='Ok', + width=8,command=self.Ok) + self.buttonOk.grid(row=0,column=0,padx=5,pady=5) + self.buttonCancel = Button(frameButtons,text='Cancel', + width=8,command=self.Cancel) + self.buttonCancel.grid(row=0,column=1,padx=5,pady=5) + + def NameOk(self): + #simple validity check for a sensible + #ConfigParser file section name + nameOk=1 + name=self.name.get() + name.strip() + if not name: #no name specified + tkMessageBox.showerror(title='Name Error', + message='No name specified.', parent=self) + nameOk=0 + elif len(name)>30: #name too long + tkMessageBox.showerror(title='Name Error', + message='Name too long. It should be no more than '+ + '30 characters.', parent=self) + nameOk=0 + elif name in self.usedNames: + tkMessageBox.showerror(title='Name Error', + message='This name is already in use.', parent=self) + nameOk=0 + return nameOk + + def Ok(self, event=None): + if self.NameOk(): + self.result=self.name.get().strip() + self.destroy() + + def Cancel(self, event=None): + self.result='' + self.destroy() + +if __name__ == '__main__': + #test the dialog + root=Tk() + def run(): + keySeq='' + dlg=GetCfgSectionNameDialog(root,'Get Name', + 'The information here should need to be word wrapped. Test.') + print dlg.result + Button(root,text='Dialog',command=run).pack() + root.mainloop() diff --git a/Tools/idle/dynOptionMenuWidget.py b/Tools/idle/dynOptionMenuWidget.py new file mode 100644 index 0000000..e81f7ba --- /dev/null +++ b/Tools/idle/dynOptionMenuWidget.py @@ -0,0 +1,35 @@ +""" +OptionMenu widget modified to allow dynamic menu reconfiguration +and setting of highlightthickness +""" +from Tkinter import OptionMenu +from Tkinter import _setit +import copy + +class DynOptionMenu(OptionMenu): + """ + unlike OptionMenu, our kwargs can include highlightthickness + """ + def __init__(self, master, variable, value, *values, **kwargs): + #get a copy of kwargs before OptionMenu.__init__ munges them + kwargsCopy=copy.copy(kwargs) + if 'highlightthickness' in kwargs.keys(): + del(kwargs['highlightthickness']) + OptionMenu.__init__(self, master, variable, value, *values, **kwargs) + self.config(highlightthickness=kwargsCopy.get('highlightthickness')) + #self.menu=self['menu'] + self.variable=variable + self.command=kwargs.get('command') + + def SetMenu(self,valueList,value=None): + """ + clear and reload the menu with a new set of options. + valueList - list of new options + value - initial value to set the optionmenu's menubutton to + """ + self['menu'].delete(0,'end') + for item in valueList: + self['menu'].add_command(label=item, + command=_setit(self.variable,item,self.command)) + if value: + self.variable.set(value) diff --git a/Tools/idle/eventparse.py b/Tools/idle/eventparse.py deleted file mode 100644 index f253b2a..0000000 --- a/Tools/idle/eventparse.py +++ /dev/null @@ -1,89 +0,0 @@ -#! /usr/bin/env python - -"""Parse event definitions out of comments in source files.""" - -import sys -import glob -import fileinput -import pprint - -def main(): - hits = [] - sublist = [] - args = sys.argv[1:] - if not args: - args = filter(lambda s: 'A' <= s[0] <= 'Z', glob.glob("*.py")) - if not args: - print "No arguments, no [A-Z]*.py files." - return 1 - for line in fileinput.input(args): - if line[:2] == '#$': - if not sublist: - sublist.append('file %s' % fileinput.filename()) - sublist.append('line %d' % fileinput.lineno()) - sublist.append(line[2:-1].strip()) - else: - if sublist: - hits.append(sublist) - sublist = [] - if sublist: - hits.append(sublist) - sublist = [] - dd = {} - for sublist in hits: - d = {} - for line in sublist: - words = line.split(None, 1) - if len(words) != 2: - continue - tag = words[0] - l = d.get(tag, []) - l.append(words[1]) - d[tag] = l - if d.has_key('event'): - keys = d['event'] - if len(keys) != 1: - print "Multiple event keys in", d - print 'File "%s", line %d' % (d['file'], d['line']) - key = keys[0] - if dd.has_key(key): - print "Duplicate event in", d - print 'File "%s", line %d' % (d['file'], d['line']) - return - dd[key] = d - else: - print "No event key in", d - print 'File "%s", line %d' % (d['file'], d['line']) - winevents = getevents(dd, "win") - unixevents = getevents(dd, "unix") - save = sys.stdout - f = open("keydefs.py", "w") - try: - sys.stdout = f - print "windows_keydefs = \\" - pprint.pprint(winevents) - print - print "unix_keydefs = \\" - pprint.pprint(unixevents) - finally: - sys.stdout = save - f.close() - -def getevents(dd, key): - res = {} - events = dd.keys() - events.sort() - for e in events: - d = dd[e] - if d.has_key(key) or d.has_key("all"): - list = [] - for x in d.get(key, []) + d.get("all", []): - list.append(x) - if key == "unix" and x[:5] == "<Alt-": - x = "<Meta-" + x[5:] - list.append(x) - res[e] = list - return res - -if __name__ == '__main__': - sys.exit(main()) diff --git a/Tools/idle/interruptmodule.c b/Tools/idle/interruptmodule.c new file mode 100644 index 0000000..8e18d5a --- /dev/null +++ b/Tools/idle/interruptmodule.c @@ -0,0 +1,49 @@ +/*********************************************************************** + * interruptmodule.c + * + * Python extension implementing the interrupt module. + * + **********************************************************************/ + +#include "Python.h" + +#ifndef PyDoc_STR +#define PyDoc_VAR(name) static char name[] +#define PyDoc_STR(str) str +#define PyDoc_STRVAR(name,str) PyDoc_VAR(name) = PyDoc_STR(str) +#endif + +/* module documentation */ + +PyDoc_STRVAR(module_doc, +"Provide a way to interrupt the main thread from a subthread.\n\n\ +In threaded Python code the KeyboardInterrupt is always directed to\n\ +the thread which raised it. This extension provides a method,\n\ +interrupt_main, which a subthread can use to raise a KeyboardInterrupt\n\ +in the main thread."); + +/* module functions */ + +static PyObject * +setinterrupt(PyObject * self, PyObject * args) +{ + PyErr_SetInterrupt(); + Py_INCREF(Py_None); + return Py_None; +} + +/* registration table */ + +static struct PyMethodDef methods[] = { + {"interrupt_main", setinterrupt, METH_VARARGS, + PyDoc_STR("Interrupt the main thread")}, + {NULL, NULL} +}; + +/* module initialization */ + +void +initinterrupt(void) +{ + (void) Py_InitModule3("interrupt", methods, module_doc); +} diff --git a/Tools/idle/keybindingDialog.py b/Tools/idle/keybindingDialog.py new file mode 100644 index 0000000..df024e7 --- /dev/null +++ b/Tools/idle/keybindingDialog.py @@ -0,0 +1,262 @@ +""" +dialog for building tkinter accelerator key bindings +""" +from Tkinter import * +import tkMessageBox +import string, os + +class GetKeysDialog(Toplevel): + def __init__(self,parent,title,action,currentKeySequences): + """ + action - string, the name of the virtual event these keys will be + mapped to + currentKeys - list, a list of all key sequence lists currently mapped + to virtual events, for overlap checking + """ + Toplevel.__init__(self, parent) + self.configure(borderwidth=5) + self.resizable(height=FALSE,width=FALSE) + self.title(title) + self.transient(parent) + self.grab_set() + self.protocol("WM_DELETE_WINDOW", self.Cancel) + self.parent = parent + self.action=action + self.currentKeySequences=currentKeySequences + self.result='' + self.keyString=StringVar(self) + self.keyString.set('') + self.SetModifiersForPlatform() + self.modifier_vars = [] + for modifier in self.modifiers: + variable = StringVar(self) + variable.set('') + self.modifier_vars.append(variable) + self.CreateWidgets() + self.LoadFinalKeyList() + self.withdraw() #hide while setting geometry + self.update_idletasks() + self.geometry("+%d+%d" % + ((parent.winfo_rootx()+((parent.winfo_width()/2) + -(self.winfo_reqwidth()/2)), + parent.winfo_rooty()+((parent.winfo_height()/2) + -(self.winfo_reqheight()/2)) )) ) #centre dialog over parent + self.deiconify() #geometry set, unhide + self.wait_window() + + def CreateWidgets(self): + frameMain = Frame(self,borderwidth=2,relief=SUNKEN) + frameMain.pack(side=TOP,expand=TRUE,fill=BOTH) + frameButtons=Frame(self) + frameButtons.pack(side=BOTTOM,fill=X) + self.buttonOk = Button(frameButtons,text='Ok', + width=8,command=self.Ok) + self.buttonOk.grid(row=0,column=0,padx=5,pady=5) + self.buttonCancel = Button(frameButtons,text='Cancel', + width=8,command=self.Cancel) + self.buttonCancel.grid(row=0,column=1,padx=5,pady=5) + self.frameKeySeqBasic = Frame(frameMain) + self.frameKeySeqAdvanced = Frame(frameMain) + self.frameControlsBasic = Frame(frameMain) + self.frameHelpAdvanced = Frame(frameMain) + self.frameKeySeqAdvanced.grid(row=0,column=0,sticky=NSEW,padx=5,pady=5) + self.frameKeySeqBasic.grid(row=0,column=0,sticky=NSEW,padx=5,pady=5) + self.frameKeySeqBasic.lift() + self.frameHelpAdvanced.grid(row=1,column=0,sticky=NSEW,padx=5) + self.frameControlsBasic.grid(row=1,column=0,sticky=NSEW,padx=5) + self.frameControlsBasic.lift() + self.buttonLevel = Button(frameMain,command=self.ToggleLevel, + text='Advanced Key Binding Entry >>') + self.buttonLevel.grid(row=2,column=0,stick=EW,padx=5,pady=5) + labelTitleBasic = Label(self.frameKeySeqBasic, + text="New keys for '"+self.action+"' :") + labelTitleBasic.pack(anchor=W) + labelKeysBasic = Label(self.frameKeySeqBasic,justify=LEFT, + textvariable=self.keyString,relief=GROOVE,borderwidth=2) + labelKeysBasic.pack(ipadx=5,ipady=5,fill=X) + self.modifier_checkbuttons = {} + column = 0 + for modifier, variable in zip(self.modifiers, self.modifier_vars): + label = self.modifier_label.get(modifier, modifier) + check=Checkbutton(self.frameControlsBasic, + command=self.BuildKeyString, + text=label,variable=variable,onvalue=modifier,offvalue='') + check.grid(row=0,column=column,padx=2,sticky=W) + self.modifier_checkbuttons[modifier] = check + column += 1 + labelFnAdvice=Label(self.frameControlsBasic,justify=LEFT, + text="Select the desired modifier\n"+ + "keys above, and final key\n"+ + "from the list on the right.") + labelFnAdvice.grid(row=1,column=0,columnspan=4,padx=2,sticky=W) + self.listKeysFinal=Listbox(self.frameControlsBasic,width=15,height=10, + selectmode=SINGLE) + self.listKeysFinal.bind('<ButtonRelease-1>',self.FinalKeySelected) + self.listKeysFinal.grid(row=0,column=4,rowspan=4,sticky=NS) + scrollKeysFinal=Scrollbar(self.frameControlsBasic,orient=VERTICAL, + command=self.listKeysFinal.yview) + self.listKeysFinal.config(yscrollcommand=scrollKeysFinal.set) + scrollKeysFinal.grid(row=0,column=5,rowspan=4,sticky=NS) + self.buttonClear=Button(self.frameControlsBasic, + text='Clear Keys',command=self.ClearKeySeq) + self.buttonClear.grid(row=2,column=0,columnspan=4) + labelTitleAdvanced = Label(self.frameKeySeqAdvanced,justify=LEFT, + text="Enter new binding(s) for '"+self.action+"' :\n"+ + "(will not be checked for validity)") + labelTitleAdvanced.pack(anchor=W) + self.entryKeysAdvanced=Entry(self.frameKeySeqAdvanced, + textvariable=self.keyString) + self.entryKeysAdvanced.pack(fill=X) + labelHelpAdvanced=Label(self.frameHelpAdvanced,justify=LEFT, + text="Key bindings are specified using tkinter key id's as\n"+ + "in these samples: <Control-f>, <Shift-F2>, <F12>,\n" + "<Control-space>, <Meta-less>, <Control-Alt-Shift-x>.\n\n"+ + "'Emacs style' multi-keystroke bindings are specified as\n"+ + "follows: <Control-x><Control-y> or <Meta-f><Meta-g>.\n\n"+ + "Multiple separate bindings for one action should be\n"+ + "separated by a space, eg., <Alt-v> <Meta-v>." ) + labelHelpAdvanced.grid(row=0,column=0,sticky=NSEW) + + def SetModifiersForPlatform(self): + """Determine list of names of key modifiers for this platform. + + The names are used to build Tk bindings -- it doesn't matter if the + keyboard has these keys, it matters if Tk understands them. The + order is also important: key binding equality depends on it, so + config-keys.def must use the same ordering. + """ + import sys + if sys.platform == 'darwin' and sys.executable.count('.app'): + self.modifiers = ['Shift', 'Control', 'Option', 'Command'] + else: + self.modifiers = ['Control', 'Alt', 'Shift'] + self.modifier_label = {'Control': 'Ctrl'} + + def ToggleLevel(self): + if self.buttonLevel.cget('text')[:8]=='Advanced': + self.ClearKeySeq() + self.buttonLevel.config(text='<< Basic Key Binding Entry') + self.frameKeySeqAdvanced.lift() + self.frameHelpAdvanced.lift() + self.entryKeysAdvanced.focus_set() + else: + self.ClearKeySeq() + self.buttonLevel.config(text='Advanced Key Binding Entry >>') + self.frameKeySeqBasic.lift() + self.frameControlsBasic.lift() + + def FinalKeySelected(self,event): + self.BuildKeyString() + + def BuildKeyString(self): + keyList=[] + modifiers=self.GetModifiers() + finalKey=self.listKeysFinal.get(ANCHOR) + if modifiers: modifiers[0]='<'+modifiers[0] + keyList=keyList+modifiers + if finalKey: + if (not modifiers) and (finalKey not + in self.alphanumKeys+self.punctuationKeys): + finalKey='<'+self.TranslateKey(finalKey) + else: + finalKey=self.TranslateKey(finalKey) + keyList.append(finalKey+'>') + keyStr=string.join(keyList,'-') + self.keyString.set(keyStr) + + def GetModifiers(self): + modList = [variable.get() for variable in self.modifier_vars] + return filter(None, modList) + + def ClearKeySeq(self): + self.listKeysFinal.select_clear(0,END) + self.listKeysFinal.yview(MOVETO, '0.0') + for variable in self.modifier_vars: + variable.set('') + self.keyString.set('') + + def LoadFinalKeyList(self): + #these tuples are also available for use in validity checks + self.functionKeys=('F1','F2','F2','F4','F5','F6','F7','F8','F9', + 'F10','F11','F12') + self.alphanumKeys=tuple(string.ascii_lowercase+string.digits) + self.punctuationKeys=tuple('~!@#%^&*()_-+={}[]|;:,.<>/?') + self.whitespaceKeys=('Tab','Space','Return') + self.editKeys=('BackSpace','Delete','Insert') + self.moveKeys=('Home','End','Page Up','Page Down','Left Arrow', + 'Right Arrow','Up Arrow','Down Arrow') + #make a tuple of most of the useful common 'final' keys + keys=(self.alphanumKeys+self.punctuationKeys+self.functionKeys+ + self.whitespaceKeys+self.editKeys+self.moveKeys) + apply(self.listKeysFinal.insert, + (END,)+keys) + + def TranslateKey(self,key): + #translate from key list value to tkinter key-id + translateDict={'~':'asciitilde','!':'exclam','@':'at','#':'numbersign', + '%':'percent','^':'asciicircum','&':'ampersand','*':'asterisk', + '(':'parenleft',')':'parenright','_':'underscore','-':'minus', + '+':'plus','=':'equal','{':'braceleft','}':'braceright', + '[':'bracketleft',']':'bracketright','|':'bar',';':'semicolon', + ':':'colon',',':'comma','.':'period','<':'less','>':'greater', + '/':'slash','?':'question','Page Up':'Prior','Page Down':'Next', + 'Left Arrow':'Left','Right Arrow':'Right','Up Arrow':'Up', + 'Down Arrow': 'Down'} + if key in translateDict.keys(): + key=translateDict[key] + key='Key-'+key + return key + + def Ok(self, event=None): + if self.KeysOk(): + self.result=self.keyString.get() + self.destroy() + + def Cancel(self, event=None): + self.result='' + self.destroy() + + def KeysOk(self): + #simple validity check + keysOk=1 + keys=self.keyString.get() + keys.strip() + finalKey=self.listKeysFinal.get(ANCHOR) + modifiers=self.GetModifiers() + keySequence=keys.split()#make into a key sequence list for overlap check + if not keys: #no keys specified + tkMessageBox.showerror(title='Key Sequence Error', + message='No keys specified.') + keysOk=0 + elif not keys.endswith('>'): #no final key specified + tkMessageBox.showerror(title='Key Sequence Error', + message='No final key specified.') + keysOk=0 + elif (not modifiers) and (finalKey in + self.alphanumKeys+self.punctuationKeys): + #modifier required + tkMessageBox.showerror(title='Key Sequence Error', + message='No modifier key(s) specified.') + keysOk=0 + elif (modifiers==['Shift']) and (finalKey not + in self.functionKeys+('Tab',)): + #shift alone is only a useful modifier with a function key + tkMessageBox.showerror(title='Key Sequence Error', + message='Shift alone is not a useful modifier '+ + 'when used with this final key key.') + keysOk=0 + elif keySequence in self.currentKeySequences: #keys combo already in use + tkMessageBox.showerror(title='Key Sequence Error', + message='This key combination is already in use.') + keysOk=0 + return keysOk + +if __name__ == '__main__': + #test the dialog + root=Tk() + def run(): + keySeq='' + dlg=GetKeysDialog(root,'Get Keys','find-again',[]) + print dlg.result + Button(root,text='Dialog',command=run).pack() + root.mainloop() diff --git a/Tools/idle/keydefs.py b/Tools/idle/keydefs.py deleted file mode 100644 index 9761258..0000000 --- a/Tools/idle/keydefs.py +++ /dev/null @@ -1,57 +0,0 @@ -windows_keydefs = \ -{'<<Copy>>': ['<Control-c>', '<Control-C>'], - '<<Cut>>': ['<Control-x>', '<Control-X>'], - '<<Paste>>': ['<Control-v>', '<Control-V>'], - '<<beginning-of-line>>': ['<Control-a>', '<Home>'], - '<<center-insert>>': ['<Control-l>'], - '<<close-all-windows>>': ['<Control-q>'], - '<<close-window>>': ['<Alt-F4>'], - '<<dump-undo-state>>': ['<Control-backslash>'], - '<<end-of-file>>': ['<Control-d>'], - '<<help>>': ['<F1>'], - '<<history-next>>': ['<Alt-n>'], - '<<history-previous>>': ['<Alt-p>'], - '<<interrupt-execution>>': ['<Control-c>'], - '<<open-class-browser>>': ['<Alt-c>'], - '<<open-module>>': ['<Alt-m>'], - '<<open-new-window>>': ['<Control-n>'], - '<<open-window-from-file>>': ['<Control-o>'], - '<<plain-newline-and-indent>>': ['<Control-j>'], - '<<print-window>>': ['<Control-p>'], - '<<redo>>': ['<Control-y>'], - '<<remove-selection>>': ['<Escape>'], - '<<save-copy-of-window-as-file>>': ['<Alt-Shift-s>'], - '<<save-window-as-file>>': ['<Alt-s>'], - '<<save-window>>': ['<Control-s>'], - '<<select-all>>': ['<Control-a>'], - '<<toggle-auto-coloring>>': ['<Control-slash>'], - '<<undo>>': ['<Control-z>']} - -unix_keydefs = \ -{'<<Copy>>': ['<Alt-w>', '<Meta-w>'], - '<<Cut>>': ['<Control-w>'], - '<<Paste>>': ['<Control-y>'], - '<<beginning-of-line>>': ['<Control-a>', '<Home>'], - '<<center-insert>>': ['<Control-l>'], - '<<close-all-windows>>': ['<Control-x><Control-c>'], - '<<close-window>>': ['<Control-x><Control-0>', '<Control-x><Key-0>'], - '<<do-nothing>>': ['<Control-x>'], - '<<dump-undo-state>>': ['<Control-backslash>'], - '<<end-of-file>>': ['<Control-d>'], - '<<help>>': ['<F1>'], - '<<history-next>>': ['<Alt-n>', '<Meta-n>'], - '<<history-previous>>': ['<Alt-p>', '<Meta-p>'], - '<<interrupt-execution>>': ['<Control-c>'], - '<<open-class-browser>>': ['<Control-x><Control-b>'], - '<<open-module>>': ['<Control-x><Control-m>'], - '<<open-new-window>>': ['<Control-x><Control-n>'], - '<<open-window-from-file>>': ['<Control-x><Control-f>'], - '<<plain-newline-and-indent>>': ['<Control-j>'], - '<<print-window>>': ['<Control-x><Control-p>'], - '<<redo>>': ['<Alt-z>', '<Meta-z>'], - '<<save-copy-of-window-as-file>>': ['<Control-x><w>'], - '<<save-window-as-file>>': ['<Control-x><Control-w>'], - '<<save-window>>': ['<Control-x><Control-s>'], - '<<select-all>>': ['<Alt-a>', '<Meta-a>'], - '<<toggle-auto-coloring>>': ['<Control-slash>'], - '<<undo>>': ['<Control-z>']} diff --git a/Tools/idle/macosx_main.py b/Tools/idle/macosx_main.py new file mode 100644 index 0000000..bc91a0b --- /dev/null +++ b/Tools/idle/macosx_main.py @@ -0,0 +1,42 @@ +#!/usr/bin/env pythonw +# IDLE.app +# +# Installation: +# see the install_IDLE target in python/dist/src/Mac/OSX/Makefile +# +# Usage: +# +# 1. Double clicking IDLE icon will open IDLE. +# 2. Dropping file on IDLE icon will open that file in IDLE. +# 3. Launch from command line with files with this command-line: +# +# /Applications/Python/IDLE.app/Contents/MacOS/python file1 file2 file3 +# +# + +# Add IDLE.app/Contents/Resources/idlelib to path. +# __file__ refers to this file when it is used as a module, sys.argv[0] +# refers to this file when it is used as a script (pythonw macosx_main.py) +import sys + +from os.path import split, join, isdir +try: + __file__ +except NameError: + __file__ = sys.argv[0] +idlelib = join(split(__file__)[0], 'idlelib') +if isdir(idlelib): + sys.path.append(idlelib) + +# see if we are being asked to execute the subprocess code +if '-p' in sys.argv: + # run expects only the port number in sys.argv + sys.argv.remove('-p') + + # this module will become the namespace used by the interactive + # interpreter; remove all variables we have defined. + del sys, __file__, split, join, isdir, idlelib + __import__('run').main() +else: + # Load idlelib/idle.py which starts the application. + import idle diff --git a/Tools/idle/rpc.py b/Tools/idle/rpc.py new file mode 100644 index 0000000..15946a6 --- /dev/null +++ b/Tools/idle/rpc.py @@ -0,0 +1,580 @@ +"""RPC Implemention, originally written for the Python Idle IDE + +For security reasons, GvR requested that Idle's Python execution server process +connect to the Idle process, which listens for the connection. Since Idle has +has only one client per server, this was not a limitation. + + +---------------------------------+ +-------------+ + | SocketServer.BaseRequestHandler | | SocketIO | + +---------------------------------+ +-------------+ + ^ | register() | + | | unregister()| + | +-------------+ + | ^ ^ + | | | + | + -------------------+ | + | | | + +-------------------------+ +-----------------+ + | RPCHandler | | RPCClient | + | [attribute of RPCServer]| | | + +-------------------------+ +-----------------+ + +The RPCServer handler class is expected to provide register/unregister methods. +RPCHandler inherits the mix-in class SocketIO, which provides these methods. + +See the Idle run.main() docstring for further information on how this was +accomplished in Idle. + +""" + +import sys +import socket +import select +import SocketServer +import struct +import cPickle as pickle +import threading +import traceback +import copy_reg +import types +import marshal + +def unpickle_code(ms): + co = marshal.loads(ms) + assert isinstance(co, types.CodeType) + return co + +def pickle_code(co): + assert isinstance(co, types.CodeType) + ms = marshal.dumps(co) + return unpickle_code, (ms,) + +# XXX KBK 24Aug02 function pickling capability not used in Idle +# def unpickle_function(ms): +# return ms + +# def pickle_function(fn): +# assert isinstance(fn, type.FunctionType) +# return `fn` + +copy_reg.pickle(types.CodeType, pickle_code, unpickle_code) +# copy_reg.pickle(types.FunctionType, pickle_function, unpickle_function) + +BUFSIZE = 8*1024 + +class RPCServer(SocketServer.TCPServer): + + def __init__(self, addr, handlerclass=None): + if handlerclass is None: + handlerclass = RPCHandler + SocketServer.TCPServer.__init__(self, addr, handlerclass) + + def server_bind(self): + "Override TCPServer method, no bind() phase for connecting entity" + pass + + def server_activate(self): + """Override TCPServer method, connect() instead of listen() + + Due to the reversed connection, self.server_address is actually the + address of the Idle Client to which we are connecting. + + """ + self.socket.connect(self.server_address) + + def get_request(self): + "Override TCPServer method, return already connected socket" + return self.socket, self.server_address + + def handle_error(self, request, client_address): + """Override TCPServer method + + Error message goes to __stderr__. No error message if exiting + normally or socket raised EOF. Other exceptions not handled in + server code will cause os._exit. + + """ + try: + raise + except SystemExit: + raise + except EOFError: + pass + except: + erf = sys.__stderr__ + print>>erf, '\n' + '-'*40 + print>>erf, 'Unhandled server exception!' + print>>erf, 'Thread: %s' % threading.currentThread().getName() + print>>erf, 'Client Address: ', client_address + print>>erf, 'Request: ', repr(request) + traceback.print_exc(file=erf) + print>>erf, '\n*** Unrecoverable, server exiting!' + print>>erf, '-'*40 + import os + os._exit(0) + + +objecttable = {} + +class SocketIO: + + nextseq = 0 + + def __init__(self, sock, objtable=None, debugging=None): + self.mainthread = threading.currentThread() + if debugging is not None: + self.debugging = debugging + self.sock = sock + if objtable is None: + objtable = objecttable + self.objtable = objtable + self.cvar = threading.Condition() + self.responses = {} + self.cvars = {} + self.interrupted = False + + def close(self): + sock = self.sock + self.sock = None + if sock is not None: + sock.close() + + def debug(self, *args): + if not self.debugging: + return + s = self.location + " " + str(threading.currentThread().getName()) + for a in args: + s = s + " " + str(a) + print>>sys.__stderr__, s + + def register(self, oid, object): + self.objtable[oid] = object + + def unregister(self, oid): + try: + del self.objtable[oid] + except KeyError: + pass + + def localcall(self, request): + self.debug("localcall:", request) + try: + how, (oid, methodname, args, kwargs) = request + except TypeError: + return ("ERROR", "Bad request format") + assert how == "call" + if not self.objtable.has_key(oid): + return ("ERROR", "Unknown object id: %s" % `oid`) + obj = self.objtable[oid] + if methodname == "__methods__": + methods = {} + _getmethods(obj, methods) + return ("OK", methods) + if methodname == "__attributes__": + attributes = {} + _getattributes(obj, attributes) + return ("OK", attributes) + if not hasattr(obj, methodname): + return ("ERROR", "Unsupported method name: %s" % `methodname`) + method = getattr(obj, methodname) + try: + ret = method(*args, **kwargs) + if isinstance(ret, RemoteObject): + ret = remoteref(ret) + return ("OK", ret) + except SystemExit: + raise + except socket.error: + pass + except: + self.debug("localcall:EXCEPTION") + traceback.print_exc(file=sys.__stderr__) + return ("EXCEPTION", None) + + def remotecall(self, oid, methodname, args, kwargs): + self.debug("remotecall:asynccall: ", oid, methodname) + # XXX KBK 06Feb03 self.interrupted logic may not be necessary if + # subprocess is threaded. + if self.interrupted: + self.interrupted = False + raise KeyboardInterrupt + seq = self.asynccall(oid, methodname, args, kwargs) + return self.asyncreturn(seq) + + def asynccall(self, oid, methodname, args, kwargs): + request = ("call", (oid, methodname, args, kwargs)) + seq = self.newseq() + self.debug(("asynccall:%d:" % seq), oid, methodname, args, kwargs) + self.putmessage((seq, request)) + return seq + + def asyncreturn(self, seq): + self.debug("asyncreturn:%d:call getresponse(): " % seq) + response = self.getresponse(seq, wait=None) + self.debug(("asyncreturn:%d:response: " % seq), response) + return self.decoderesponse(response) + + def decoderesponse(self, response): + how, what = response + if how == "OK": + return what + if how == "EXCEPTION": + self.debug("decoderesponse: EXCEPTION") + return None + if how == "ERROR": + self.debug("decoderesponse: Internal ERROR:", what) + raise RuntimeError, what + raise SystemError, (how, what) + + def mainloop(self): + """Listen on socket until I/O not ready or EOF + + Main thread pollresponse() will loop looking for seq number None, which + never comes, and exit on EOFError. + + """ + try: + self.getresponse(myseq=None, wait=None) + except EOFError: + pass + + def getresponse(self, myseq, wait): + response = self._getresponse(myseq, wait) + if response is not None: + how, what = response + if how == "OK": + response = how, self._proxify(what) + return response + + def _proxify(self, obj): + if isinstance(obj, RemoteProxy): + return RPCProxy(self, obj.oid) + if isinstance(obj, types.ListType): + return map(self._proxify, obj) + # XXX Check for other types -- not currently needed + return obj + + def _getresponse(self, myseq, wait): + self.debug("_getresponse:myseq:", myseq) + if threading.currentThread() is self.mainthread: + # Main thread: does all reading of requests or responses + # Loop here, blocking each time until socket is ready. + while 1: + response = self.pollresponse(myseq, wait) + if response is not None: + return response + else: + # Auxiliary thread: wait for notification from main thread + self.cvar.acquire() + self.cvars[myseq] = self.cvar + while not self.responses.has_key(myseq): + self.cvar.wait() + response = self.responses[myseq] + del self.responses[myseq] + del self.cvars[myseq] + self.cvar.release() + return response + + def newseq(self): + self.nextseq = seq = self.nextseq + 2 + return seq + + def putmessage(self, message): + self.debug("putmessage:%d:" % message[0]) + try: + s = pickle.dumps(message) + except: + print >>sys.__stderr__, "Cannot pickle:", `message` + raise + s = struct.pack("<i", len(s)) + s + while len(s) > 0: + try: + n = self.sock.send(s) + except AttributeError: + # socket was closed + raise IOError + else: + s = s[n:] + + def ioready(self, wait=0.0): + r, w, x = select.select([self.sock.fileno()], [], [], wait) + return len(r) + + buffer = "" + bufneed = 4 + bufstate = 0 # meaning: 0 => reading count; 1 => reading data + + def pollpacket(self, wait=0.0): + self._stage0() + if len(self.buffer) < self.bufneed: + if not self.ioready(wait): + return None + try: + s = self.sock.recv(BUFSIZE) + except socket.error: + raise EOFError + if len(s) == 0: + raise EOFError + self.buffer += s + self._stage0() + return self._stage1() + + def _stage0(self): + if self.bufstate == 0 and len(self.buffer) >= 4: + s = self.buffer[:4] + self.buffer = self.buffer[4:] + self.bufneed = struct.unpack("<i", s)[0] + self.bufstate = 1 + + def _stage1(self): + if self.bufstate == 1 and len(self.buffer) >= self.bufneed: + packet = self.buffer[:self.bufneed] + self.buffer = self.buffer[self.bufneed:] + self.bufneed = 4 + self.bufstate = 0 + return packet + + def pollmessage(self, wait=0.0): + packet = self.pollpacket(wait) + if packet is None: + return None + try: + message = pickle.loads(packet) + except: + print >>sys.__stderr__, "-----------------------" + print >>sys.__stderr__, "cannot unpickle packet:", `packet` + traceback.print_stack(file=sys.__stderr__) + print >>sys.__stderr__, "-----------------------" + raise + return message + + def pollresponse(self, myseq, wait=0.0): + """Handle messages received on the socket. + + Some messages received may be asynchronous 'call' commands, and + some may be responses intended for other threads. + + Loop until message with myseq sequence number is received. Save others + in self.responses and notify the owning thread, except that 'call' + commands are handed off to localcall() and the response sent back + across the link with the appropriate sequence number. + + """ + while 1: + message = self.pollmessage(wait) + if message is None: # socket not ready + return None + #wait = 0.0 # poll on subsequent passes instead of blocking + seq, resq = message + self.debug("pollresponse:%d:myseq:%s" % (seq, myseq)) + if resq[0] == "call": + self.debug("pollresponse:%d:localcall:call:" % seq) + response = self.localcall(resq) + self.debug("pollresponse:%d:localcall:response:%s" + % (seq, response)) + self.putmessage((seq, response)) + continue + elif seq == myseq: + return resq + else: + self.cvar.acquire() + cv = self.cvars.get(seq) + # response involving unknown sequence number is discarded, + # probably intended for prior incarnation + if cv is not None: + self.responses[seq] = resq + cv.notify() + self.cvar.release() + continue + +#----------------- end class SocketIO -------------------- + +class RemoteObject: + # Token mix-in class + pass + +def remoteref(obj): + oid = id(obj) + objecttable[oid] = obj + return RemoteProxy(oid) + +class RemoteProxy: + + def __init__(self, oid): + self.oid = oid + +class RPCHandler(SocketServer.BaseRequestHandler, SocketIO): + + debugging = False + location = "#S" # Server + + def __init__(self, sock, addr, svr): + svr.current_handler = self ## cgt xxx + SocketIO.__init__(self, sock) + SocketServer.BaseRequestHandler.__init__(self, sock, addr, svr) + + def handle(self): + "handle() method required by SocketServer" + self.mainloop() + + def get_remote_proxy(self, oid): + return RPCProxy(self, oid) + +class RPCClient(SocketIO): + + debugging = False + location = "#C" # Client + + nextseq = 1 # Requests coming from the client are odd numbered + + def __init__(self, address, family=socket.AF_INET, type=socket.SOCK_STREAM): + self.listening_sock = socket.socket(family, type) + self.listening_sock.setsockopt(socket.SOL_SOCKET, + socket.SO_REUSEADDR, 1) + self.listening_sock.bind(address) + self.listening_sock.listen(1) + + def accept(self): + working_sock, address = self.listening_sock.accept() + if self.debugging: + print>>sys.__stderr__, "****** Connection request from ", address + if address[0] == '127.0.0.1': + SocketIO.__init__(self, working_sock) + else: + print>>sys.__stderr__, "** Invalid host: ", address + raise socket.error + + def get_remote_proxy(self, oid): + return RPCProxy(self, oid) + +class RPCProxy: + + __methods = None + __attributes = None + + def __init__(self, sockio, oid): + self.sockio = sockio + self.oid = oid + + def __getattr__(self, name): + if self.__methods is None: + self.__getmethods() + if self.__methods.get(name): + return MethodProxy(self.sockio, self.oid, name) + if self.__attributes is None: + self.__getattributes() + if not self.__attributes.has_key(name): + raise AttributeError, name + __getattr__.DebuggerStepThrough=1 + + def __getattributes(self): + self.__attributes = self.sockio.remotecall(self.oid, + "__attributes__", (), {}) + + def __getmethods(self): + self.__methods = self.sockio.remotecall(self.oid, + "__methods__", (), {}) + +def _getmethods(obj, methods): + # Helper to get a list of methods from an object + # Adds names to dictionary argument 'methods' + for name in dir(obj): + attr = getattr(obj, name) + if callable(attr): + methods[name] = 1 + if type(obj) == types.InstanceType: + _getmethods(obj.__class__, methods) + if type(obj) == types.ClassType: + for super in obj.__bases__: + _getmethods(super, methods) + +def _getattributes(obj, attributes): + for name in dir(obj): + attr = getattr(obj, name) + if not callable(attr): + attributes[name] = 1 + +class MethodProxy: + + def __init__(self, sockio, oid, name): + self.sockio = sockio + self.oid = oid + self.name = name + + def __call__(self, *args, **kwargs): + value = self.sockio.remotecall(self.oid, self.name, args, kwargs) + return value + +# +# Self Test +# + +def testServer(addr): + # XXX 25 Jul 02 KBK needs update to use rpc.py register/unregister methods + class RemotePerson: + def __init__(self,name): + self.name = name + def greet(self, name): + print "(someone called greet)" + print "Hello %s, I am %s." % (name, self.name) + print + def getName(self): + print "(someone called getName)" + print + return self.name + def greet_this_guy(self, name): + print "(someone called greet_this_guy)" + print "About to greet %s ..." % name + remote_guy = self.server.current_handler.get_remote_proxy(name) + remote_guy.greet("Thomas Edison") + print "Done." + print + + person = RemotePerson("Thomas Edison") + svr = RPCServer(addr) + svr.register('thomas', person) + person.server = svr # only required if callbacks are used + + # svr.serve_forever() + svr.handle_request() # process once only + +def testClient(addr): + "demonstrates RPC Client" + # XXX 25 Jul 02 KBK needs update to use rpc.py register/unregister methods + import time + clt=RPCClient(addr) + thomas = clt.get_remote_proxy("thomas") + print "The remote person's name is ..." + print thomas.getName() + # print clt.remotecall("thomas", "getName", (), {}) + print + time.sleep(1) + print "Getting remote thomas to say hi..." + thomas.greet("Alexander Bell") + #clt.remotecall("thomas","greet",("Alexander Bell",), {}) + print "Done." + print + time.sleep(2) + # demonstrates remote server calling local instance + class LocalPerson: + def __init__(self,name): + self.name = name + def greet(self, name): + print "You've greeted me!" + def getName(self): + return self.name + person = LocalPerson("Alexander Bell") + clt.register("alexander",person) + thomas.greet_this_guy("alexander") + # clt.remotecall("thomas","greet_this_guy",("alexander",), {}) + +def test(): + addr=("localhost",8833) + if len(sys.argv) == 2: + if sys.argv[1]=='-server': + testServer(addr) + return + testClient(addr) + +if __name__ == '__main__': + test() diff --git a/Tools/idle/run.py b/Tools/idle/run.py new file mode 100644 index 0000000..497cbbd --- /dev/null +++ b/Tools/idle/run.py @@ -0,0 +1,216 @@ +import sys +import time +import socket +import traceback +import threading +import Queue + +import boolcheck + +import CallTips +import RemoteDebugger +import RemoteObjectBrowser +import StackViewer +import rpc +import interrupt + +import __main__ + +# Thread shared globals: Establish a queue between a subthread (which handles +# the socket) and the main thread (which runs user code), plus global +# completion and exit flags: + +server = None # RPCServer instance +queue = Queue.Queue(0) +execution_finished = False +exit_requested = False + + +def main(): + """Start the Python execution server in a subprocess + + In the Python subprocess, RPCServer is instantiated with handlerclass + MyHandler, which inherits register/unregister methods from RPCHandler via + the mix-in class SocketIO. + + When the RPCServer 'server' is instantiated, the TCPServer initialization + creates an instance of run.MyHandler and calls its handle() method. + handle() instantiates a run.Executive object, passing it a reference to the + MyHandler object. That reference is saved as attribute rpchandler of the + Executive instance. The Executive methods have access to the reference and + can pass it on to entities that they command + (e.g. RemoteDebugger.Debugger.start_debugger()). The latter, in turn, can + call MyHandler(SocketIO) register/unregister methods via the reference to + register and unregister themselves. + + """ + global queue, execution_finished, exit_requested + + port = 8833 + if sys.argv[1:]: + port = int(sys.argv[1]) + sys.argv[:] = [""] + sockthread = threading.Thread(target=manage_socket, + name='SockThread', + args=(('localhost', port),)) + sockthread.setDaemon(True) + sockthread.start() + while 1: + try: + if exit_requested: + sys.exit() + # XXX KBK 22Mar03 eventually check queue here! + pass + time.sleep(0.05) + except KeyboardInterrupt: + ##execution_finished = True + continue + +def manage_socket(address): + global server, exit_requested + + for i in range(6): + time.sleep(i) + try: + server = rpc.RPCServer(address, MyHandler) + break + except socket.error, err: + if i < 3: + print>>sys.__stderr__, ".. ", + else: + print>>sys.__stderr__,"\nPython subprocess socket error: "\ + + err[1] + ", retrying...." + else: + print>>sys.__stderr__, "\nConnection to Idle failed, exiting." + exit_requested = True + server.handle_request() # A single request only + + +class MyHandler(rpc.RPCHandler): + + def handle(self): + """Override base method""" + executive = Executive(self) + self.register("exec", executive) + sys.stdin = self.get_remote_proxy("stdin") + sys.stdout = self.get_remote_proxy("stdout") + sys.stderr = self.get_remote_proxy("stderr") + rpc.RPCHandler.getresponse(self, myseq=None, wait=0.5) + + +class Executive: + + def __init__(self, rpchandler): + self.rpchandler = rpchandler + self.locals = __main__.__dict__ + self.calltip = CallTips.CallTips() + + def runcode(self, code): + global queue, execution_finished + + execution_finished = False + queue.put(code) + # dequeue and run in subthread + self.runcode_from_queue() + while not execution_finished: + time.sleep(0.05) + + def runcode_from_queue(self): + global queue, execution_finished + + # poll until queue has code object, using threads, just block? + while True: + try: + code = queue.get(0) + break + except Queue.Empty: + time.sleep(0.05) + try: + exec code in self.locals + except: + self.flush_stdout() + efile = sys.stderr + typ, val, tb = info = sys.exc_info() + sys.last_type, sys.last_value, sys.last_traceback = info + tbe = traceback.extract_tb(tb) + print >>efile, 'Traceback (most recent call last):' + exclude = ("run.py", "rpc.py", "RemoteDebugger.py", "bdb.py") + self.cleanup_traceback(tbe, exclude) + traceback.print_list(tbe, file=efile) + lines = traceback.format_exception_only(typ, val) + for line in lines: + print>>efile, line, + execution_finished = True + else: + self.flush_stdout() + execution_finished = True + + def flush_stdout(self): + try: + if sys.stdout.softspace: + sys.stdout.softspace = 0 + sys.stdout.write("\n") + except (AttributeError, EOFError): + pass + + def cleanup_traceback(self, tb, exclude): + "Remove excluded traces from beginning/end of tb; get cached lines" + orig_tb = tb[:] + while tb: + for rpcfile in exclude: + if tb[0][0].count(rpcfile): + break # found an exclude, break for: and delete tb[0] + else: + break # no excludes, have left RPC code, break while: + del tb[0] + while tb: + for rpcfile in exclude: + if tb[-1][0].count(rpcfile): + break + else: + break + del tb[-1] + if len(tb) == 0: + # exception was in IDLE internals, don't prune! + tb[:] = orig_tb[:] + print>>sys.stderr, "** IDLE Internal Exception: " + for i in range(len(tb)): + fn, ln, nm, line = tb[i] + if nm == '?': + nm = "-toplevel-" + if not line and fn.startswith("<pyshell#"): + line = self.rpchandler.remotecall('linecache', 'getline', + (fn, ln), {}) + tb[i] = fn, ln, nm, line + + def interrupt_the_server(self): + self.rpchandler.interrupted = True + ##print>>sys.__stderr__, "** Interrupt main!" + interrupt.interrupt_main() + + def shutdown_the_server(self): + global exit_requested + + exit_requested = True + + def start_the_debugger(self, gui_adap_oid): + return RemoteDebugger.start_debugger(self.rpchandler, gui_adap_oid) + + def stop_the_debugger(self, idb_adap_oid): + "Unregister the Idb Adapter. Link objects and Idb then subject to GC" + self.rpchandler.unregister(idb_adap_oid) + + def get_the_calltip(self, name): + return self.calltip.fetch_tip(name) + + def stackviewer(self, flist_oid=None): + if not hasattr(sys, "last_traceback"): + return None + flist = None + if flist_oid is not None: + flist = self.rpchandler.get_remote_proxy(flist_oid) + tb = sys.last_traceback + while tb and tb.tb_frame.f_globals["__name__"] in ["rpc", "run"]: + tb = tb.tb_next + item = StackViewer.StackTreeItem(flist, tb) + return RemoteObjectBrowser.remote_object_tree_item(item) diff --git a/Tools/idle/setup.cfg b/Tools/idle/setup.cfg new file mode 100644 index 0000000..c4b5729 --- /dev/null +++ b/Tools/idle/setup.cfg @@ -0,0 +1,4 @@ +[bdist_rpm] +release = 1 +packager = Kurt B. Kaiser <kbk@shore.net> + diff --git a/Tools/idle/tabpage.py b/Tools/idle/tabpage.py new file mode 100644 index 0000000..12f8929 --- /dev/null +++ b/Tools/idle/tabpage.py @@ -0,0 +1,113 @@ +""" +a couple of classes for implementing partial tabbed-page like behaviour +""" + +from Tkinter import * + +class InvalidTabPage(Exception): pass +class AlreadyExists(Exception): pass + +class PageTab(Frame): + """ + a 'page tab' like framed button + """ + def __init__(self,parent): + Frame.__init__(self, parent,borderwidth=2,relief=RIDGE) + self.button=Radiobutton(self,padx=5,pady=5,takefocus=FALSE, + indicatoron=FALSE,highlightthickness=0, + borderwidth=0,selectcolor=self.cget('bg')) + self.button.pack() + +class TabPageSet(Frame): + """ + a set of 'pages' with TabButtons for controlling their display + """ + def __init__(self,parent,pageNames=[],**kw): + """ + pageNames - a list of strings, each string will be the dictionary key + to a page's data, and the name displayed on the page's tab. Should be + specified in desired page order. The first page will be the default + and first active page. + """ + Frame.__init__(self, parent, kw) + self.grid_location(0,0) + self.columnconfigure(0,weight=1) + self.rowconfigure(1,weight=1) + self.tabBar=Frame(self) + self.tabBar.grid(row=0,column=0,sticky=EW) + self.activePage=StringVar(self) + self.defaultPage='' + self.pages={} + for name in pageNames: + self.AddPage(name) + + def ChangePage(self,pageName=None): + if pageName: + if pageName in self.pages.keys(): + self.activePage.set(pageName) + else: + raise InvalidTabPage, 'Invalid TabPage Name' + ## pop up the active 'tab' only + for page in self.pages.keys(): + self.pages[page]['tab'].config(relief=RIDGE) + self.pages[self.GetActivePage()]['tab'].config(relief=RAISED) + ## switch page + self.pages[self.GetActivePage()]['page'].lift() + + def GetActivePage(self): + return self.activePage.get() + + def AddPage(self,pageName): + if pageName in self.pages.keys(): + raise AlreadyExists, 'TabPage Name Already Exists' + self.pages[pageName]={'tab':PageTab(self.tabBar), + 'page':Frame(self,borderwidth=2,relief=RAISED)} + self.pages[pageName]['tab'].button.config(text=pageName, + command=self.ChangePage,variable=self.activePage, + value=pageName) + self.pages[pageName]['tab'].pack(side=LEFT) + self.pages[pageName]['page'].grid(row=1,column=0,sticky=NSEW) + if len(self.pages)==1: # adding first page + self.defaultPage=pageName + self.activePage.set(self.defaultPage) + self.ChangePage() + + def RemovePage(self,pageName): + if not pageName in self.pages.keys(): + raise InvalidTabPage, 'Invalid TabPage Name' + self.pages[pageName]['tab'].pack_forget() + self.pages[pageName]['page'].grid_forget() + self.pages[pageName]['tab'].destroy() + self.pages[pageName]['page'].destroy() + del(self.pages[pageName]) + # handle removing last remaining, or default, or active page + if not self.pages: # removed last remaining page + self.defaultPage='' + return + if pageName==self.defaultPage: # set a new default page + self.defaultPage=\ + self.tabBar.winfo_children()[0].button.cget('text') + if pageName==self.GetActivePage(): # set a new active page + self.activePage.set(self.defaultPage) + self.ChangePage() + +if __name__ == '__main__': + #test dialog + root=Tk() + tabPage=TabPageSet(root,pageNames=['Foobar','Baz']) + tabPage.pack(expand=TRUE,fill=BOTH) + Label(tabPage.pages['Foobar']['page'],text='Foo',pady=20).pack() + Label(tabPage.pages['Foobar']['page'],text='Bar',pady=20).pack() + Label(tabPage.pages['Baz']['page'],text='Baz').pack() + entryPgName=Entry(root) + buttonAdd=Button(root,text='Add Page', + command=lambda:tabPage.AddPage(entryPgName.get())) + buttonRemove=Button(root,text='Remove Page', + command=lambda:tabPage.RemovePage(entryPgName.get())) + labelPgName=Label(root,text='name of page to add/remove:') + buttonAdd.pack(padx=5,pady=5) + buttonRemove.pack(padx=5,pady=5) + labelPgName.pack(padx=5) + entryPgName.pack(padx=5) + tabPage.ChangePage() + root.mainloop() diff --git a/Tools/idle/textView.py b/Tools/idle/textView.py new file mode 100644 index 0000000..23e8bed --- /dev/null +++ b/Tools/idle/textView.py @@ -0,0 +1,77 @@ +##---------------------------------------------------------------------------## +## +## idle - simple text view dialog +## elguavas +## +##---------------------------------------------------------------------------## +""" +simple text browser for idle +""" +from Tkinter import * +import tkMessageBox + +class TextViewer(Toplevel): + """ + simple text viewer dialog for idle + """ + def __init__(self,parent,title,fileName): + """ + fileName - string,should be an absoulute filename + """ + Toplevel.__init__(self, parent) + self.configure(borderwidth=5) + self.geometry("+%d+%d" % (parent.winfo_rootx()+10, + parent.winfo_rooty()+10)) + #elguavas - config placeholders til config stuff completed + self.bg=None + self.fg=None + + self.CreateWidgets() + self.title(title) + self.transient(parent) + self.grab_set() + self.protocol("WM_DELETE_WINDOW", self.Ok) + self.parent = parent + self.textView.focus_set() + #key bindings for this dialog + self.bind('<Return>',self.Ok) #dismiss dialog + self.bind('<Escape>',self.Ok) #dismiss dialog + self.LoadTextFile(fileName) + self.textView.config(state=DISABLED) + self.wait_window() + + def LoadTextFile(self, fileName): + textFile = None + try: + textFile = open(fileName, 'r') + except IOError: + tkMessageBox.showerror(title='File Load Error', + message='Unable to load file '+`fileName`+' .') + else: + self.textView.insert(0.0,textFile.read()) + + def CreateWidgets(self): + frameText = Frame(self) + frameButtons = Frame(self) + self.buttonOk = Button(frameButtons,text='Ok', + command=self.Ok,takefocus=FALSE,default=ACTIVE) + self.scrollbarView = Scrollbar(frameText,orient=VERTICAL, + takefocus=FALSE,highlightthickness=0) + self.textView = Text(frameText,wrap=WORD,highlightthickness=0) + self.scrollbarView.config(command=self.textView.yview) + self.textView.config(yscrollcommand=self.scrollbarView.set) + self.buttonOk.pack(padx=5,pady=5) + self.scrollbarView.pack(side=RIGHT,fill=Y) + self.textView.pack(side=LEFT,expand=TRUE,fill=BOTH) + frameButtons.pack(side=BOTTOM,fill=X) + frameText.pack(side=TOP,expand=TRUE,fill=BOTH) + + def Ok(self, event=None): + self.destroy() + +if __name__ == '__main__': + #test the dialog + root=Tk() + Button(root,text='View', + command=lambda:TextViewer(root,'Text','./textView.py')).pack() + root.mainloop() |