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