mirror of https://github.com/python/cpython
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.
This commit is contained in:
parent
1ed915e8ae
commit
3221a63c69
|
@ -33,6 +33,7 @@ from idlelib.codecontext import CodeContext
|
||||||
from idlelib.parenmatch import ParenMatch
|
from idlelib.parenmatch import ParenMatch
|
||||||
from idlelib.format import FormatParagraph
|
from idlelib.format import FormatParagraph
|
||||||
from idlelib.squeezer import Squeezer
|
from idlelib.squeezer import Squeezer
|
||||||
|
from idlelib.textview import ScrollableTextFrame
|
||||||
|
|
||||||
changes = ConfigChanges()
|
changes = ConfigChanges()
|
||||||
# Reload changed options in the following classes.
|
# Reload changed options in the following classes.
|
||||||
|
@ -556,7 +557,9 @@ class FontPage(Frame):
|
||||||
frame_font_param, variable=self.font_bold,
|
frame_font_param, variable=self.font_bold,
|
||||||
onvalue=1, offvalue=0, text='Bold')
|
onvalue=1, offvalue=0, text='Bold')
|
||||||
# frame_sample.
|
# 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)
|
self.font_sample.insert(END, font_sample_text)
|
||||||
# frame_indent.
|
# frame_indent.
|
||||||
indent_title = Label(
|
indent_title = Label(
|
||||||
|
@ -568,8 +571,9 @@ class FontPage(Frame):
|
||||||
|
|
||||||
# Grid and pack widgets:
|
# Grid and pack widgets:
|
||||||
self.columnconfigure(1, weight=1)
|
self.columnconfigure(1, weight=1)
|
||||||
|
self.rowconfigure(2, weight=1)
|
||||||
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=2, 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_indent.grid(row=1, column=0, padx=5, pady=5, sticky='ew')
|
||||||
# frame_font.
|
# frame_font.
|
||||||
|
@ -582,7 +586,7 @@ class FontPage(Frame):
|
||||||
self.sizelist.pack(side=LEFT, anchor=W)
|
self.sizelist.pack(side=LEFT, anchor=W)
|
||||||
self.bold_toggle.pack(side=LEFT, anchor=W, padx=20)
|
self.bold_toggle.pack(side=LEFT, anchor=W, padx=20)
|
||||||
# frame_sample.
|
# frame_sample.
|
||||||
self.font_sample.pack(expand=TRUE, fill=BOTH)
|
font_sample_frame.pack(expand=TRUE, fill=BOTH)
|
||||||
# frame_indent.
|
# frame_indent.
|
||||||
indent_title.pack(side=TOP, anchor=W, padx=5)
|
indent_title.pack(side=TOP, anchor=W, padx=5)
|
||||||
self.indent_scale.pack(side=TOP, padx=5, fill=X)
|
self.indent_scale.pack(side=TOP, padx=5, fill=X)
|
||||||
|
@ -840,9 +844,11 @@ class HighPage(Frame):
|
||||||
frame_theme = LabelFrame(self, borderwidth=2, relief=GROOVE,
|
frame_theme = LabelFrame(self, borderwidth=2, relief=GROOVE,
|
||||||
text=' Highlighting Theme ')
|
text=' Highlighting Theme ')
|
||||||
# frame_custom.
|
# frame_custom.
|
||||||
text = self.highlight_sample = Text(
|
sample_frame = ScrollableTextFrame(
|
||||||
frame_custom, relief=SOLID, borderwidth=1,
|
frame_custom, relief=SOLID, borderwidth=1)
|
||||||
font=('courier', 12, ''), cursor='hand2', width=21, height=13,
|
text = self.highlight_sample = sample_frame.text
|
||||||
|
text.configure(
|
||||||
|
font=('courier', 12, ''), cursor='hand2', width=1, height=1,
|
||||||
takefocus=FALSE, highlightthickness=0, wrap=NONE)
|
takefocus=FALSE, highlightthickness=0, wrap=NONE)
|
||||||
text.bind('<Double-Button-1>', lambda e: 'break')
|
text.bind('<Double-Button-1>', lambda e: 'break')
|
||||||
text.bind('<B1-Motion>', lambda e: 'break')
|
text.bind('<B1-Motion>', lambda e: 'break')
|
||||||
|
@ -868,7 +874,7 @@ class HighPage(Frame):
|
||||||
for texttag in text_and_tags:
|
for texttag in text_and_tags:
|
||||||
text.insert(END, texttag[0], texttag[1])
|
text.insert(END, texttag[0], texttag[1])
|
||||||
n_lines = len(text.get('1.0', END).splitlines())
|
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',
|
text.insert(f'{lineno}.0',
|
||||||
f'{lineno:{len(str(n_lines))}d} ',
|
f'{lineno:{len(str(n_lines))}d} ',
|
||||||
'linenumber')
|
'linenumber')
|
||||||
|
@ -920,9 +926,9 @@ class HighPage(Frame):
|
||||||
frame_custom.pack(side=LEFT, padx=5, pady=5, expand=TRUE, fill=BOTH)
|
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_theme.pack(side=TOP, padx=5, pady=5, fill=X)
|
||||||
# frame_custom.
|
# 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)
|
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)
|
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.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)
|
self.targetlist.pack(side=TOP, expand=TRUE, fill=X, padx=8, pady=3)
|
||||||
|
|
|
@ -349,7 +349,7 @@ _undo_delegator_spec = {
|
||||||
ViewWindow_spec = {
|
ViewWindow_spec = {
|
||||||
'file': 'textview',
|
'file': 'textview',
|
||||||
'kwds': {'title': 'Test 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},
|
'_htest': True},
|
||||||
'msg': "Test for read-only property of text.\n"
|
'msg': "Test for read-only property of text.\n"
|
||||||
"Select text, scroll window, close"
|
"Select text, scroll window, close"
|
||||||
|
|
|
@ -6,12 +6,12 @@ Using mock Text would not change this. Other mocks are used to retrieve
|
||||||
information about calls.
|
information about calls.
|
||||||
"""
|
"""
|
||||||
from idlelib import textview as tv
|
from idlelib import textview as tv
|
||||||
import unittest
|
|
||||||
from test.support import requires
|
from test.support import requires
|
||||||
requires('gui')
|
requires('gui')
|
||||||
|
|
||||||
import os
|
import os
|
||||||
from tkinter import Tk
|
import unittest
|
||||||
|
from tkinter import Tk, TclError, CHAR, NONE, WORD
|
||||||
from tkinter.ttk import Button
|
from tkinter.ttk import Button
|
||||||
from idlelib.idle_test.mock_idle import Func
|
from idlelib.idle_test.mock_idle import Func
|
||||||
from idlelib.idle_test.mock_tk import Mbox_func
|
from idlelib.idle_test.mock_tk import Mbox_func
|
||||||
|
@ -69,13 +69,65 @@ class ViewWindowTest(unittest.TestCase):
|
||||||
view.destroy()
|
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
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
cls.root = root = Tk()
|
cls.root = root = Tk()
|
||||||
root.withdraw()
|
root.withdraw()
|
||||||
cls.frame = tv.TextFrame(root, 'test text')
|
|
||||||
|
@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.ViewFrame(root, 'test text')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def tearDownClass(cls):
|
def tearDownClass(cls):
|
||||||
|
|
|
@ -2,14 +2,15 @@
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from tkinter import Toplevel, Text, TclError,\
|
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.ttk import Frame, Scrollbar, Button
|
||||||
from tkinter.messagebox import showerror
|
from tkinter.messagebox import showerror
|
||||||
|
|
||||||
|
from functools import update_wrapper
|
||||||
from idlelib.colorizer import color_config
|
from idlelib.colorizer import color_config
|
||||||
|
|
||||||
|
|
||||||
class AutoHiddenScrollbar(Scrollbar):
|
class AutoHideScrollbar(Scrollbar):
|
||||||
"""A scrollbar that is automatically hidden when not needed.
|
"""A scrollbar that is automatically hidden when not needed.
|
||||||
|
|
||||||
Only the grid geometry manager is supported.
|
Only the grid geometry manager is supported.
|
||||||
|
@ -28,52 +29,70 @@ class AutoHiddenScrollbar(Scrollbar):
|
||||||
raise TclError(f'{self.__class__.__name__} does not support "place"')
|
raise TclError(f'{self.__class__.__name__} does not support "place"')
|
||||||
|
|
||||||
|
|
||||||
class TextFrame(Frame):
|
class ScrollableTextFrame(Frame):
|
||||||
"Display text with scrollbar."
|
"""Display text with scrollbar(s)."""
|
||||||
|
|
||||||
def __init__(self, parent, rawtext, wrap='word'):
|
def __init__(self, master, wrap=NONE, **kwargs):
|
||||||
"""Create a frame for Textview.
|
"""Create a frame for Textview.
|
||||||
|
|
||||||
parent - parent widget for this frame
|
master - master widget for this frame
|
||||||
rawtext - text to display
|
wrap - type of text wrapping to use ('word', 'char' or 'none')
|
||||||
"""
|
|
||||||
super().__init__(parent)
|
|
||||||
self['relief'] = 'sunken'
|
|
||||||
self['height'] = 700
|
|
||||||
|
|
||||||
self.text = text = Text(self, wrap=wrap, highlightthickness=0)
|
All parameters except for 'wrap' are passed to Frame.__init__().
|
||||||
color_config(text)
|
|
||||||
text.grid(row=0, column=0, sticky=N+S+E+W)
|
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__(master, **kwargs)
|
||||||
|
|
||||||
|
text = self.text = Text(self, wrap=wrap)
|
||||||
|
text.grid(row=0, column=0, sticky=NSEW)
|
||||||
self.grid_rowconfigure(0, weight=1)
|
self.grid_rowconfigure(0, weight=1)
|
||||||
self.grid_columnconfigure(0, weight=1)
|
self.grid_columnconfigure(0, weight=1)
|
||||||
text.insert(0.0, rawtext)
|
|
||||||
text['state'] = 'disabled'
|
|
||||||
text.focus_set()
|
|
||||||
|
|
||||||
# vertical scrollbar
|
# vertical scrollbar
|
||||||
self.yscroll = yscroll = AutoHiddenScrollbar(self, orient=VERTICAL,
|
self.yscroll = AutoHideScrollbar(self, orient=VERTICAL,
|
||||||
takefocus=False,
|
takefocus=False,
|
||||||
command=text.yview)
|
command=text.yview)
|
||||||
text['yscrollcommand'] = yscroll.set
|
self.yscroll.grid(row=0, column=1, sticky=NS)
|
||||||
yscroll.grid(row=0, column=1, sticky=N+S)
|
text['yscrollcommand'] = self.yscroll.set
|
||||||
|
|
||||||
if wrap == 'none':
|
# horizontal scrollbar - only when wrap is set to NONE
|
||||||
# horizontal scrollbar
|
if wrap == NONE:
|
||||||
self.xscroll = xscroll = AutoHiddenScrollbar(self, orient=HORIZONTAL,
|
self.xscroll = AutoHideScrollbar(self, orient=HORIZONTAL,
|
||||||
takefocus=False,
|
takefocus=False,
|
||||||
command=text.xview)
|
command=text.xview)
|
||||||
text['xscrollcommand'] = xscroll.set
|
self.xscroll.grid(row=1, column=0, sticky=EW)
|
||||||
xscroll.grid(row=1, column=0, sticky=E+W)
|
text['xscrollcommand'] = self.xscroll.set
|
||||||
|
else:
|
||||||
|
self.xscroll = None
|
||||||
|
|
||||||
|
|
||||||
class ViewFrame(Frame):
|
class ViewFrame(Frame):
|
||||||
"Display TextFrame and Close button."
|
"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)
|
super().__init__(parent)
|
||||||
self.parent = parent
|
self.parent = parent
|
||||||
self.bind('<Return>', self.ok)
|
self.bind('<Return>', self.ok)
|
||||||
self.bind('<Escape>', 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.button_ok = button_ok = Button(
|
||||||
self, text='Close', command=self.ok, takefocus=False)
|
self, text='Close', command=self.ok, takefocus=False)
|
||||||
self.textframe.pack(side='top', expand=True, fill='both')
|
self.textframe.pack(side='top', expand=True, fill='both')
|
||||||
|
@ -87,7 +106,7 @@ class ViewFrame(Frame):
|
||||||
class ViewWindow(Toplevel):
|
class ViewWindow(Toplevel):
|
||||||
"A simple text viewer dialog for IDLE."
|
"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):
|
*, _htest=False, _utest=False):
|
||||||
"""Show the given text in a scrollable window with a 'close' button.
|
"""Show the given text in a scrollable window with a 'close' button.
|
||||||
|
|
||||||
|
@ -96,7 +115,7 @@ class ViewWindow(Toplevel):
|
||||||
|
|
||||||
parent - parent of this dialog
|
parent - parent of this dialog
|
||||||
title - string which is title of popup 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')
|
wrap - type of text wrapping to use ('word', 'char' or 'none')
|
||||||
_htest - bool; change box location when running htest.
|
_htest - bool; change box location when running htest.
|
||||||
_utest - bool; don't wait_window when running unittest.
|
_utest - bool; don't wait_window when running unittest.
|
||||||
|
@ -109,7 +128,7 @@ class ViewWindow(Toplevel):
|
||||||
self.geometry(f'=750x500+{x}+{y}')
|
self.geometry(f'=750x500+{x}+{y}')
|
||||||
|
|
||||||
self.title(title)
|
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.protocol("WM_DELETE_WINDOW", self.ok)
|
||||||
self.button_ok = button_ok = Button(self, text='Close',
|
self.button_ok = button_ok = Button(self, text='Close',
|
||||||
command=self.ok, takefocus=False)
|
command=self.ok, takefocus=False)
|
||||||
|
@ -129,18 +148,18 @@ class ViewWindow(Toplevel):
|
||||||
self.destroy()
|
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.
|
"""Create text viewer for given text.
|
||||||
|
|
||||||
parent - parent of this dialog
|
parent - parent of this dialog
|
||||||
title - string which is the title of popup 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')
|
wrap - type of text wrapping to use ('word', 'char' or 'none')
|
||||||
modal - controls if users can interact with other windows while this
|
modal - controls if users can interact with other windows while this
|
||||||
dialog is displayed
|
dialog is displayed
|
||||||
_utest - bool; controls wait_window on unittest
|
_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',
|
def view_file(parent, title, filename, encoding, modal=True, wrap='word',
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
Settings dialog no longer expands with font size.
|
Loading…
Reference in New Issue