summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Lib/idlelib/configdialog.py408
-rw-r--r--Lib/idlelib/idle_test/test_configdialog.py389
-rw-r--r--Misc/NEWS.d/next/IDLE/2017-08-03-17-54-02.bpo-31002.kUSgTE.rst1
3 files changed, 610 insertions, 188 deletions
diff --git a/Lib/idlelib/configdialog.py b/Lib/idlelib/configdialog.py
index 6317237..66219f1 100644
--- a/Lib/idlelib/configdialog.py
+++ b/Lib/idlelib/configdialog.py
@@ -251,25 +251,24 @@ class ConfigDialog(Toplevel):
delete_custom_theme: Ativate default [button_delete_custom_theme].
save_new_theme: Save to userCfg['theme'] (is function).
- Widget Structure: (*) widgets bound to self
- frame
- frame_custom: LabelFrame
- (*)highlight_sample: Text
- (*)frame_color_set: Frame
- button_set_color: Button
- (*)opt_menu_highlight_target: DynOptionMenu - highlight_target
- frame_fg_bg_toggle: Frame
- (*)radio_fg: Radiobutton - fg_bg_toggle
- (*)radio_bg: Radiobutton - fg_bg_toggle
- button_save_custom_theme: Button
- frame_theme: LabelFrame
- theme_type_title: Label
- (*)radio_theme_builtin: Radiobutton - is_builtin_theme
- (*)radio_theme_custom: Radiobutton - is_builtin_theme
- (*)opt_menu_theme_builtin: DynOptionMenu - builtin_theme
- (*)opt_menu_theme_custom: DynOptionMenu - custom_theme
- (*)button_delete_custom_theme: Button
- (*)new_custom_theme: Label
+ Widgets of highlights page frame: (*) widgets bound to self
+ frame_custom: LabelFrame
+ (*)highlight_sample: Text
+ (*)frame_color_set: Frame
+ button_set_color: Button
+ (*)opt_menu_highlight_target: DynOptionMenu - highlight_target
+ frame_fg_bg_toggle: Frame
+ (*)radio_fg: Radiobutton - fg_bg_toggle
+ (*)radio_bg: Radiobutton - fg_bg_toggle
+ button_save_custom_theme: Button
+ frame_theme: LabelFrame
+ theme_type_title: Label
+ (*)radio_theme_builtin: Radiobutton - is_builtin_theme
+ (*)radio_theme_custom: Radiobutton - is_builtin_theme
+ (*)opt_menu_theme_builtin: DynOptionMenu - builtin_theme
+ (*)opt_menu_theme_custom: DynOptionMenu - custom_theme
+ (*)button_delete_custom_theme: Button
+ (*)new_custom_theme: Label
"""
self.theme_elements={
'Normal Text': ('normal', '00'),
@@ -796,52 +795,92 @@ class ConfigDialog(Toplevel):
def create_page_keys(self):
"""Return frame of widgets for Keys tab.
+ Enable users to provisionally change both individual and sets of
+ keybindings (shortcut keys). Except for features implemented as
+ extensions, keybindings are stored in complete sets called
+ keysets. Built-in keysets in idlelib/config-keys.def are fixed
+ as far as the dialog is concerned. Any keyset can be used as the
+ base for a new custom keyset, stored in .idlerc/config-keys.cfg.
+
+ Function load_key_cfg() initializes tk variables and keyset
+ lists and calls load_keys_list for the current keyset.
+ Radiobuttons builtin_keyset_on and custom_keyset_on toggle var
+ keyset_source, which controls if the current set of keybindings
+ are from a builtin or custom keyset. DynOptionMenus builtinlist
+ and customlist contain lists of the builtin and custom keysets,
+ respectively, and the current item from each list is stored in
+ vars builtin_name and custom_name.
+
+ Button delete_custom_keys invokes delete_custom_keys() to delete
+ a custom keyset from idleConf.userCfg['keys'] and changes. Button
+ save_custom_keys invokes save_as_new_key_set() which calls
+ get_new_keys_name() and create_new_key_set() to save a custom keyset
+ and its keybindings to idleConf.userCfg['keys'].
+
+ Listbox bindingslist contains all of the keybindings for the
+ selected keyset. The keybindings are loaded in load_keys_list()
+ and are pairs of (event, [keys]) where keys can be a list
+ of one or more key combinations to bind to the same event.
+ Mouse button 1 click invokes on_bindingslist_select(), which
+ allows button_new_keys to be clicked.
+
+ So, an item is selected in listbindings, which activates
+ button_new_keys, and clicking button_new_keys calls function
+ get_new_keys(). Function get_new_keys() gets the key mappings from the
+ current keyset for the binding event item that was selected. The
+ function then displays another dialog, GetKeysDialog, with the
+ selected binding event and current keys and always new key sequences
+ to be entered for that binding event. If the keys aren't
+ changed, nothing happens. If the keys are changed and the keyset
+ is a builtin, function get_new_keys_name() will be called
+ for input of a custom keyset name. If no name is given, then the
+ change to the keybinding will abort and no updates will be made. If
+ a custom name is entered in the prompt or if the current keyset was
+ already custom (and thus didn't require a prompt), then
+ idleConf.userCfg['keys'] is updated in function create_new_key_set()
+ with the change to the event binding. The item listing in bindingslist
+ is updated with the new keys. Var keybinding is also set which invokes
+ the callback function, var_changed_keybinding, to add the change to
+ the 'keys' or 'extensions' changes tracker based on the binding type.
+
Tk Variables:
- builtin_keys: Menu variable for built-in keybindings.
- custom_keys: Menu variable for custom keybindings.
- are_keys_builtin: Selector for built-in or custom keybindings.
keybinding: Action/key bindings.
Methods:
- load_key_config: Set table.
load_keys_list: Reload active set.
- keybinding_selected: Bound to list_bindings button release.
- get_new_keys: Command for button_new_keys.
- get_new_keys_name: Call popup.
create_new_key_set: Combine active keyset and changes.
- set_keys_type: Command for are_keys_builtin.
- delete_custom_keys: Command for button_delete_custom_keys.
- save_as_new_key_set: Command for button_save_custom_keys.
+ set_keys_type: Command for keyset_source.
save_new_key_set: Save to idleConf.userCfg['keys'] (is function).
deactivate_current_config: Remove keys bindings in editors.
- Widget Structure: (*) widgets bound to self
- frame
- frame_custom: LabelFrame
- frame_target: Frame
- target_title: Label
- scroll_target_y: Scrollbar
- scroll_target_x: Scrollbar
- (*)list_bindings: ListBox
- (*)button_new_keys: Button
- frame_key_sets: LabelFrame
- frames[0]: Frame
- (*)radio_keys_builtin: Radiobutton - are_keys_builtin
- (*)radio_keys_custom: Radiobutton - are_keys_builtin
- (*)opt_menu_keys_builtin: DynOptionMenu - builtin_keys
- (*)opt_menu_keys_custom: DynOptionMenu - custom_keys
- (*)new_custom_keys: Label
- frames[1]: Frame
- (*)button_delete_custom_keys: Button
- button_save_custom_keys: Button
+ Widgets for keys page frame: (*) widgets bound to self
+ frame_key_sets: LabelFrame
+ frames[0]: Frame
+ (*)builtin_keyset_on: Radiobutton - var keyset_source
+ (*)custom_keyset_on: Radiobutton - var keyset_source
+ (*)builtinlist: DynOptionMenu - var builtin_name,
+ func keybinding_selected
+ (*)customlist: DynOptionMenu - var custom_name,
+ func keybinding_selected
+ (*)keys_message: Label
+ frames[1]: Frame
+ (*)button_delete_custom_keys: Button - delete_custom_keys
+ (*)button_save_custom_keys: Button - save_as_new_key_set
+ frame_custom: LabelFrame
+ frame_target: Frame
+ target_title: Label
+ scroll_target_y: Scrollbar
+ scroll_target_x: Scrollbar
+ (*)bindingslist: ListBox - on_bindingslist_select
+ (*)button_new_keys: Button - get_new_keys & ..._name
"""
parent = self.parent
- self.builtin_keys = tracers.add(
- StringVar(parent), self.var_changed_builtin_keys)
- self.custom_keys = tracers.add(
- StringVar(parent), self.var_changed_custom_keys)
- self.are_keys_builtin = tracers.add(
- BooleanVar(parent), self.var_changed_are_keys_builtin)
+ self.builtin_name = tracers.add(
+ StringVar(parent), self.var_changed_builtin_name)
+ self.custom_name = tracers.add(
+ StringVar(parent), self.var_changed_custom_name)
+ self.keyset_source = tracers.add(
+ BooleanVar(parent), self.var_changed_keyset_source)
self.keybinding = tracers.add(
StringVar(parent), self.var_changed_keybinding)
@@ -858,36 +897,37 @@ class ConfigDialog(Toplevel):
target_title = Label(frame_target, text='Action - Key(s)')
scroll_target_y = Scrollbar(frame_target)
scroll_target_x = Scrollbar(frame_target, orient=HORIZONTAL)
- self.list_bindings = Listbox(
+ self.bindingslist = Listbox(
frame_target, takefocus=FALSE, exportselection=FALSE)
- self.list_bindings.bind('<ButtonRelease-1>', self.keybinding_selected)
- scroll_target_y.config(command=self.list_bindings.yview)
- scroll_target_x.config(command=self.list_bindings.xview)
- self.list_bindings.config(yscrollcommand=scroll_target_y.set)
- self.list_bindings.config(xscrollcommand=scroll_target_x.set)
+ self.bindingslist.bind('<ButtonRelease-1>',
+ self.on_bindingslist_select)
+ scroll_target_y['command'] = self.bindingslist.yview
+ scroll_target_x['command'] = self.bindingslist.xview
+ self.bindingslist['yscrollcommand'] = scroll_target_y.set
+ self.bindingslist['xscrollcommand'] = scroll_target_x.set
self.button_new_keys = Button(
frame_custom, text='Get New Keys for Selection',
command=self.get_new_keys, state=DISABLED)
#frame_key_sets
frames = [Frame(frame_key_sets, padx=2, pady=2, borderwidth=0)
for i in range(2)]
- self.radio_keys_builtin = Radiobutton(
- frames[0], variable=self.are_keys_builtin, value=1,
+ self.builtin_keyset_on = Radiobutton(
+ frames[0], variable=self.keyset_source, value=1,
command=self.set_keys_type, text='Use a Built-in Key Set')
- self.radio_keys_custom = Radiobutton(
- frames[0], variable=self.are_keys_builtin, value=0,
+ self.custom_keyset_on = Radiobutton(
+ frames[0], variable=self.keyset_source, value=0,
command=self.set_keys_type, text='Use a Custom Key Set')
- self.opt_menu_keys_builtin = DynOptionMenu(
- frames[0], self.builtin_keys, None, command=None)
- self.opt_menu_keys_custom = DynOptionMenu(
- frames[0], self.custom_keys, None, command=None)
+ self.builtinlist = DynOptionMenu(
+ frames[0], self.builtin_name, None, command=None)
+ self.customlist = DynOptionMenu(
+ frames[0], self.custom_name, None, command=None)
self.button_delete_custom_keys = Button(
frames[1], text='Delete Custom Key Set',
command=self.delete_custom_keys)
- button_save_custom_keys = Button(
+ self.button_save_custom_keys = Button(
frames[1], text='Save as New Custom Key Set',
command=self.save_as_new_key_set)
- self.new_custom_keys = Label(frames[0], bd=2)
+ self.keys_message = Label(frames[0], bd=2)
##widget packing
#body
@@ -900,17 +940,17 @@ class ConfigDialog(Toplevel):
frame_target.columnconfigure(0, weight=1)
frame_target.rowconfigure(1, weight=1)
target_title.grid(row=0, column=0, columnspan=2, sticky=W)
- self.list_bindings.grid(row=1, column=0, sticky=NSEW)
+ self.bindingslist.grid(row=1, column=0, sticky=NSEW)
scroll_target_y.grid(row=1, column=1, sticky=NS)
scroll_target_x.grid(row=2, column=0, sticky=EW)
#frame_key_sets
- self.radio_keys_builtin.grid(row=0, column=0, sticky=W+NS)
- self.radio_keys_custom.grid(row=1, column=0, sticky=W+NS)
- self.opt_menu_keys_builtin.grid(row=0, column=1, sticky=NSEW)
- self.opt_menu_keys_custom.grid(row=1, column=1, sticky=NSEW)
- self.new_custom_keys.grid(row=0, column=2, sticky=NSEW, padx=5, pady=5)
+ self.builtin_keyset_on.grid(row=0, column=0, sticky=W+NS)
+ self.custom_keyset_on.grid(row=1, column=0, sticky=W+NS)
+ self.builtinlist.grid(row=0, column=1, sticky=NSEW)
+ self.customlist.grid(row=1, column=1, sticky=NSEW)
+ self.keys_message.grid(row=0, column=2, sticky=NSEW, padx=5, pady=5)
self.button_delete_custom_keys.pack(side=LEFT, fill=X, expand=True, padx=2)
- button_save_custom_keys.pack(side=LEFT, fill=X, expand=True, padx=2)
+ self.button_save_custom_keys.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
@@ -918,35 +958,35 @@ class ConfigDialog(Toplevel):
def load_key_cfg(self):
"Load current configuration settings for the keybinding options."
# Set current keys type radiobutton.
- self.are_keys_builtin.set(idleConf.GetOption(
+ self.keyset_source.set(idleConf.GetOption(
'main', 'Keys', 'default', type='bool', default=1))
# Set current keys.
current_option = idleConf.CurrentKeys()
# Load available keyset option menus.
- if self.are_keys_builtin.get(): # Default theme selected.
+ if self.keyset_source.get(): # Default theme selected.
item_list = idleConf.GetSectionList('default', 'keys')
item_list.sort()
- self.opt_menu_keys_builtin.SetMenu(item_list, current_option)
+ self.builtinlist.SetMenu(item_list, current_option)
item_list = idleConf.GetSectionList('user', 'keys')
item_list.sort()
if not item_list:
- self.radio_keys_custom['state'] = DISABLED
- self.custom_keys.set('- no custom keys -')
+ self.custom_keyset_on['state'] = DISABLED
+ self.custom_name.set('- no custom keys -')
else:
- self.opt_menu_keys_custom.SetMenu(item_list, item_list[0])
+ self.customlist.SetMenu(item_list, item_list[0])
else: # User key set selected.
item_list = idleConf.GetSectionList('user', 'keys')
item_list.sort()
- self.opt_menu_keys_custom.SetMenu(item_list, current_option)
+ self.customlist.SetMenu(item_list, current_option)
item_list = idleConf.GetSectionList('default', 'keys')
item_list.sort()
- self.opt_menu_keys_builtin.SetMenu(item_list, idleConf.default_keys())
+ self.builtinlist.SetMenu(item_list, idleConf.default_keys())
self.set_keys_type()
# Load keyset element list.
keyset_name = idleConf.CurrentKeys()
self.load_keys_list(keyset_name)
- def var_changed_builtin_keys(self, *params):
+ def var_changed_builtin_name(self, *params):
"Process selection of builtin key set."
old_keys = (
'IDLE Classic Windows',
@@ -954,40 +994,41 @@ class ConfigDialog(Toplevel):
'IDLE Classic Mac',
'IDLE Classic OSX',
)
- value = self.builtin_keys.get()
+ value = self.builtin_name.get()
if value not in old_keys:
if idleConf.GetOption('main', 'Keys', 'name') not in old_keys:
changes.add_option('main', 'Keys', 'name', old_keys[0])
changes.add_option('main', 'Keys', 'name2', value)
- self.new_custom_keys.config(text='New key set, see Help',
- fg='#500000')
+ self.keys_message['text'] = 'New key set, see Help'
+ self.keys_message['fg'] = '#500000'
else:
changes.add_option('main', 'Keys', 'name', value)
changes.add_option('main', 'Keys', 'name2', '')
- self.new_custom_keys.config(text='', fg='black')
+ self.keys_message['text'] = ''
+ self.keys_message['fg'] = 'black'
self.load_keys_list(value)
- def var_changed_custom_keys(self, *params):
+ def var_changed_custom_name(self, *params):
"Process selection of custom key set."
- value = self.custom_keys.get()
+ value = self.custom_name.get()
if value != '- no custom keys -':
changes.add_option('main', 'Keys', 'name', value)
self.load_keys_list(value)
- def var_changed_are_keys_builtin(self, *params):
+ def var_changed_keyset_source(self, *params):
"Process toggle between builtin key set and custom key set."
- value = self.are_keys_builtin.get()
+ value = self.keyset_source.get()
changes.add_option('main', 'Keys', 'default', value)
if value:
- self.var_changed_builtin_keys()
+ self.var_changed_builtin_name()
else:
- self.var_changed_custom_keys()
+ self.var_changed_custom_name()
def var_changed_keybinding(self, *params):
"Store change to a keybinding."
value = self.keybinding.get()
- key_set = self.custom_keys.get()
- event = self.list_bindings.get(ANCHOR).split()[0]
+ key_set = self.custom_name.get()
+ event = self.bindingslist.get(ANCHOR).split()[0]
if idleConf.IsCoreBinding(event):
changes.add_option('keys', key_set, event, value)
else: # Event is an extension binding.
@@ -997,14 +1038,14 @@ class ConfigDialog(Toplevel):
def set_keys_type(self):
"Set available screen options based on builtin or custom key set."
- if self.are_keys_builtin.get():
- self.opt_menu_keys_builtin['state'] = NORMAL
- self.opt_menu_keys_custom['state'] = DISABLED
+ if self.keyset_source.get():
+ self.builtinlist['state'] = NORMAL
+ self.customlist['state'] = DISABLED
self.button_delete_custom_keys['state'] = DISABLED
else:
- self.opt_menu_keys_builtin['state'] = DISABLED
- self.radio_keys_custom['state'] = NORMAL
- self.opt_menu_keys_custom['state'] = NORMAL
+ self.builtinlist['state'] = DISABLED
+ self.custom_keyset_on['state'] = NORMAL
+ self.customlist['state'] = NORMAL
self.button_delete_custom_keys['state'] = NORMAL
def get_new_keys(self):
@@ -1016,13 +1057,13 @@ class ConfigDialog(Toplevel):
changed, then a name for a custom key set needs to be
entered for the change to be applied.
"""
- list_index = self.list_bindings.index(ANCHOR)
- binding = self.list_bindings.get(list_index)
+ list_index = self.bindingslist.index(ANCHOR)
+ binding = self.bindingslist.get(list_index)
bind_name = binding.split()[0]
- if self.are_keys_builtin.get():
- current_key_set_name = self.builtin_keys.get()
+ if self.keyset_source.get():
+ current_key_set_name = self.builtin_name.get()
else:
- current_key_set_name = self.custom_keys.get()
+ current_key_set_name = self.custom_name.get()
current_bindings = idleConf.GetCurrentKeySet()
if current_key_set_name in changes['keys']: # unsaved changes
key_set_changes = changes['keys'][current_key_set_name]
@@ -1032,24 +1073,24 @@ class ConfigDialog(Toplevel):
new_keys = GetKeysDialog(self, 'Get New Keys', bind_name,
current_key_sequences).result
if new_keys:
- if self.are_keys_builtin.get(): # Current key set is a built-in.
+ if self.keyset_source.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.')
new_keyset = self.get_new_keys_name(message)
if not new_keyset: # User cancelled custom key set creation.
- self.list_bindings.select_set(list_index)
- self.list_bindings.select_anchor(list_index)
+ self.bindingslist.select_set(list_index)
+ self.bindingslist.select_anchor(list_index)
return
else: # Create new custom key set based on previously active key set.
self.create_new_key_set(new_keyset)
- self.list_bindings.delete(list_index)
- self.list_bindings.insert(list_index, bind_name+' - '+new_keys)
- self.list_bindings.select_set(list_index)
- self.list_bindings.select_anchor(list_index)
+ self.bindingslist.delete(list_index)
+ self.bindingslist.insert(list_index, bind_name+' - '+new_keys)
+ self.bindingslist.select_set(list_index)
+ self.bindingslist.select_anchor(list_index)
self.keybinding.set(new_keys)
else:
- self.list_bindings.select_set(list_index)
- self.list_bindings.select_anchor(list_index)
+ self.bindingslist.select_set(list_index)
+ self.bindingslist.select_anchor(list_index)
def get_new_keys_name(self, message):
"Return new key set name from query popup."
@@ -1065,21 +1106,20 @@ class ConfigDialog(Toplevel):
if new_keys_name:
self.create_new_key_set(new_keys_name)
- def keybinding_selected(self, event):
+ def on_bindingslist_select(self, event):
"Activate button to assign new keys to selected action."
self.button_new_keys['state'] = NORMAL
def create_new_key_set(self, new_key_set_name):
"""Create a new custom key set with the given name.
- Create the new key set based on the previously active set
- with the current changes applied. Once it is saved, then
- activate the new key set.
+ Copy the bindings/keys from the previously active keyset
+ to the new keyset and activate the new custom keyset.
"""
- if self.are_keys_builtin.get():
- prev_key_set_name = self.builtin_keys.get()
+ if self.keyset_source.get():
+ prev_key_set_name = self.builtin_name.get()
else:
- prev_key_set_name = self.custom_keys.get()
+ prev_key_set_name = self.custom_name.get()
prev_keys = idleConf.GetCoreKeys(prev_key_set_name)
new_keys = {}
for event in prev_keys: # Add key set to changed items.
@@ -1096,8 +1136,8 @@ class ConfigDialog(Toplevel):
# Change GUI over to the new key set.
custom_key_list = idleConf.GetSectionList('user', 'keys')
custom_key_list.sort()
- self.opt_menu_keys_custom.SetMenu(custom_key_list, new_key_set_name)
- self.are_keys_builtin.set(0)
+ self.customlist.SetMenu(custom_key_list, new_key_set_name)
+ self.keyset_source.set(0)
self.set_keys_type()
def load_keys_list(self, keyset_name):
@@ -1105,14 +1145,14 @@ class ConfigDialog(Toplevel):
An action/key binding can be selected to change the key binding.
"""
- reselect = 0
- if self.list_bindings.curselection():
- reselect = 1
- list_index = self.list_bindings.index(ANCHOR)
+ reselect = False
+ if self.bindingslist.curselection():
+ reselect = True
+ list_index = self.bindingslist.index(ANCHOR)
keyset = idleConf.GetKeySet(keyset_name)
bind_names = list(keyset.keys())
bind_names.sort()
- self.list_bindings.delete(0, END)
+ self.bindingslist.delete(0, END)
for bind_name in bind_names:
key = ' '.join(keyset[bind_name])
bind_name = bind_name[2:-2] # Trim off the angle brackets.
@@ -1120,17 +1160,21 @@ class ConfigDialog(Toplevel):
# Handle any unsaved changes to this key set.
if bind_name in changes['keys'][keyset_name]:
key = changes['keys'][keyset_name][bind_name]
- self.list_bindings.insert(END, bind_name+' - '+key)
+ self.bindingslist.insert(END, bind_name+' - '+key)
if reselect:
- self.list_bindings.see(list_index)
- self.list_bindings.select_set(list_index)
- self.list_bindings.select_anchor(list_index)
+ self.bindingslist.see(list_index)
+ self.bindingslist.select_set(list_index)
+ self.bindingslist.select_anchor(list_index)
def save_new_key_set(self, keyset_name, keyset):
"""Save a newly created core key set.
+ Add keyset to idleConf.userCfg['keys'], not to disk.
+ If the keyset doesn't exist, it is created. The
+ binding/keys are taken from the keyset argument.
+
keyset_name - string, the name of the new key set
- keyset - dictionary containing the new key set
+ keyset - dictionary containing the new keybindings
"""
if not idleConf.userCfg['keys'].has_section(keyset_name):
idleConf.userCfg['keys'].add_section(keyset_name)
@@ -1145,7 +1189,7 @@ class ConfigDialog(Toplevel):
reverts to the default. The custom key set is permanently
deleted from the config file.
"""
- keyset_name=self.custom_keys.get()
+ keyset_name=self.custom_name.get()
delmsg = 'Are you sure you wish to delete the key set %r ?'
if not tkMessageBox.askyesno(
'Delete Key Set', delmsg % keyset_name, parent=self):
@@ -1157,14 +1201,14 @@ class ConfigDialog(Toplevel):
item_list = idleConf.GetSectionList('user', 'keys')
item_list.sort()
if not item_list:
- self.radio_keys_custom['state'] = DISABLED
- self.opt_menu_keys_custom.SetMenu(item_list, '- no custom keys -')
+ self.custom_keyset_on['state'] = DISABLED
+ self.customlist.SetMenu(item_list, '- no custom keys -')
else:
- self.opt_menu_keys_custom.SetMenu(item_list, item_list[0])
+ self.customlist.SetMenu(item_list, item_list[0])
# Revert to default key set.
- self.are_keys_builtin.set(idleConf.defaultCfg['main']
+ self.keyset_source.set(idleConf.defaultCfg['main']
.Get('Keys', 'default'))
- self.builtin_keys.set(idleConf.defaultCfg['main'].Get('Keys', 'name')
+ self.builtin_name.set(idleConf.defaultCfg['main'].Get('Keys', 'name')
or idleConf.default_keys())
# User can't back out of these changes, they must be applied now.
changes.save_all()
@@ -1438,22 +1482,21 @@ class FontPage(Frame):
which invokes the default callback to add an entry to
changes. Load_tab_cfg initializes space_num to default.
- Widget Structure: (*) widgets bound to self
- frame (of tab_pages)
- frame_font: LabelFrame
- frame_font_name: Frame
- font_name_title: Label
- (*)fontlist: ListBox - font_name
- scroll_font: Scrollbar
- frame_font_param: Frame
- font_size_title: Label
- (*)sizelist: DynOptionMenu - font_size
- (*)bold_toggle: Checkbutton - font_bold
- frame_font_sample: Frame
- (*)font_sample: Label
- frame_indent: LabelFrame
- indent_title: Label
- (*)indent_scale: Scale - space_num
+ Widgets for FontPage(Frame): (*) widgets bound to self
+ frame_font: LabelFrame
+ frame_font_name: Frame
+ font_name_title: Label
+ (*)fontlist: ListBox - font_name
+ scroll_font: Scrollbar
+ frame_font_param: Frame
+ font_size_title: Label
+ (*)sizelist: DynOptionMenu - font_size
+ (*)bold_toggle: Checkbutton - font_bold
+ frame_font_sample: Frame
+ (*)font_sample: Label
+ frame_indent: LabelFrame
+ indent_title: Label
+ (*)indent_scale: Scale - space_num
"""
self.font_name = tracers.add(StringVar(self), self.var_changed_font)
self.font_size = tracers.add(StringVar(self), self.var_changed_font)
@@ -1633,30 +1676,29 @@ class GenPage(Frame):
set_add_delete_state. All but load call update_help_changes to
rewrite changes['main']['HelpFiles'].
- Widget Structure: (*) widgets bound to self
- frame
- frame_run: LabelFrame
- startup_title: Label
- (*)startup_editor_on: Radiobutton - startup_edit
- (*)startup_shell_on: Radiobutton - startup_edit
- frame_save: LabelFrame
- run_save_title: Label
- (*)save_ask_on: Radiobutton - autosave
- (*)save_auto_on: Radiobutton - autosave
- frame_win_size: LabelFrame
- win_size_title: Label
- win_width_title: Label
- (*)win_width_int: Entry - win_width
- win_height_title: Label
- (*)win_height_int: Entry - win_height
- frame_help: LabelFrame
- frame_helplist: Frame
- frame_helplist_buttons: Frame
- (*)button_helplist_edit
- (*)button_helplist_add
- (*)button_helplist_remove
- (*)helplist: ListBox
- scroll_helplist: Scrollbar
+ Widgets for GenPage(Frame): (*) widgets bound to self
+ frame_run: LabelFrame
+ startup_title: Label
+ (*)startup_editor_on: Radiobutton - startup_edit
+ (*)startup_shell_on: Radiobutton - startup_edit
+ frame_save: LabelFrame
+ run_save_title: Label
+ (*)save_ask_on: Radiobutton - autosave
+ (*)save_auto_on: Radiobutton - autosave
+ frame_win_size: LabelFrame
+ win_size_title: Label
+ win_width_title: Label
+ (*)win_width_int: Entry - win_width
+ win_height_title: Label
+ (*)win_height_int: Entry - win_height
+ frame_help: LabelFrame
+ frame_helplist: Frame
+ frame_helplist_buttons: Frame
+ (*)button_helplist_edit
+ (*)button_helplist_add
+ (*)button_helplist_remove
+ (*)helplist: ListBox
+ scroll_helplist: Scrollbar
"""
self.startup_edit = tracers.add(
IntVar(self), ('main', 'General', 'editor-on-startup'))
diff --git a/Lib/idlelib/idle_test/test_configdialog.py b/Lib/idlelib/idle_test/test_configdialog.py
index cd78482..b07a65c 100644
--- a/Lib/idlelib/idle_test/test_configdialog.py
+++ b/Lib/idlelib/idle_test/test_configdialog.py
@@ -1,7 +1,7 @@
"""Test idlelib.configdialog.
Half the class creates dialog, half works with user customizations.
-Coverage: 63%.
+Coverage: 81%.
"""
from idlelib import configdialog
from test.support import requires
@@ -29,6 +29,7 @@ dialog = None
mainpage = changes['main']
highpage = changes['highlight']
keyspage = changes['keys']
+extpage = changes['extensions']
def setUpModule():
global root, dialog
@@ -59,8 +60,6 @@ class FontPageTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
page = cls.page = dialog.fontpage
- #dialog.note.insert(0, page, text='copy')
- #dialog.note.add(page, text='copyfgfg')
dialog.note.select(page)
page.set_samples = Func() # Mask instance method.
@@ -120,7 +119,7 @@ class FontPageTest(unittest.TestCase):
# Click on item should select that item.
d = self.page
if d.fontlist.size() < 2:
- cls.skipTest('need at least 2 fonts')
+ self.skipTest('need at least 2 fonts')
fontlist = d.fontlist
fontlist.activate(0)
@@ -233,11 +232,391 @@ class HighlightTest(unittest.TestCase):
changes.clear()
-class KeysTest(unittest.TestCase):
+class KeyTest(unittest.TestCase):
+
+ @classmethod
+ def setUpClass(cls):
+ d = dialog
+ dialog.note.select(d.keyspage)
+ d.set_keys_type = Func()
+ d.load_keys_list = Func()
+
+ @classmethod
+ def tearDownClass(cls):
+ d = dialog
+ del d.set_keys_type, d.load_keys_list
def setUp(self):
+ d = dialog
+ # The following is needed for test_load_key_cfg, _delete_custom_keys.
+ # This may indicate a defect in some test or function.
+ for section in idleConf.GetSectionList('user', 'keys'):
+ idleConf.userCfg['keys'].remove_section(section)
+ changes.clear()
+ d.set_keys_type.called = 0
+ d.load_keys_list.called = 0
+
+ def test_load_key_cfg(self):
+ tracers.detach()
+ d = dialog
+ eq = self.assertEqual
+
+ # Use builtin keyset with no user keysets created.
+ idleConf.CurrentKeys = mock.Mock(return_value='IDLE Classic OSX')
+ d.load_key_cfg()
+ self.assertTrue(d.keyset_source.get())
+ # builtinlist sets variable builtin_name to the CurrentKeys default.
+ eq(d.builtin_name.get(), 'IDLE Classic OSX')
+ eq(d.custom_name.get(), '- no custom keys -')
+ eq(d.custom_keyset_on['state'], DISABLED)
+ eq(d.set_keys_type.called, 1)
+ eq(d.load_keys_list.called, 1)
+ eq(d.load_keys_list.args, ('IDLE Classic OSX', ))
+
+ # Builtin keyset with non-empty user keyset list.
+ idleConf.SetOption('keys', 'test1', 'option', 'value')
+ idleConf.SetOption('keys', 'test2', 'option2', 'value2')
+ d.load_key_cfg()
+ eq(d.builtin_name.get(), 'IDLE Classic OSX')
+ eq(d.custom_name.get(), 'test1')
+ eq(d.set_keys_type.called, 2)
+ eq(d.load_keys_list.called, 2)
+ eq(d.load_keys_list.args, ('IDLE Classic OSX', ))
+
+ # Use custom keyset.
+ idleConf.CurrentKeys = mock.Mock(return_value='test2')
+ idleConf.default_keys = mock.Mock(return_value='IDLE Modern Unix')
+ idleConf.SetOption('main', 'Keys', 'default', '0')
+ d.load_key_cfg()
+ self.assertFalse(d.keyset_source.get())
+ eq(d.builtin_name.get(), 'IDLE Modern Unix')
+ eq(d.custom_name.get(), 'test2')
+ eq(d.set_keys_type.called, 3)
+ eq(d.load_keys_list.called, 3)
+ eq(d.load_keys_list.args, ('test2', ))
+
+ del idleConf.CurrentKeys, idleConf.default_keys
+ tracers.attach()
+
+ def test_keyset_source(self):
+ eq = self.assertEqual
+ d = dialog
+ # Test these separately.
+ d.var_changed_builtin_name = Func()
+ d.var_changed_custom_name = Func()
+ # Builtin selected.
+ d.builtin_keyset_on.invoke()
+ eq(mainpage, {'Keys': {'default': 'True'}})
+ eq(d.var_changed_builtin_name.called, 1)
+ eq(d.var_changed_custom_name.called, 0)
changes.clear()
+ # Custom selected.
+ d.custom_keyset_on['state'] = NORMAL
+ d.custom_keyset_on.invoke()
+ self.assertEqual(mainpage, {'Keys': {'default': 'False'}})
+ eq(d.var_changed_builtin_name.called, 1)
+ eq(d.var_changed_custom_name.called, 1)
+ del d.var_changed_builtin_name, d.var_changed_custom_name
+
+ def test_builtin_name(self):
+ eq = self.assertEqual
+ d = dialog
+ idleConf.userCfg['main'].remove_section('Keys')
+ item_list = ['IDLE Classic Windows', 'IDLE Classic OSX',
+ 'IDLE Modern UNIX']
+
+ # Not in old_keys, defaults name to first item.
+ d.builtinlist.SetMenu(item_list, 'IDLE Modern UNIX')
+ eq(mainpage, {'Keys': {'name': 'IDLE Classic Windows',
+ 'name2': 'IDLE Modern UNIX'}})
+ eq(d.keys_message['text'], 'New key set, see Help')
+ eq(d.load_keys_list.called, 1)
+ eq(d.load_keys_list.args, ('IDLE Modern UNIX', ))
+
+ # Not in old keys - uses name2.
+ changes.clear()
+ idleConf.SetOption('main', 'Keys', 'name', 'IDLE Classic Unix')
+ d.builtinlist.SetMenu(item_list, 'IDLE Modern UNIX')
+ eq(mainpage, {'Keys': {'name2': 'IDLE Modern UNIX'}})
+ eq(d.keys_message['text'], 'New key set, see Help')
+ eq(d.load_keys_list.called, 2)
+ eq(d.load_keys_list.args, ('IDLE Modern UNIX', ))
+
+ # Builtin name in old_keys.
+ changes.clear()
+ d.builtinlist.SetMenu(item_list, 'IDLE Classic OSX')
+ eq(mainpage, {'Keys': {'name': 'IDLE Classic OSX', 'name2': ''}})
+ eq(d.keys_message['text'], '')
+ eq(d.load_keys_list.called, 3)
+ eq(d.load_keys_list.args, ('IDLE Classic OSX', ))
+
+ def test_custom_name(self):
+ d = dialog
+
+ # If no selections, doesn't get added.
+ d.customlist.SetMenu([], '- no custom keys -')
+ self.assertNotIn('Keys', mainpage)
+ self.assertEqual(d.load_keys_list.called, 0)
+
+ # Custom name selected.
+ changes.clear()
+ d.customlist.SetMenu(['a', 'b', 'c'], 'c')
+ self.assertEqual(mainpage, {'Keys': {'name': 'c'}})
+ self.assertEqual(d.load_keys_list.called, 1)
+
+ def test_keybinding(self):
+ d = dialog
+ d.custom_name.set('my custom keys')
+ d.bindingslist.delete(0, 'end')
+ d.bindingslist.insert(0, 'copy')
+ d.bindingslist.insert(1, 'expand-word')
+ d.bindingslist.selection_set(0)
+ d.bindingslist.selection_anchor(0)
+ # Core binding - adds to keys.
+ d.keybinding.set('<Key-F11>')
+ self.assertEqual(keyspage,
+ {'my custom keys': {'copy': '<Key-F11>'}})
+ # Not a core binding - adds to extensions.
+ d.bindingslist.selection_set(1)
+ d.bindingslist.selection_anchor(1)
+ d.keybinding.set('<Key-F11>')
+ self.assertEqual(extpage,
+ {'AutoExpand_cfgBindings': {'expand-word': '<Key-F11>'}})
+
+ def test_set_keys_type(self):
+ eq = self.assertEqual
+ d = dialog
+ del d.set_keys_type
+
+ # Builtin keyset selected.
+ d.keyset_source.set(True)
+ d.set_keys_type()
+ eq(d.builtinlist['state'], NORMAL)
+ eq(d.customlist['state'], DISABLED)
+ eq(d.button_delete_custom_keys['state'], DISABLED)
+
+ # Custom keyset selected.
+ d.keyset_source.set(False)
+ d.set_keys_type()
+ eq(d.builtinlist['state'], DISABLED)
+ eq(d.custom_keyset_on['state'], NORMAL)
+ eq(d.customlist['state'], NORMAL)
+ eq(d.button_delete_custom_keys['state'], NORMAL)
+ d.set_keys_type = Func()
+
+ def test_get_new_keys(self):
+ eq = self.assertEqual
+ d = dialog
+ orig_getkeysdialog = configdialog.GetKeysDialog
+ gkd = configdialog.GetKeysDialog = Func(return_self=True)
+ gnkn = d.get_new_keys_name = Func()
+
+ d.button_new_keys['state'] = NORMAL
+ d.bindingslist.delete(0, 'end')
+ d.bindingslist.insert(0, 'copy - <Control-Shift-Key-C>')
+ d.bindingslist.selection_set(0)
+ d.bindingslist.selection_anchor(0)
+ d.keybinding.set('Key-a')
+ d.keyset_source.set(True) # Default keyset.
+
+ # Default keyset; no change to binding.
+ gkd.result = ''
+ d.button_new_keys.invoke()
+ eq(d.bindingslist.get('anchor'), 'copy - <Control-Shift-Key-C>')
+ # Keybinding isn't changed when there isn't a change entered.
+ eq(d.keybinding.get(), 'Key-a')
+
+ # Default keyset; binding changed.
+ gkd.result = '<Key-F11>'
+ # No keyset name selected therefore binding not saved.
+ gnkn.result = ''
+ d.button_new_keys.invoke()
+ eq(gnkn.called, 1)
+ eq(d.bindingslist.get('anchor'), 'copy - <Control-Shift-Key-C>')
+ # Keyset name selected.
+ gnkn.result = 'My New Key Set'
+ d.button_new_keys.invoke()
+ eq(d.custom_name.get(), gnkn.result)
+ eq(d.bindingslist.get('anchor'), 'copy - <Key-F11>')
+ eq(d.keybinding.get(), '<Key-F11>')
+
+ # User keyset; binding changed.
+ d.keyset_source.set(False) # Custom keyset.
+ gnkn.called = 0
+ gkd.result = '<Key-p>'
+ d.button_new_keys.invoke()
+ eq(gnkn.called, 0)
+ eq(d.bindingslist.get('anchor'), 'copy - <Key-p>')
+ eq(d.keybinding.get(), '<Key-p>')
+
+ del d.get_new_keys_name
+ configdialog.GetKeysDialog = orig_getkeysdialog
+
+ def test_get_new_keys_name(self):
+ orig_sectionname = configdialog.SectionName
+ sn = configdialog.SectionName = Func(return_self=True)
+ d = dialog
+
+ sn.result = 'New Keys'
+ self.assertEqual(d.get_new_keys_name(''), 'New Keys')
+
+ configdialog.SectionName = orig_sectionname
+
+ def test_save_as_new_key_set(self):
+ d = dialog
+ gnkn = d.get_new_keys_name = Func()
+ d.keyset_source.set(True)
+
+ # No name entered.
+ gnkn.result = ''
+ d.button_save_custom_keys.invoke()
+
+ # Name entered.
+ gnkn.result = 'my new key set'
+ gnkn.called = 0
+ self.assertNotIn(gnkn.result, idleConf.userCfg['keys'])
+ d.button_save_custom_keys.invoke()
+ self.assertIn(gnkn.result, idleConf.userCfg['keys'])
+
+ del d.get_new_keys_name
+
+ def test_on_bindingslist_select(self):
+ d = dialog
+ b = d.bindingslist
+ b.delete(0, 'end')
+ b.insert(0, 'copy')
+ b.insert(1, 'find')
+ b.activate(0)
+
+ b.focus_force()
+ b.see(1)
+ b.update()
+ x, y, dx, dy = b.bbox(1)
+ x += dx // 2
+ y += dy // 2
+ b.event_generate('<Enter>', x=0, y=0)
+ b.event_generate('<Motion>', x=x, y=y)
+ b.event_generate('<Button-1>', x=x, y=y)
+ b.event_generate('<ButtonRelease-1>', x=x, y=y)
+ self.assertEqual(b.get('anchor'), 'find')
+ self.assertEqual(d.button_new_keys['state'], NORMAL)
+
+ def test_create_new_key_set_and_save_new_key_set(self):
+ eq = self.assertEqual
+ d = dialog
+
+ # Use default as previously active keyset.
+ d.keyset_source.set(True)
+ d.builtin_name.set('IDLE Classic Windows')
+ first_new = 'my new custom key set'
+ second_new = 'my second custom keyset'
+
+ # No changes, so keysets are an exact copy.
+ self.assertNotIn(first_new, idleConf.userCfg)
+ d.create_new_key_set(first_new)
+ eq(idleConf.GetSectionList('user', 'keys'), [first_new])
+ eq(idleConf.GetKeySet('IDLE Classic Windows'),
+ idleConf.GetKeySet(first_new))
+ eq(d.custom_name.get(), first_new)
+ self.assertFalse(d.keyset_source.get()) # Use custom set.
+ eq(d.set_keys_type.called, 1)
+
+ # Test that changed keybindings are in new keyset.
+ changes.add_option('keys', first_new, 'copy', '<Key-F11>')
+ self.assertNotIn(second_new, idleConf.userCfg)
+ d.create_new_key_set(second_new)
+ eq(idleConf.GetSectionList('user', 'keys'), [first_new, second_new])
+ self.assertNotEqual(idleConf.GetKeySet(first_new),
+ idleConf.GetKeySet(second_new))
+ # Check that difference in keysets was in option `copy` from `changes`.
+ idleConf.SetOption('keys', first_new, 'copy', '<Key-F11>')
+ eq(idleConf.GetKeySet(first_new), idleConf.GetKeySet(second_new))
+
+ def test_load_keys_list(self):
+ eq = self.assertEqual
+ d = dialog
+ gks = idleConf.GetKeySet = Func()
+ del d.load_keys_list
+ b = d.bindingslist
+
+ b.delete(0, 'end')
+ b.insert(0, '<<find>>')
+ b.insert(1, '<<help>>')
+ gks.result = {'<<copy>>': ['<Control-Key-c>', '<Control-Key-C>'],
+ '<<force-open-completions>>': ['<Control-Key-space>'],
+ '<<spam>>': ['<Key-F11>']}
+ changes.add_option('keys', 'my keys', 'spam', '<Shift-Key-a>')
+ expected = ('copy - <Control-Key-c> <Control-Key-C>',
+ 'force-open-completions - <Control-Key-space>',
+ 'spam - <Shift-Key-a>')
+
+ # No current selection.
+ d.load_keys_list('my keys')
+ eq(b.get(0, 'end'), expected)
+ eq(b.get('anchor'), '')
+ eq(b.curselection(), ())
+
+ # Check selection.
+ b.selection_set(1)
+ b.selection_anchor(1)
+ d.load_keys_list('my keys')
+ eq(b.get(0, 'end'), expected)
+ eq(b.get('anchor'), 'force-open-completions - <Control-Key-space>')
+ eq(b.curselection(), (1, ))
+
+ # Change selection.
+ b.selection_set(2)
+ b.selection_anchor(2)
+ d.load_keys_list('my keys')
+ eq(b.get(0, 'end'), expected)
+ eq(b.get('anchor'), 'spam - <Shift-Key-a>')
+ eq(b.curselection(), (2, ))
+ d.load_keys_list = Func()
+
+ del idleConf.GetKeySet
+
+ def test_delete_custom_keys(self):
+ eq = self.assertEqual
+ d = dialog
+ d.button_delete_custom_keys['state'] = NORMAL
+ yesno = configdialog.tkMessageBox.askyesno = Func()
+ d.deactivate_current_config = Func()
+ d.activate_config_changes = Func()
+
+ keyset_name = 'spam key set'
+ idleConf.userCfg['keys'].SetOption(keyset_name, 'name', 'value')
+ keyspage[keyset_name] = {'option': 'True'}
+
+ # Force custom keyset.
+ d.keyset_source.set(False)
+ d.custom_name.set(keyset_name)
+
+ # Cancel deletion.
+ yesno.result = False
+ d.button_delete_custom_keys.invoke()
+ eq(yesno.called, 1)
+ eq(keyspage[keyset_name], {'option': 'True'})
+ eq(idleConf.GetSectionList('user', 'keys'), ['spam key set'])
+ eq(d.deactivate_current_config.called, 0)
+ eq(d.activate_config_changes.called, 0)
+ eq(d.set_keys_type.called, 0)
+
+ # Confirm deletion.
+ yesno.result = True
+ d.button_delete_custom_keys.invoke()
+ eq(yesno.called, 2)
+ self.assertNotIn(keyset_name, keyspage)
+ eq(idleConf.GetSectionList('user', 'keys'), [])
+ eq(d.custom_keyset_on['state'], DISABLED)
+ eq(d.custom_name.get(), '- no custom keys -')
+ eq(d.deactivate_current_config.called, 1)
+ eq(d.activate_config_changes.called, 1)
+ eq(d.set_keys_type.called, 1)
+
+ del d.activate_config_changes, d.deactivate_current_config
+ del configdialog.tkMessageBox.askyesno
+
class GenPageTest(unittest.TestCase):
"""Test that general tab widgets enable users to make changes.
diff --git a/Misc/NEWS.d/next/IDLE/2017-08-03-17-54-02.bpo-31002.kUSgTE.rst b/Misc/NEWS.d/next/IDLE/2017-08-03-17-54-02.bpo-31002.kUSgTE.rst
new file mode 100644
index 0000000..1708be7
--- /dev/null
+++ b/Misc/NEWS.d/next/IDLE/2017-08-03-17-54-02.bpo-31002.kUSgTE.rst
@@ -0,0 +1 @@
+Add tests for configdialog keys tab. Patch by Cheryl Sabella.