bpo-31205: IDLE: Factor KeysPage class from ConfigDialog (#3096)

The slightly modified tests continue to pass.  Patch by Cheryl Sabella.
This commit is contained in:
Cheryl Sabella 2017-08-15 18:26:23 -04:00 committed by Terry Jan Reedy
parent 1f512b9a34
commit e36d9f5568
3 changed files with 470 additions and 457 deletions

View File

@ -92,9 +92,9 @@ class ConfigDialog(Toplevel):
note: Notebook
highpage: self.create_page_highlight
fontpage: FontPage
keyspage: self.create_page_keys
keyspage: KeysPage
genpage: GenPage
extpageL self.create_page_extensions
extpage: self.create_page_extensions
Methods:
create_action_buttons
@ -104,7 +104,7 @@ class ConfigDialog(Toplevel):
self.note = note = Notebook(self, width=450, height=450)
self.highpage = self.create_page_highlight()
self.fontpage = FontPage(note, self.highpage)
self.keyspage = self.create_page_keys()
self.keyspage = KeysPage(note)
self.genpage = GenPage(note)
self.extpage = self.create_page_extensions()
note.add(self.fontpage, text='Fonts/Tabs')
@ -132,7 +132,7 @@ class ConfigDialog(Toplevel):
#self.load_font_cfg()
#self.load_tab_cfg()
self.load_theme_cfg()
self.load_key_cfg()
# self.load_key_cfg()
# self.load_general_cfg()
# note: extension page handled separately
@ -791,431 +791,6 @@ class ConfigDialog(Toplevel):
self.activate_config_changes()
self.set_theme_type()
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:
keybinding: Action/key bindings.
Methods:
load_keys_list: Reload active set.
create_new_key_set: Combine active keyset and changes.
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.
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_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)
# Widget creation:
# body and section frames.
frame = Frame(self.note)
frame_custom = LabelFrame(
frame, borderwidth=2, relief=GROOVE,
text=' Custom Key Bindings ')
frame_key_sets = LabelFrame(
frame, borderwidth=2, relief=GROOVE, text=' Key Set ')
#frame_custom
frame_target = Frame(frame_custom)
target_title = Label(frame_target, text='Action - Key(s)')
scroll_target_y = Scrollbar(frame_target)
scroll_target_x = Scrollbar(frame_target, orient=HORIZONTAL)
self.bindingslist = Listbox(
frame_target, takefocus=FALSE, exportselection=FALSE)
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.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.custom_keyset_on = Radiobutton(
frames[0], variable=self.keyset_source, value=0,
command=self.set_keys_type, text='Use a Custom Key Set')
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)
self.button_save_custom_keys = Button(
frames[1], text='Save as New Custom Key Set',
command=self.save_as_new_key_set)
self.keys_message = Label(frames[0], bd=2)
##widget packing
#body
frame_custom.pack(side=BOTTOM, padx=5, pady=5, expand=TRUE, fill=BOTH)
frame_key_sets.pack(side=BOTTOM, padx=5, pady=5, fill=BOTH)
#frame_custom
self.button_new_keys.pack(side=BOTTOM, fill=X, padx=5, pady=5)
frame_target.pack(side=LEFT, padx=5, pady=5, expand=TRUE, fill=BOTH)
#frame target
frame_target.columnconfigure(0, weight=1)
frame_target.rowconfigure(1, weight=1)
target_title.grid(row=0, column=0, columnspan=2, sticky=W)
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.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)
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
def load_key_cfg(self):
"Load current configuration settings for the keybinding options."
# Set current keys type radiobutton.
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.keyset_source.get(): # Default theme selected.
item_list = idleConf.GetSectionList('default', 'keys')
item_list.sort()
self.builtinlist.SetMenu(item_list, current_option)
item_list = idleConf.GetSectionList('user', 'keys')
item_list.sort()
if not item_list:
self.custom_keyset_on['state'] = DISABLED
self.custom_name.set('- no custom keys -')
else:
self.customlist.SetMenu(item_list, item_list[0])
else: # User key set selected.
item_list = idleConf.GetSectionList('user', 'keys')
item_list.sort()
self.customlist.SetMenu(item_list, current_option)
item_list = idleConf.GetSectionList('default', 'keys')
item_list.sort()
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_name(self, *params):
"Process selection of builtin key set."
old_keys = (
'IDLE Classic Windows',
'IDLE Classic Unix',
'IDLE Classic Mac',
'IDLE Classic OSX',
)
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.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.keys_message['text'] = ''
self.keys_message['fg'] = 'black'
self.load_keys_list(value)
def var_changed_custom_name(self, *params):
"Process selection of custom key set."
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_keyset_source(self, *params):
"Process toggle between builtin key set and custom key set."
value = self.keyset_source.get()
changes.add_option('main', 'Keys', 'default', value)
if value:
self.var_changed_builtin_name()
else:
self.var_changed_custom_name()
def var_changed_keybinding(self, *params):
"Store change to a keybinding."
value = self.keybinding.get()
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.
ext_name = idleConf.GetExtnNameForEvent(event)
ext_keybind_section = ext_name + '_cfgBindings'
changes.add_option('extensions', ext_keybind_section, event, value)
def set_keys_type(self):
"Set available screen options based on builtin or custom key set."
if self.keyset_source.get():
self.builtinlist['state'] = NORMAL
self.customlist['state'] = DISABLED
self.button_delete_custom_keys['state'] = DISABLED
else:
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):
"""Handle event to change key binding for selected line.
A selection of a key/binding in the list of current
bindings pops up a dialog to enter a new binding. If
the current key set is builtin and a binding has
changed, then a name for a custom key set needs to be
entered for the change to be applied.
"""
list_index = self.bindingslist.index(ANCHOR)
binding = self.bindingslist.get(list_index)
bind_name = binding.split()[0]
if self.keyset_source.get():
current_key_set_name = self.builtin_name.get()
else:
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]
for event in key_set_changes:
current_bindings[event] = key_set_changes[event].split()
current_key_sequences = list(current_bindings.values())
new_keys = GetKeysDialog(self, 'Get New Keys', bind_name,
current_key_sequences).result
if new_keys:
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.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.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.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."
used_names = (idleConf.GetSectionList('user', 'keys') +
idleConf.GetSectionList('default', 'keys'))
new_keyset = SectionName(
self, 'New Custom Key Set', message, used_names).result
return new_keyset
def save_as_new_key_set(self):
"Prompt for name of new key set and save changes using that name."
new_keys_name = self.get_new_keys_name('New Key Set Name:')
if new_keys_name:
self.create_new_key_set(new_keys_name)
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.
Copy the bindings/keys from the previously active keyset
to the new keyset and activate the new custom keyset.
"""
if self.keyset_source.get():
prev_key_set_name = self.builtin_name.get()
else:
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.
event_name = event[2:-2] # Trim off the angle brackets.
binding = ' '.join(prev_keys[event])
new_keys[event_name] = binding
# Handle any unsaved changes to prev key set.
if prev_key_set_name in changes['keys']:
key_set_changes = changes['keys'][prev_key_set_name]
for event in key_set_changes:
new_keys[event] = key_set_changes[event]
# Save the new key set.
self.save_new_key_set(new_key_set_name, new_keys)
# Change GUI over to the new key set.
custom_key_list = idleConf.GetSectionList('user', 'keys')
custom_key_list.sort()
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):
"""Reload the list of action/key binding pairs for the active key set.
An action/key binding can be selected to change the key binding.
"""
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.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.
if keyset_name in changes['keys']:
# Handle any unsaved changes to this key set.
if bind_name in changes['keys'][keyset_name]:
key = changes['keys'][keyset_name][bind_name]
self.bindingslist.insert(END, bind_name+' - '+key)
if reselect:
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 keybindings
"""
if not idleConf.userCfg['keys'].has_section(keyset_name):
idleConf.userCfg['keys'].add_section(keyset_name)
for event in keyset:
value = keyset[event]
idleConf.userCfg['keys'].SetOption(keyset_name, event, value)
def delete_custom_keys(self):
"""Handle event to delete a custom key set.
Applying the delete deactivates the current configuration and
reverts to the default. The custom key set is permanently
deleted from the config file.
"""
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):
return
self.deactivate_current_config()
# Remove key set from changes, config, and file.
changes.delete_section('keys', keyset_name)
# Reload user key set list.
item_list = idleConf.GetSectionList('user', 'keys')
item_list.sort()
if not item_list:
self.custom_keyset_on['state'] = DISABLED
self.customlist.SetMenu(item_list, '- no custom keys -')
else:
self.customlist.SetMenu(item_list, item_list[0])
# Revert to default key set.
self.keyset_source.set(idleConf.defaultCfg['main']
.Get('Keys', 'default'))
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()
self.save_all_changed_extensions()
self.activate_config_changes()
self.set_keys_type()
def deactivate_current_config(self):
"""Remove current key bindings.
@ -1650,6 +1225,437 @@ class FontPage(Frame):
changes.add_option('main', 'Indent', 'num-spaces', value)
class KeysPage(Frame):
def __init__(self, master):
super().__init__(master)
self.cd = master.master
self.create_page_keys()
self.load_key_cfg()
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:
keybinding: Action/key bindings.
Methods:
load_keys_list: Reload active set.
create_new_key_set: Combine active keyset and changes.
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.
Widgets for KeysPage(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
"""
self.builtin_name = tracers.add(
StringVar(self), self.var_changed_builtin_name)
self.custom_name = tracers.add(
StringVar(self), self.var_changed_custom_name)
self.keyset_source = tracers.add(
BooleanVar(self), self.var_changed_keyset_source)
self.keybinding = tracers.add(
StringVar(self), self.var_changed_keybinding)
# Create widgets:
# body and section frames.
frame_custom = LabelFrame(
self, borderwidth=2, relief=GROOVE,
text=' Custom Key Bindings ')
frame_key_sets = LabelFrame(
self, borderwidth=2, relief=GROOVE, text=' Key Set ')
# frame_custom.
frame_target = Frame(frame_custom)
target_title = Label(frame_target, text='Action - Key(s)')
scroll_target_y = Scrollbar(frame_target)
scroll_target_x = Scrollbar(frame_target, orient=HORIZONTAL)
self.bindingslist = Listbox(
frame_target, takefocus=FALSE, exportselection=FALSE)
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.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.custom_keyset_on = Radiobutton(
frames[0], variable=self.keyset_source, value=0,
command=self.set_keys_type, text='Use a Custom Key Set')
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)
self.button_save_custom_keys = Button(
frames[1], text='Save as New Custom Key Set',
command=self.save_as_new_key_set)
self.keys_message = Label(frames[0], bd=2)
# Pack widgets:
# body.
frame_custom.pack(side=BOTTOM, padx=5, pady=5, expand=TRUE, fill=BOTH)
frame_key_sets.pack(side=BOTTOM, padx=5, pady=5, fill=BOTH)
# frame_custom.
self.button_new_keys.pack(side=BOTTOM, fill=X, padx=5, pady=5)
frame_target.pack(side=LEFT, padx=5, pady=5, expand=TRUE, fill=BOTH)
# frame_target.
frame_target.columnconfigure(0, weight=1)
frame_target.rowconfigure(1, weight=1)
target_title.grid(row=0, column=0, columnspan=2, sticky=W)
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.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)
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)
def load_key_cfg(self):
"Load current configuration settings for the keybinding options."
# Set current keys type radiobutton.
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.keyset_source.get(): # Default theme selected.
item_list = idleConf.GetSectionList('default', 'keys')
item_list.sort()
self.builtinlist.SetMenu(item_list, current_option)
item_list = idleConf.GetSectionList('user', 'keys')
item_list.sort()
if not item_list:
self.custom_keyset_on['state'] = DISABLED
self.custom_name.set('- no custom keys -')
else:
self.customlist.SetMenu(item_list, item_list[0])
else: # User key set selected.
item_list = idleConf.GetSectionList('user', 'keys')
item_list.sort()
self.customlist.SetMenu(item_list, current_option)
item_list = idleConf.GetSectionList('default', 'keys')
item_list.sort()
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_name(self, *params):
"Process selection of builtin key set."
old_keys = (
'IDLE Classic Windows',
'IDLE Classic Unix',
'IDLE Classic Mac',
'IDLE Classic OSX',
)
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.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.keys_message['text'] = ''
self.keys_message['fg'] = 'black'
self.load_keys_list(value)
def var_changed_custom_name(self, *params):
"Process selection of custom key set."
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_keyset_source(self, *params):
"Process toggle between builtin key set and custom key set."
value = self.keyset_source.get()
changes.add_option('main', 'Keys', 'default', value)
if value:
self.var_changed_builtin_name()
else:
self.var_changed_custom_name()
def var_changed_keybinding(self, *params):
"Store change to a keybinding."
value = self.keybinding.get()
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.
ext_name = idleConf.GetExtnNameForEvent(event)
ext_keybind_section = ext_name + '_cfgBindings'
changes.add_option('extensions', ext_keybind_section, event, value)
def set_keys_type(self):
"Set available screen options based on builtin or custom key set."
if self.keyset_source.get():
self.builtinlist['state'] = NORMAL
self.customlist['state'] = DISABLED
self.button_delete_custom_keys['state'] = DISABLED
else:
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):
"""Handle event to change key binding for selected line.
A selection of a key/binding in the list of current
bindings pops up a dialog to enter a new binding. If
the current key set is builtin and a binding has
changed, then a name for a custom key set needs to be
entered for the change to be applied.
"""
list_index = self.bindingslist.index(ANCHOR)
binding = self.bindingslist.get(list_index)
bind_name = binding.split()[0]
if self.keyset_source.get():
current_key_set_name = self.builtin_name.get()
else:
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]
for event in key_set_changes:
current_bindings[event] = key_set_changes[event].split()
current_key_sequences = list(current_bindings.values())
new_keys = GetKeysDialog(self, 'Get New Keys', bind_name,
current_key_sequences).result
if new_keys:
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.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.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.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."
used_names = (idleConf.GetSectionList('user', 'keys') +
idleConf.GetSectionList('default', 'keys'))
new_keyset = SectionName(
self, 'New Custom Key Set', message, used_names).result
return new_keyset
def save_as_new_key_set(self):
"Prompt for name of new key set and save changes using that name."
new_keys_name = self.get_new_keys_name('New Key Set Name:')
if new_keys_name:
self.create_new_key_set(new_keys_name)
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.
Copy the bindings/keys from the previously active keyset
to the new keyset and activate the new custom keyset.
"""
if self.keyset_source.get():
prev_key_set_name = self.builtin_name.get()
else:
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.
event_name = event[2:-2] # Trim off the angle brackets.
binding = ' '.join(prev_keys[event])
new_keys[event_name] = binding
# Handle any unsaved changes to prev key set.
if prev_key_set_name in changes['keys']:
key_set_changes = changes['keys'][prev_key_set_name]
for event in key_set_changes:
new_keys[event] = key_set_changes[event]
# Save the new key set.
self.save_new_key_set(new_key_set_name, new_keys)
# Change GUI over to the new key set.
custom_key_list = idleConf.GetSectionList('user', 'keys')
custom_key_list.sort()
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):
"""Reload the list of action/key binding pairs for the active key set.
An action/key binding can be selected to change the key binding.
"""
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.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.
if keyset_name in changes['keys']:
# Handle any unsaved changes to this key set.
if bind_name in changes['keys'][keyset_name]:
key = changes['keys'][keyset_name][bind_name]
self.bindingslist.insert(END, bind_name+' - '+key)
if reselect:
self.bindingslist.see(list_index)
self.bindingslist.select_set(list_index)
self.bindingslist.select_anchor(list_index)
@staticmethod
def save_new_key_set(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 keybindings
"""
if not idleConf.userCfg['keys'].has_section(keyset_name):
idleConf.userCfg['keys'].add_section(keyset_name)
for event in keyset:
value = keyset[event]
idleConf.userCfg['keys'].SetOption(keyset_name, event, value)
def delete_custom_keys(self):
"""Handle event to delete a custom key set.
Applying the delete deactivates the current configuration and
reverts to the default. The custom key set is permanently
deleted from the config file.
"""
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):
return
self.cd.deactivate_current_config()
# Remove key set from changes, config, and file.
changes.delete_section('keys', keyset_name)
# Reload user key set list.
item_list = idleConf.GetSectionList('user', 'keys')
item_list.sort()
if not item_list:
self.custom_keyset_on['state'] = DISABLED
self.customlist.SetMenu(item_list, '- no custom keys -')
else:
self.customlist.SetMenu(item_list, item_list[0])
# Revert to default key set.
self.keyset_source.set(idleConf.defaultCfg['main']
.Get('Keys', 'default'))
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()
self.cd.save_all_changed_extensions()
self.cd.activate_config_changes()
self.set_keys_type()
class GenPage(Frame):
def __init__(self, master):

