Author: Carl Friedrich Bolz-Tereick <[email protected]>
Branch: py3.6
Changeset: r95996:9387f96a5518
Date: 2019-02-13 13:07 +0100
http://bitbucket.org/pypy/pypy/changeset/9387f96a5518/
Log: seems we never updated idlelib with the 3.6 changes!
diff too long, truncating to 2000 out of 7060 lines
diff --git a/lib-python/3/idlelib/autocomplete.py
b/lib-python/3/idlelib/autocomplete.py
new file mode 100644
--- /dev/null
+++ b/lib-python/3/idlelib/autocomplete.py
@@ -0,0 +1,232 @@
+"""autocomplete.py - An IDLE extension for automatically completing names.
+
+This extension can complete either attribute names or file names. It can pop
+a window with all available names, for the user to select from.
+"""
+import os
+import string
+import sys
+
+# These constants represent the two different types of completions.
+# They must be defined here so autocomple_w can import them.
+COMPLETE_ATTRIBUTES, COMPLETE_FILES = range(1, 2+1)
+
+from idlelib import autocomplete_w
+from idlelib.config import idleConf
+from idlelib.hyperparser import HyperParser
+import __main__
+
+# This string includes all chars that may be in an identifier.
+# TODO Update this here and elsewhere.
+ID_CHARS = string.ascii_letters + string.digits + "_"
+
+SEPS = os.sep
+if os.altsep: # e.g. '/' on Windows...
+ SEPS += os.altsep
+
+
+class AutoComplete:
+
+ menudefs = [
+ ('edit', [
+ ("Show Completions", "<<force-open-completions>>"),
+ ])
+ ]
+
+ popupwait = idleConf.GetOption("extensions", "AutoComplete",
+ "popupwait", type="int", default=0)
+
+ def __init__(self, editwin=None):
+ self.editwin = editwin
+ if editwin is not None: # not in subprocess or test
+ self.text = editwin.text
+ self.autocompletewindow = None
+ # id of delayed call, and the index of the text insert when
+ # the delayed call was issued. If _delayed_completion_id is
+ # None, there is no delayed call.
+ self._delayed_completion_id = None
+ self._delayed_completion_index = None
+
+ def _make_autocomplete_window(self):
+ return autocomplete_w.AutoCompleteWindow(self.text)
+
+ def _remove_autocomplete_window(self, event=None):
+ if self.autocompletewindow:
+ self.autocompletewindow.hide_window()
+ self.autocompletewindow = None
+
+ def force_open_completions_event(self, event):
+ """Happens when the user really wants to open a completion list, even
+ if a function call is needed.
+ """
+ self.open_completions(True, False, True)
+
+ def try_open_completions_event(self, event):
+ """Happens when it would be nice to open a completion list, but not
+ really necessary, for example after a dot, so function
+ calls won't be made.
+ """
+ lastchar = self.text.get("insert-1c")
+ if lastchar == ".":
+ self._open_completions_later(False, False, False,
+ COMPLETE_ATTRIBUTES)
+ elif lastchar in SEPS:
+ self._open_completions_later(False, False, False,
+ COMPLETE_FILES)
+
+ def autocomplete_event(self, event):
+ """Happens when the user wants to complete his word, and if necessary,
+ open a completion list after that (if there is more than one
+ completion)
+ """
+ if hasattr(event, "mc_state") and event.mc_state or\
+ not self.text.get("insert linestart", "insert").strip():
+ # A modifier was pressed along with the tab or
+ # there is only previous whitespace on this line, so tab.
+ return None
+ if self.autocompletewindow and self.autocompletewindow.is_active():
+ self.autocompletewindow.complete()
+ return "break"
+ else:
+ opened = self.open_completions(False, True, True)
+ return "break" if opened else None
+
+ def _open_completions_later(self, *args):
+ self._delayed_completion_index = self.text.index("insert")
+ if self._delayed_completion_id is not None:
+ self.text.after_cancel(self._delayed_completion_id)
+ self._delayed_completion_id = \
+ self.text.after(self.popupwait, self._delayed_open_completions,
+ *args)
+
+ def _delayed_open_completions(self, *args):
+ self._delayed_completion_id = None
+ if self.text.index("insert") == self._delayed_completion_index:
+ self.open_completions(*args)
+
+ def open_completions(self, evalfuncs, complete, userWantsWin, mode=None):
+ """Find the completions and create the AutoCompleteWindow.
+ Return True if successful (no syntax error or so found).
+ if complete is True, then if there's nothing to complete and no
+ start of completion, won't open completions and return False.
+ If mode is given, will open a completion list only in this mode.
+ """
+ # Cancel another delayed call, if it exists.
+ if self._delayed_completion_id is not None:
+ self.text.after_cancel(self._delayed_completion_id)
+ self._delayed_completion_id = None
+
+ hp = HyperParser(self.editwin, "insert")
+ curline = self.text.get("insert linestart", "insert")
+ i = j = len(curline)
+ if hp.is_in_string() and (not mode or mode==COMPLETE_FILES):
+ # Find the beginning of the string
+ # fetch_completions will look at the file system to determine
whether the
+ # string value constitutes an actual file name
+ # XXX could consider raw strings here and unescape the string
value if it's
+ # not raw.
+ self._remove_autocomplete_window()
+ mode = COMPLETE_FILES
+ # Find last separator or string start
+ while i and curline[i-1] not in "'\"" + SEPS:
+ i -= 1
+ comp_start = curline[i:j]
+ j = i
+ # Find string start
+ while i and curline[i-1] not in "'\"":
+ i -= 1
+ comp_what = curline[i:j]
+ elif hp.is_in_code() and (not mode or mode==COMPLETE_ATTRIBUTES):
+ self._remove_autocomplete_window()
+ mode = COMPLETE_ATTRIBUTES
+ while i and (curline[i-1] in ID_CHARS or ord(curline[i-1]) > 127):
+ i -= 1
+ comp_start = curline[i:j]
+ if i and curline[i-1] == '.':
+ hp.set_index("insert-%dc" % (len(curline)-(i-1)))
+ comp_what = hp.get_expression()
+ if not comp_what or \
+ (not evalfuncs and comp_what.find('(') != -1):
+ return None
+ else:
+ comp_what = ""
+ else:
+ return None
+
+ if complete and not comp_what and not comp_start:
+ return None
+ comp_lists = self.fetch_completions(comp_what, mode)
+ if not comp_lists[0]:
+ return None
+ self.autocompletewindow = self._make_autocomplete_window()
+ return not self.autocompletewindow.show_window(
+ comp_lists, "insert-%dc" % len(comp_start),
+ complete, mode, userWantsWin)
+
+ def fetch_completions(self, what, mode):
+ """Return a pair of lists of completions for something. The first list
+ is a sublist of the second. Both are sorted.
+
+ If there is a Python subprocess, get the comp. list there. Otherwise,
+ either fetch_completions() is running in the subprocess itself or it
+ was called in an IDLE EditorWindow before any script had been run.
+
+ The subprocess environment is that of the most recently run script. If
+ two unrelated modules are being edited some calltips in the current
+ module may be inoperative if the module was not the last to run.
+ """
+ try:
+ rpcclt = self.editwin.flist.pyshell.interp.rpcclt
+ except:
+ rpcclt = None
+ if rpcclt:
+ return rpcclt.remotecall("exec", "get_the_completion_list",
+ (what, mode), {})
+ else:
+ if mode == COMPLETE_ATTRIBUTES:
+ if what == "":
+ namespace = __main__.__dict__.copy()
+ namespace.update(__main__.__builtins__.__dict__)
+ bigl = eval("dir()", namespace)
+ bigl.sort()
+ if "__all__" in bigl:
+ smalll = sorted(eval("__all__", namespace))
+ else:
+ smalll = [s for s in bigl if s[:1] != '_']
+ else:
+ try:
+ entity = self.get_entity(what)
+ bigl = dir(entity)
+ bigl.sort()
+ if "__all__" in bigl:
+ smalll = sorted(entity.__all__)
+ else:
+ smalll = [s for s in bigl if s[:1] != '_']
+ except:
+ return [], []
+
+ elif mode == COMPLETE_FILES:
+ if what == "":
+ what = "."
+ try:
+ expandedpath = os.path.expanduser(what)
+ bigl = os.listdir(expandedpath)
+ bigl.sort()
+ smalll = [s for s in bigl if s[:1] != '.']
+ except OSError:
+ return [], []
+
+ if not smalll:
+ smalll = bigl
+ return smalll, bigl
+
+ def get_entity(self, name):
+ """Lookup name in a namespace spanning sys.modules and __main.dict__"""
+ namespace = sys.modules.copy()
+ namespace.update(__main__.__dict__)
+ return eval(name, namespace)
+
+
+if __name__ == '__main__':
+ from unittest import main
+ main('idlelib.idle_test.test_autocomplete', verbosity=2)
diff --git a/lib-python/3/idlelib/autoexpand.py
b/lib-python/3/idlelib/autoexpand.py
new file mode 100644
--- /dev/null
+++ b/lib-python/3/idlelib/autoexpand.py
@@ -0,0 +1,105 @@
+'''Complete the current word before the cursor with words in the editor.
+
+Each menu selection or shortcut key selection replaces the word with a
+different word with the same prefix. The search for matches begins
+before the target and moves toward the top of the editor. It then starts
+after the cursor and moves down. It then returns to the original word and
+the cycle starts again.
+
+Changing the current text line or leaving the cursor in a different
+place before requesting the next selection causes AutoExpand to reset
+its state.
+
+This is an extension file and there is only one instance of AutoExpand.
+'''
+import re
+import string
+
+###$ event <<expand-word>>
+###$ win <Alt-slash>
+###$ unix <Alt-slash>
+
+class AutoExpand:
+
+ menudefs = [
+ ('edit', [
+ ('E_xpand Word', '<<expand-word>>'),
+ ]),
+ ]
+
+ wordchars = string.ascii_letters + string.digits + "_"
+
+ def __init__(self, editwin):
+ self.text = editwin.text
+ self.bell = self.text.bell
+ self.state = None
+
+ def expand_word_event(self, event):
+ "Replace the current word with the next expansion."
+ curinsert = self.text.index("insert")
+ curline = self.text.get("insert linestart", "insert lineend")
+ if not self.state:
+ words = self.getwords()
+ index = 0
+ else:
+ words, index, insert, line = self.state
+ if insert != curinsert or line != curline:
+ words = self.getwords()
+ index = 0
+ if not words:
+ self.bell()
+ return "break"
+ word = self.getprevword()
+ self.text.delete("insert - %d chars" % len(word), "insert")
+ newword = words[index]
+ index = (index + 1) % len(words)
+ if index == 0:
+ self.bell() # Warn we cycled around
+ self.text.insert("insert", newword)
+ curinsert = self.text.index("insert")
+ curline = self.text.get("insert linestart", "insert lineend")
+ self.state = words, index, curinsert, curline
+ return "break"
+
+ def getwords(self):
+ "Return a list of words that match the prefix before the cursor."
+ word = self.getprevword()
+ if not word:
+ return []
+ before = self.text.get("1.0", "insert wordstart")
+ wbefore = re.findall(r"\b" + word + r"\w+\b", before)
+ del before
+ after = self.text.get("insert wordend", "end")
+ wafter = re.findall(r"\b" + word + r"\w+\b", after)
+ del after
+ if not wbefore and not wafter:
+ return []
+ words = []
+ dict = {}
+ # search backwards through words before
+ wbefore.reverse()
+ for w in wbefore:
+ if dict.get(w):
+ continue
+ words.append(w)
+ dict[w] = w
+ # search onwards through words after
+ for w in wafter:
+ if dict.get(w):
+ continue
+ words.append(w)
+ dict[w] = w
+ words.append(word)
+ return words
+
+ def getprevword(self):
+ "Return the word prefix before the cursor."
+ line = self.text.get("insert linestart", "insert")
+ i = len(line)
+ while i > 0 and line[i-1] in self.wordchars:
+ i = i-1
+ return line[i:]
+
+if __name__ == '__main__':
+ import unittest
+ unittest.main('idlelib.idle_test.test_autoexpand', verbosity=2)
diff --git a/lib-python/3/idlelib/calltips.py b/lib-python/3/idlelib/calltips.py
new file mode 100644
--- /dev/null
+++ b/lib-python/3/idlelib/calltips.py
@@ -0,0 +1,175 @@
+"""calltips.py - An IDLE Extension to Jog Your Memory
+
+Call Tips are floating windows which display function, class, and method
+parameter and docstring information when you type an opening parenthesis, and
+which disappear when you type a closing parenthesis.
+
+"""
+import inspect
+import re
+import sys
+import textwrap
+import types
+
+from idlelib import calltip_w
+from idlelib.hyperparser import HyperParser
+import __main__
+
+class CallTips:
+
+ menudefs = [
+ ('edit', [
+ ("Show call tip", "<<force-open-calltip>>"),
+ ])
+ ]
+
+ def __init__(self, editwin=None):
+ if editwin is None: # subprocess and test
+ self.editwin = None
+ else:
+ self.editwin = editwin
+ self.text = editwin.text
+ self.active_calltip = None
+ self._calltip_window = self._make_tk_calltip_window
+
+ def close(self):
+ self._calltip_window = None
+
+ def _make_tk_calltip_window(self):
+ # See __init__ for usage
+ return calltip_w.CallTip(self.text)
+
+ def _remove_calltip_window(self, event=None):
+ if self.active_calltip:
+ self.active_calltip.hidetip()
+ self.active_calltip = None
+
+ def force_open_calltip_event(self, event):
+ "The user selected the menu entry or hotkey, open the tip."
+ self.open_calltip(True)
+
+ def try_open_calltip_event(self, event):
+ """Happens when it would be nice to open a CallTip, but not really
+ necessary, for example after an opening bracket, so function calls
+ won't be made.
+ """
+ self.open_calltip(False)
+
+ def refresh_calltip_event(self, event):
+ if self.active_calltip and self.active_calltip.is_active():
+ self.open_calltip(False)
+
+ def open_calltip(self, evalfuncs):
+ self._remove_calltip_window()
+
+ hp = HyperParser(self.editwin, "insert")
+ sur_paren = hp.get_surrounding_brackets('(')
+ if not sur_paren:
+ return
+ hp.set_index(sur_paren[0])
+ expression = hp.get_expression()
+ if not expression:
+ return
+ if not evalfuncs and (expression.find('(') != -1):
+ return
+ argspec = self.fetch_tip(expression)
+ if not argspec:
+ return
+ self.active_calltip = self._calltip_window()
+ self.active_calltip.showtip(argspec, sur_paren[0], sur_paren[1])
+
+ def fetch_tip(self, expression):
+ """Return the argument list and docstring of a function or class.
+
+ If there is a Python subprocess, get the calltip there. Otherwise,
+ either this fetch_tip() is running in the subprocess or it was
+ called in an IDLE running without the subprocess.
+
+ The subprocess environment is that of the most recently run script. If
+ two unrelated modules are being edited some calltips in the current
+ module may be inoperative if the module was not the last to run.
+
+ To find methods, fetch_tip must be fed a fully qualified name.
+
+ """
+ try:
+ rpcclt = self.editwin.flist.pyshell.interp.rpcclt
+ except AttributeError:
+ rpcclt = None
+ if rpcclt:
+ return rpcclt.remotecall("exec", "get_the_calltip",
+ (expression,), {})
+ else:
+ return get_argspec(get_entity(expression))
+
+def get_entity(expression):
+ """Return the object corresponding to expression evaluated
+ in a namespace spanning sys.modules and __main.dict__.
+ """
+ if expression:
+ namespace = sys.modules.copy()
+ namespace.update(__main__.__dict__)
+ try:
+ return eval(expression, namespace)
+ except BaseException:
+ # An uncaught exception closes idle, and eval can raise any
+ # exception, especially if user classes are involved.
+ return None
+
+# The following are used in get_argspec and some in tests
+_MAX_COLS = 85
+_MAX_LINES = 5 # enough for bytes
+_INDENT = ' '*4 # for wrapped signatures
+_first_param = re.compile(r'(?<=\()\w*\,?\s*')
+_default_callable_argspec = "See source or doc"
+
+
+def get_argspec(ob):
+ '''Return a string describing the signature of a callable object, or ''.
+
+ For Python-coded functions and methods, the first line is introspected.
+ Delete 'self' parameter for classes (.__init__) and bound methods.
+ The next lines are the first lines of the doc string up to the first
+ empty line or _MAX_LINES. For builtins, this typically includes
+ the arguments in addition to the return value.
+ '''
+ argspec = ""
+ try:
+ ob_call = ob.__call__
+ except BaseException:
+ return argspec
+ if isinstance(ob, type):
+ fob = ob.__init__
+ elif isinstance(ob_call, types.MethodType):
+ fob = ob_call
+ else:
+ fob = ob
+ if isinstance(fob, (types.FunctionType, types.MethodType)):
+ argspec = inspect.formatargspec(*inspect.getfullargspec(fob))
+ if (isinstance(ob, (type, types.MethodType)) or
+ isinstance(ob_call, types.MethodType)):
+ argspec = _first_param.sub("", argspec)
+
+ lines = (textwrap.wrap(argspec, _MAX_COLS, subsequent_indent=_INDENT)
+ if len(argspec) > _MAX_COLS else [argspec] if argspec else [])
+
+ if isinstance(ob_call, types.MethodType):
+ doc = ob_call.__doc__
+ else:
+ doc = getattr(ob, "__doc__", "")
+ if doc:
+ for line in doc.split('\n', _MAX_LINES)[:_MAX_LINES]:
+ line = line.strip()
+ if not line:
+ break
+ if len(line) > _MAX_COLS:
+ line = line[: _MAX_COLS - 3] + '...'
+ lines.append(line)
+ argspec = '\n'.join(lines)
+ if not argspec:
+ argspec = _default_callable_argspec
+ return argspec
+
+if __name__ == '__main__':
+ from unittest import main
+ main('idlelib.idle_test.test_calltips', verbosity=2)
diff --git a/lib-python/3/idlelib/codecontext.py
b/lib-python/3/idlelib/codecontext.py
new file mode 100644
--- /dev/null
+++ b/lib-python/3/idlelib/codecontext.py
@@ -0,0 +1,178 @@
+"""codecontext - Extension to display the block context above the edit window
+
+Once code has scrolled off the top of a window, it can be difficult to
+determine which block you are in. This extension implements a pane at the top
+of each IDLE edit window which provides block structure hints. These hints are
+the lines which contain the block opening keywords, e.g. 'if', for the
+enclosing block. The number of hint lines is determined by the numlines
+variable in the codecontext section of config-extensions.def. Lines which do
+not open blocks are not shown in the context hints pane.
+
+"""
+import re
+from sys import maxsize as INFINITY
+
+import tkinter
+from tkinter.constants import TOP, LEFT, X, W, SUNKEN
+
+from idlelib.config import idleConf
+
+BLOCKOPENERS = {"class", "def", "elif", "else", "except", "finally", "for",
+ "if", "try", "while", "with"}
+UPDATEINTERVAL = 100 # millisec
+FONTUPDATEINTERVAL = 1000 # millisec
+
+getspacesfirstword =\
+ lambda s, c=re.compile(r"^(\s*)(\w*)"): c.match(s).groups()
+
+class CodeContext:
+ menudefs = [('options', [('!Code Conte_xt', '<<toggle-code-context>>')])]
+ context_depth = idleConf.GetOption("extensions", "CodeContext",
+ "numlines", type="int", default=3)
+ bgcolor = idleConf.GetOption("extensions", "CodeContext",
+ "bgcolor", type="str", default="LightGray")
+ fgcolor = idleConf.GetOption("extensions", "CodeContext",
+ "fgcolor", type="str", default="Black")
+ def __init__(self, editwin):
+ self.editwin = editwin
+ self.text = editwin.text
+ self.textfont = self.text["font"]
+ self.label = None
+ # self.info is a list of (line number, indent level, line text, block
+ # keyword) tuples providing the block structure associated with
+ # self.topvisible (the linenumber of the line displayed at the top of
+ # the edit window). self.info[0] is initialized as a 'dummy' line which
+ # starts the toplevel 'block' of the module.
+ self.info = [(0, -1, "", False)]
+ self.topvisible = 1
+ visible = idleConf.GetOption("extensions", "CodeContext",
+ "visible", type="bool", default=False)
+ if visible:
+ self.toggle_code_context_event()
+ self.editwin.setvar('<<toggle-code-context>>', True)
+ # Start two update cycles, one for context lines, one for font changes.
+ self.text.after(UPDATEINTERVAL, self.timer_event)
+ self.text.after(FONTUPDATEINTERVAL, self.font_timer_event)
+
+ def toggle_code_context_event(self, event=None):
+ if not self.label:
+ # Calculate the border width and horizontal padding required to
+ # align the context with the text in the main Text widget.
+ #
+ # All values are passed through getint(), since some
+ # values may be pixel objects, which can't simply be added to ints.
+ widgets = self.editwin.text, self.editwin.text_frame
+ # Calculate the required vertical padding
+ padx = 0
+ for widget in widgets:
+ padx += widget.tk.getint(widget.pack_info()['padx'])
+ padx += widget.tk.getint(widget.cget('padx'))
+ # Calculate the required border width
+ border = 0
+ for widget in widgets:
+ border += widget.tk.getint(widget.cget('border'))
+ self.label = tkinter.Label(self.editwin.top,
+ text="\n" * (self.context_depth - 1),
+ anchor=W, justify=LEFT,
+ font=self.textfont,
+ bg=self.bgcolor, fg=self.fgcolor,
+ width=1, #don't request more than we get
+ padx=padx, border=border,
+ relief=SUNKEN)
+ # Pack the label widget before and above the text_frame widget,
+ # thus ensuring that it will appear directly above text_frame
+ self.label.pack(side=TOP, fill=X, expand=False,
+ before=self.editwin.text_frame)
+ else:
+ self.label.destroy()
+ self.label = None
+ idleConf.SetOption("extensions", "CodeContext", "visible",
+ str(self.label is not None))
+ idleConf.SaveUserCfgFiles()
+
+ def get_line_info(self, linenum):
+ """Get the line indent value, text, and any block start keyword
+
+ If the line does not start a block, the keyword value is False.
+ The indentation of empty lines (or comment lines) is INFINITY.
+
+ """
+ text = self.text.get("%d.0" % linenum, "%d.end" % linenum)
+ spaces, firstword = getspacesfirstword(text)
+ opener = firstword in BLOCKOPENERS and firstword
+ if len(text) == len(spaces) or text[len(spaces)] == '#':
+ indent = INFINITY
+ else:
+ indent = len(spaces)
+ return indent, text, opener
+
+ def get_context(self, new_topvisible, stopline=1, stopindent=0):
+ """Get context lines, starting at new_topvisible and working backwards.
+
+ Stop when stopline or stopindent is reached. Return a tuple of context
+ data and the indent level at the top of the region inspected.
+
+ """
+ assert stopline > 0
+ lines = []
+ # The indentation level we are currently in:
+ lastindent = INFINITY
+ # For a line to be interesting, it must begin with a block opening
+ # keyword, and have less indentation than lastindent.
+ for linenum in range(new_topvisible, stopline-1, -1):
+ indent, text, opener = self.get_line_info(linenum)
+ if indent < lastindent:
+ lastindent = indent
+ if opener in ("else", "elif"):
+ # We also show the if statement
+ lastindent += 1
+ if opener and linenum < new_topvisible and indent >=
stopindent:
+ lines.append((linenum, indent, text, opener))
+ if lastindent <= stopindent:
+ break
+ lines.reverse()
+ return lines, lastindent
+
+ def update_code_context(self):
+ """Update context information and lines visible in the context pane.
+
+ """
+ new_topvisible = int(self.text.index("@0,0").split('.')[0])
+ if self.topvisible == new_topvisible: # haven't scrolled
+ return
+ if self.topvisible < new_topvisible: # scroll down
+ lines, lastindent = self.get_context(new_topvisible,
+ self.topvisible)
+ # retain only context info applicable to the region
+ # between topvisible and new_topvisible:
+ while self.info[-1][1] >= lastindent:
+ del self.info[-1]
+ elif self.topvisible > new_topvisible: # scroll up
+ stopindent = self.info[-1][1] + 1
+ # retain only context info associated
+ # with lines above new_topvisible:
+ while self.info[-1][0] >= new_topvisible:
+ stopindent = self.info[-1][1]
+ del self.info[-1]
+ lines, lastindent = self.get_context(new_topvisible,
+ self.info[-1][0]+1,
+ stopindent)
+ self.info.extend(lines)
+ self.topvisible = new_topvisible
+ # empty lines in context pane:
+ context_strings = [""] * max(0, self.context_depth - len(self.info))
+ # followed by the context hint lines:
+ context_strings += [x[2] for x in self.info[-self.context_depth:]]
+ self.label["text"] = '\n'.join(context_strings)
+
+ def timer_event(self):
+ if self.label:
+ self.update_code_context()
+ self.text.after(UPDATEINTERVAL, self.timer_event)
+
+ def font_timer_event(self):
+ newtextfont = self.text["font"]
+ if self.label and newtextfont != self.textfont:
+ self.textfont = newtextfont
+ self.label["font"] = self.textfont
+ self.text.after(FONTUPDATEINTERVAL, self.font_timer_event)
diff --git a/lib-python/3/idlelib/configdialog.py
b/lib-python/3/idlelib/configdialog.py
new file mode 100644
--- /dev/null
+++ b/lib-python/3/idlelib/configdialog.py
@@ -0,0 +1,1467 @@
+"""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 comments in EditorWindow autoindent code for details.
+
+"""
+from tkinter import *
+from tkinter.ttk import Scrollbar
+import tkinter.colorchooser as tkColorChooser
+import tkinter.font as tkFont
+import tkinter.messagebox as tkMessageBox
+
+from idlelib.config import idleConf
+from idlelib.config_key import GetKeysDialog
+from idlelib.dynoption import DynOptionMenu
+from idlelib import macosx
+from idlelib.query import SectionName, HelpSource
+from idlelib.tabbedpages import TabbedPageSet
+from idlelib.textview import view_text
+
+class ConfigDialog(Toplevel):
+
+ def __init__(self, parent, title='', _htest=False, _utest=False):
+ """
+ _htest - bool, change box location when running htest
+ _utest - bool, don't wait_window when running unittest
+ """
+ Toplevel.__init__(self, parent)
+ self.parent = parent
+ if _htest:
+ parent.instance_dict = {}
+ self.wm_withdraw()
+
+ self.configure(borderwidth=5)
+ self.title(title or 'IDLE Preferences')
+ self.geometry(
+ "+%d+%d" % (parent.winfo_rootx() + 20,
+ parent.winfo_rooty() + (30 if not _htest else 150)))
+ #Theme Elements. Each theme element key is its 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 Builtins': ('builtin', '03'),
+ 'Python Comments': ('comment', '04'),
+ 'Python Strings': ('string', '05'),
+ 'Selected Text': ('hilite', '06'),
+ 'Found Text': ('hit', '07'),
+ 'Cursor': ('cursor', '08'),
+ 'Editor Breakpoint': ('break', '09'),
+ 'Shell Normal Text': ('console', '10'),
+ 'Shell Error Text': ('error', '11'),
+ 'Shell Stdout Text': ('stdout', '12'),
+ 'Shell Stderr Text': ('stderr', '13'),
+ }
+ 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.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
+
+ if not _utest:
+ self.wm_deiconify()
+ self.wait_window()
+
+ def CreateWidgets(self):
+ self.tabPages = TabbedPageSet(self,
+ page_names=['Fonts/Tabs', 'Highlighting', 'Keys', 'General',
+ 'Extensions'])
+ self.tabPages.pack(side=TOP, expand=TRUE, fill=BOTH)
+ self.CreatePageFontTab()
+ self.CreatePageHighlight()
+ self.CreatePageKeys()
+ self.CreatePageGeneral()
+ self.CreatePageExtensions()
+ self.create_action_buttons().pack(side=BOTTOM)
+
+ def create_action_buttons(self):
+ if macosx.isAquaTk():
+ # Changing the default padding on OSX results in unreadable
+ # text in the buttons
+ paddingArgs = {}
+ else:
+ paddingArgs = {'padx':6, 'pady':3}
+ outer = Frame(self, pady=2)
+ buttons = Frame(outer, pady=2)
+ for txt, cmd in (
+ ('Ok', self.Ok),
+ ('Apply', self.Apply),
+ ('Cancel', self.Cancel),
+ ('Help', self.Help)):
+ Button(buttons, text=txt, command=cmd, takefocus=FALSE,
+ **paddingArgs).pack(side=LEFT, padx=5)
+ # add space above buttons
+ Frame(outer, height=2, borderwidth=0).pack(side=TOP)
+ buttons.pack(side=BOTTOM)
+ return outer
+
+ def CreatePageFontTab(self):
+ parent = self.parent
+ self.fontSize = StringVar(parent)
+ self.fontBold = BooleanVar(parent)
+ self.fontName = StringVar(parent)
+ self.spaceNum = IntVar(parent)
+ self.editFont = tkFont.Font(parent, ('courier', 10, 'normal'))
+
+ ##widget creation
+ #body frame
+ frame = self.tabPages.pages['Fonts/Tabs'].frame
+ #body section frames
+ frameFont = LabelFrame(
+ frame, borderwidth=2, relief=GROOVE, text=' Base Editor Font ')
+ frameIndent = LabelFrame(
+ frame, borderwidth=2, relief=GROOVE, text=' Indentation Width
')
+ #frameFont
+ frameFontName = Frame(frameFont)
+ frameFontParam = Frame(frameFont)
+ labelFontNameTitle = Label(
+ frameFontName, justify=LEFT, text='Font Face :')
+ 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, justify=LEFT, font=self.editFont,
+ text='AaBbCcDdEe\nFfGgHhIiJjK\n1234567890\n#:+=(){}[]')
+ #frameIndent
+ frameIndentSize = Frame(frameIndent)
+ labelSpaceNumTitle = Label(
+ frameIndentSize, justify=LEFT,
+ text='Python Standard: 4 Spaces!')
+ self.scaleSpaceNum = Scale(
+ frameIndentSize, variable=self.spaceNum,
+ orient='horizontal', tickinterval=2, from_=2, to=16)
+
+ #widget packing
+ #body
+ frameFont.pack(side=LEFT, padx=5, pady=5, expand=TRUE, fill=BOTH)
+ frameIndent.pack(side=LEFT, padx=5, pady=5, fill=Y)
+ #frameFont
+ 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
+ frameIndentSize.pack(side=TOP, fill=X)
+ labelSpaceNumTitle.pack(side=TOP, anchor=W, padx=5)
+ self.scaleSpaceNum.pack(side=TOP, padx=5, fill=X)
+ return frame
+
+ def CreatePageHighlight(self):
+ parent = self.parent
+ self.builtinTheme = StringVar(parent)
+ self.customTheme = StringVar(parent)
+ self.fgHilite = BooleanVar(parent)
+ self.colour = StringVar(parent)
+ self.fontName = StringVar(parent)
+ self.themeIsBuiltin = BooleanVar(parent)
+ self.highlightTarget = StringVar(parent)
+
+ ##widget creation
+ #body frame
+ frame = self.tabPages.pages['Highlighting'].frame
+ #body section frames
+ frameCustom = LabelFrame(frame, borderwidth=2, relief=GROOVE,
+ text=' Custom Highlighting ')
+ frameTheme = LabelFrame(frame, borderwidth=2, relief=GROOVE,
+ text=' Highlighting Theme ')
+ #frameCustom
+ self.textHighlightSample=Text(
+ frameCustom, relief=SOLID, borderwidth=1,
+ font=('courier', 12, ''), cursor='hand2', width=21, height=11,
+ 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):\n ', 'normal'),
+ ('"""string"""', 'string'), ('\n var0 = ', 'normal'),
+ ("'string'", 'string'), ('\n var1 = ', 'normal'),
+ ("'selected'", 'hilite'), ('\n var2 = ', 'normal'),
+ ("'found'", 'hit'), ('\n var3 = ', 'normal'),
+ ('list', 'builtin'), ('(', 'normal'),
+ ('None', 'keyword'), (')\n', 'normal'),
+ (' breakpoint("line")', 'break'), ('\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:
+ def tem(event, elem=element):
+ event.widget.winfo_toplevel().highlightTarget.set(elem)
+ text.tag_bind(
+ self.themeElements[element][0], '<ButtonPress-1>', tem)
+ text.config(state=DISABLED)
+ self.frameColourSet = Frame(frameCustom, relief=SOLID, borderwidth=1)
+ frameFgBg = Frame(frameCustom)
+ 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
+ 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)
+ self.new_custom_theme = Label(frameTheme, bd=2)
+
+ ##widget packing
+ #body
+ frameCustom.pack(side=LEFT, padx=5, pady=5, expand=TRUE, fill=BOTH)
+ frameTheme.pack(side=LEFT, padx=5, pady=5, fill=Y)
+ #frameCustom
+ 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
+ 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)
+ self.new_custom_theme.pack(side=TOP, fill=X, pady=5)
+ return frame
+
+ def CreatePageKeys(self):
+ parent = self.parent
+ self.bindingTarget = StringVar(parent)
+ self.builtinKeys = StringVar(parent)
+ self.customKeys = StringVar(parent)
+ self.keysAreBuiltin = BooleanVar(parent)
+ self.keyBinding = StringVar(parent)
+
+ ##widget creation
+ #body frame
+ frame = self.tabPages.pages['Keys'].frame
+ #body section frames
+ frameCustom = LabelFrame(
+ frame, borderwidth=2, relief=GROOVE,
+ text=' Custom Key Bindings ')
+ frameKeySets = LabelFrame(
+ frame, borderwidth=2, relief=GROOVE, text=' Key Set ')
+ #frameCustom
+ frameTarget = Frame(frameCustom)
+ 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)
+ #frameKeySets
+ frames = [Frame(frameKeySets, padx=2, pady=2, borderwidth=0)
+ for i in range(2)]
+ self.radioKeysBuiltin = Radiobutton(
+ frames[0], variable=self.keysAreBuiltin, value=1,
+ command=self.SetKeysType, text='Use a Built-in Key Set')
+ self.radioKeysCustom = Radiobutton(
+ frames[0], variable=self.keysAreBuiltin, value=0,
+ command=self.SetKeysType, text='Use a Custom Key Set')
+ self.optMenuKeysBuiltin = DynOptionMenu(
+ frames[0], self.builtinKeys, None, command=None)
+ self.optMenuKeysCustom = DynOptionMenu(
+ frames[0], self.customKeys, None, command=None)
+ self.buttonDeleteCustomKeys = Button(
+ frames[1], text='Delete Custom Key Set',
+ command=self.DeleteCustomKeys)
+ buttonSaveCustomKeys = Button(
+ frames[1], text='Save as New Custom Key Set',
+ command=self.SaveAsNewKeySet)
+ self.new_custom_keys = Label(frames[0], bd=2)
+
+ ##widget packing
+ #body
+ frameCustom.pack(side=BOTTOM, padx=5, pady=5, expand=TRUE, fill=BOTH)
+ frameKeySets.pack(side=BOTTOM, padx=5, pady=5, fill=BOTH)
+ #frameCustom
+ 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
+ self.radioKeysBuiltin.grid(row=0, column=0, sticky=W+NS)
+ self.radioKeysCustom.grid(row=1, column=0, sticky=W+NS)
+ self.optMenuKeysBuiltin.grid(row=0, column=1, sticky=NSEW)
+ self.optMenuKeysCustom.grid(row=1, column=1, sticky=NSEW)
+ self.new_custom_keys.grid(row=0, column=2, sticky=NSEW, padx=5, pady=5)
+ self.buttonDeleteCustomKeys.pack(side=LEFT, fill=X, expand=True,
padx=2)
+ buttonSaveCustomKeys.pack(side=LEFT, fill=X, expand=True, padx=2)
+ frames[0].pack(side=TOP, fill=BOTH, expand=True)
+ frames[1].pack(side=TOP, fill=X, expand=True, pady=2)
+ return frame
+
+ def CreatePageGeneral(self):
+ parent = self.parent
+ self.winWidth = StringVar(parent)
+ self.winHeight = StringVar(parent)
+ self.startupEdit = IntVar(parent)
+ self.autoSave = IntVar(parent)
+ self.encoding = StringVar(parent)
+ self.userHelpBrowser = BooleanVar(parent)
+ self.helpBrowser = StringVar(parent)
+
+ #widget creation
+ #body
+ frame = self.tabPages.pages['General'].frame
+ #body section frames
+ frameRun = LabelFrame(frame, borderwidth=2, relief=GROOVE,
+ text=' Startup Preferences ')
+ frameSave = LabelFrame(frame, borderwidth=2, relief=GROOVE,
+ text=' Autosave Preferences ')
+ frameWinSize = Frame(frame, borderwidth=2, relief=GROOVE)
+ frameHelp = LabelFrame(frame, borderwidth=2, relief=GROOVE,
+ text=' Additional Help Sources ')
+ #frameRun
+ labelRunChoiceTitle = Label(frameRun, text='At Startup')
+ self.radioStartupEdit = Radiobutton(
+ frameRun, variable=self.startupEdit, value=1,
+ text="Open Edit Window")
+ self.radioStartupShell = Radiobutton(
+ frameRun, variable=self.startupEdit, value=0,
+ text='Open Shell Window')
+ #frameSave
+ labelRunSaveTitle = Label(frameSave, text='At Start of Run (F5) ')
+ self.radioSaveAsk = Radiobutton(
+ frameSave, variable=self.autoSave, value=0,
+ text="Prompt to Save")
+ self.radioSaveAuto = Radiobutton(
+ frameSave, variable=self.autoSave, value=1,
+ text='No Prompt')
+ #frameWinSize
+ labelWinSizeTitle = Label(
+ frameWinSize, text='Initial Window Size (in characters)')
+ labelWinWidthTitle = Label(frameWinSize, text='Width')
+ self.entryWinWidth = Entry(
+ frameWinSize, textvariable=self.winWidth, width=3)
+ labelWinHeightTitle = Label(frameWinSize, text='Height')
+ self.entryWinHeight = Entry(
+ frameWinSize, textvariable=self.winHeight, width=3)
+ #frameHelp
+ frameHelpList = Frame(frameHelp)
+ frameHelpListButtons = Frame(frameHelpList)
+ 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)
+
+ #widget packing
+ #body
+ frameRun.pack(side=TOP, padx=5, pady=5, fill=X)
+ frameSave.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
+ labelRunChoiceTitle.pack(side=LEFT, anchor=W, padx=5, pady=5)
+ self.radioStartupShell.pack(side=RIGHT, anchor=W, padx=5, pady=5)
+ self.radioStartupEdit.pack(side=RIGHT, anchor=W, padx=5, pady=5)
+ #frameSave
+ labelRunSaveTitle.pack(side=LEFT, anchor=W, padx=5, pady=5)
+ self.radioSaveAuto.pack(side=RIGHT, anchor=W, padx=5, pady=5)
+ self.radioSaveAsk.pack(side=RIGHT, anchor=W, padx=5, pady=5)
+ #frameWinSize
+ labelWinSizeTitle.pack(side=LEFT, anchor=W, padx=5, pady=5)
+ self.entryWinHeight.pack(side=RIGHT, anchor=E, padx=10, pady=5)
+ labelWinHeightTitle.pack(side=RIGHT, anchor=E, pady=5)
+ self.entryWinWidth.pack(side=RIGHT, anchor=E, padx=10, pady=5)
+ labelWinWidthTitle.pack(side=RIGHT, anchor=E, pady=5)
+ #frameHelp
+ frameHelpListButtons.pack(side=RIGHT, padx=5, pady=5, fill=Y)
+ frameHelpList.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
+ 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)
+ return frame
+
+ def AttachVarCallbacks(self):
+ self.fontSize.trace_add('write', self.VarChanged_font)
+ self.fontName.trace_add('write', self.VarChanged_font)
+ self.fontBold.trace_add('write', self.VarChanged_font)
+ self.spaceNum.trace_add('write', self.VarChanged_spaceNum)
+ self.colour.trace_add('write', self.VarChanged_colour)
+ self.builtinTheme.trace_add('write', self.VarChanged_builtinTheme)
+ self.customTheme.trace_add('write', self.VarChanged_customTheme)
+ self.themeIsBuiltin.trace_add('write', self.VarChanged_themeIsBuiltin)
+ self.highlightTarget.trace_add('write',
self.VarChanged_highlightTarget)
+ self.keyBinding.trace_add('write', self.VarChanged_keyBinding)
+ self.builtinKeys.trace_add('write', self.VarChanged_builtinKeys)
+ self.customKeys.trace_add('write', self.VarChanged_customKeys)
+ self.keysAreBuiltin.trace_add('write', self.VarChanged_keysAreBuiltin)
+ self.winWidth.trace_add('write', self.VarChanged_winWidth)
+ self.winHeight.trace_add('write', self.VarChanged_winHeight)
+ self.startupEdit.trace_add('write', self.VarChanged_startupEdit)
+ self.autoSave.trace_add('write', self.VarChanged_autoSave)
+ self.encoding.trace_add('write', self.VarChanged_encoding)
+
+ def remove_var_callbacks(self):
+ "Remove callbacks to prevent memory leaks."
+ for var in (
+ self.fontSize, self.fontName, self.fontBold,
+ self.spaceNum, self.colour, self.builtinTheme,
+ self.customTheme, self.themeIsBuiltin, self.highlightTarget,
+ self.keyBinding, self.builtinKeys, self.customKeys,
+ self.keysAreBuiltin, self.winWidth, self.winHeight,
+ self.startupEdit, self.autoSave, self.encoding,):
+ var.trace_remove('write', var.trace_info()[0][1])
+
+ def VarChanged_font(self, *params):
+ '''When one font attribute changes, save them all, as they are
+ not independent from each other. In particular, when we are
+ overriding the default font, we need to write out everything.
+ '''
+ value = self.fontName.get()
+ self.AddChangedItem('main', 'EditorWindow', 'font', value)
+ value = self.fontSize.get()
+ self.AddChangedItem('main', 'EditorWindow', 'font-size', value)
+ value = self.fontBold.get()
+ self.AddChangedItem('main', 'EditorWindow', 'font-bold', value)
+
+ def VarChanged_spaceNum(self, *params):
+ value = self.spaceNum.get()
+ self.AddChangedItem('main', 'Indent', 'num-spaces', value)
+
+ def VarChanged_colour(self, *params):
+ self.OnNewColourSet()
+
+ def VarChanged_builtinTheme(self, *params):
+ oldthemes = ('IDLE Classic', 'IDLE New')
+ value = self.builtinTheme.get()
+ if value not in oldthemes:
+ if idleConf.GetOption('main', 'Theme', 'name') not in oldthemes:
+ self.AddChangedItem('main', 'Theme', 'name', oldthemes[0])
+ self.AddChangedItem('main', 'Theme', 'name2', value)
+ self.new_custom_theme.config(text='New theme, see Help',
+ fg='#500000')
+ else:
+ self.AddChangedItem('main', 'Theme', 'name', value)
+ self.AddChangedItem('main', 'Theme', 'name2', '')
+ self.new_custom_theme.config(text='', fg='black')
+ 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):
+ oldkeys = (
+ 'IDLE Classic Windows',
+ 'IDLE Classic Unix',
+ 'IDLE Classic Mac',
+ 'IDLE Classic OSX',
+ )
+ value = self.builtinKeys.get()
+ if value not in oldkeys:
+ if idleConf.GetOption('main', 'Keys', 'name') not in oldkeys:
+ self.AddChangedItem('main', 'Keys', 'name', oldkeys[0])
+ self.AddChangedItem('main', 'Keys', 'name2', value)
+ self.new_custom_keys.config(text='New key set, see Help',
+ fg='#500000')
+ else:
+ self.AddChangedItem('main', 'Keys', 'name', value)
+ self.AddChangedItem('main', 'Keys', 'name2', '')
+ self.new_custom_keys.config(text='', fg='black')
+ 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 VarChanged_autoSave(self, *params):
+ value = self.autoSave.get()
+ self.AddChangedItem('main', 'General', 'autosave', value)
+
+ def VarChanged_encoding(self, *params):
+ value = self.encoding.get()
+ self.AddChangedItem('main', 'EditorWindow', 'encoding', 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, typ, section, item, value):
+ value = str(value) #make sure we use a string
+ if section not in self.changedItems[typ]:
+ self.changedItems[typ][section] = {}
+ self.changedItems[typ][section][item] = value
+
+ def GetDefaultItems(self):
+ dItems={'main':{}, 'highlight':{}, 'keys':{}, 'extensions':{}}
+ for configType in dItems:
+ 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']: #unsaved changes
+ keySetChanges = self.changedItems['keys'][currentKeySetName]
+ for event in keySetChanges:
+ currentBindings[event] = keySetChanges[event].split()
+ currentKeySequences = list(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 = SectionName(
+ 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: #add key set to changed items
+ eventName = event[2:-2] #trim off the angle brackets
+ binding = ' '.join(prevKeys[event])
+ newKeys[eventName] = binding
+ #handle any unsaved changes to prev key set
+ if prevKeySetName in self.changedItems['keys']:
+ keySetChanges = self.changedItems['keys'][prevKeySetName]
+ for event in keySetChanges:
+ 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 = list(keySet.keys())
+ bindNames.sort()
+ self.listBindings.delete(0, END)
+ for bindName in bindNames:
+ key = ' '.join(keySet[bindName]) #make key(s) into a string
+ bindName = bindName[2:-2] #trim off the angle brackets
+ if keySetName in self.changedItems['keys']:
+ #handle any unsaved changes to this key set
+ if bindName in self.changedItems['keys'][keySetName]:
+ 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()
+ delmsg = 'Are you sure you wish to delete the key set %r ?'
+ if not tkMessageBox.askyesno(
+ 'Delete Key Set', delmsg % keySetName, parent=self):
+ return
+ self.DeactivateCurrentConfig()
+ #remove key set from config
+ idleConf.userCfg['keys'].remove_section(keySetName)
+ if keySetName in self.changedItems['keys']:
+ 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')
+ or idleConf.default_keys())
+ #user can't back out of these changes, they must be applied now
+ self.SaveAllChangedConfigs()
+ self.ActivateConfigChanges()
+ self.SetKeysType()
+
+ def DeleteCustomTheme(self):
+ themeName = self.customTheme.get()
+ delmsg = 'Are you sure you wish to delete the theme %r ?'
+ if not tkMessageBox.askyesno(
+ 'Delete Theme', delmsg % themeName, parent=self):
+ return
+ self.DeactivateCurrentConfig()
+ #remove theme from config
+ idleConf.userCfg['highlight'].remove_section(themeName)
+ if themeName in self.changedItems['highlight']:
+ 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.SaveAllChangedConfigs()
+ self.ActivateConfigChanges()
+ 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
+ plane ='foreground' if self.fgHilite.get() else 'background'
+ sampleElement = self.themeElements[self.highlightTarget.get()][0]
+ 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 = SectionName(
+ 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']:
+ themeChanges = self.changedItems['highlight'][themeName]
+ for element in themeChanges:
+ 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):
+ font = self.listFontName.get(ANCHOR)
+ self.fontName.set(font.lower())
+ self.SetFontSample()
+
+ def SetFontSample(self, event=None):
+ fontName = self.fontName.get()
+ fontWeight = tkFont.BOLD if self.fontBold.get() else tkFont.NORMAL
+ newFont = (fontName, self.fontSize.get(), fontWeight)
+ self.labelFontSample.config(font=newFont)
+ self.textHighlightSample.configure(font=newFont)
+
+ 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]
+ plane = 'foreground' if self.fgHilite.get() else '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:
+ 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']:
+ themeDict = self.changedItems['highlight'][theme]
+ if element + '-foreground' in themeDict:
+ colours['foreground'] = themeDict[element + '-foreground']
+ if element + '-background' in themeDict:
+ colours['background'] = themeDict[element + '-background']
+ self.textHighlightSample.tag_config(element, **colours)
+ self.SetColourSample()
+
+ 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 = HelpSource(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 = HelpSource(
+ self, 'Edit Help Source',
+ menuitem=helpSource[0],
+ filepath=helpSource[1],
+ ).result
+ if newHelpSource and newHelpSource != helpSource:
+ 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),
+ ';'.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.GetFont(self, 'main', 'EditorWindow')
+ fontName = configuredFont[0].lower()
+ fontSize = configuredFont[1]
+ fontBold = configuredFont[2]=='bold'
+ self.fontName.set(fontName)
+ lc_fonts = [s.lower() for s in fonts]
+ try:
+ currentFontIndex = lc_fonts.index(fontName)
+ self.listFontName.see(currentFontIndex)
+ self.listFontName.select_set(currentFontIndex)
+ self.listFontName.select_anchor(currentFontIndex)
+ except ValueError:
+ pass
+ ##font size dropdown
+ self.optMenuFontSize.SetMenu(('7', '8', '9', '10', '11', '12', '13',
+ '14', '16', '18', '20', '22',
+ '25', '29', '34', '40'), fontSize )
+ ##fontWeight
+ self.fontBold.set(fontBold)
+ ##font sample
+ self.SetFontSample()
+
+ def LoadTabCfg(self):
+ ##indent sizes
+ spaceNum = idleConf.GetOption(
+ 'main', 'Indent', 'num-spaces', default=4, type='int')
+ self.spaceNum.set(spaceNum)
+
+ 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 = list(self.themeElements.keys())
+ themeNames.sort(key=lambda x: self.themeElements[x][1])
+ self.optMenuHighlightTarget.SetMenu(themeNames, themeNames[0])
+ self.PaintThemeSample()
+ self.SetHighlightTarget()
+
+ 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, idleConf.default_keys())
+ 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'))
+ #autosave state
+ self.autoSave.set(idleConf.GetOption(
+ 'main', 'General', 'autosave', default=0, type='bool'))
+ #initial window size
+ self.winWidth.set(idleConf.GetOption(
+ 'main', 'EditorWindow', 'width', type='int'))
+ self.winHeight.set(idleConf.GetOption(
+ 'main', 'EditorWindow', 'height', type='int'))
+ # default source encoding
+ self.encoding.set(idleConf.GetOption(
+ 'main', 'EditorWindow', 'encoding', default='none'))
+ # additional help sources
+ self.userHelpList = idleConf.GetAllExtraHelpSourcesList()
+ for helpItem in self.userHelpList:
+ self.listHelp.insert(END, helpItem[0])
+ self.SetHelpListButtonStates()
+
+ 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()
+ # note: extension page handled separately
+
+ 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:
+ 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:
+ 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:
+ cfgTypeHasChanges = False
+ for section in self.changedItems[configType]:
+ if section == 'HelpFiles':
+ #this section gets completely replaced
+ idleConf.userCfg['main'].remove_section('HelpFiles')
+ cfgTypeHasChanges = True
+ for item in self.changedItems[configType][section]:
+ value = self.changedItems[configType][section][item]
+ if self.SetUserValue(configType, section, item, value):
+ cfgTypeHasChanges = True
+ if cfgTypeHasChanges:
+ idleConf.userCfg[configType].Save()
+ for configType in ['keys', 'highlight']:
+ # save these even if unchanged!
+ idleConf.userCfg[configType].Save()
+ self.ResetChangedItems() #clear the changed items dict
+ self.save_all_changed_extensions() # uses a different mechanism
+
+ def DeactivateCurrentConfig(self):
+ #Before a config is saved, some cleanup of current
+ #config must be done - remove the previous keybindings
+ winInstances = self.parent.instance_dict.keys()
+ for instance in winInstances:
+ instance.RemoveKeybindings()
+
+ def ActivateConfigChanges(self):
+ "Dynamically apply configuration changes"
+ winInstances = self.parent.instance_dict.keys()
+ for instance in winInstances:
+ instance.ResetColorizer()
+ instance.ResetFont()
+ instance.set_notabs_indentwidth()
+ instance.ApplyKeybindings()
+ instance.reset_help_menu_entries()
+
+ def Cancel(self):
+ self.destroy()
+
+ def Ok(self):
+ self.Apply()
+ self.destroy()
+
+ def Apply(self):
+ self.DeactivateCurrentConfig()
+ self.SaveAllChangedConfigs()
+ self.ActivateConfigChanges()
+
+ def Help(self):
+ page = self.tabPages._current_page
+ view_text(self, title='Help for IDLE preferences',
+ text=help_common+help_pages.get(page, ''))
+
+ def CreatePageExtensions(self):
+ """Part of the config dialog used for configuring IDLE extensions.
+
+ This code is generic - it works for any and all IDLE extensions.
+
+ IDLE extensions save their configuration options using idleConf.
+ This code reads the current configuration using idleConf, supplies a
+ GUI interface to change the configuration values, and saves the
+ changes using idleConf.
+
+ Not all changes take effect immediately - some may require restarting
IDLE.
+ This depends on each extension's implementation.
+
+ All values are treated as text, and it is up to the user to supply
+ reasonable values. The only exception to this are the 'enable*'
options,
+ which are boolean, and can be toggled with a True/False button.
+ """
+ parent = self.parent
+ frame = self.tabPages.pages['Extensions'].frame
+ self.ext_defaultCfg = idleConf.defaultCfg['extensions']
+ self.ext_userCfg = idleConf.userCfg['extensions']
+ self.is_int = self.register(is_int)
+ self.load_extensions()
+ # create widgets - a listbox shows all available extensions, with the
+ # controls for the extension selected in the listbox to the right
+ self.extension_names = StringVar(self)
+ frame.rowconfigure(0, weight=1)
+ frame.columnconfigure(2, weight=1)
+ self.extension_list = Listbox(frame, listvariable=self.extension_names,
+ selectmode='browse')
+ self.extension_list.bind('<<ListboxSelect>>', self.extension_selected)
+ scroll = Scrollbar(frame, command=self.extension_list.yview)
+ self.extension_list.yscrollcommand=scroll.set
+ self.details_frame = LabelFrame(frame, width=250, height=250)
+ self.extension_list.grid(column=0, row=0, sticky='nws')
+ scroll.grid(column=1, row=0, sticky='ns')
+ self.details_frame.grid(column=2, row=0, sticky='nsew', padx=[10, 0])
+ frame.configure(padx=10, pady=10)
+ self.config_frame = {}
+ self.current_extension = None
+
+ self.outerframe = self # TEMPORARY
+ self.tabbed_page_set = self.extension_list # TEMPORARY
+
+ # create the frame holding controls for each extension
+ ext_names = ''
+ for ext_name in sorted(self.extensions):
+ self.create_extension_frame(ext_name)
+ ext_names = ext_names + '{' + ext_name + '} '
+ self.extension_names.set(ext_names)
+ self.extension_list.selection_set(0)
+ self.extension_selected(None)
+
+ def load_extensions(self):
+ "Fill self.extensions with data from the default and user configs."
+ self.extensions = {}
+ for ext_name in idleConf.GetExtensions(active_only=False):
+ self.extensions[ext_name] = []
+
+ for ext_name in self.extensions:
+ opt_list = sorted(self.ext_defaultCfg.GetOptionList(ext_name))
+
+ # bring 'enable' options to the beginning of the list
+ enables = [opt_name for opt_name in opt_list
_______________________________________________
pypy-commit mailing list
[email protected]
https://mail.python.org/mailman/listinfo/pypy-commit