Skip to content

Commit 1710193

Browse files
bpo-37628: Fix IDLE config sample sizes (GH-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. (cherry picked from commit 3221a63) Co-authored-by: Tal Einat <taleinat+github@gmail.com>
1 parent d38fa58 commit 1710193

File tree

5 files changed

+129
-51
lines changed

5 files changed

+129
-51
lines changed

Lib/idlelib/configdialog.py

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
from idlelib.parenmatch import ParenMatch
3434
from idlelib.format import FormatParagraph
3535
from idlelib.squeezer import Squeezer
36+
from idlelib.textview import ScrollableTextFrame
3637

3738
changes = ConfigChanges()
3839
# Reload changed options in the following classes.
@@ -556,7 +557,9 @@ def create_page_font_tab(self):
556557
frame_font_param, variable=self.font_bold,
557558
onvalue=1, offvalue=0, text='Bold')
558559
# frame_sample.
559-
self.font_sample = Text(frame_sample, width=20, height=20)
560+
font_sample_frame = ScrollableTextFrame(frame_sample)
561+
self.font_sample = font_sample_frame.text
562+
self.font_sample.config(wrap=NONE, width=1, height=1)
560563
self.font_sample.insert(END, font_sample_text)
561564
# frame_indent.
562565
indent_title = Label(
@@ -568,8 +571,9 @@ def create_page_font_tab(self):
568571

569572
# Grid and pack widgets:
570573
self.columnconfigure(1, weight=1)
574+
self.rowconfigure(2, weight=1)
571575
frame_font.grid(row=0, column=0, padx=5, pady=5)
572-
frame_sample.grid(row=0, column=1, rowspan=2, padx=5, pady=5,
576+
frame_sample.grid(row=0, column=1, rowspan=3, padx=5, pady=5,
573577
sticky='nsew')
574578
frame_indent.grid(row=1, column=0, padx=5, pady=5, sticky='ew')
575579
# frame_font.
@@ -582,7 +586,7 @@ def create_page_font_tab(self):
582586
self.sizelist.pack(side=LEFT, anchor=W)
583587
self.bold_toggle.pack(side=LEFT, anchor=W, padx=20)
584588
# frame_sample.
585-
self.font_sample.pack(expand=TRUE, fill=BOTH)
589+
font_sample_frame.pack(expand=TRUE, fill=BOTH)
586590
# frame_indent.
587591
indent_title.pack(side=TOP, anchor=W, padx=5)
588592
self.indent_scale.pack(side=TOP, padx=5, fill=X)
@@ -840,9 +844,11 @@ def create_page_highlight(self):
840844
frame_theme = LabelFrame(self, borderwidth=2, relief=GROOVE,
841845
text=' Highlighting Theme ')
842846
# frame_custom.
843-
text = self.highlight_sample = Text(
844-
frame_custom, relief=SOLID, borderwidth=1,
845-
font=('courier', 12, ''), cursor='hand2', width=21, height=13,
847+
sample_frame = ScrollableTextFrame(
848+
frame_custom, relief=SOLID, borderwidth=1)
849+
text = self.highlight_sample = sample_frame.text
850+
text.configure(
851+
font=('courier', 12, ''), cursor='hand2', width=1, height=1,
846852
takefocus=FALSE, highlightthickness=0, wrap=NONE)
847853
text.bind('<Double-Button-1>', lambda e: 'break')
848854
text.bind('<B1-Motion>', lambda e: 'break')
@@ -868,7 +874,7 @@ def create_page_highlight(self):
868874
for texttag in text_and_tags:
869875
text.insert(END, texttag[0], texttag[1])
870876
n_lines = len(text.get('1.0', END).splitlines())
871-
for lineno in range(1, n_lines + 1):
877+
for lineno in range(1, n_lines):
872878
text.insert(f'{lineno}.0',
873879
f'{lineno:{len(str(n_lines))}d} ',
874880
'linenumber')
@@ -920,9 +926,9 @@ def tem(event, elem=element):
920926
frame_custom.pack(side=LEFT, padx=5, pady=5, expand=TRUE, fill=BOTH)
921927
frame_theme.pack(side=TOP, padx=5, pady=5, fill=X)
922928
# frame_custom.
923-
self.frame_color_set.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=X)
929+
self.frame_color_set.pack(side=TOP, padx=5, pady=5, fill=X)
924930
frame_fg_bg_toggle.pack(side=TOP, padx=5, pady=0)
925-
self.highlight_sample.pack(
931+
sample_frame.pack(
926932
side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
927933
self.button_set_color.pack(side=TOP, expand=TRUE, fill=X, padx=8, pady=4)
928934
self.targetlist.pack(side=TOP, expand=TRUE, fill=X, padx=8, pady=3)

Lib/idlelib/idle_test/htest.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -349,7 +349,7 @@ def _wrapper(parent): # htest #
349349
ViewWindow_spec = {
350350
'file': 'textview',
351351
'kwds': {'title': 'Test textview',
352-
'text': 'The quick brown fox jumps over the lazy dog.\n'*35,
352+
'contents': 'The quick brown fox jumps over the lazy dog.\n'*35,
353353
'_htest': True},
354354
'msg': "Test for read-only property of text.\n"
355355
"Select text, scroll window, close"

Lib/idlelib/idle_test/test_textview.py

Lines changed: 56 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@
66
information about calls.
77
"""
88
from idlelib import textview as tv
9-
import unittest
109
from test.support import requires
1110
requires('gui')
1211

1312
import os
14-
from tkinter import Tk
13+
import unittest
14+
from tkinter import Tk, TclError, CHAR, NONE, WORD
1515
from tkinter.ttk import Button
1616
from idlelib.idle_test.mock_idle import Func
1717
from idlelib.idle_test.mock_tk import Mbox_func
@@ -69,13 +69,65 @@ def test_ok(self):
6969
view.destroy()
7070

7171

72-
class TextFrameTest(unittest.TestCase):
72+
class AutoHideScrollbarTest(unittest.TestCase):
73+
# Method set is tested in ScrollableTextFrameTest
74+
def test_forbidden_geometry(self):
75+
scroll = tv.AutoHideScrollbar(root)
76+
self.assertRaises(TclError, scroll.pack)
77+
self.assertRaises(TclError, scroll.place)
78+
79+
80+
class ScrollableTextFrameTest(unittest.TestCase):
81+
82+
@classmethod
83+
def setUpClass(cls):
84+
cls.root = root = Tk()
85+
root.withdraw()
86+
87+
@classmethod
88+
def tearDownClass(cls):
89+
cls.root.update_idletasks()
90+
cls.root.destroy()
91+
del cls.root
92+
93+
def make_frame(self, wrap=NONE, **kwargs):
94+
frame = tv.ScrollableTextFrame(self.root, wrap=wrap, **kwargs)
95+
def cleanup_frame():
96+
frame.update_idletasks()
97+
frame.destroy()
98+
self.addCleanup(cleanup_frame)
99+
return frame
100+
101+
def test_line1(self):
102+
frame = self.make_frame()
103+
frame.text.insert('1.0', 'test text')
104+
self.assertEqual(frame.text.get('1.0', '1.end'), 'test text')
105+
106+
def test_horiz_scrollbar(self):
107+
# The horizontal scrollbar should be shown/hidden according to
108+
# the 'wrap' setting: It should only be shown when 'wrap' is
109+
# set to NONE.
110+
111+
# wrap = NONE -> with horizontal scrolling
112+
frame = self.make_frame(wrap=NONE)
113+
self.assertEqual(frame.text.cget('wrap'), NONE)
114+
self.assertIsNotNone(frame.xscroll)
115+
116+
# wrap != NONE -> no horizontal scrolling
117+
for wrap in [CHAR, WORD]:
118+
with self.subTest(wrap=wrap):
119+
frame = self.make_frame(wrap=wrap)
120+
self.assertEqual(frame.text.cget('wrap'), wrap)
121+
self.assertIsNone(frame.xscroll)
122+
123+
124+
class ViewFrameTest(unittest.TestCase):
73125

74126
@classmethod
75127
def setUpClass(cls):
76128
cls.root = root = Tk()
77129
root.withdraw()
78-
cls.frame = tv.TextFrame(root, 'test text')
130+
cls.frame = tv.ViewFrame(root, 'test text')
79131

80132
@classmethod
81133
def tearDownClass(cls):

Lib/idlelib/textview.py

Lines changed: 56 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@
22
33
"""
44
from tkinter import Toplevel, Text, TclError,\
5-
HORIZONTAL, VERTICAL, N, S, E, W
5+
HORIZONTAL, VERTICAL, NS, EW, NSEW, NONE, WORD, SUNKEN
66
from tkinter.ttk import Frame, Scrollbar, Button
77
from tkinter.messagebox import showerror
88

9+
from functools import update_wrapper
910
from idlelib.colorizer import color_config
1011

1112

12-
class AutoHiddenScrollbar(Scrollbar):
13+
class AutoHideScrollbar(Scrollbar):
1314
"""A scrollbar that is automatically hidden when not needed.
1415
1516
Only the grid geometry manager is supported.
@@ -28,52 +29,70 @@ def place(self, **kwargs):
2829
raise TclError(f'{self.__class__.__name__} does not support "place"')
2930

3031

31-
class TextFrame(Frame):
32-
"Display text with scrollbar."
32+
class ScrollableTextFrame(Frame):
33+
"""Display text with scrollbar(s)."""
3334

34-
def __init__(self, parent, rawtext, wrap='word'):
35+
def __init__(self, master, wrap=NONE, **kwargs):
3536
"""Create a frame for Textview.
3637
37-
parent - parent widget for this frame
38-
rawtext - text to display
38+
master - master widget for this frame
39+
wrap - type of text wrapping to use ('word', 'char' or 'none')
40+
41+
All parameters except for 'wrap' are passed to Frame.__init__().
42+
43+
The Text widget is accessible via the 'text' attribute.
44+
45+
Note: Changing the wrapping mode of the text widget after
46+
instantiation is not supported.
3947
"""
40-
super().__init__(parent)
41-
self['relief'] = 'sunken'
42-
self['height'] = 700
48+
super().__init__(master, **kwargs)
4349

44-
self.text = text = Text(self, wrap=wrap, highlightthickness=0)
45-
color_config(text)
46-
text.grid(row=0, column=0, sticky=N+S+E+W)
50+
text = self.text = Text(self, wrap=wrap)
51+
text.grid(row=0, column=0, sticky=NSEW)
4752
self.grid_rowconfigure(0, weight=1)
4853
self.grid_columnconfigure(0, weight=1)
49-
text.insert(0.0, rawtext)
50-
text['state'] = 'disabled'
51-
text.focus_set()
5254

5355
# vertical scrollbar
54-
self.yscroll = yscroll = AutoHiddenScrollbar(self, orient=VERTICAL,
55-
takefocus=False,
56-
command=text.yview)
57-
text['yscrollcommand'] = yscroll.set
58-
yscroll.grid(row=0, column=1, sticky=N+S)
59-
60-
if wrap == 'none':
61-
# horizontal scrollbar
62-
self.xscroll = xscroll = AutoHiddenScrollbar(self, orient=HORIZONTAL,
63-
takefocus=False,
64-
command=text.xview)
65-
text['xscrollcommand'] = xscroll.set
66-
xscroll.grid(row=1, column=0, sticky=E+W)
56+
self.yscroll = AutoHideScrollbar(self, orient=VERTICAL,
57+
takefocus=False,
58+
command=text.yview)
59+
self.yscroll.grid(row=0, column=1, sticky=NS)
60+
text['yscrollcommand'] = self.yscroll.set
61+
62+
# horizontal scrollbar - only when wrap is set to NONE
63+
if wrap == NONE:
64+
self.xscroll = AutoHideScrollbar(self, orient=HORIZONTAL,
65+
takefocus=False,
66+
command=text.xview)
67+
self.xscroll.grid(row=1, column=0, sticky=EW)
68+
text['xscrollcommand'] = self.xscroll.set
69+
else:
70+
self.xscroll = None
6771

6872

6973
class ViewFrame(Frame):
7074
"Display TextFrame and Close button."
71-
def __init__(self, parent, text, wrap='word'):
75+
def __init__(self, parent, contents, wrap='word'):
76+
"""Create a frame for viewing text with a "Close" button.
77+
78+
parent - parent widget for this frame
79+
contents - text to display
80+
wrap - type of text wrapping to use ('word', 'char' or 'none')
81+
82+
The Text widget is accessible via the 'text' attribute.
83+
"""
7284
super().__init__(parent)
7385
self.parent = parent
7486
self.bind('<Return>', self.ok)
7587
self.bind('<Escape>', self.ok)
76-
self.textframe = TextFrame(self, text, wrap=wrap)
88+
self.textframe = ScrollableTextFrame(self, relief=SUNKEN, height=700)
89+
90+
text = self.text = self.textframe.text
91+
text.insert('1.0', contents)
92+
text.configure(wrap=wrap, highlightthickness=0, state='disabled')
93+
color_config(text)
94+
text.focus_set()
95+
7796
self.button_ok = button_ok = Button(
7897
self, text='Close', command=self.ok, takefocus=False)
7998
self.textframe.pack(side='top', expand=True, fill='both')
@@ -87,7 +106,7 @@ def ok(self, event=None):
87106
class ViewWindow(Toplevel):
88107
"A simple text viewer dialog for IDLE."
89108

90-
def __init__(self, parent, title, text, modal=True, wrap='word',
109+
def __init__(self, parent, title, contents, modal=True, wrap=WORD,
91110
*, _htest=False, _utest=False):
92111
"""Show the given text in a scrollable window with a 'close' button.
93112
@@ -96,7 +115,7 @@ def __init__(self, parent, title, text, modal=True, wrap='word',
96115
97116
parent - parent of this dialog
98117
title - string which is title of popup dialog
99-
text - text to display in dialog
118+
contents - text to display in dialog
100119
wrap - type of text wrapping to use ('word', 'char' or 'none')
101120
_htest - bool; change box location when running htest.
102121
_utest - bool; don't wait_window when running unittest.
@@ -109,7 +128,7 @@ def __init__(self, parent, title, text, modal=True, wrap='word',
109128
self.geometry(f'=750x500+{x}+{y}')
110129

111130
self.title(title)
112-
self.viewframe = ViewFrame(self, text, wrap=wrap)
131+
self.viewframe = ViewFrame(self, contents, wrap=wrap)
113132
self.protocol("WM_DELETE_WINDOW", self.ok)
114133
self.button_ok = button_ok = Button(self, text='Close',
115134
command=self.ok, takefocus=False)
@@ -129,18 +148,18 @@ def ok(self, event=None):
129148
self.destroy()
130149

131150

132-
def view_text(parent, title, text, modal=True, wrap='word', _utest=False):
151+
def view_text(parent, title, contents, modal=True, wrap='word', _utest=False):
133152
"""Create text viewer for given text.
134153
135154
parent - parent of this dialog
136155
title - string which is the title of popup dialog
137-
text - text to display in this dialog
156+
contents - text to display in this dialog
138157
wrap - type of text wrapping to use ('word', 'char' or 'none')
139158
modal - controls if users can interact with other windows while this
140159
dialog is displayed
141160
_utest - bool; controls wait_window on unittest
142161
"""
143-
return ViewWindow(parent, title, text, modal, wrap=wrap, _utest=_utest)
162+
return ViewWindow(parent, title, contents, modal, wrap=wrap, _utest=_utest)
144163

145164

146165
def view_file(parent, title, filename, encoding, modal=True, wrap='word',
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Settings dialog no longer expands with font size.

0 commit comments

Comments
 (0)