Commit 93f3542a authored by Terry Jan Reedy's avatar Terry Jan Reedy

Issue #24782: Finish converting the Configure Extension dialog into a new

tab in the IDLE Preferences dialog.  Code patch by Mark Roseman.
parent 5805ddee
......@@ -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.
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
......
......@@ -78,7 +78,6 @@ menudefs = [
]),
('options', [
('Configure _IDLE', '<<open-config-dialog>>'),
('Configure _Extensions', '<<open-config-extensions-dialog>>'),
None,
]),
('help', [
......
......@@ -191,8 +191,6 @@ class EditorWindow(object):
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)
......@@ -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.
......
......@@ -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,127 +1184,58 @@ class ConfigDialog(Toplevel):
view_text(self, title='Help for IDLE preferences',
text=help_common+help_pages.get(page, ''))
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
a .idlerc directory in your home directory. Except as noted,
hese changes apply to all versions of IDLE installed on this
machine. Some do not take affect until IDLE is restarted.
[Cancel] only cancels changes made since the last save.
'''
help_pages = {
'Highlighting':'''
Highlighting:
The IDLE Dark color theme is new in Octover 2015. It can only
be used with older IDLE releases if it is saved as a custom
theme, with a different name.
'''
}
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)
interior.bind('<Configure>', _configure_interior)
def CreatePageExtensions(self):
"""Part of the config dialog used for configuring IDLE extensions.
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
This code is generic - it works for any and all IDLE extensions.
# 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.
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.
This dialog is generic - it works for any and all IDLE extensions.
Not all changes take effect immediately - some may require restarting IDLE.
This depends on each extension's implementation.
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']
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()
self.create_widgets()
# 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.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()
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."
......@@ -1309,7 +1244,7 @@ class ConfigExtensionsDialog(Toplevel):
self.extensions[ext_name] = []
for ext_name in self.extensions:
opt_list = sorted(self.defaultCfg.GetOptionList(ext_name))
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
......@@ -1319,7 +1254,7 @@ class ConfigExtensionsDialog(Toplevel):
opt_list = enables + opt_list
for opt_name in opt_list:
def_str = self.defaultCfg.Get(
def_str = self.ext_defaultCfg.Get(
ext_name, opt_name, raw=True)
try:
def_obj = {'True':True, 'False':False}[def_str]
......@@ -1332,7 +1267,7 @@ class ConfigExtensionsDialog(Toplevel):
def_obj = def_str
opt_type = None
try:
value = self.userCfg.Get(
value = self.ext_userCfg.Get(
ext_name, opt_name, type=opt_type, raw=True,
default=def_obj)
except ValueError: # Need this until .Get fixed
......@@ -1347,37 +1282,6 @@ class ConfigExtensionsDialog(Toplevel):
'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('<<ListboxSelect>>', 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:
......@@ -1392,8 +1296,6 @@ class ConfigExtensionsDialog(Toplevel):
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)
......@@ -1420,19 +1322,7 @@ class ConfigExtensionsDialog(Toplevel):
).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):
def set_extension_value(self, section, opt):
name = opt['name']
default = opt['default']
value = opt['var'].get().strip() or default
......@@ -1440,20 +1330,92 @@ class ConfigExtensionsDialog(Toplevel):
# if self.defaultCfg.has_section(section):
# Currently, always true; if not, indent to return
if (value == default):
return self.userCfg.RemoveOption(section, name)
return self.ext_userCfg.RemoveOption(section, name)
# set the option
return self.userCfg.SetOption(section, name, value)
return self.ext_userCfg.SetOption(section, name, value)
def save_all_changed_configs(self):
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_user_value(ext_name, opt):
if self.set_extension_value(ext_name, opt):
has_changes = True
if has_changes:
self.userCfg.Save()
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
a .idlerc directory in your home directory. Except as noted,
hese changes apply to all versions of IDLE installed on this
machine. Some do not take affect until IDLE is restarted.
[Cancel] only cancels changes made since the last save.
'''
help_pages = {
'Highlighting':'''
Highlighting:
The IDLE Dark color theme is new in Octover 2015. It can only
be used with older IDLE releases if it is saved as a custom
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.
* 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)
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
if __name__ == '__main__':
......
......@@ -266,16 +266,16 @@ access to locals and globals.</dd>
<h3>25.5.1.7. Options menu (Shell and Editor)<a class="headerlink" href="#options-menu-shell-and-editor" title="Permalink to this headline"></a></h3>
<dl class="docutils">
<dt>Configure IDLE</dt>
<dd>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&#8217;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.</dd>
<dt>Configure Extensions</dt>
<dd>Open a configuration dialog for setting preferences for extensions
(discussed below). See note above about the location of user settings.</dd>
<dd><p class="first">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.</p>
<p class="last">Non-default user settings are saved in a .idlerc directory in the user&#8217;s
home directory. Problems caused by bad user configuration files are solved
by editing or deleting one or more of the files in .idlerc.</p>
</dd>
<dt>Code Context (toggle)(Editor Window only)</dt>
<dd>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.</dd>
......@@ -699,7 +699,7 @@ are currently:</p>
The Python Software Foundation is a non-profit corporation.
<a href="https://www.python.org/psf/donations/">Please donate.</a>
<br />
Last updated on Oct 02, 2015.
Last updated on Oct 13, 2015.
<a href="../bugs.html">Found a bug</a>?
<br />
Created using <a href="http://sphinx.pocoo.org/">Sphinx</a> 1.2.3.
......
......@@ -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."
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment