diff options
-rw-r--r-- | Lib/idlelib/configdialog.py | 408 | ||||
-rw-r--r-- | Lib/idlelib/idle_test/test_configdialog.py | 389 | ||||
-rw-r--r-- | Misc/NEWS.d/next/IDLE/2017-08-03-17-54-02.bpo-31002.kUSgTE.rst | 1 |
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. |