View File

@ -232,22 +232,27 @@ class HighlightTest(unittest.TestCase):
changes.clear()
class KeyTest(unittest.TestCase):
class KeysPageTest(unittest.TestCase):
"""Test that keys tab widgets enable users to make changes.
Test that widget actions set vars, that var changes add
options to changes and that key sets works correctly.
"""
@classmethod
def setUpClass(cls):
d = dialog
dialog.note.select(d.keyspage)
d.set_keys_type = Func()
d.load_keys_list = Func()
page = cls.page = dialog.keyspage
dialog.note.select(page)
page.set_keys_type = Func()
page.load_keys_list = Func()
@classmethod
def tearDownClass(cls):
d = dialog
del d.set_keys_type, d.load_keys_list
page = cls.page
del page.set_keys_type, page.load_keys_list
def setUp(self):
d = dialog
d = self.page
# 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'):
@ -258,7 +263,7 @@ class KeyTest(unittest.TestCase):
def test_load_key_cfg(self):
tracers.detach()
d = dialog
d = self.page
eq = self.assertEqual
# Use builtin keyset with no user keysets created.
@ -300,7 +305,7 @@ class KeyTest(unittest.TestCase):
def test_keyset_source(self):
eq = self.assertEqual
d = dialog
d = self.page
# Test these separately.
d.var_changed_builtin_name = Func()
d.var_changed_custom_name = Func()
@ -321,7 +326,7 @@ class KeyTest(unittest.TestCase):
def test_builtin_name(self):
eq = self.assertEqual
d = dialog
d = self.page
idleConf.userCfg['main'].remove_section('Keys')
item_list = ['IDLE Classic Windows', 'IDLE Classic OSX',
'IDLE Modern UNIX']
@ -352,7 +357,7 @@ class KeyTest(unittest.TestCase):
eq(d.load_keys_list.args, ('IDLE Classic OSX', ))
def test_custom_name(self):
d = dialog
d = self.page
# If no selections, doesn't get added.
d.customlist.SetMenu([], '- no custom keys -')
@ -366,7 +371,7 @@ class KeyTest(unittest.TestCase):
self.assertEqual(d.load_keys_list.called, 1)
def test_keybinding(self):
d = dialog
d = self.page
d.custom_name.set('my custom keys')
d.bindingslist.delete(0, 'end')
d.bindingslist.insert(0, 'copy')
@ -386,7 +391,7 @@ class KeyTest(unittest.TestCase):
def test_set_keys_type(self):
eq = self.assertEqual
d = dialog
d = self.page
del d.set_keys_type
# Builtin keyset selected.
@ -407,7 +412,7 @@ class KeyTest(unittest.TestCase):
def test_get_new_keys(self):
eq = self.assertEqual
d = dialog
d = self.page
orig_getkeysdialog = configdialog.GetKeysDialog
gkd = configdialog.GetKeysDialog = Func(return_self=True)
gnkn = d.get_new_keys_name = Func()
@ -456,7 +461,7 @@ class KeyTest(unittest.TestCase):
def test_get_new_keys_name(self):
orig_sectionname = configdialog.SectionName
sn = configdialog.SectionName = Func(return_self=True)
d = dialog
d = self.page
sn.result = 'New Keys'
self.assertEqual(d.get_new_keys_name(''), 'New Keys')
@ -464,7 +469,7 @@ class KeyTest(unittest.TestCase):
configdialog.SectionName = orig_sectionname
def test_save_as_new_key_set(self):
d = dialog
d = self.page
gnkn = d.get_new_keys_name = Func()
d.keyset_source.set(True)
@ -482,7 +487,7 @@ class KeyTest(unittest.TestCase):
del d.get_new_keys_name
def test_on_bindingslist_select(self):
d = dialog
d = self.page
b = d.bindingslist
b.delete(0, 'end')
b.insert(0, 'copy')
@ -504,7 +509,7 @@ class KeyTest(unittest.TestCase):
def test_create_new_key_set_and_save_new_key_set(self):
eq = self.assertEqual
d = dialog
d = self.page
# Use default as previously active keyset.
d.keyset_source.set(True)
@ -535,7 +540,7 @@ class KeyTest(unittest.TestCase):
def test_load_keys_list(self):
eq = self.assertEqual
d = dialog
d = self.page
gks = idleConf.GetKeySet = Func()
del d.load_keys_list
b = d.bindingslist
@ -578,11 +583,11 @@ class KeyTest(unittest.TestCase):
def test_delete_custom_keys(self):
eq = self.assertEqual
d = dialog
d = self.page
d.button_delete_custom_keys['state'] = NORMAL
yesno = configdialog.tkMessageBox.askyesno = Func()
d.deactivate_current_config = Func()
d.activate_config_changes = Func()
dialog.deactivate_current_config = Func()
dialog.activate_config_changes = Func()
keyset_name = 'spam key set'
idleConf.userCfg['keys'].SetOption(keyset_name, 'name', 'value')
@ -598,8 +603,8 @@ class KeyTest(unittest.TestCase):
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(dialog.deactivate_current_config.called, 0)
eq(dialog.activate_config_changes.called, 0)
eq(d.set_keys_type.called, 0)
# Confirm deletion.
@ -610,11 +615,11 @@ class KeyTest(unittest.TestCase):
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(dialog.deactivate_current_config.called, 1)
eq(dialog.activate_config_changes.called, 1)
eq(d.set_keys_type.called, 1)
del d.activate_config_changes, d.deactivate_current_config
del dialog.activate_config_changes, dialog.deactivate_current_config
del configdialog.tkMessageBox.askyesno

View File

@ -0,0 +1,2 @@
IDLE: Factor KeysPage(Frame) class from ConfigDialog. The slightly
modified tests continue to pass. Patch by Cheryl Sabella.