bpo-40468: Split IDLE settings General tab (GH-26621)


Replace it with Windows tab for Shell and Editor options
and Shell/Ed for options exclusive to one of them.

Create room for more options and make dialog shorter,
to better fit small windows.
(cherry picked from commit 275d5f7957dbb56a6d5e1248addff210ee2e7270)

Co-authored-by: Terry Jan Reedy <tjreedy@udel.edu>
diff --git a/Lib/idlelib/configdialog.py b/Lib/idlelib/configdialog.py
index 9c0153b..e3fa34f 100644
--- a/Lib/idlelib/configdialog.py
+++ b/Lib/idlelib/configdialog.py
@@ -116,11 +116,14 @@ def create_widgets(self):
         self.highpage = HighPage(note, self.extpage)
         self.fontpage = FontPage(note, self.highpage)
         self.keyspage = KeysPage(note, self.extpage)
-        self.genpage = GenPage(note)
+        self.winpage = WinPage(note)
+        self.shedpage = ShedPage(note)
+
         note.add(self.fontpage, text='Fonts/Tabs')
         note.add(self.highpage, text='Highlights')
         note.add(self.keyspage, text=' Keys ')
-        note.add(self.genpage, text=' General ')
+        note.add(self.winpage, text=' Windows ')
+        note.add(self.shedpage, text=' Shell/Ed ')
         note.add(self.extpage, text='Extensions')
         note.enable_traversal()
         note.pack(side=TOP, expand=TRUE, fill=BOTH)
@@ -1594,14 +1597,14 @@ def delete_custom_keys(self):
         self.set_keys_type()
 
 
-class GenPage(Frame):
+class WinPage(Frame):
 
     def __init__(self, master):
         super().__init__(master)
 
         self.init_validators()
-        self.create_page_general()
-        self.load_general_cfg()
+        self.create_page_windows()
+        self.load_windows_cfg()
 
     def init_validators(self):
         digits_or_empty_re = re.compile(r'[0-9]*')
@@ -1610,26 +1613,17 @@ def is_digits_or_empty(s):
             return digits_or_empty_re.fullmatch(s) is not None
         self.digits_only = (self.register(is_digits_or_empty), '%P',)
 
-    def create_page_general(self):
-        """Return frame of widgets for General tab.
+    def create_page_windows(self):
+        """Return frame of widgets for Windows tab.
 
-        Enable users to provisionally change general options. Function
-        load_general_cfg initializes tk variables and helplist using
-        idleConf.  Radiobuttons startup_shell_on and startup_editor_on
-        set var startup_edit. Radiobuttons save_ask_on and save_auto_on
-        set var autosave. Entry boxes win_width_int and win_height_int
-        set var win_width and win_height.  Setting var_name invokes the
-        default callback that adds option to changes.
+        Enable users to provisionally change general window options.
+        Function load_windows_cfg initializes tk variables idleConf.
+        Radiobuttons startup_shell_on and startup_editor_on 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
+        callback that adds option to changes.
 
-        Helplist: load_general_cfg loads list user_helplist with
-        name, position pairs and copies names to listbox helplist.
-        Clicking a name invokes help_source selected. Clicking
-        button_helplist_name invokes helplist_item_name, which also
-        changes user_helplist.  These functions all call
-        set_add_delete_state. All but load call update_help_changes to
-        rewrite changes['main']['HelpFiles'].
-
-        Widgets for GenPage(Frame):  (*) widgets bound to self
+        Widgets for WinPage(Frame):  (*) widgets bound to self
             frame_window: LabelFrame
                 frame_run: Frame
                     startup_title: Label
@@ -1654,24 +1648,9 @@ def create_page_general(self):
                     paren_time_title: Label
                     (*)paren_flash_time: Entry - flash_delay
                     (*)bell_on: Checkbutton - paren_bell
-            frame_editor: LabelFrame
-                frame_save: Frame
-                    run_save_title: Label
-                    (*)save_ask_on: Radiobutton - autosave
-                    (*)save_auto_on: Radiobutton - autosave
                 frame_format: Frame
                     format_width_title: Label
                     (*)format_width_int: Entry - format_width
