mirror of https://github.com/python/cpython
Merge with 3.4 #3068
This commit is contained in:
commit
637e1ab960
|
@ -77,7 +77,8 @@ menudefs = [
|
||||||
('!_Auto-open Stack Viewer', '<<toggle-jit-stack-viewer>>'),
|
('!_Auto-open Stack Viewer', '<<toggle-jit-stack-viewer>>'),
|
||||||
]),
|
]),
|
||||||
('options', [
|
('options', [
|
||||||
('_Configure IDLE...', '<<open-config-dialog>>'),
|
('Configure _IDLE', '<<open-config-dialog>>'),
|
||||||
|
('Configure _Extensions', '<<open-config-extensions-dialog>>'),
|
||||||
None,
|
None,
|
||||||
]),
|
]),
|
||||||
('help', [
|
('help', [
|
||||||
|
|
|
@ -189,6 +189,8 @@ class EditorWindow(object):
|
||||||
text.bind("<<python-docs>>", self.python_docs)
|
text.bind("<<python-docs>>", self.python_docs)
|
||||||
text.bind("<<about-idle>>", self.about_dialog)
|
text.bind("<<about-idle>>", self.about_dialog)
|
||||||
text.bind("<<open-config-dialog>>", self.config_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("<<open-module>>", self.open_module)
|
||||||
text.bind("<<do-nothing>>", lambda event: "break")
|
text.bind("<<do-nothing>>", lambda event: "break")
|
||||||
text.bind("<<select-all>>", self.select_all)
|
text.bind("<<select-all>>", self.select_all)
|
||||||
|
@ -543,6 +545,8 @@ class EditorWindow(object):
|
||||||
|
|
||||||
def config_dialog(self, event=None):
|
def config_dialog(self, event=None):
|
||||||
configDialog.ConfigDialog(self.top,'Settings')
|
configDialog.ConfigDialog(self.top,'Settings')
|
||||||
|
def config_extensions_dialog(self, event=None):
|
||||||
|
configDialog.ConfigExtensionsDialog(self.top)
|
||||||
|
|
||||||
def help_dialog(self, event=None):
|
def help_dialog(self, event=None):
|
||||||
if self.root:
|
if self.root:
|
||||||
|
|
|
@ -20,8 +20,8 @@ from idlelib.tabbedpages import TabbedPageSet
|
||||||
from idlelib.keybindingDialog import GetKeysDialog
|
from idlelib.keybindingDialog import GetKeysDialog
|
||||||
from idlelib.configSectionNameDialog import GetCfgSectionNameDialog
|
from idlelib.configSectionNameDialog import GetCfgSectionNameDialog
|
||||||
from idlelib.configHelpSourceEdit import GetHelpSourceDialog
|
from idlelib.configHelpSourceEdit import GetHelpSourceDialog
|
||||||
|
from idlelib.tabbedpages import TabbedPageSet
|
||||||
from idlelib import macosxSupport
|
from idlelib import macosxSupport
|
||||||
|
|
||||||
class ConfigDialog(Toplevel):
|
class ConfigDialog(Toplevel):
|
||||||
|
|
||||||
def __init__(self, parent, title='', _htest=False, _utest=False):
|
def __init__(self, parent, title='', _htest=False, _utest=False):
|
||||||
|
@ -85,8 +85,6 @@ class ConfigDialog(Toplevel):
|
||||||
self.CreatePageKeys()
|
self.CreatePageKeys()
|
||||||
self.CreatePageGeneral()
|
self.CreatePageGeneral()
|
||||||
self.create_action_buttons().pack(side=BOTTOM)
|
self.create_action_buttons().pack(side=BOTTOM)
|
||||||
Frame(self, height=2, borderwidth=0).pack(side=BOTTOM)
|
|
||||||
|
|
||||||
def create_action_buttons(self):
|
def create_action_buttons(self):
|
||||||
if macosxSupport.isAquaTk():
|
if macosxSupport.isAquaTk():
|
||||||
# Changing the default padding on OSX results in unreadable
|
# Changing the default padding on OSX results in unreadable
|
||||||
|
@ -94,27 +92,30 @@ class ConfigDialog(Toplevel):
|
||||||
paddingArgs = {}
|
paddingArgs = {}
|
||||||
else:
|
else:
|
||||||
paddingArgs = {'padx':6, 'pady':3}
|
paddingArgs = {'padx':6, 'pady':3}
|
||||||
|
outer = Frame(self, pady=2)
|
||||||
frame = Frame(self, pady=2)
|
buttons = Frame(outer, pady=2)
|
||||||
self.buttonOk = Button(
|
self.buttonOk = Button(
|
||||||
frame, text='Ok', command=self.Ok,
|
buttons, text='Ok', command=self.Ok,
|
||||||
takefocus=FALSE, **paddingArgs)
|
takefocus=FALSE, **paddingArgs)
|
||||||
self.buttonApply = Button(
|
self.buttonApply = Button(
|
||||||
frame, text='Apply', command=self.Apply,
|
buttons, text='Apply', command=self.Apply,
|
||||||
takefocus=FALSE, **paddingArgs)
|
takefocus=FALSE, **paddingArgs)
|
||||||
self.buttonCancel = Button(
|
self.buttonCancel = Button(
|
||||||
frame, text='Cancel', command=self.Cancel,
|
buttons, text='Cancel', command=self.Cancel,
|
||||||
takefocus=FALSE, **paddingArgs)
|
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.buttonOk.pack(side=LEFT, padx=5)
|
||||||
self.buttonApply.pack(side=LEFT, padx=5)
|
self.buttonApply.pack(side=LEFT, padx=5)
|
||||||
self.buttonCancel.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):
|
def CreatePageFontTab(self):
|
||||||
parent = self.parent
|
parent = self.parent
|
||||||
self.fontSize = StringVar(parent)
|
self.fontSize = StringVar(parent)
|
||||||
|
@ -1188,10 +1189,252 @@ class ConfigDialog(Toplevel):
|
||||||
def Help(self):
|
def Help(self):
|
||||||
pass
|
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__':
|
if __name__ == '__main__':
|
||||||
import unittest
|
import unittest
|
||||||
unittest.main('idlelib.idle_test.test_configdialog',
|
unittest.main('idlelib.idle_test.test_configdialog',
|
||||||
verbosity=2, exit=False)
|
verbosity=2, exit=False)
|
||||||
|
|
||||||
from idlelib.idle_test.htest import run
|
from idlelib.idle_test.htest import run
|
||||||
run(ConfigDialog)
|
run(ConfigDialog, ConfigExtensionsDialog)
|
||||||
|
|
|
@ -44,6 +44,9 @@ class IdleConfParser(ConfigParser):
|
||||||
Get an option value for given section/option or return default.
|
Get an option value for given section/option or return default.
|
||||||
If type is specified, return as type.
|
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):
|
if not self.has_option(section, option):
|
||||||
return default
|
return default
|
||||||
if type == 'bool':
|
if type == 'bool':
|
||||||
|
|
|
@ -93,6 +93,15 @@ _class_browser_spec = {
|
||||||
"Double clicking on items prints a traceback for an exception "
|
"Double clicking on items prints a traceback for an exception "
|
||||||
"that is ignored."
|
"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 = {
|
_color_delegator_spec = {
|
||||||
'file': 'ColorDelegator',
|
'file': 'ColorDelegator',
|
||||||
|
|
|
@ -140,11 +140,9 @@ def overrideRootMenu(root, flist):
|
||||||
# Remove the 'About' entry from the help menu, it is in the application
|
# Remove the 'About' entry from the help menu, it is in the application
|
||||||
# menu
|
# menu
|
||||||
del Bindings.menudefs[-1][1][0:2]
|
del Bindings.menudefs[-1][1][0:2]
|
||||||
|
# Remove the 'Configure Idle' entry from the options menu, it is in the
|
||||||
# Remove the 'Configure' entry from the options menu, it is in the
|
|
||||||
# application menu as 'Preferences'
|
# application menu as 'Preferences'
|
||||||
del Bindings.menudefs[-2][1][0:2]
|
del Bindings.menudefs[-2][1][0]
|
||||||
|
|
||||||
menubar = Menu(root)
|
menubar = Menu(root)
|
||||||
root.configure(menu=menubar)
|
root.configure(menu=menubar)
|
||||||
menudict = {}
|
menudict = {}
|
||||||
|
|
Loading…
Reference in New Issue