[Python-checkins] bpo-37628: Fix IDLE config sample sizes (#14958)

Terry Jan Reedy webhook-mailer at python.org
Sat Jul 27 12:58:13 EDT 2019


https://github.com/python/cpython/commit/3221a63c69268a9362802371a616f49d522a5c4f
commit: 3221a63c69268a9362802371a616f49d522a5c4f
branch: master
author: Tal Einat <taleinat+github at gmail.com>
committer: Terry Jan Reedy <tjreedy at udel.edu>
date: 2019-07-27T12:57:48-04:00
summary:

bpo-37628: Fix IDLE config sample sizes (#14958)

The boxes for the font and highlight samples are now constrained by the overall config dialog size.  They gain scrollbars when the when a large font size makes the samples too large for the box.

files:
A Misc/NEWS.d/next/IDLE/2019-07-26-17-51-13.bpo-37628.kX4AUF.rst
M Lib/idlelib/configdialog.py
M Lib/idlelib/idle_test/htest.py
M Lib/idlelib/idle_test/test_textview.py
M Lib/idlelib/textview.py

diff --git a/Lib/idlelib/configdialog.py b/Lib/idlelib/configdialog.py
index 217f8fd0a5fb..4df6ecee69f1 100644
--- a/Lib/idlelib/configdialog.py
+++ b/Lib/idlelib/configdialog.py
@@ -33,6 +33,7 @@
 from idlelib.parenmatch import ParenMatch
 from idlelib.format import FormatParagraph
 from idlelib.squeezer import Squeezer
+from idlelib.textview import ScrollableTextFrame
 
 changes = ConfigChanges()
 # Reload changed options in the following classes.
@@ -556,7 +557,9 @@ def create_page_font_tab(self):
                 frame_font_param, variable=self.font_bold,
                 onvalue=1, offvalue=0, text='Bold')
         # frame_sample.
-        self.font_sample = Text(frame_sample, width=20, height=20)
+        font_sample_frame = ScrollableTextFrame(frame_sample)
+        self.font_sample = font_sample_frame.text
+        self.font_sample.config(wrap=NONE, width=1, height=1)
         self.font_sample.insert(END, font_sample_text)
         # frame_indent.
         indent_title = Label(
@@ -568,8 +571,9 @@ def create_page_font_tab(self):
 
         # Grid and pack widgets:
         self.columnconfigure(1, weight=1)
+        self.rowconfigure(2, weight=1)
         frame_font.grid(row=0, column=0, padx=5, pady=5)
-        frame_sample.grid(row=0, column=1, rowspan=2, padx=5, pady=5,
+        frame_sample.grid(row=0, column=1, rowspan=3, padx=5, pady=5,
                           sticky='nsew')
         frame_indent.grid(row=1, column=0, padx=5, pady=5, sticky='ew')
         # frame_font.
@@ -582,7 +586,7 @@ def create_page_font_tab(self):
         self.sizelist.pack(side=LEFT, anchor=W)
         self.bold_toggle.pack(side=LEFT, anchor=W, padx=20)
         # frame_sample.
-        self.font_sample.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)
@@ -840,9 +844,11 @@ def create_page_highlight(self):
         frame_theme = LabelFrame(self, borderwidth=2, relief=GROOVE,
                                  text=' Highlighting Theme ')
         # frame_custom.
-        text = self.highlight_sample = Text(
-                frame_custom, relief=SOLID, borderwidth=1,
-                font=('courier', 12, ''), cursor='hand2', width=21, height=13,
+        sample_frame = ScrollableTextFrame(
+                frame_custom, relief=SOLID, borderwidth=1)
+        text = self.highlight_sample = sample_frame.text
+        text.configure(
+                font=('courier', 12, ''), cursor='hand2', width=1, height=1,
                 takefocus=FALSE, highlightthickness=0, wrap=NONE)
         text.bind('<Double-Button-1>', lambda e: 'break')
         text.bind('<B1-Motion>', lambda e: 'break')
@@ -868,7 +874,7 @@ def create_page_highlight(self):
         for texttag in text_and_tags:
             text.insert(END, texttag[0], texttag[1])
         n_lines = len(text.get('1.0', END).splitlines())
-        for lineno in range(1, n_lines + 1):
+        for lineno in range(1, n_lines):
             text.insert(f'{lineno}.0',
                         f'{lineno:{len(str(n_lines))}d} ',
                         'linenumber')
@@ -920,9 +926,9 @@ def tem(event, elem=element):
         frame_custom.pack(side=LEFT, padx=5, pady=5, expand=TRUE, fill=BOTH)
         frame_theme.pack(side=TOP, padx=5, pady=5, fill=X)
         # frame_custom.
-        self.frame_color_set.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=X)
+        self.frame_color_set.pack(side=TOP, padx=5, pady=5, fill=X)
         frame_fg_bg_toggle.pack(side=TOP, padx=5, pady=0)
-        self.highlight_sample.pack(
+        sample_frame.pack(
                 side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
         self.button_set_color.pack(side=TOP, expand=TRUE, fill=X, padx=8, pady=4)
         self.targetlist.pack(side=TOP, expand=TRUE, fill=X, padx=8, pady=3)
diff --git a/Lib/idlelib/idle_test/htest.py b/Lib/idlelib/idle_test/htest.py
index f2f37e161632..6990af519b1f 100644
--- a/Lib/idlelib/idle_test/htest.py
+++ b/Lib/idlelib/idle_test/htest.py
@@ -349,7 +349,7 @@ def _wrapper(parent):  # htest #
 ViewWindow_spec = {
     'file': 'textview',
     'kwds': {'title': 'Test textview',
-             'text': 'The quick brown fox jumps over the lazy dog.\n'*35,
+             'contents': 'The quick brown fox jumps over the lazy dog.\n'*35,
              '_htest': True},
     'msg': "Test for read-only property of text.\n"
            "Select text, scroll window, close"
diff --git a/Lib/idlelib/idle_test/test_textview.py b/Lib/idlelib/idle_test/test_textview.py
index 6f0c1930518a..7189378ab3dd 100644
--- a/Lib/idlelib/idle_test/test_textview.py
+++ b/Lib/idlelib/idle_test/test_textview.py
@@ -6,12 +6,12 @@
 information about calls.
 """
 from idlelib import textview as tv
-import unittest
 from test.support import requires
 requires('gui')
 
 import os
-from tkinter import Tk
+import unittest
+from tkinter import Tk, TclError, CHAR, NONE, WORD
 from tkinter.ttk import Button
 from idlelib.idle_test.mock_idle import Func
 from idlelib.idle_test.mock_tk import Mbox_func
@@ -69,13 +69,65 @@ def test_ok(self):
         view.destroy()
 
 
-class TextFrameTest(unittest.TestCase):
+class AutoHideScrollbarTest(unittest.TestCase):
+    # Method set is tested in ScrollableTextFrameTest
+    def test_forbidden_geometry(self):
+        scroll = tv.AutoHideScrollbar(root)
+        self.assertRaises(TclError, scroll.pack)
+        self.assertRaises(TclError, scroll.place)
+
+
+class ScrollableTextFrameTest(unittest.TestCase):
+
+    @classmethod
+    def setUpClass(cls):
+        cls.root = root = Tk()
+        root.withdraw()
+
+    @classmethod
+    def tearDownClass(cls):
+        cls.root.update_idletasks()
+        cls.root.destroy()
+        del cls.root
+
+    def make_frame(self, wrap=NONE, **kwargs):
+        frame = tv.ScrollableTextFrame(self.root, wrap=wrap, **kwargs)
+        def cleanup_frame():
+            frame.update_idletasks()
+            frame.destroy()
+        self.addCleanup(cleanup_frame)
+        return frame
+
+    def test_line1(self):
+        frame = self.make_frame()
+        frame.text.insert('1.0', 'test text')
+        self.assertEqual(frame.text.get('1.0', '1.end'), 'test text')
+
+    def test_horiz_scrollbar(self):
+        # The horizontal scrollbar should be shown/hidden according to
+        # the 'wrap' setting: It should only be shown when 'wrap' is
+        # set to NONE.
+
+        # wrap = NONE -> with horizontal scrolling
+        frame = self.make_frame(wrap=NONE)
+        self.assertEqual(frame.text.cget('wrap'), NONE)
+        self.assertIsNotNone(frame.xscroll)
+
+        # wrap != NONE -> no horizontal scrolling
+        for wrap in [CHAR, WORD]:
+            with self.subTest(wrap=wrap):
+                frame = self.make_frame(wrap=wrap)
+                self.assertEqual(frame.text.cget('wrap'), wrap)
+                self.assertIsNone(frame.xscroll)
+
+
+class ViewFrameTest(unittest.TestCase):
 
     @classmethod
     def setUpClass(cls):
         cls.root = root = Tk()
         root.withdraw()
-        cls.frame = tv.TextFrame(root, 'test text')
+        cls.frame = tv.ViewFrame(root, 'test text')
 
     @classmethod
     def tearDownClass(cls):
diff --git a/Lib/idlelib/textview.py b/Lib/idlelib/textview.py
index 4867a80db1ab..808a2aefab4f 100644
--- a/Lib/idlelib/textview.py
+++ b/Lib/idlelib/textview.py
@@ -2,14 +2,15 @@
 
 """
 from tkinter import Toplevel, Text, TclError,\
-    HORIZONTAL, VERTICAL, N, S, E, W
+    HORIZONTAL, VERTICAL, NS, EW, NSEW, NONE, WORD, SUNKEN
 from tkinter.ttk import Frame, Scrollbar, Button
 from tkinter.messagebox import showerror
 
+from functools import update_wrapper
 from idlelib.colorizer import color_config
 
 
-class AutoHiddenScrollbar(Scrollbar):
+class AutoHideScrollbar(Scrollbar):
     """A scrollbar that is automatically hidden when not needed.
 
     Only the grid geometry manager is supported.
@@ -28,52 +29,70 @@ def place(self, **kwargs):
         raise TclError(f'{self.__class__.__name__} does not support "place"')
 
 
-class TextFrame(Frame):
-    "Display text with scrollbar."
+class ScrollableTextFrame(Frame):
+    """Display text with scrollbar(s)."""
 
-    def __init__(self, parent, rawtext, wrap='word'):
+    def __init__(self, master, wrap=NONE, **kwargs):
         """Create a frame for Textview.
 
-        parent - parent widget for this frame
-        rawtext - text to display
+        master - master widget for this frame
+        wrap - type of text wrapping to use ('word', 'char' or 'none')
+
+        All parameters except for 'wrap' are passed to Frame.__init__().
+
+        The Text widget is accessible via the 'text' attribute.
+
+        Note: Changing the wrapping mode of the text widget after
+        instantiation is not supported.
         """
-        super().__init__(parent)
-        self['relief'] = 'sunken'
-        self['height'] = 700
+        super().__init__(master, **kwargs)
 
-        self.text = text = Text(self, wrap=wrap, highlightthickness=0)
-        color_config(text)
-        text.grid(row=0, column=0, sticky=N+S+E+W)
+        text = self.text = Text(self, wrap=wrap)
+        text.grid(row=0, column=0, sticky=NSEW)
         self.grid_rowconfigure(0, weight=1)
         self.grid_columnconfigure(0, weight=1)
-        text.insert(0.0, rawtext)
-        text['state'] = 'disabled'
-        text.focus_set()
 
         # vertical scrollbar
-        self.yscroll = yscroll = AutoHiddenScrollbar(self, orient=VERTICAL,
-                                                     takefocus=False,
-                                                     command=text.yview)
-        text['yscrollcommand'] = yscroll.set
-        yscroll.grid(row=0, column=1, sticky=N+S)
-
-        if wrap == 'none':
-            # horizontal scrollbar
-            self.xscroll = xscroll = AutoHiddenScrollbar(self, orient=HORIZONTAL,
-                                                         takefocus=False,
-                                                         command=text.xview)
-            text['xscrollcommand'] = xscroll.set
-            xscroll.grid(row=1, column=0, sticky=E+W)
+        self.yscroll = AutoHideScrollbar(self, orient=VERTICAL,
+                                         takefocus=False,
+                                         command=text.yview)
+        self.yscroll.grid(row=0, column=1, sticky=NS)
+        text['yscrollcommand'] = self.yscroll.set
+
+        # horizontal scrollbar - only when wrap is set to NONE
+        if wrap == NONE:
+            self.xscroll = AutoHideScrollbar(self, orient=HORIZONTAL,
+                                             takefocus=False,
+                                             command=text.xview)
+            self.xscroll.grid(row=1, column=0, sticky=EW)
+            text['xscrollcommand'] = self.xscroll.set
+        else:
+            self.xscroll = None
 
 
 class ViewFrame(Frame):
     "Display TextFrame and Close button."
-    def __init__(self, parent, text, wrap='word'):
+    def __init__(self, parent, contents, wrap='word'):
+        """Create a frame for viewing text with a "Close" button.
+
+        parent - parent widget for this frame
+        contents - text to display
+        wrap - type of text wrapping to use ('word', 'char' or 'none')
+
+        The Text widget is accessible via the 'text' attribute.
+        """
         super().__init__(parent)
         self.parent = parent
         self.bind('<Return>', self.ok)
         self.bind('<Escape>', self.ok)
-        self.textframe = TextFrame(self, text, wrap=wrap)
+        self.textframe = ScrollableTextFrame(self, relief=SUNKEN, height=700)
+
+        text = self.text = self.textframe.text
+        text.insert('1.0', contents)
+        text.configure(wrap=wrap, highlightthickness=0, state='disabled')
+        color_config(text)
+        text.focus_set()
+
         self.button_ok = button_ok = Button(
                 self, text='Close', command=self.ok, takefocus=False)
         self.textframe.pack(side='top', expand=True, fill='both')
@@ -87,7 +106,7 @@ def ok(self, event=None):
 class ViewWindow(Toplevel):
     "A simple text viewer dialog for IDLE."
 
-    def __init__(self, parent, title, text, modal=True, wrap='word',
+    def __init__(self, parent, title, contents, modal=True, wrap=WORD,
                  *, _htest=False, _utest=False):
         """Show the given text in a scrollable window with a 'close' button.
 
@@ -96,7 +115,7 @@ def __init__(self, parent, title, text, modal=True, wrap='word',
 
         parent - parent of this dialog
         title - string which is title of popup dialog
-        text - text to display in dialog
+        contents - text to display in dialog
         wrap - type of text wrapping to use ('word', 'char' or 'none')
         _htest - bool; change box location when running htest.
         _utest - bool; don't wait_window when running unittest.
@@ -109,7 +128,7 @@ def __init__(self, parent, title, text, modal=True, wrap='word',
         self.geometry(f'=750x500+{x}+{y}')
 
         self.title(title)
-        self.viewframe = ViewFrame(self, text, wrap=wrap)
+        self.viewframe = ViewFrame(self, contents, wrap=wrap)
         self.protocol("WM_DELETE_WINDOW", self.ok)
         self.button_ok = button_ok = Button(self, text='Close',
                                             command=self.ok, takefocus=False)
@@ -129,18 +148,18 @@ def ok(self, event=None):
         self.destroy()
 
 
-def view_text(parent, title, text, modal=True, wrap='word', _utest=False):
+def view_text(parent, title, contents, modal=True, wrap='word', _utest=False):
     """Create text viewer for given text.
 
     parent - parent of this dialog
     title - string which is the title of popup dialog
-    text - text to display in this dialog
+    contents - text to display in this dialog
     wrap - type of text wrapping to use ('word', 'char' or 'none')
     modal - controls if users can interact with other windows while this
             dialog is displayed
     _utest - bool; controls wait_window on unittest
     """
-    return ViewWindow(parent, title, text, modal, wrap=wrap, _utest=_utest)
+    return ViewWindow(parent, title, contents, modal, wrap=wrap, _utest=_utest)
 
 
 def view_file(parent, title, filename, encoding, modal=True, wrap='word',
diff --git a/Misc/NEWS.d/next/IDLE/2019-07-26-17-51-13.bpo-37628.kX4AUF.rst b/Misc/NEWS.d/next/IDLE/2019-07-26-17-51-13.bpo-37628.kX4AUF.rst
new file mode 100644
index 000000000000..60910c47e65b
--- /dev/null
+++ b/Misc/NEWS.d/next/IDLE/2019-07-26-17-51-13.bpo-37628.kX4AUF.rst
@@ -0,0 +1 @@
+Settings dialog no longer expands with font size.



More information about the Python-checkins mailing list