From 93f3542ae4501a8520cb6769db82038986ecf8d5 Mon Sep 17 00:00:00 2001
From: Terry Jan Reedy
Date: Tue, 13 Oct 2015 22:03:51 -0400
Subject: [PATCH] Issue #24782: Finish converting the Configure Extension
dialog into a new tab in the IDLE Preferences dialog. Code patch by Mark
Roseman.
---
Doc/library/idle.rst | 19 +-
Lib/idlelib/Bindings.py | 1 -
Lib/idlelib/EditorWindow.py | 6 -
Lib/idlelib/configDialog.py | 394 +++++++++++++++------------------
Lib/idlelib/help.html | 22 +-
Lib/idlelib/idle_test/htest.py | 12 +-
6 files changed, 200 insertions(+), 254 deletions(-)
diff --git a/Doc/library/idle.rst b/Doc/library/idle.rst
index 2797057153f..4384d56814e 100644
--- a/Doc/library/idle.rst
+++ b/Doc/library/idle.rst
@@ -252,17 +252,16 @@ Options menu (Shell and Editor)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Configure IDLE
- Open a configuration dialog. Fonts, indentation, keybindings, and color
- themes may be altered. Startup Preferences may be set, and additional
- help sources can be specified. Non-default user setting are saved in a
- .idlerc directory in the user's home directory. Problems caused by bad user
- configuration files are solved by editing or deleting one or more of the
- files in .idlerc. On OS X, open the configuration dialog by selecting
- Preferences in the application menu.
+ Open a configuration dialog and change preferences for the following:
+ fonts, indentation, keybindings, text color themes, startup windows and
+ size, additional help sources, and extensions (see below). On OS X,
+ open the configuration dialog by selecting Preferences in the application
+ menu. To use a new built-in color theme (IDLE Dark) with older IDLEs,
+ save it as a new custom theme.
-Configure Extensions
- Open a configuration dialog for setting preferences for extensions
- (discussed below). See note above about the location of user settings.
+ Non-default user settings are saved in a .idlerc directory in the user's
+ home directory. Problems caused by bad user configuration files are solved
+ by editing or deleting one or more of the files in .idlerc.
Code Context (toggle)(Editor Window only)
Open a pane at the top of the edit window which shows the block context
diff --git a/Lib/idlelib/Bindings.py b/Lib/idlelib/Bindings.py
index 226671ccdb0..ab25ff18b6c 100644
--- a/Lib/idlelib/Bindings.py
+++ b/Lib/idlelib/Bindings.py
@@ -78,7 +78,6 @@ menudefs = [
]),
('options', [
('Configure _IDLE', '<>'),
- ('Configure _Extensions', '<>'),
None,
]),
('help', [
diff --git a/Lib/idlelib/EditorWindow.py b/Lib/idlelib/EditorWindow.py
index a634d1f323a..0196344d8dc 100644
--- a/Lib/idlelib/EditorWindow.py
+++ b/Lib/idlelib/EditorWindow.py
@@ -191,8 +191,6 @@ class EditorWindow(object):
text.bind("<>", self.python_docs)
text.bind("<>", self.about_dialog)
text.bind("<>", self.config_dialog)
- text.bind("<>",
- self.config_extensions_dialog)
text.bind("<>", self.open_module)
text.bind("<>", lambda event: "break")
text.bind("<>", self.select_all)
@@ -514,10 +512,6 @@ class EditorWindow(object):
# Synchronize with macosxSupport.overrideRootMenu.config_dialog.
configDialog.ConfigDialog(self.top,'Settings')
- def config_extensions_dialog(self, event=None):
- "Handle Options 'Configure Extensions' event."
- configDialog.ConfigExtensionsDialog(self.top)
-
def help_dialog(self, event=None):
"Handle Help 'IDLE Help' event."
# Synchronize with macosxSupport.overrideRootMenu.help_dialog.
diff --git a/Lib/idlelib/configDialog.py b/Lib/idlelib/configDialog.py
index 48cd9915926..3f1207b56ee 100644
--- a/Lib/idlelib/configDialog.py
+++ b/Lib/idlelib/configDialog.py
@@ -80,12 +80,14 @@ class ConfigDialog(Toplevel):
def CreateWidgets(self):
self.tabPages = TabbedPageSet(self,
- page_names=['Fonts/Tabs', 'Highlighting', 'Keys', 'General'])
+ 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):
@@ -1092,6 +1094,7 @@ class ConfigDialog(Toplevel):
self.LoadKeyCfg()
### general page
self.LoadGeneralCfg()
+ # note: extension page handled separately
def SaveNewKeySet(self, keySetName, keySet):
"""
@@ -1145,6 +1148,7 @@ class ConfigDialog(Toplevel):
# 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
@@ -1180,6 +1184,168 @@ class ConfigDialog(Toplevel):
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 an 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('<>', 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
+ 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.ext_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.ext_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 extension_selected(self, event):
+ newsel = self.extension_list.curselection()
+ if newsel:
+ newsel = self.extension_list.get(newsel)
+ if newsel is None or newsel != self.current_extension:
+ if self.current_extension:
+ self.details_frame.config(text='')
+ self.config_frame[self.current_extension].grid_forget()
+ self.current_extension = None
+ if newsel:
+ self.details_frame.config(text=newsel)
+ self.config_frame[newsel].grid(column=0, row=0, sticky='nsew')
+ self.current_extension = newsel
+
+ def create_extension_frame(self, ext_name):
+ """Create a frame holding the widgets to configure one extension"""
+ f = VerticalScrolledFrame(self.details_frame, height=250, width=250)
+ self.config_frame[ext_name] = f
+ entry_area = f.interior
+ # 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
+
+ def set_extension_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.ext_userCfg.RemoveOption(section, name)
+ # set the option
+ return self.ext_userCfg.SetOption(section, name, value)
+
+ def save_all_changed_extensions(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_extension_value(ext_name, opt):
+ has_changes = True
+ if has_changes:
+ self.ext_userCfg.Save()
+
+
help_common = '''\
When you click either the Apply or Ok buttons, settings in this
dialog that are different from IDLE's default are saved in
@@ -1198,6 +1364,17 @@ theme, with a different name.
}
+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
+
+
class VerticalScrolledFrame(Frame):
"""A pure Tkinter vertically scrollable frame.
@@ -1240,221 +1417,6 @@ class VerticalScrolledFrame(Frame):
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.extension_names = StringVar(self)
- self.rowconfigure(0, weight=1)
- self.columnconfigure(2, weight=1)
- self.extension_list = Listbox(self, listvariable=self.extension_names,
- selectmode='browse')
- self.extension_list.bind('<>', self.extension_selected)
- scroll = Scrollbar(self, command=self.extension_list.yview)
- self.extension_list.yscrollcommand=scroll.set
- self.details_frame = LabelFrame(self, 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])
- self.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 individual pages
- 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)
- self.create_action_buttons().grid(row=1, columnspan=3)
-
- def extension_selected(self, event):
- newsel = self.extension_list.curselection()
- if newsel:
- newsel = self.extension_list.get(newsel)
- if newsel is None or newsel != self.current_extension:
- if self.current_extension:
- self.details_frame.config(text='')
- self.config_frame[self.current_extension].grid_forget()
- self.current_extension = None
- if newsel:
- self.details_frame.config(text=newsel)
- self.config_frame[newsel].grid(column=0, row=0, sticky='nsew')
- self.current_extension = newsel
-
- create_action_buttons = ConfigDialog.create_action_buttons
-
- def create_extension_frame(self, ext_name):
- """Create a frame holding the widgets to configure one extension"""
- f = VerticalScrolledFrame(self.details_frame, height=250, width=250)
- self.config_frame[ext_name] = f
- entry_area = f.interior
- # 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
diff --git a/Lib/idlelib/help.html b/Lib/idlelib/help.html
index 379b277d31f..2189fd4cf0c 100644
--- a/Lib/idlelib/help.html
+++ b/Lib/idlelib/help.html
@@ -266,16 +266,16 @@ access to locals and globals.
25.5.1.7. Options menu (Shell and Editor)
- Configure IDLE
-- Open a configuration dialog. Fonts, indentation, keybindings, and color
-themes may be altered. Startup Preferences may be set, and additional
-help sources can be specified. Non-default user setting are saved in a
-.idlerc directory in the user’s home directory. Problems caused by bad user
-configuration files are solved by editing or deleting one or more of the
-files in .idlerc. On OS X, open the configuration dialog by selecting
-Preferences in the application menu.
-- Configure Extensions
-- Open a configuration dialog for setting preferences for extensions
-(discussed below). See note above about the location of user settings.
+Open a configuration dialog and change preferences for the following:
+fonts, indentation, keybindings, text color themes, startup windows and
+size, additional help sources, and extensions (see below). On OS X,
+open the configuration dialog by selecting Preferences in the application
+menu. To use a new built-in color theme (IDLE Dark) with older IDLEs,
+save it as a new custom theme.
+Non-default user settings are saved in a .idlerc directory in the user’s
+home directory. Problems caused by bad user configuration files are solved
+by editing or deleting one or more of the files in .idlerc.
+
- Code Context (toggle)(Editor Window only)
- Open a pane at the top of the edit window which shows the block context
of the code which has scrolled above the top of the window.
@@ -699,7 +699,7 @@ are currently:
The Python Software Foundation is a non-profit corporation.
Please donate.
- Last updated on Oct 02, 2015.
+ Last updated on Oct 13, 2015.
Found a bug?
Created using Sphinx 1.2.3.
diff --git a/Lib/idlelib/idle_test/htest.py b/Lib/idlelib/idle_test/htest.py
index 3a9885c7732..b0f434046bf 100644
--- a/Lib/idlelib/idle_test/htest.py
+++ b/Lib/idlelib/idle_test/htest.py
@@ -93,15 +93,6 @@ _class_browser_spec = {
"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',
@@ -121,7 +112,8 @@ ConfigDialog_spec = {
"font face of the text in the area below it.\nIn the "
"'Highlighting' tab, try different color schemes. Clicking "
"items in the sample program should update the choices above it."
- "\nIn the 'Keys' and 'General' tab, test settings of interest."
+ "\nIn the 'Keys', 'General' and 'Extensions' tabs, test settings"
+ "of interest."
"\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."