diff options
| author | Guido van Rossum <guido@python.org> | 2003-04-29 10:23:27 +0000 | 
|---|---|---|
| committer | Guido van Rossum <guido@python.org> | 2003-04-29 10:23:27 +0000 | 
| commit | 57cd21fde285d25021ee978cd09ed58159166bf8 (patch) | |
| tree | 57936b5a729c1219ae080d518832b9e17aa219ec /Tools | |
| parent | 19691360c7a8c203d1a94f6074036e810a6f0527 (diff) | |
| download | cpython-git-57cd21fde285d25021ee978cd09ed58159166bf8.tar.gz | |
Checking in IDLEFORK exactly as it appears in the idlefork CVS.
On a branch, for now.
Diffstat (limited to 'Tools')
40 files changed, 4632 insertions, 1865 deletions
diff --git a/Tools/idle/AutoIndent.py b/Tools/idle/AutoIndent.py deleted file mode 100644 index 7bc195b19b..0000000000 --- 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 0000000000..fd7af95bde --- /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 2ce0935ba3..0000000000 --- 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 0000000000..9312f32af3 --- /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 0000000000..ed7ac69b9e --- /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 8eaa8e06b5..0000000000 --- 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 0000000000..f7a839585b --- /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 0000000000..f118ebf574 --- /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 6398b86dd3..0000000000 --- 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 4f295e8dbe..0000000000 --- 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 0000000000..41f910f45b --- /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 e6f767102e..0000000000 --- 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 0000000000..6ba3391372 --- /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 5943e3baec..0000000000 --- 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 7145559c7b..0000000000 --- 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 0000000000..2fc4a428b2 --- /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 0000000000..f682232e49 --- /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 0000000000..d4905e8336 --- /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 0000000000..2b7deab382 --- /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 0000000000..4f2be59f8a --- /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 ee36e13fe7..0000000000 --- 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 0000000000..9d520c106a --- /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 782965f7d1..0000000000 --- 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 aeb6ab96c4..0000000000 --- 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 6f98a3e8e4..0000000000 --- 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 0000000000..814689c821 --- /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 0000000000..fd9cbc4580 --- /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 0000000000..b7818846b3 --- /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 0000000000..4f1b002afc --- /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 0000000000..e81f7babe0 --- /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 f253b2a984..0000000000 --- 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 0000000000..8e18d5af90 --- /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 0000000000..df024e7dc9 --- /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 9761258e5f..0000000000 --- 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 0000000000..bc91a0b35a --- /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 0000000000..15946a660f --- /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 0000000000..497cbbd622 --- /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 0000000000..c4b57294f1 --- /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 0000000000..12f89291da --- /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 0000000000..23e8beda41 --- /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()  | 