-                frame_line_numbers_default: Frame
-                    line_numbers_default_title: Label
-                    (*)line_numbers_default_bool: Checkbutton - line_numbers_default
-                frame_context: Frame
-                    context_title: Label
-                    (*)context_int: Entry - context_lines
-            frame_shell: LabelFrame
-                frame_auto_squeeze_min_lines: Frame
-                    auto_squeeze_min_lines_title: Label
-                    (*)auto_squeeze_min_lines_int: Entry - auto_squeeze_min_lines
         """
         # Integer values need StringVar because int('') raises.
         self.startup_edit = tracers.add(
@@ -1690,29 +1669,13 @@ def create_page_general(self):
                 StringVar(self), ('extensions', 'ParenMatch', 'flash-delay'))
         self.paren_bell = tracers.add(
                 BooleanVar(self), ('extensions', 'ParenMatch', 'bell'))
-
-        self.auto_squeeze_min_lines = tracers.add(
-                StringVar(self), ('main', 'PyShell', 'auto-squeeze-min-lines'))
-
-        self.autosave = tracers.add(
-                IntVar(self), ('main', 'General', 'autosave'))
         self.format_width = tracers.add(
                 StringVar(self), ('extensions', 'FormatParagraph', 'max-width'))
-        self.line_numbers_default = tracers.add(
-                BooleanVar(self),
-                ('main', 'EditorWindow', 'line-numbers-default'))
-        self.context_lines = tracers.add(
-                StringVar(self), ('extensions', 'CodeContext', 'maxlines'))
 
         # Create widgets:
-        # Section frames.
         frame_window = LabelFrame(self, borderwidth=2, relief=GROOVE,
                                   text=' Window Preferences')
-        frame_editor = LabelFrame(self, borderwidth=2, relief=GROOVE,
-                                  text=' Editor Preferences')
-        frame_shell = LabelFrame(self, borderwidth=2, relief=GROOVE,
-                                 text=' Shell Preferences')
-        # Frame_window.
+
         frame_run = Frame(frame_window, borderwidth=0)
         startup_title = Label(frame_run, text='At Startup')
         self.startup_editor_on = Radiobutton(
@@ -1747,8 +1710,7 @@ def create_page_general(self):
         self.auto_wait_int = Entry(frame_autocomplete, width=6,
                                    textvariable=self.autocomplete_wait,
                                    validatecommand=self.digits_only,
-                                   validate='key',
-                                   )
+                                   validate='key')
 
         frame_paren1 = Frame(frame_window, borderwidth=0)
         paren_style_title = Label(frame_paren1, text='Paren Match Style')
@@ -1763,55 +1725,16 @@ def create_page_general(self):
                 frame_paren2, textvariable=self.flash_delay, width=6)
         self.bell_on = Checkbutton(
                 frame_paren2, text="Bell on Mismatch", variable=self.paren_bell)
-
-        # Frame_editor.
-        frame_save = Frame(frame_editor, borderwidth=0)
-        run_save_title = Label(frame_save, text='At Start of Run (F5)  ')
-        self.save_ask_on = Radiobutton(
-                frame_save, variable=self.autosave, value=0,
-                text="Prompt to Save")
-        self.save_auto_on = Radiobutton(
-                frame_save, variable=self.autosave, value=1,
-                text='No Prompt')
-
-        frame_format = Frame(frame_editor, borderwidth=0)
+        frame_format = Frame(frame_window, borderwidth=0)
         format_width_title = Label(frame_format,
                                    text='Format Paragraph Max Width')
         self.format_width_int = Entry(
                 frame_format, textvariable=self.format_width, width=4,
                 validatecommand=self.digits_only, validate='key',
-        )
-
-        frame_line_numbers_default = Frame(frame_editor, borderwidth=0)
-        line_numbers_default_title = Label(
-            frame_line_numbers_default, text='Show line numbers in new windows')
-        self.line_numbers_default_bool = Checkbutton(
-                frame_line_numbers_default,
-                variable=self.line_numbers_default,
-                width=1)
-
-        frame_context = Frame(frame_editor, borderwidth=0)
-        context_title = Label(frame_context, text='Max Context Lines :')
-        self.context_int = Entry(
-                frame_context, textvariable=self.context_lines, width=3,
-                validatecommand=self.digits_only, validate='key',
-        )
-
-        # Frame_shell.
-        frame_auto_squeeze_min_lines = Frame(frame_shell, borderwidth=0)
-        auto_squeeze_min_lines_title = Label(frame_auto_squeeze_min_lines,
-                                             text='Auto-Squeeze Min. Lines:')
-        self.auto_squeeze_min_lines_int = Entry(
-                frame_auto_squeeze_min_lines, width=4,
-                textvariable=self.auto_squeeze_min_lines,
-                validatecommand=self.digits_only, validate='key',
-        )
+                )
 
         # Pack widgets:
-        # Body.
         frame_window.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
-        frame_editor.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
-        frame_shell.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
         # frame_run.
         frame_run.pack(side=TOP, padx=5, pady=0, fill=X)
         startup_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
@@ -1840,34 +1763,10 @@ def create_page_general(self):
         paren_time_title.pack(side=LEFT, anchor=W, padx=5)
         self.bell_on.pack(side=RIGHT, anchor=E, padx=15, pady=5)
         self.paren_flash_time.pack(side=TOP, anchor=W, padx=15, pady=5)
-
-        # frame_save.
-        frame_save.pack(side=TOP, padx=5, pady=0, fill=X)
-        run_save_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
-        self.save_auto_on.pack(side=RIGHT, anchor=W, padx=5, pady=5)
-        self.save_ask_on.pack(side=RIGHT, anchor=W, padx=5, pady=5)
         # frame_format.
         frame_format.pack(side=TOP, padx=5, pady=0, fill=X)
         format_width_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
         self.format_width_int.pack(side=TOP, padx=10, pady=5)
-        # frame_line_numbers_default.
-        frame_line_numbers_default.pack(side=TOP, padx=5, pady=0, fill=X)
-        line_numbers_default_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
-        self.line_numbers_default_bool.pack(side=LEFT, padx=5, pady=5)
-        # frame_context.
-        frame_context.pack(side=TOP, padx=5, pady=0, fill=X)
-        context_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
-        self.context_int.pack(side=TOP, padx=5, pady=5)
-
-        # frame_auto_squeeze_min_lines
-        frame_auto_squeeze_min_lines.pack(side=TOP, padx=5, pady=0, fill=X)
-        auto_squeeze_min_lines_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
-        self.auto_squeeze_min_lines_int.pack(side=TOP, padx=5, pady=5)
-
-    def load_general_cfg(self):
-        "Load current configuration settings for the general options."
-        self.load_windows_cfg()
-        self.load_shelled_cfg()
 
     def load_windows_cfg(self):
         # Set variables for all windows.
@@ -1887,22 +1786,142 @@ def load_windows_cfg(self):
                 'extensions', 'ParenMatch', 'flash-delay', type='int'))
         self.paren_bell.set(idleConf.GetOption(
                 'extensions', 'ParenMatch', 'bell'))
+        self.format_width.set(idleConf.GetOption(
+                'extensions', 'FormatParagraph', 'max-width', type='int'))
+
+
+class ShedPage(Frame):
+
+    def __init__(self, master):
+        super().__init__(master)
+
+        self.init_validators()
+        self.create_page_shed()
+        self.load_shelled_cfg()
+
+    def init_validators(self):
+        digits_or_empty_re = re.compile(r'[0-9]*')
+        def is_digits_or_empty(s):
+            "Return 's is blank or contains only digits'"
+            return digits_or_empty_re.fullmatch(s) is not None
+        self.digits_only = (self.register(is_digits_or_empty), '%P',)
+
+    def create_page_shed(self):
+        """Return frame of widgets for Shell/Ed tab.
+
+        Enable users to provisionally change shell and editor options.
+        Function load_shed_cfg initializes tk variables using idleConf.
+        Entry box auto_squeeze_min_lines_int sets
+        auto_squeeze_min_lines_int.  Setting var_name invokes the
+        default callback that adds option to changes.
+
+        Widgets for ShedPage(Frame):  (*) widgets bound to self
+            frame_shell: LabelFrame
+                frame_auto_squeeze_min_lines: Frame
+                    auto_squeeze_min_lines_title: Label
+                    (*)auto_squeeze_min_lines_int: Entry -
+                       auto_squeeze_min_lines
+            frame_editor: LabelFrame
+                frame_save: Frame
+                    run_save_title: Label
+                    (*)save_ask_on: Radiobutton - autosave
+                    (*)save_auto_on: Radiobutton - autosave
+                frame_format: Frame
+                    format_width_title: Label
+                    (*)format_width_int: Entry - format_width
+                frame_line_numbers_default: Frame
+                    line_numbers_default_title: Label
+                    (*)line_numbers_default_bool: Checkbutton - line_numbers_default
+                frame_context: Frame
+                    context_title: Label
+                    (*)context_int: Entry - context_lines
+        """
+        # Integer values need StringVar because int('') raises.
+        self.auto_squeeze_min_lines = tracers.add(
+                StringVar(self), ('main', 'PyShell', 'auto-squeeze-min-lines'))
+
+        self.autosave = tracers.add(
+                IntVar(self), ('main', 'General', 'autosave'))
+        self.line_numbers_default = tracers.add(
+                BooleanVar(self),
+                ('main', 'EditorWindow', 'line-numbers-default'))
+        self.context_lines = tracers.add(
+                StringVar(self), ('extensions', 'CodeContext', 'maxlines'))
+
+        # Create widgets:
+        frame_shell = LabelFrame(self, borderwidth=2, relief=GROOVE,
+                                 text=' Shell Preferences')
+        frame_editor = LabelFrame(self, borderwidth=2, relief=GROOVE,
+                                  text=' Editor Preferences')
+        # Frame_shell.
+        frame_auto_squeeze_min_lines = Frame(frame_shell, borderwidth=0)
+        auto_squeeze_min_lines_title = Label(frame_auto_squeeze_min_lines,
+                                             text='Auto-Squeeze Min. Lines:')
+        self.auto_squeeze_min_lines_int = Entry(
+                frame_auto_squeeze_min_lines, width=4,
+                textvariable=self.auto_squeeze_min_lines,
+                validatecommand=self.digits_only, validate='key',
+        )
+        # Frame_editor.
+        frame_save = Frame(frame_editor, borderwidth=0)
+        run_save_title = Label(frame_save, text='At Start of Run (F5)  ')
+
+        self.save_ask_on = Radiobutton(
+                frame_save, variable=self.autosave, value=0,
+                text="Prompt to Save")
+        self.save_auto_on = Radiobutton(
+                frame_save, variable=self.autosave, value=1,
+                text='No Prompt')
+
+        frame_line_numbers_default = Frame(frame_editor, borderwidth=0)
+        line_numbers_default_title = Label(
+            frame_line_numbers_default, text='Show line numbers in new windows')
+        self.line_numbers_default_bool = Checkbutton(
+                frame_line_numbers_default,
+                variable=self.line_numbers_default,
+                width=1)
+
+        frame_context = Frame(frame_editor, borderwidth=0)
+        context_title = Label(frame_context, text='Max Context Lines :')
+        self.context_int = Entry(
+                frame_context, textvariable=self.context_lines, width=3,
+                validatecommand=self.digits_only, validate='key',
+        )
+
+        # Pack widgets:
+        frame_shell.pack(side=TOP, padx=5, pady=5, fill=BOTH)
+        Label(self).pack()  # Spacer -- better solution?
+        frame_editor.pack(side=TOP, padx=5, pady=5, fill=BOTH)
+        # frame_auto_squeeze_min_lines
+        frame_auto_squeeze_min_lines.pack(side=TOP, padx=5, pady=0, fill=X)
+        auto_squeeze_min_lines_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
+        self.auto_squeeze_min_lines_int.pack(side=TOP, padx=5, pady=5)
+        # frame_save.
+        frame_save.pack(side=TOP, padx=5, pady=0, fill=X)
+        run_save_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
+        self.save_auto_on.pack(side=RIGHT, anchor=W, padx=5, pady=5)
+        self.save_ask_on.pack(side=RIGHT, anchor=W, padx=5, pady=5)
+        # frame_line_numbers_default.
+        frame_line_numbers_default.pack(side=TOP, padx=5, pady=0, fill=X)
+        line_numbers_default_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
+        self.line_numbers_default_bool.pack(side=LEFT, padx=5, pady=5)
+        # frame_context.
+        frame_context.pack(side=TOP, padx=5, pady=0, fill=X)
+        context_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
+        self.context_int.pack(side=TOP, padx=5, pady=5)
 
     def load_shelled_cfg(self):
+        # Set variables for shell windows.
+        self.auto_squeeze_min_lines.set(idleConf.GetOption(
+                'main', 'PyShell', 'auto-squeeze-min-lines', type='int'))
         # Set variables for editor windows.
         self.autosave.set(idleConf.GetOption(
                 'main', 'General', 'autosave', default=0, type='bool'))
-        self.format_width.set(idleConf.GetOption(
-                'extensions', 'FormatParagraph', 'max-width', type='int'))
         self.line_numbers_default.set(idleConf.GetOption(
                 'main', 'EditorWindow', 'line-numbers-default', type='bool'))
         self.context_lines.set(idleConf.GetOption(
                 'extensions', 'CodeContext', 'maxlines', type='int'))
 
-        # Set variables for shell windows.
-        self.auto_squeeze_min_lines.set(idleConf.GetOption(
-                'main', 'PyShell', 'auto-squeeze-min-lines', type='int'))
-
 
 class ExtPage(Frame):
     def __init__(self, master):