|
|
|
@ -80,12 +80,14 @@ class ConfigDialog(Toplevel): |
|
|
|
|
|
|
|
def CreateWidgets(self): |
|
|
|
self.tabPages = TabbedPageSet(self, |
|
|
|
page_names=['Fonts/Tabs', 'Highlighting', 'Keys', 'General']) |
|
|
|
page_names=['Fonts/Tabs', 'Highlighting', 'Keys', 'General', |
|
|
|
'Extensions']) |
|
|
|
self.tabPages.pack(side=TOP, expand=TRUE, fill=BOTH) |
|
|
|
self.CreatePageFontTab() |
|
|
|
self.CreatePageHighlight() |
|
|
|
self.CreatePageKeys() |
|
|
|
self.CreatePageGeneral() |
|
|
|
self.CreatePageExtensions() |
|
|
|
self.create_action_buttons().pack(side=BOTTOM) |
|
|
|
|
|
|
|
def create_action_buttons(self): |
|
|
|
@ -1092,6 +1094,7 @@ class ConfigDialog(Toplevel): |
|
|
|
self.LoadKeyCfg() |
|
|
|
### general page |
|
|
|
self.LoadGeneralCfg() |
|
|
|
# note: extension page handled separately |
|
|
|
|
|
|
|
def SaveNewKeySet(self, keySetName, keySet): |
|
|
|
""" |
|
|
|
@ -1145,6 +1148,7 @@ class ConfigDialog(Toplevel): |
|
|
|
# save these even if unchanged! |
|
|
|
idleConf.userCfg[configType].Save() |
|
|
|
self.ResetChangedItems() #clear the changed items dict |
|
|
|
self.save_all_changed_extensions() # uses a different mechanism |
|
|
|
|
|
|
|
def DeactivateCurrentConfig(self): |
|
|
|
#Before a config is saved, some cleanup of current |
|
|
|
@ -1180,127 +1184,58 @@ class ConfigDialog(Toplevel): |
|
|
|
view_text(self, title='Help for IDLE preferences', |
|
|
|
text=help_common+help_pages.get(page, '')) |
|
|
|
|
|
|
|
help_common = '''\ |
|
|
|
When you click either the Apply or Ok buttons, settings in this |
|
|
|
dialog that are different from IDLE's default are saved in |
|
|
|
a .idlerc directory in your home directory. Except as noted, |
|
|
|
hese changes apply to all versions of IDLE installed on this |
|
|
|
machine. Some do not take affect until IDLE is restarted. |
|
|
|
[Cancel] only cancels changes made since the last save. |
|
|
|
''' |
|
|
|
help_pages = { |
|
|
|
'Highlighting':''' |
|
|
|
Highlighting: |
|
|
|
The IDLE Dark color theme is new in Octover 2015. It can only |
|
|
|
be used with older IDLE releases if it is saved as a custom |
|
|
|
theme, with a different name. |
|
|
|
''' |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
class VerticalScrolledFrame(Frame): |
|
|
|
"""A pure Tkinter vertically scrollable frame. |
|
|
|
|
|
|
|
* Use the 'interior' attribute to place widgets inside the scrollable frame |
|
|
|
* Construct and pack/place/grid normally |
|
|
|
* This frame only allows vertical scrolling |
|
|
|
""" |
|
|
|
def __init__(self, parent, *args, **kw): |
|
|
|
Frame.__init__(self, parent, *args, **kw) |
|
|
|
|
|
|
|
# create a canvas object and a vertical scrollbar for scrolling it |
|
|
|
vscrollbar = Scrollbar(self, orient=VERTICAL) |
|
|
|
vscrollbar.pack(fill=Y, side=RIGHT, expand=FALSE) |
|
|
|
canvas = Canvas(self, bd=0, highlightthickness=0, |
|
|
|
yscrollcommand=vscrollbar.set) |
|
|
|
canvas.pack(side=LEFT, fill=BOTH, expand=TRUE) |
|
|
|
vscrollbar.config(command=canvas.yview) |
|
|
|
|
|
|
|
# reset the view |
|
|
|
canvas.xview_moveto(0) |
|
|
|
canvas.yview_moveto(0) |
|
|
|
|
|
|
|
# create a frame inside the canvas which will be scrolled with it |
|
|
|
self.interior = interior = Frame(canvas) |
|
|
|
interior_id = canvas.create_window(0, 0, window=interior, anchor=NW) |
|
|
|
|
|
|
|
# track changes to the canvas and frame width and sync them, |
|
|
|
# also updating the scrollbar |
|
|
|
def _configure_interior(event): |
|
|
|
# update the scrollbars to match the size of the inner frame |
|
|
|
size = (interior.winfo_reqwidth(), interior.winfo_reqheight()) |
|
|
|
canvas.config(scrollregion="0 0 %s %s" % size) |
|
|
|
interior.bind('<Configure>', _configure_interior) |
|
|
|
def CreatePageExtensions(self): |
|
|
|
"""Part of the config dialog used for configuring IDLE extensions. |
|
|
|
|
|
|
|
def _configure_canvas(event): |
|
|
|
if interior.winfo_reqwidth() != canvas.winfo_width(): |
|
|
|
# update the inner frame's width to fill the canvas |
|
|
|
canvas.itemconfigure(interior_id, width=canvas.winfo_width()) |
|
|
|
canvas.bind('<Configure>', _configure_canvas) |
|
|
|
|
|
|
|
return |
|
|
|
|
|
|
|
def is_int(s): |
|
|
|
"Return 's is blank or represents an int'" |
|
|
|
if not s: |
|
|
|
return True |
|
|
|
try: |
|
|
|
int(s) |
|
|
|
return True |
|
|
|
except ValueError: |
|
|
|
return False |
|
|
|
This code is generic - it works for any and all IDLE extensions. |
|
|
|
|
|
|
|
# TODO: |
|
|
|
# * Revert to default(s)? Per option or per extension? |
|
|
|
# * List options in their original order (possible??) |
|
|
|
class ConfigExtensionsDialog(Toplevel): |
|
|
|
"""A dialog for configuring IDLE extensions. |
|
|
|
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. |
|
|
|
|
|
|
|
This dialog is generic - it works for any and all IDLE extensions. |
|
|
|
Not all changes take effect immediately - some may require restarting IDLE. |
|
|
|
This depends on each extension's implementation. |
|
|
|
|
|
|
|
IDLE extensions save their configuration options using idleConf. |
|
|
|
ConfigExtensionsDialog reads the current configuration using idleConf, |
|
|
|
supplies a GUI interface to change the configuration values, and saves the |
|
|
|
changes using idleConf. |
|
|
|
|
|
|
|
Not all changes take effect immediately - some may require restarting IDLE. |
|
|
|
This depends on each extension's implementation. |
|
|
|
|
|
|
|
All values are treated as text, and it is up to the user to supply |
|
|
|
reasonable values. The only exception to this are the 'enable*' options, |
|
|
|
which are boolean, and can be toggled with an True/False button. |
|
|
|
""" |
|
|
|
def __init__(self, parent, title=None, _htest=False): |
|
|
|
Toplevel.__init__(self, parent) |
|
|
|
self.wm_withdraw() |
|
|
|
|
|
|
|
self.configure(borderwidth=5) |
|
|
|
self.geometry( |
|
|
|
"+%d+%d" % (parent.winfo_rootx() + 20, |
|
|
|
parent.winfo_rooty() + (30 if not _htest else 150))) |
|
|
|
self.wm_title(title or 'IDLE Extensions Configuration') |
|
|
|
|
|
|
|
self.defaultCfg = idleConf.defaultCfg['extensions'] |
|
|
|
self.userCfg = idleConf.userCfg['extensions'] |
|
|
|
All values are treated as text, and it is up to the user to supply |
|
|
|
reasonable values. The only exception to this are the 'enable*' options, |
|
|
|
which are boolean, and can be toggled with an True/False button. |
|
|
|
""" |
|
|
|
parent = self.parent |
|
|
|
frame = self.tabPages.pages['Extensions'].frame |
|
|
|
self.ext_defaultCfg = idleConf.defaultCfg['extensions'] |
|
|
|
self.ext_userCfg = idleConf.userCfg['extensions'] |
|
|
|
self.is_int = self.register(is_int) |
|
|
|
self.load_extensions() |
|
|
|
self.create_widgets() |
|
|
|
# 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.rowconfigure(0, weight=1) |
|
|
|
frame.columnconfigure(2, weight=1) |
|
|
|
self.extension_list = Listbox(frame, listvariable=self.extension_names, |
|
|
|
selectmode='browse') |
|
|
|
self.extension_list.bind('<<ListboxSelect>>', self.extension_selected) |
|
|
|
scroll = Scrollbar(frame, command=self.extension_list.yview) |
|
|
|
self.extension_list.yscrollcommand=scroll.set |
|
|
|
self.details_frame = LabelFrame(frame, 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.configure(padx=10, pady=10) |
|
|
|
self.config_frame = {} |
|
|
|
self.current_extension = None |
|
|
|
|
|
|
|
self.resizable(height=FALSE, width=FALSE) # don't allow resizing yet |
|
|
|
self.transient(parent) |
|
|
|
self.protocol("WM_DELETE_WINDOW", self.Cancel) |
|
|
|
self.tabbed_page_set.focus_set() |
|
|
|
# wait for window to be generated |
|
|
|
self.update() |
|
|
|
# set current width as the minimum width |
|
|
|
self.wm_minsize(self.winfo_width(), 1) |
|
|
|
# now allow resizing |
|
|
|
self.resizable(height=TRUE, width=TRUE) |
|
|
|
|
|
|
|
self.wm_deiconify() |
|
|
|
if not _htest: |
|
|
|
self.grab_set() |
|
|
|
self.wait_window() |
|
|
|
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) |
|
|
|
|
|
|
|
def load_extensions(self): |
|
|
|
"Fill self.extensions with data from the default and user configs." |
|
|
|
@ -1309,7 +1244,7 @@ class ConfigExtensionsDialog(Toplevel): |
|
|
|
self.extensions[ext_name] = [] |
|
|
|
|
|
|
|
for ext_name in self.extensions: |
|
|
|
opt_list = sorted(self.defaultCfg.GetOptionList(ext_name)) |
|
|
|
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 |
|
|
|
@ -1319,7 +1254,7 @@ class ConfigExtensionsDialog(Toplevel): |
|
|
|
opt_list = enables + opt_list |
|
|
|
|
|
|
|
for opt_name in opt_list: |
|
|
|
def_str = self.defaultCfg.Get( |
|
|
|
def_str = self.ext_defaultCfg.Get( |
|
|
|
ext_name, opt_name, raw=True) |
|
|
|
try: |
|
|
|
def_obj = {'True':True, 'False':False}[def_str] |
|
|
|
@ -1332,7 +1267,7 @@ class ConfigExtensionsDialog(Toplevel): |
|
|
|
def_obj = def_str |
|
|
|
opt_type = None |
|
|
|
try: |
|
|
|
value = self.userCfg.Get( |
|
|
|
value = self.ext_userCfg.Get( |
|
|
|
ext_name, opt_name, type=opt_type, raw=True, |
|
|
|
default=def_obj) |
|
|
|
except ValueError: # Need this until .Get fixed |
|
|
|
@ -1347,37 +1282,6 @@ class ConfigExtensionsDialog(Toplevel): |
|
|
|
'var': var, |
|
|
|
}) |
|
|
|
|
|
|
|
def create_widgets(self): |
|
|
|
"""Create the dialog's widgets.""" |
|
|
|
self.extension_names = StringVar(self) |
|
|
|
self.rowconfigure(0, weight=1) |
|
|
|
self.columnconfigure(2, weight=1) |
|
|
|
self.extension_list = Listbox(self, listvariable=self.extension_names, |
|
|
|
selectmode='browse') |
|
|
|
self.extension_list.bind('<<ListboxSelect>>', self.extension_selected) |
|
|
|
scroll = Scrollbar(self, command=self.extension_list.yview) |
|
|
|
self.extension_list.yscrollcommand=scroll.set |
|
|
|
self.details_frame = LabelFrame(self, 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]) |
|
|
|
self.configure(padx=10, pady=10) |
|
|
|
self.config_frame = {} |
|
|
|
self.current_extension = None |
|
|
|
|
|
|
|
self.outerframe = self # TEMPORARY |
|
|
|
self.tabbed_page_set = self.extension_list # TEMPORARY |
|
|
|
|
|
|
|
# create the individual pages |
|
|
|
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.create_action_buttons().grid(row=1, columnspan=3) |
|
|
|
|
|
|
|
def extension_selected(self, event): |
|
|
|
newsel = self.extension_list.curselection() |
|
|
|
if newsel: |
|
|
|
@ -1392,8 +1296,6 @@ class ConfigExtensionsDialog(Toplevel): |
|
|
|
self.config_frame[newsel].grid(column=0, row=0, sticky='nsew') |
|
|
|
self.current_extension = newsel |
|
|
|
|
|
|
|
create_action_buttons = ConfigDialog.create_action_buttons |
|
|
|
|
|
|
|
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) |
|
|
|
@ -1420,19 +1322,7 @@ class ConfigExtensionsDialog(Toplevel): |
|
|
|
).grid(row=row, column=1, sticky=NSEW, padx=7) |
|
|
|
return |
|
|
|
|
|
|
|
|
|
|
|
Ok = ConfigDialog.Ok |
|
|
|
|
|
|
|
def Apply(self): |
|
|
|
self.save_all_changed_configs() |
|
|
|
pass |
|
|
|
|
|
|
|
Cancel = ConfigDialog.Cancel |
|
|
|
|
|
|
|
def Help(self): |
|
|
|
pass |
|
|
|
|
|
|
|
def set_user_value(self, section, opt): |
|
|
|
def set_extension_value(self, section, opt): |
|
|
|
name = opt['name'] |
|
|
|
default = opt['default'] |
|
|
|
value = opt['var'].get().strip() or default |
|
|
|
@ -1440,20 +1330,92 @@ class ConfigExtensionsDialog(Toplevel): |
|
|
|
# if self.defaultCfg.has_section(section): |
|
|
|
# Currently, always true; if not, indent to return |
|
|
|
if (value == default): |
|
|
|
return self.userCfg.RemoveOption(section, name) |
|
|
|
return self.ext_userCfg.RemoveOption(section, name) |
|
|
|
# set the option |
|
|
|
return self.userCfg.SetOption(section, name, value) |
|
|
|
return self.ext_userCfg.SetOption(section, name, value) |
|
|
|
|
|
|
|
def save_all_changed_configs(self): |
|
|
|
def save_all_changed_extensions(self): |
|
|
|
"""Save configuration changes to the user config file.""" |
|
|
|
has_changes = False |
|
|
|
for ext_name in self.extensions: |
|
|
|
options = self.extensions[ext_name] |
|
|
|
for opt in options: |
|
|
|
if self.set_user_value(ext_name, opt): |
|
|
|
if self.set_extension_value(ext_name, opt): |
|
|
|
has_changes = True |
|
|
|
if has_changes: |
|
|
|
self.userCfg.Save() |
|
|
|
self.ext_userCfg.Save() |
|
|
|
|
|
|
|
|
|
|
|
help_common = '''\ |
|
|
|
When you click either the Apply or Ok buttons, settings in this |
|
|
|
dialog that are different from IDLE's default are saved in |
|
|
|
a .idlerc directory in your home directory. Except as noted, |
|
|
|
hese changes apply to all versions of IDLE installed on this |
|
|
|
machine. Some do not take affect until IDLE is restarted. |
|
|
|
[Cancel] only cancels changes made since the last save. |
|
|
|
''' |
|
|
|
help_pages = { |
|
|
|
'Highlighting':''' |
|
|
|
Highlighting: |
|
|
|
The IDLE Dark color theme is new in Octover 2015. It can only |
|
|
|
be used with older IDLE releases if it is saved as a custom |
|
|
|
theme, with a different name. |
|
|
|
''' |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
def is_int(s): |
|
|
|
"Return 's is blank or represents an int'" |
|
|
|
if not s: |
|
|
|
return True |
|
|
|
try: |
|
|
|
int(s) |
|
|
|
return True |
|
|
|
except ValueError: |
|
|
|
return False |
|
|
|
|
|
|
|
|
|
|
|
class VerticalScrolledFrame(Frame): |
|
|
|
"""A pure Tkinter vertically scrollable frame. |
|
|
|
|
|
|
|
* Use the 'interior' attribute to place widgets inside the scrollable frame |
|
|
|
* Construct and pack/place/grid normally |
|
|
|
* This frame only allows vertical scrolling |
|
|
|
""" |
|
|
|
def __init__(self, parent, *args, **kw): |
|
|
|
Frame.__init__(self, parent, *args, **kw) |
|
|
|
|
|
|
|
# create a canvas object and a vertical scrollbar for scrolling it |
|
|
|
vscrollbar = Scrollbar(self, orient=VERTICAL) |
|
|
|
vscrollbar.pack(fill=Y, side=RIGHT, expand=FALSE) |
|
|
|
canvas = Canvas(self, bd=0, highlightthickness=0, |
|
|
|
yscrollcommand=vscrollbar.set) |
|
|
|
canvas.pack(side=LEFT, fill=BOTH, expand=TRUE) |
|
|
|
vscrollbar.config(command=canvas.yview) |
|
|
|
|
|
|
|
# reset the view |
|
|
|
canvas.xview_moveto(0) |
|
|
|
canvas.yview_moveto(0) |
|
|
|
|
|
|
|
# create a frame inside the canvas which will be scrolled with it |
|
|
|
self.interior = interior = Frame(canvas) |
|
|
|
interior_id = canvas.create_window(0, 0, window=interior, anchor=NW) |
|
|
|
|
|
|
|
# track changes to the canvas and frame width and sync them, |
|
|
|
# also updating the scrollbar |
|
|
|
def _configure_interior(event): |
|
|
|
# update the scrollbars to match the size of the inner frame |
|
|
|
size = (interior.winfo_reqwidth(), interior.winfo_reqheight()) |
|
|
|
canvas.config(scrollregion="0 0 %s %s" % size) |
|
|
|
interior.bind('<Configure>', _configure_interior) |
|
|
|
|
|
|
|
def _configure_canvas(event): |
|
|
|
if interior.winfo_reqwidth() != canvas.winfo_width(): |
|
|
|
# update the inner frame's width to fill the canvas |
|
|
|
canvas.itemconfigure(interior_id, width=canvas.winfo_width()) |
|
|
|
canvas.bind('<Configure>', _configure_canvas) |
|
|
|
|
|
|
|
return |
|
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__': |
|
|
|
|