[Python-checkins] cpython (merge 3.4 -> default): Merge with 3.4 #3068

terry.reedy python-checkins at python.org
Thu Oct 23 02:16:42 CEST 2014


https://hg.python.org/cpython/rev/d70b70a661c6
changeset:   93160:d70b70a661c6
parent:      93155:f2ce9603346c
parent:      93159:d2a045855c4e
user:        Terry Jan Reedy <tjreedy at udel.edu>
date:        Wed Oct 22 20:16:17 2014 -0400
summary:
  Merge with 3.4 #3068

files:
  Lib/idlelib/Bindings.py        |    3 +-
  Lib/idlelib/EditorWindow.py    |    4 +
  Lib/idlelib/configDialog.py    |  275 +++++++++++++++++++-
  Lib/idlelib/configHandler.py   |    3 +
  Lib/idlelib/idle_test/htest.py |    9 +
  Lib/idlelib/macosxSupport.py   |    6 +-
  6 files changed, 279 insertions(+), 21 deletions(-)


diff --git a/Lib/idlelib/Bindings.py b/Lib/idlelib/Bindings.py
--- a/Lib/idlelib/Bindings.py
+++ b/Lib/idlelib/Bindings.py
@@ -77,7 +77,8 @@
    ('!_Auto-open Stack Viewer', '<<toggle-jit-stack-viewer>>'),
    ]),
  ('options', [
-   ('_Configure IDLE...', '<<open-config-dialog>>'),
+   ('Configure _IDLE', '<<open-config-dialog>>'),
+   ('Configure _Extensions', '<<open-config-extensions-dialog>>'),
    None,
    ]),
  ('help', [
diff --git a/Lib/idlelib/EditorWindow.py b/Lib/idlelib/EditorWindow.py
--- a/Lib/idlelib/EditorWindow.py
+++ b/Lib/idlelib/EditorWindow.py
@@ -189,6 +189,8 @@
         text.bind("<<python-docs>>", self.python_docs)
         text.bind("<<about-idle>>", self.about_dialog)
         text.bind("<<open-config-dialog>>", self.config_dialog)
+        text.bind("<<open-config-extensions-dialog>>",
+                  self.config_extensions_dialog)
         text.bind("<<open-module>>", self.open_module)
         text.bind("<<do-nothing>>", lambda event: "break")
         text.bind("<<select-all>>", self.select_all)
@@ -543,6 +545,8 @@
 
     def config_dialog(self, event=None):
         configDialog.ConfigDialog(self.top,'Settings')
+    def config_extensions_dialog(self, event=None):
+        configDialog.ConfigExtensionsDialog(self.top)
 
     def help_dialog(self, event=None):
         if self.root:
diff --git a/Lib/idlelib/configDialog.py b/Lib/idlelib/configDialog.py
--- a/Lib/idlelib/configDialog.py
+++ b/Lib/idlelib/configDialog.py
@@ -20,8 +20,8 @@
 from idlelib.keybindingDialog import GetKeysDialog
 from idlelib.configSectionNameDialog import GetCfgSectionNameDialog
 from idlelib.configHelpSourceEdit import GetHelpSourceDialog
+from idlelib.tabbedpages import TabbedPageSet
 from idlelib import macosxSupport
-
 class ConfigDialog(Toplevel):
 
     def __init__(self, parent, title='', _htest=False, _utest=False):
@@ -85,8 +85,6 @@
         self.CreatePageKeys()
         self.CreatePageGeneral()
         self.create_action_buttons().pack(side=BOTTOM)
-        Frame(self, height=2, borderwidth=0).pack(side=BOTTOM)
-
     def create_action_buttons(self):
         if macosxSupport.isAquaTk():
             # Changing the default padding on OSX results in unreadable
@@ -94,27 +92,30 @@
             paddingArgs = {}
         else:
             paddingArgs = {'padx':6, 'pady':3}
-
-        frame = Frame(self, pady=2)
+        outer = Frame(self, pady=2)
+        buttons = Frame(outer, pady=2)
         self.buttonOk = Button(
-                frame, text='Ok', command=self.Ok,
+                buttons, text='Ok', command=self.Ok,
                 takefocus=FALSE, **paddingArgs)
         self.buttonApply = Button(
-                frame, text='Apply', command=self.Apply,
+                buttons, text='Apply', command=self.Apply,
                 takefocus=FALSE, **paddingArgs)
         self.buttonCancel = Button(
-                frame, text='Cancel', command=self.Cancel,
+                buttons, text='Cancel', command=self.Cancel,
                 takefocus=FALSE, **paddingArgs)
-# Comment out Help button creation and packing until implement self.Help
-##        self.buttonHelp = Button(
-##                frame, text='Help', command=self.Help,
-##                takefocus=FALSE, **paddingArgs)
-##        self.buttonHelp.pack(side=RIGHT, padx=5)
         self.buttonOk.pack(side=LEFT, padx=5)
         self.buttonApply.pack(side=LEFT, padx=5)
         self.buttonCancel.pack(side=LEFT, padx=5)
-        return frame
+# Comment out Help button creation and packing until implement self.Help
+##        self.buttonHelp = Button(
+##                buttons, text='Help', command=self.Help,
+##                takefocus=FALSE, **paddingArgs)
+##        self.buttonHelp.pack(side=RIGHT, 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)
@@ -1188,10 +1189,252 @@
     def Help(self):
         pass
 
+class VerticalScrolledFrame(Frame):
+    """A pure Tkinter vertically scrollable frame.
+
+    * Use the 'interior' attribute to place widgets inside the scrollable frame
+    * Construct and pack/place/grid normally
+    * This frame only allows vertical scrolling
+    """
+    def __init__(self, parent, *args, **kw):
+        Frame.__init__(self, parent, *args, **kw)
+
+        # create a canvas object and a vertical scrollbar for scrolling it
+        vscrollbar = Scrollbar(self, orient=VERTICAL)
+        vscrollbar.pack(fill=Y, side=RIGHT, expand=FALSE)
+        canvas = Canvas(self, bd=0, highlightthickness=0,
+                        yscrollcommand=vscrollbar.set)
+        canvas.pack(side=LEFT, fill=BOTH, expand=TRUE)
+        vscrollbar.config(command=canvas.yview)
+
+        # reset the view
+        canvas.xview_moveto(0)
+        canvas.yview_moveto(0)
+
+        # create a frame inside the canvas which will be scrolled with it
+        self.interior = interior = Frame(canvas)
+        interior_id = canvas.create_window(0, 0, window=interior, anchor=NW)
+
+        # track changes to the canvas and frame width and sync them,
+        # also updating the scrollbar
+        def _configure_interior(event):
+            # update the scrollbars to match the size of the inner frame
+            size = (interior.winfo_reqwidth(), interior.winfo_reqheight())
+            canvas.config(scrollregion="0 0 %s %s" % size)
+            if interior.winfo_reqwidth() != canvas.winfo_width():
+                # update the canvas's width to fit the inner frame
+                canvas.config(width=interior.winfo_reqwidth())
+        interior.bind('<Configure>', _configure_interior)
+
+        def _configure_canvas(event):
+            if interior.winfo_reqwidth() != canvas.winfo_width():
+                # update the inner frame's width to fill the canvas
+                canvas.itemconfigure(interior_id, width=canvas.winfo_width())
+        canvas.bind('<Configure>', _configure_canvas)
+
+        return
+
+def is_int(s):
+    "Return 's is blank or represents an int'"
+    if not s:
+        return True
+    try:
+        int(s)
+        return True
+    except ValueError:
+        return False
+
+# TODO:
+# * Revert to default(s)? Per option or per extension?
+# * List options in their original order (possible??)
+class ConfigExtensionsDialog(Toplevel):
+    """A dialog for configuring IDLE extensions.
+
+    This dialog is generic - it works for any and all IDLE extensions.
+
+    IDLE extensions save their configuration options using idleConf.
+    ConfigExtensionsDialog 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 an True/False button.
+    """
+    def __init__(self, parent, title=None, _htest=False):
+        Toplevel.__init__(self, parent)
+        self.wm_withdraw()
+
+        self.configure(borderwidth=5)
+        self.geometry(
+                "+%d+%d" % (parent.winfo_rootx() + 20,
+                parent.winfo_rooty() + (30 if not _htest else 150)))
+        self.wm_title(title or 'IDLE Extensions Configuration')
+
+        self.defaultCfg = idleConf.defaultCfg['extensions']
+        self.userCfg = idleConf.userCfg['extensions']
+        self.is_int = self.register(is_int)
+        self.load_extensions()
+        self.create_widgets()
+
+        self.resizable(height=FALSE, width=FALSE) # don't allow resizing yet
+        self.transient(parent)
+        self.protocol("WM_DELETE_WINDOW", self.Cancel)
+        self.tabbed_page_set.focus_set()
+        # wait for window to be generated
+        self.update()
+        # set current width as the minimum width
+        self.wm_minsize(self.winfo_width(), 1)
+        # now allow resizing
+        self.resizable(height=TRUE, width=TRUE)
+
+        self.wm_deiconify()
+        if not _htest:
+            self.grab_set()
+            self.wait_window()
+
+    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.defaultCfg.GetOptionList(ext_name))
+
+            # bring 'enable' options to the beginning of the list
+            enables = [opt_name for opt_name in opt_list
+                       if opt_name.startswith('enable')]
+            for opt_name in enables:
+                opt_list.remove(opt_name)
+            opt_list = enables + opt_list
+
+            for opt_name in opt_list:
+                def_str = self.defaultCfg.Get(
+                        ext_name, opt_name, raw=True)
+                try:
+                    def_obj = {'True':True, 'False':False}[def_str]
+                    opt_type = 'bool'
+                except KeyError:
+                    try:
+                        def_obj = int(def_str)
+                        opt_type = 'int'
+                    except ValueError:
+                        def_obj = def_str
+                        opt_type = None
+                try:
+                    value = self.userCfg.Get(
+                            ext_name, opt_name, type=opt_type, raw=True,
+                            default=def_obj)
+                except ValueError:  # Need this until .Get fixed
+                    value = def_obj  # bad values overwritten by entry
+                var = StringVar(self)
+                var.set(str(value))
+
+                self.extensions[ext_name].append({'name': opt_name,
+                                                  'type': opt_type,
+                                                  'default': def_str,
+                                                  'value': value,
+                                                  'var': var,
+                                                 })
+
+    def create_widgets(self):
+        """Create the dialog's widgets."""
+        self.rowconfigure(0, weight=1)
+        self.rowconfigure(1, weight=0)
+        self.columnconfigure(0, weight=1)
+
+        # create the tabbed pages
+        self.tabbed_page_set = TabbedPageSet(
+                self, page_names=self.extensions.keys(),
+                n_rows=None, max_tabs_per_row=5,
+                page_class=TabbedPageSet.PageRemove)
+        self.tabbed_page_set.grid(row=0, column=0, sticky=NSEW)
+        for ext_name in self.extensions:
+            self.create_tab_page(ext_name)
+
+        self.create_action_buttons().grid(row=1)
+
+    create_action_buttons = ConfigDialog.create_action_buttons
+
+    def create_tab_page(self, ext_name):
+        """Create the page for an extension."""
+
+        page = LabelFrame(self.tabbed_page_set.pages[ext_name].frame,
+                          border=2, padx=2, relief=GROOVE,
+                          text=' %s ' % ext_name)
+        page.pack(fill=BOTH, expand=True, padx=12, pady=2)
+
+        # create the scrollable frame which will contain the entries
+        scrolled_frame = VerticalScrolledFrame(page, pady=2, height=250)
+        scrolled_frame.pack(side=BOTTOM, fill=BOTH, expand=TRUE)
+        entry_area = scrolled_frame.interior
+        entry_area.columnconfigure(0, weight=0)
+        entry_area.columnconfigure(1, weight=1)
+
+        # create an entry for each configuration option
+        for row, opt in enumerate(self.extensions[ext_name]):
+            # create a row with a label and entry/checkbutton
+            label = Label(entry_area, text=opt['name'])
+            label.grid(row=row, column=0, sticky=NW)
+            var = opt['var']
+            if opt['type'] == 'bool':
+                Checkbutton(entry_area, textvariable=var, variable=var,
+                            onvalue='True', offvalue='False',
+                            indicatoron=FALSE, selectcolor='', width=8
+                    ).grid(row=row, column=1, sticky=W, padx=7)
+            elif opt['type'] == 'int':
+                Entry(entry_area, textvariable=var, validate='key',
+                    validatecommand=(self.is_int, '%P')
+                    ).grid(row=row, column=1, sticky=NSEW, padx=7)
+
+            else:
+                Entry(entry_area, textvariable=var
+                    ).grid(row=row, column=1, sticky=NSEW, padx=7)
+        return
+
+
+    Ok = ConfigDialog.Ok
+
+    def Apply(self):
+        self.save_all_changed_configs()
+        pass
+
+    Cancel = ConfigDialog.Cancel
+
+    def Help(self):
+        pass
+
+    def set_user_value(self, section, opt):
+        name = opt['name']
+        default = opt['default']
+        value = opt['var'].get().strip() or default
+        opt['var'].set(value)
+        # if self.defaultCfg.has_section(section):
+        # Currently, always true; if not, indent to return
+        if (value == default):
+            return self.userCfg.RemoveOption(section, name)
+        # set the option
+        return self.userCfg.SetOption(section, name, value)
+
+    def save_all_changed_configs(self):
+        """Save configuration changes to the user config file."""
+        has_changes = False
+        for ext_name in self.extensions:
+            options = self.extensions[ext_name]
+            for opt in options:
+                if self.set_user_value(ext_name, opt):
+                    has_changes = True
+        if has_changes:
+            self.userCfg.Save()
+
+
 if __name__ == '__main__':
     import unittest
     unittest.main('idlelib.idle_test.test_configdialog',
                   verbosity=2, exit=False)
-
     from idlelib.idle_test.htest import run
-    run(ConfigDialog)
+    run(ConfigDialog, ConfigExtensionsDialog)
diff --git a/Lib/idlelib/configHandler.py b/Lib/idlelib/configHandler.py
--- a/Lib/idlelib/configHandler.py
+++ b/Lib/idlelib/configHandler.py
@@ -44,6 +44,9 @@
         Get an option value for given section/option or return default.
         If type is specified, return as type.
         """
+        # TODO Use default as fallback, at least if not None
+        # Should also print Warning(file, section, option).
+        # Currently may raise ValueError
         if not self.has_option(section, option):
             return default
         if type == 'bool':
diff --git a/Lib/idlelib/idle_test/htest.py b/Lib/idlelib/idle_test/htest.py
--- a/Lib/idlelib/idle_test/htest.py
+++ b/Lib/idlelib/idle_test/htest.py
@@ -93,6 +93,15 @@
            "Double clicking on items prints a traceback for an exception "
            "that is ignored."
     }
+ConfigExtensionsDialog_spec = {
+    'file': 'configDialog',
+    'kwds': {'title': 'Test Extension Configuration',
+             '_htest': True,},
+    'msg': "IDLE extensions dialog.\n"
+           "\n[Ok] to close the dialog.[Apply] to apply the settings and "
+           "and [Cancel] to revert all changes.\nRe-run the test to ensure "
+           "changes made have persisted."
+    }
 
 _color_delegator_spec = {
     'file': 'ColorDelegator',
diff --git a/Lib/idlelib/macosxSupport.py b/Lib/idlelib/macosxSupport.py
--- a/Lib/idlelib/macosxSupport.py
+++ b/Lib/idlelib/macosxSupport.py
@@ -140,11 +140,9 @@
     # Remove the 'About' entry from the help menu, it is in the application
     # menu
     del Bindings.menudefs[-1][1][0:2]
-
-    # Remove the 'Configure' entry from the options menu, it is in the
+    # Remove the 'Configure Idle' entry from the options menu, it is in the
     # application menu as 'Preferences'
-    del Bindings.menudefs[-2][1][0:2]
-
+    del Bindings.menudefs[-2][1][0]
     menubar = Menu(root)
     root.configure(menu=menubar)
     menudict = {}

-- 
Repository URL: https://hg.python.org/cpython


More information about the Python-checkins mailing list