bpo-40468: Factor out class ExtPage in idlelib.configdialog (GH-26618)

This commit is contained in:
Terry Jan Reedy 2021-06-08 21:43:49 -04:00 committed by GitHub
parent 9fd21f649d
commit 5571cabf1b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 216 additions and 201 deletions

View File

@ -112,11 +112,11 @@ class ConfigDialog(Toplevel):
self.frame = frame = Frame(self, padding="5px")
self.frame.grid(sticky="nwes")
self.note = note = Notebook(frame)
self.highpage = HighPage(note)
self.extpage = ExtPage(note)
self.highpage = HighPage(note, self.extpage)
self.fontpage = FontPage(note, self.highpage)
self.keyspage = KeysPage(note)
self.keyspage = KeysPage(note, self.extpage)
self.genpage = GenPage(note)
self.extpage = self.create_page_extensions()
note.add(self.fontpage, text='Fonts/Tabs')
note.add(self.highpage, text='Highlights')
note.add(self.keyspage, text=' Keys ')
@ -244,198 +244,6 @@ class ConfigDialog(Toplevel):
for klass in reloadables:
klass.reload()
def create_page_extensions(self):
"""Part of the config dialog used for configuring IDLE extensions.
This code is generic - it works for any and all IDLE extensions.
IDLE extensions save their configuration options using idleConf.
This code reads the current configuration using idleConf, supplies a
GUI interface to change the configuration values, and saves the
changes using idleConf.
Not all changes take effect immediately - some may require restarting IDLE.
This depends on each extension's implementation.
All values are treated as text, and it is up to the user to supply
reasonable values. The only exception to this are the 'enable*' options,
which are boolean, and can be toggled with a True/False button.
Methods:
load_extensions:
extension_selected: Handle selection from list.
create_extension_frame: Hold widgets for one extension.
set_extension_value: Set in userCfg['extensions'].
save_all_changed_extensions: Call extension page Save().
"""
self.ext_defaultCfg = idleConf.defaultCfg['extensions']
self.ext_userCfg = idleConf.userCfg['extensions']
self.is_int = self.register(is_int)
self.load_extensions()
# Create widgets - a listbox shows all available extensions, with the
# controls for the extension selected in the listbox to the right.
self.extension_names = StringVar(self)
frame = Frame(self.note)
frame_ext = LabelFrame(frame, borderwidth=2, relief=GROOVE,
text=' Feature Extensions ')
frame_ext.rowconfigure(0, weight=1)
frame_ext.columnconfigure(2, weight=1)
self.extension_list = Listbox(frame_ext, listvariable=self.extension_names,
selectmode='browse')
self.extension_list.bind('<<ListboxSelect>>', self.extension_selected)
scroll = Scrollbar(frame_ext, command=self.extension_list.yview)
self.extension_list.yscrollcommand=scroll.set
self.details_frame = LabelFrame(frame_ext, width=250, height=250)
self.extension_list.grid(column=0, row=0, sticky='nws')
scroll.grid(column=1, row=0, sticky='ns')
self.details_frame.grid(column=2, row=0, sticky='nsew', padx=[10, 0])
frame_ext.configure(padding=10)
self.config_frame = {}
self.current_extension = None
self.outerframe = self # TEMPORARY
self.tabbed_page_set = self.extension_list # TEMPORARY
# Create the frame holding controls for each extension.
ext_names = ''
for ext_name in sorted(self.extensions):
self.create_extension_frame(ext_name)
ext_names = ext_names + '{' + ext_name + '} '
self.extension_names.set(ext_names)
self.extension_list.selection_set(0)
self.extension_selected(None)
self.frame_help = HelpFrame(frame, borderwidth=2, relief=GROOVE,
text=' Help Menu Extensions ')
frame_ext.grid(row=0, column=0, sticky='nsew')
Label(frame).grid(row=1, column=0)
self.frame_help.grid(row=2, column=0, sticky='sew')
return frame
def load_extensions(self):
"Fill self.extensions with data from the default and user configs."
self.extensions = {}
for ext_name in idleConf.GetExtensions(active_only=False):
# Former built-in extensions are already filtered out.
self.extensions[ext_name] = []
for ext_name in self.extensions:
opt_list = sorted(self.ext_defaultCfg.GetOptionList(ext_name))
# Bring 'enable' options to the beginning of the list.
enables = [opt_name for opt_name in opt_list
if opt_name.startswith('enable')]
for opt_name in enables:
opt_list.remove(opt_name)
opt_list = enables + opt_list
for opt_name in opt_list:
def_str = self.ext_defaultCfg.Get(
ext_name, opt_name, raw=True)
try:
def_obj = {'True':True, 'False':False}[def_str]
opt_type = 'bool'
except KeyError:
try:
def_obj = int(def_str)
opt_type = 'int'
except ValueError:
def_obj = def_str
opt_type = None
try:
value = self.ext_userCfg.Get(
ext_name, opt_name, type=opt_type, raw=True,
default=def_obj)
except ValueError: # Need this until .Get fixed.
value = def_obj # Bad values overwritten by entry.
var = StringVar(self)
var.set(str(value))
self.extensions[ext_name].append({'name': opt_name,
'type': opt_type,
'default': def_str,
'value': value,
'var': var,
})
def extension_selected(self, event):
"Handle selection of an extension from the list."
newsel = self.extension_list.curselection()
if newsel:
newsel = self.extension_list.get(newsel)
if newsel is None or newsel != self.current_extension:
if self.current_extension:
self.details_frame.config(text='')
self.config_frame[self.current_extension].grid_forget()
self.current_extension = None
if newsel:
self.details_frame.config(text=newsel)
self.config_frame[newsel].grid(column=0, row=0, sticky='nsew')
self.current_extension = newsel
def create_extension_frame(self, ext_name):
"""Create a frame holding the widgets to configure one extension"""
f = VerticalScrolledFrame(self.details_frame, height=250, width=250)
self.config_frame[ext_name] = f
entry_area = f.interior
# Create an entry for each configuration option.
for row, opt in enumerate(self.extensions[ext_name]):
# Create a row with a label and entry/checkbutton.
label = Label(entry_area, text=opt['name'])
label.grid(row=row, column=0, sticky=NW)
var = opt['var']
if opt['type'] == 'bool':
Checkbutton(entry_area, variable=var,
onvalue='True', offvalue='False', width=8
).grid(row=row, column=1, sticky=W, padx=7)
elif opt['type'] == 'int':
Entry(entry_area, textvariable=var, validate='key',
validatecommand=(self.is_int, '%P'), width=10
).grid(row=row, column=1, sticky=NSEW, padx=7)
else: # type == 'str'
# Limit size to fit non-expanding space with larger font.
Entry(entry_area, textvariable=var, width=15
).grid(row=row, column=1, sticky=NSEW, padx=7)
return
def set_extension_value(self, section, opt):
"""Return True if the configuration was added or changed.
If the value is the same as the default, then remove it
from user config file.
"""
name = opt['name']
default = opt['default']
value = opt['var'].get().strip() or default
opt['var'].set(value)
# if self.defaultCfg.has_section(section):
# Currently, always true; if not, indent to return.
if (value == default):
return self.ext_userCfg.RemoveOption(section, name)
# Set the option.
return self.ext_userCfg.SetOption(section, name, value)
def save_all_changed_extensions(self):
"""Save configuration changes to the user config file.
Attributes accessed:
extensions
Methods:
set_extension_value
"""
has_changes = False
for ext_name in self.extensions:
options = self.extensions[ext_name]
for opt in options:
if self.set_extension_value(ext_name, opt):
has_changes = True
if has_changes:
self.ext_userCfg.Save()
# class TabPage(Frame): # A template for Page classes.
# def __init__(self, master):
@ -695,8 +503,9 @@ class FontPage(Frame):
class HighPage(Frame):
def __init__(self, master):
def __init__(self, master, extpage):
super().__init__(master)
self.extpage = extpage
self.cd = master.winfo_toplevel()
self.style = Style(master)
self.create_page_highlight()
@ -1347,15 +1156,16 @@ class HighPage(Frame):
self.builtin_name.set(idleConf.defaultCfg['main'].Get('Theme', 'name'))
# User can't back out of these changes, they must be applied now.
changes.save_all()
self.cd.save_all_changed_extensions()
self.extpage.save_all_changed_extensions()
self.cd.activate_config_changes()
self.set_theme_type()
class KeysPage(Frame):
def __init__(self, master):
def __init__(self, master, extpage):
super().__init__(master)
self.extpage = extpage
self.cd = master.winfo_toplevel()
self.create_page_keys()
self.load_key_cfg()
@ -1779,7 +1589,7 @@ class KeysPage(Frame):
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.extpage.save_all_changed_extensions()
self.cd.activate_config_changes()
self.set_keys_type()
@ -2094,6 +1904,201 @@ class GenPage(Frame):
'main', 'PyShell', 'auto-squeeze-min-lines', type='int'))
class ExtPage(Frame):
def __init__(self, master):
super().__init__(master)
self.ext_defaultCfg = idleConf.defaultCfg['extensions']
self.ext_userCfg = idleConf.userCfg['extensions']
self.is_int = self.register(is_int)
self.load_extensions()
self.create_page_extensions() # Requires extension names.
def create_page_extensions(self):
"""Configure IDLE feature extensions and help menu extensions.
List the feature extensions and a configuration box for the
selected extension. Help menu extensions are in a HelpFrame.
This code reads the current configuration using idleConf,
supplies a GUI interface to change the configuration values,
and saves the changes using idleConf.
Some changes may require restarting IDLE. This depends on each
extension's implementation.
All values are treated as text, and it is up to the user to
supply reasonable values. The only exception to this are the
'enable*' options, which are boolean, and can be toggled with a
True/False button.
Methods:
extension_selected: Handle selection from list.
create_extension_frame: Hold widgets for one extension.
set_extension_value: Set in userCfg['extensions'].
save_all_changed_extensions: Call extension page Save().
"""
self.extension_names = StringVar(self)
frame_ext = LabelFrame(self, borderwidth=2, relief=GROOVE,
text=' Feature Extensions ')
self.frame_help = HelpFrame(self, borderwidth=2, relief=GROOVE,
text=' Help Menu Extensions ')
frame_ext.rowconfigure(0, weight=1)
frame_ext.columnconfigure(2, weight=1)
self.extension_list = Listbox(frame_ext, listvariable=self.extension_names,
selectmode='browse')
self.extension_list.bind('<<ListboxSelect>>', self.extension_selected)
scroll = Scrollbar(frame_ext, command=self.extension_list.yview)
self.extension_list.yscrollcommand=scroll.set
self.details_frame = LabelFrame(frame_ext, width=250, height=250)
self.extension_list.grid(column=0, row=0, sticky='nws')
scroll.grid(column=1, row=0, sticky='ns')
self.details_frame.grid(column=2, row=0, sticky='nsew', padx=[10, 0])
frame_ext.configure(padding=10)
self.config_frame = {}
self.current_extension = None
self.outerframe = self # TEMPORARY
self.tabbed_page_set = self.extension_list # TEMPORARY
# Create the frame holding controls for each extension.
ext_names = ''
for ext_name in sorted(self.extensions):
self.create_extension_frame(ext_name)
ext_names = ext_names + '{' + ext_name + '} '
self.extension_names.set(ext_names)
self.extension_list.selection_set(0)
self.extension_selected(None)
frame_ext.grid(row=0, column=0, sticky='nsew')
Label(self).grid(row=1, column=0) # Spacer. Replace with config?
self.frame_help.grid(row=2, column=0, sticky='sew')
def load_extensions(self):
"Fill self.extensions with data from the default and user configs."
self.extensions = {}
for ext_name in idleConf.GetExtensions(active_only=False):
# Former built-in extensions are already filtered out.
self.extensions[ext_name] = []
for ext_name in self.extensions:
opt_list = sorted(self.ext_defaultCfg.GetOptionList(ext_name))
# Bring 'enable' options to the beginning of the list.
enables = [opt_name for opt_name in opt_list
if opt_name.startswith('enable')]
for opt_name in enables:
opt_list.remove(opt_name)
opt_list = enables + opt_list
for opt_name in opt_list:
def_str = self.ext_defaultCfg.Get(
ext_name, opt_name, raw=True)
try:
def_obj = {'True':True, 'False':False}[def_str]
opt_type = 'bool'
except KeyError:
try:
def_obj = int(def_str)
opt_type = 'int'
except ValueError:
def_obj = def_str
opt_type = None
try:
value = self.ext_userCfg.Get(
ext_name, opt_name, type=opt_type, raw=True,
default=def_obj)
except ValueError: # Need this until .Get fixed.
value = def_obj # Bad values overwritten by entry.
var = StringVar(self)
var.set(str(value))
self.extensions[ext_name].append({'name': opt_name,
'type': opt_type,
'default': def_str,
'value': value,
'var': var,
})
def extension_selected(self, event):
"Handle selection of an extension from the list."
newsel = self.extension_list.curselection()
if newsel:
newsel = self.extension_list.get(newsel)
if newsel is None or newsel != self.current_extension:
if self.current_extension:
self.details_frame.config(text='')
self.config_frame[self.current_extension].grid_forget()
self.current_extension = None
if newsel:
self.details_frame.config(text=newsel)
self.config_frame[newsel].grid(column=0, row=0, sticky='nsew')
self.current_extension = newsel
def create_extension_frame(self, ext_name):
"""Create a frame holding the widgets to configure one extension"""
f = VerticalScrolledFrame(self.details_frame, height=250, width=250)
self.config_frame[ext_name] = f
entry_area = f.interior
# Create an entry for each configuration option.
for row, opt in enumerate(self.extensions[ext_name]):
# Create a row with a label and entry/checkbutton.
label = Label(entry_area, text=opt['name'])
label.grid(row=row, column=0, sticky=NW)
var = opt['var']
if opt['type'] == 'bool':
Checkbutton(entry_area, variable=var,
onvalue='True', offvalue='False', width=8
).grid(row=row, column=1, sticky=W, padx=7)
elif opt['type'] == 'int':
Entry(entry_area, textvariable=var, validate='key',
validatecommand=(self.is_int, '%P'), width=10
).grid(row=row, column=1, sticky=NSEW, padx=7)
else: # type == 'str'
# Limit size to fit non-expanding space with larger font.
Entry(entry_area, textvariable=var, width=15
).grid(row=row, column=1, sticky=NSEW, padx=7)
return
def set_extension_value(self, section, opt):
"""Return True if the configuration was added or changed.
If the value is the same as the default, then remove it
from user config file.
"""
name = opt['name']
default = opt['default']
value = opt['var'].get().strip() or default
opt['var'].set(value)
# if self.defaultCfg.has_section(section):
# Currently, always true; if not, indent to return.
if (value == default):
return self.ext_userCfg.RemoveOption(section, name)
# Set the option.
return self.ext_userCfg.SetOption(section, name, value)
def save_all_changed_extensions(self):
"""Save configuration changes to the user config file.
Attributes accessed:
extensions
Methods:
set_extension_value
"""
has_changes = False
for ext_name in self.extensions:
options = self.extensions[ext_name]
for opt in options:
if self.set_extension_value(ext_name, opt):
has_changes = True
if has_changes:
self.ext_userCfg.Save()
class HelpFrame(LabelFrame):
def __init__(self, master, **cfg):

View File

@ -1292,12 +1292,22 @@ class GenPageTest(unittest.TestCase):
self.assertEqual(extpage, {'CodeContext': {'maxlines': '1'}})
#unittest.skip("Nothing here yet TODO")
class ExtPageTest(unittest.TestCase):
"""Test that the help source list works correctly."""
@classmethod
def setUpClass(cls):
page = dialog.extpage
dialog.note.select(page)
class HelpSourceTest(unittest.TestCase):
"""Test that the help source list works correctly."""
@classmethod
def setUpClass(cls):
dialog.note.select(dialog.extpage)
frame = cls.frame = dialog.frame_help
page = dialog.extpage
dialog.note.select(page)
frame = cls.frame = page.frame_help
frame.set = frame.set_add_delete_state = Func()
frame.upc = frame.update_help_changes = Func()
frame.update()