bpo-33962: Use ttk spinbox for IDLE indent space config (GH-22954)

If ttk.Spinbox is not available (Tk < 8.5.9) use readonly ttk.Combobox.

Co-authored-by: Terry Jan Reedy <tjreedy@udel.edu>
This commit is contained in:
Mark Roseman 2021-06-10 12:13:55 -07:00 committed by GitHub
parent f82262b186
commit 42d5a4fc3b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 73 additions and 118 deletions

View file

@ -15,9 +15,10 @@ from tkinter import (Toplevel, Listbox, Scale, Canvas,
StringVar, BooleanVar, IntVar, TRUE, FALSE, StringVar, BooleanVar, IntVar, TRUE, FALSE,
TOP, BOTTOM, RIGHT, LEFT, SOLID, GROOVE, TOP, BOTTOM, RIGHT, LEFT, SOLID, GROOVE,
NONE, BOTH, X, Y, W, E, EW, NS, NSEW, NW, NONE, BOTH, X, Y, W, E, EW, NS, NSEW, NW,
HORIZONTAL, VERTICAL, ANCHOR, ACTIVE, END) HORIZONTAL, VERTICAL, ANCHOR, ACTIVE, END, TclError)
from tkinter.ttk import (Frame, LabelFrame, Button, Checkbutton, Entry, Label, from tkinter.ttk import (Frame, LabelFrame, Button, Checkbutton, Entry, Label,
OptionMenu, Notebook, Radiobutton, Scrollbar, Style) OptionMenu, Notebook, Radiobutton, Scrollbar, Style,
Spinbox, Combobox)
from tkinter import colorchooser from tkinter import colorchooser
import tkinter.font as tkfont import tkinter.font as tkfont
from tkinter import messagebox from tkinter import messagebox
@ -101,8 +102,9 @@ class ConfigDialog(Toplevel):
highpage: HighPage highpage: HighPage
fontpage: FontPage fontpage: FontPage
keyspage: KeysPage keyspage: KeysPage
genpage: GenPage winpage: WinPage
extpage: self.create_page_extensions shedpage: ShedPage
extpage: ExtPage
Methods: Methods:
create_action_buttons create_action_buttons
@ -170,26 +172,15 @@ class ConfigDialog(Toplevel):
return outer return outer
def ok(self): def ok(self):
"""Apply config changes, then dismiss dialog. """Apply config changes, then dismiss dialog."""
Methods:
apply
destroy: inherited
"""
self.apply() self.apply()
self.destroy() self.destroy()
def apply(self): def apply(self):
"""Apply config changes and leave dialog open. """Apply config changes and leave dialog open."""
Methods:
deactivate_current_config
save_all_changed_extensions
activate_config_changes
"""
self.deactivate_current_config() self.deactivate_current_config()
changes.save_all() changes.save_all()
self.save_all_changed_extensions() self.extpage.save_all_changed_extensions()
self.activate_config_changes() self.activate_config_changes()
def cancel(self): def cancel(self):
@ -299,12 +290,11 @@ class FontPage(Frame):
def __init__(self, master, highpage): def __init__(self, master, highpage):
super().__init__(master) super().__init__(master)
self.highlight_sample = highpage.highlight_sample self.highlight_sample = highpage.highlight_sample
self.create_page_font_tab() self.create_page_font()
self.load_font_cfg() self.load_font_cfg()
self.load_tab_cfg()
def create_page_font_tab(self): def create_page_font(self):
"""Return frame of widgets for Font/Tabs tab. """Return frame of widgets for Font tab.
Fonts: Enable users to provisionally change font face, size, or Fonts: Enable users to provisionally change font face, size, or
boldness and to see the consequence of proposed choices. Each boldness and to see the consequence of proposed choices. Each
@ -328,11 +318,6 @@ class FontPage(Frame):
Set_samples applies a new font constructed from the font vars to Set_samples applies a new font constructed from the font vars to
font_sample and to highlight_sample on the highlight page. font_sample and to highlight_sample on the highlight page.
Tabs: Enable users to change spaces entered for indent tabs.
Changing indent_scale value with the mouse sets Var space_num,
which invokes the default callback to add an entry to
changes. Load_tab_cfg initializes space_num to default.
Widgets for FontPage(Frame): (*) widgets bound to self Widgets for FontPage(Frame): (*) widgets bound to self
frame_font: LabelFrame frame_font: LabelFrame
frame_font_name: Frame frame_font_name: Frame
@ -345,23 +330,16 @@ class FontPage(Frame):
(*)bold_toggle: Checkbutton - font_bold (*)bold_toggle: Checkbutton - font_bold
frame_sample: LabelFrame frame_sample: LabelFrame
(*)font_sample: Label (*)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_name = tracers.add(StringVar(self), self.var_changed_font)
self.font_size = tracers.add(StringVar(self), self.var_changed_font) self.font_size = tracers.add(StringVar(self), self.var_changed_font)
self.font_bold = tracers.add(BooleanVar(self), self.var_changed_font) self.font_bold = tracers.add(BooleanVar(self), self.var_changed_font)
self.space_num = tracers.add(IntVar(self), ('main', 'Indent', 'num-spaces'))
# Define frames and widgets. # Define frames and widgets.
frame_font = LabelFrame( frame_font = LabelFrame(self, borderwidth=2, relief=GROOVE,
self, borderwidth=2, relief=GROOVE, text=' Shell/Editor Font ') text=' Shell/Editor Font ')
frame_sample = LabelFrame( frame_sample = LabelFrame(self, borderwidth=2, relief=GROOVE,
self, borderwidth=2, relief=GROOVE,
text=' Font Sample (Editable) ') text=' Font Sample (Editable) ')
frame_indent = LabelFrame(
self, borderwidth=2, relief=GROOVE, text=' Indentation Width ')
# frame_font. # frame_font.
frame_font_name = Frame(frame_font) frame_font_name = Frame(frame_font)
frame_font_param = Frame(frame_font) frame_font_param = Frame(frame_font)
@ -385,13 +363,6 @@ class FontPage(Frame):
self.font_sample = font_sample_frame.text self.font_sample = font_sample_frame.text
self.font_sample.config(wrap=NONE, width=1, height=1) self.font_sample.config(wrap=NONE, width=1, height=1)
self.font_sample.insert(END, font_sample_text) self.font_sample.insert(END, font_sample_text)
# frame_indent.
indent_title = Label(
frame_indent, justify=LEFT,
text='Python Standard: 4 Spaces!')
self.indent_scale = Scale(
frame_indent, variable=self.space_num,
orient='horizontal', tickinterval=2, from_=2, to=16)
# Grid and pack widgets: # Grid and pack widgets:
self.columnconfigure(1, weight=1) self.columnconfigure(1, weight=1)
@ -399,7 +370,6 @@ class FontPage(Frame):
frame_font.grid(row=0, column=0, padx=5, pady=5) frame_font.grid(row=0, column=0, padx=5, pady=5)
frame_sample.grid(row=0, column=1, rowspan=3, padx=5, pady=5, frame_sample.grid(row=0, column=1, rowspan=3, padx=5, pady=5,
sticky='nsew') sticky='nsew')
frame_indent.grid(row=1, column=0, padx=5, pady=5, sticky='ew')
# frame_font. # frame_font.
frame_font_name.pack(side=TOP, padx=5, pady=5, fill=X) frame_font_name.pack(side=TOP, padx=5, pady=5, fill=X)
frame_font_param.pack(side=TOP, padx=5, pady=5, fill=X) frame_font_param.pack(side=TOP, padx=5, pady=5, fill=X)
@ -411,9 +381,6 @@ class FontPage(Frame):
self.bold_toggle.pack(side=LEFT, anchor=W, padx=20) self.bold_toggle.pack(side=LEFT, anchor=W, padx=20)
# frame_sample. # frame_sample.
font_sample_frame.pack(expand=TRUE, fill=BOTH) font_sample_frame.pack(expand=TRUE, fill=BOTH)
# frame_indent.
indent_title.pack(side=TOP, anchor=W, padx=5)
self.indent_scale.pack(side=TOP, padx=5, fill=X)
def load_font_cfg(self): def load_font_cfg(self):
"""Load current configuration settings for the font options. """Load current configuration settings for the font options.
@ -487,22 +454,6 @@ class FontPage(Frame):
self.font_sample['font'] = new_font self.font_sample['font'] = new_font
self.highlight_sample['font'] = new_font self.highlight_sample['font'] = new_font
def load_tab_cfg(self):
"""Load current configuration settings for the tab options.
Attributes updated:
space_num: Set to value from idleConf.
"""
# Set indent sizes.
space_num = idleConf.GetOption(
'main', 'Indent', 'num-spaces', default=4, type='int')
self.space_num.set(space_num)
def var_changed_space_num(self, *params):
"Store change to indentation size."
value = self.space_num.get()
changes.add_option('main', 'Indent', 'num-spaces', value)
class HighPage(Frame): class HighPage(Frame):
@ -515,7 +466,7 @@ class HighPage(Frame):
self.load_theme_cfg() self.load_theme_cfg()
def create_page_highlight(self): def create_page_highlight(self):
"""Return frame of widgets for Highlighting tab. """Return frame of widgets for Highlights tab.
Enable users to provisionally change foreground and background Enable users to provisionally change foreground and background
colors applied to textual tags. Color mappings are stored in colors applied to textual tags. Color mappings are stored in
@ -1617,40 +1568,41 @@ class WinPage(Frame):
"""Return frame of widgets for Windows tab. """Return frame of widgets for Windows tab.
Enable users to provisionally change general window options. Enable users to provisionally change general window options.
Function load_windows_cfg initializes tk variables idleConf. Function load_windows_cfg initializes tk variable idleConf.
Radiobuttons startup_shell_on and startup_editor_on set var Radiobuttons startup_shell_on and startup_editor_on set var
startup_edit. Entry boxes win_width_int and win_height_int set var startup_edit. Entry boxes win_width_int and win_height_int set var
win_width and win_height. Setting var_name invokes the default win_width and win_height. Setting var_name invokes the default
callback that adds option to changes. callback that adds option to changes.
Widgets for WinPage(Frame): (*) widgets bound to self Widgets for WinPage(Frame): > vars, bound to self
frame_window: LabelFrame frame_window: LabelFrame
frame_run: Frame frame_run: Frame
startup_title: Label startup_title: Label
(*)startup_editor_on: Radiobutton - startup_edit startup_editor_on: Radiobutton > startup_edit
(*)startup_shell_on: Radiobutton - startup_edit startup_shell_on: Radiobutton > startup_edit
frame_win_size: Frame frame_win_size: Frame
win_size_title: Label win_size_title: Label
win_width_title: Label win_width_title: Label
(*)win_width_int: Entry - win_width win_width_int: Entry > win_width
win_height_title: Label win_height_title: Label
(*)win_height_int: Entry - win_height win_height_int: Entry > win_height
frame_cursor_blink: Frame frame_cursor: Frame
cursor_blink_title: Label indent_title: Label
(*)cursor_blink_bool: Checkbutton - cursor_blink indent_chooser: Spinbox (Combobox < 8.5.9) > indent_spaces
blink_on: Checkbutton > cursor_blink
frame_autocomplete: Frame frame_autocomplete: Frame
auto_wait_title: Label auto_wait_title: Label
(*)auto_wait_int: Entry - autocomplete_wait auto_wait_int: Entry > autocomplete_wait
frame_paren1: Frame frame_paren1: Frame
paren_style_title: Label paren_style_title: Label
(*)paren_style_type: OptionMenu - paren_style paren_style_type: OptionMenu > paren_style
frame_paren2: Frame frame_paren2: Frame
paren_time_title: Label paren_time_title: Label
(*)paren_flash_time: Entry - flash_delay paren_flash_time: Entry > flash_delay
(*)bell_on: Checkbutton - paren_bell bell_on: Checkbutton > paren_bell
frame_format: Frame frame_format: Frame
format_width_title: Label format_width_title: Label
(*)format_width_int: Entry - format_width format_width_int: Entry > format_width
""" """
# Integer values need StringVar because int('') raises. # Integer values need StringVar because int('') raises.
self.startup_edit = tracers.add( self.startup_edit = tracers.add(
@ -1659,6 +1611,8 @@ class WinPage(Frame):
StringVar(self), ('main', 'EditorWindow', 'width')) StringVar(self), ('main', 'EditorWindow', 'width'))
self.win_height = tracers.add( self.win_height = tracers.add(
StringVar(self), ('main', 'EditorWindow', 'height')) StringVar(self), ('main', 'EditorWindow', 'height'))
self.indent_spaces = tracers.add(
StringVar(self), ('main', 'Indent', 'num-spaces'))
self.cursor_blink = tracers.add( self.cursor_blink = tracers.add(
BooleanVar(self), ('main', 'EditorWindow', 'cursor-blink')) BooleanVar(self), ('main', 'EditorWindow', 'cursor-blink'))
self.autocomplete_wait = tracers.add( self.autocomplete_wait = tracers.add(
@ -1699,18 +1653,28 @@ class WinPage(Frame):
validatecommand=self.digits_only, validate='key', validatecommand=self.digits_only, validate='key',
) )
frame_cursor_blink = Frame(frame_window, borderwidth=0) frame_cursor = Frame(frame_window, borderwidth=0)
cursor_blink_title = Label(frame_cursor_blink, text='Cursor Blink') indent_title = Label(frame_cursor,
self.cursor_blink_bool = Checkbutton(frame_cursor_blink, text='Indent spaces (4 is standard)')
variable=self.cursor_blink, width=1) try:
self.indent_chooser = Spinbox(
frame_cursor, textvariable=self.indent_spaces,
from_=1, to=10, width=2,
validatecommand=self.digits_only, validate='key')
except TclError:
self.indent_chooser = Combobox(
frame_cursor, textvariable=self.indent_spaces,
state="readonly", values=list(range(1,11)), width=3)
cursor_blink_title = Label(frame_cursor, text='Cursor Blink')
self.cursor_blink_bool = Checkbutton(frame_cursor, text="Cursor blink",
variable=self.cursor_blink)
frame_autocomplete = Frame(frame_window, borderwidth=0,) frame_autocomplete = Frame(frame_window, borderwidth=0,)
auto_wait_title = Label(frame_autocomplete, auto_wait_title = Label(frame_autocomplete,
text='Completions Popup Wait (milliseconds)') text='Completions Popup Wait (milliseconds)')
self.auto_wait_int = Entry(frame_autocomplete, width=6, self.auto_wait_int = Entry(
textvariable=self.autocomplete_wait, frame_autocomplete, textvariable=self.autocomplete_wait,
validatecommand=self.digits_only, width=6, validatecommand=self.digits_only, validate='key')
validate='key')
frame_paren1 = Frame(frame_window, borderwidth=0) frame_paren1 = Frame(frame_window, borderwidth=0)
paren_style_title = Label(frame_paren1, text='Paren Match Style') paren_style_title = Label(frame_paren1, text='Paren Match Style')
@ -1722,7 +1686,8 @@ class WinPage(Frame):
frame_paren2, text='Time Match Displayed (milliseconds)\n' frame_paren2, text='Time Match Displayed (milliseconds)\n'
'(0 is until next input)') '(0 is until next input)')
self.paren_flash_time = Entry( self.paren_flash_time = Entry(
frame_paren2, textvariable=self.flash_delay, width=6) frame_paren2, textvariable=self.flash_delay, width=6,
validatecommand=self.digits_only, validate='key')
self.bell_on = Checkbutton( self.bell_on = Checkbutton(
frame_paren2, text="Bell on Mismatch", variable=self.paren_bell) frame_paren2, text="Bell on Mismatch", variable=self.paren_bell)
frame_format = Frame(frame_window, borderwidth=0) frame_format = Frame(frame_window, borderwidth=0)
@ -1747,10 +1712,11 @@ class WinPage(Frame):
win_height_title.pack(side=RIGHT, anchor=E, pady=5) win_height_title.pack(side=RIGHT, anchor=E, pady=5)
self.win_width_int.pack(side=RIGHT, anchor=E, padx=10, pady=5) self.win_width_int.pack(side=RIGHT, anchor=E, padx=10, pady=5)
win_width_title.pack(side=RIGHT, anchor=E, pady=5) win_width_title.pack(side=RIGHT, anchor=E, pady=5)
# frame_cursor_blink. # frame_cursor.
frame_cursor_blink.pack(side=TOP, padx=5, pady=0, fill=X) frame_cursor.pack(side=TOP, padx=5, pady=0, fill=X)
cursor_blink_title.pack(side=LEFT, anchor=W, padx=5, pady=5) indent_title.pack(side=LEFT, anchor=W, padx=5)
self.cursor_blink_bool.pack(side=LEFT, padx=5, pady=5) self.indent_chooser.pack(side=LEFT, anchor=W, padx=10)
self.cursor_blink_bool.pack(side=RIGHT, anchor=E, padx=15, pady=5)
# frame_autocomplete. # frame_autocomplete.
frame_autocomplete.pack(side=TOP, padx=5, pady=0, fill=X) frame_autocomplete.pack(side=TOP, padx=5, pady=0, fill=X)
auto_wait_title.pack(side=LEFT, anchor=W, padx=5, pady=5) auto_wait_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
@ -1776,6 +1742,8 @@ class WinPage(Frame):
'main', 'EditorWindow', 'width', type='int')) 'main', 'EditorWindow', 'width', type='int'))
self.win_height.set(idleConf.GetOption( self.win_height.set(idleConf.GetOption(
'main', 'EditorWindow', 'height', type='int')) 'main', 'EditorWindow', 'height', type='int'))
self.indent_spaces.set(idleConf.GetOption(
'main', 'Indent', 'num-spaces', type='int'))
self.cursor_blink.set(idleConf.GetOption( self.cursor_blink.set(idleConf.GetOption(
'main', 'EditorWindow', 'cursor-blink', type='bool')) 'main', 'EditorWindow', 'cursor-blink', type='bool'))
self.autocomplete_wait.set(idleConf.GetOption( self.autocomplete_wait.set(idleConf.GetOption(

View file

@ -73,13 +73,13 @@ class ButtonTest(unittest.TestCase):
def test_click_apply(self): def test_click_apply(self):
d = dialog d = dialog
deactivate = d.deactivate_current_config = mock.Mock() deactivate = d.deactivate_current_config = mock.Mock()
save_ext = d.save_all_changed_extensions = mock.Mock() save_ext = d.extpage.save_all_changed_extensions = mock.Mock()
activate = d.activate_config_changes = mock.Mock() activate = d.activate_config_changes = mock.Mock()
d.buttons['Apply'].invoke() d.buttons['Apply'].invoke()
deactivate.assert_called_once() deactivate.assert_called_once()
save_ext.assert_called_once() save_ext.assert_called_once()
activate.assert_called_once() activate.assert_called_once()
del d.save_all_changed_extensions del d.extpage.save_all_changed_extensions
del d.activate_config_changes, d.deactivate_current_config del d.activate_config_changes, d.deactivate_current_config
def test_click_cancel(self): def test_click_cancel(self):
@ -260,27 +260,6 @@ class FontPageTest(unittest.TestCase):
d.set_samples = Func() # Re-mask for other tests. d.set_samples = Func() # Re-mask for other tests.
class IndentTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.page = dialog.fontpage
cls.page.update()
def test_load_tab_cfg(self):
d = self.page
d.space_num.set(16)
d.load_tab_cfg()
self.assertEqual(d.space_num.get(), 4)
def test_indent_scale(self):
d = self.page
changes.clear()
d.indent_scale.set(20)
self.assertEqual(d.space_num.get(), 16)
self.assertEqual(mainpage, {'Indent': {'num-spaces': '16'}})
class HighPageTest(unittest.TestCase): class HighPageTest(unittest.TestCase):
"""Test that highlight tab widgets enable users to make changes. """Test that highlight tab widgets enable users to make changes.
@ -1250,6 +1229,12 @@ class WinPageTest(unittest.TestCase):
d.win_width_int.insert(0, '11') d.win_width_int.insert(0, '11')
self.assertEqual(mainpage, {'EditorWindow': {'width': '11'}}) self.assertEqual(mainpage, {'EditorWindow': {'width': '11'}})
def test_indent_spaces(self):
d = self.page
d.indent_chooser.set(6)
self.assertEqual(d.indent_spaces.get(), '6')
self.assertEqual(mainpage, {'Indent': {'num-spaces': '6'}})
def test_cursor_blink(self): def test_cursor_blink(self):
self.page.cursor_blink_bool.invoke() self.page.cursor_blink_bool.invoke()
self.assertEqual(mainpage, {'EditorWindow': {'cursor-blink': 'False'}}) self.assertEqual(mainpage, {'EditorWindow': {'cursor-blink': 'False'}})
@ -1278,7 +1263,7 @@ class WinPageTest(unittest.TestCase):
self.assertEqual(extpage, {'FormatParagraph': {'max-width': '11'}}) self.assertEqual(extpage, {'FormatParagraph': {'max-width': '11'}})
class GenPageTest(unittest.TestCase): class ShedPageTest(unittest.TestCase):
"""Test that shed tab widgets enable users to make changes. """Test that shed tab widgets enable users to make changes.
Test that widget actions set vars, that var changes add Test that widget actions set vars, that var changes add

View file

@ -0,0 +1,2 @@
Move the indent space setting from the Font tab to the new Windows tab.
Patch by Mark Roseman and Terry Jan Reedy.