Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
ed7d279
refactor TextFrame, extracting just the basic scrollable text widget
taleinat Jul 26, 2019
f67f6b9
use a scrollable text widget for the font config page's preview
taleinat Jul 26, 2019
0092a93
highlight page sample also a fixed-sized, scrollable text widget
taleinat Jul 26, 2019
3380193
refactor: make self.font_sample and self.highlight_sample consistent
taleinat Jul 26, 2019
b1dc2dd
rename for clarity: add_config_hook -> add_config_callback
taleinat Jul 26, 2019
48e97ce
Omit unneeded extra line number.
terryjreedy Jul 26, 2019
f78427a
Use horizontal scroller for font sample.
terryjreedy Jul 26, 2019
221e051
Condense 'font_sample_scrollable_frame' to 'font_sample__frame.
terryjreedy Jul 26, 2019
34c218a
Blurb.
terryjreedy Jul 26, 2019
b954c06
Meaningful names for callback functions.
terryjreedy Jul 27, 2019
72015a9
rename 'text' input param to 'contents' and use 'text' as local var
taleinat Jul 27, 2019
b5c467d
fix double-underscore typo
taleinat Jul 27, 2019
7100ec9
re-instate use of 'text' local variable in ScrollableTextFrame code
taleinat Jul 27, 2019
977c219
simplify ScrollableTextFrame, removing the config callback
taleinat Jul 27, 2019
3d8729d
Rename AutoHiddenScrollbar to AutoHideScrollbar
terryjreedy Jul 27, 2019
501fde2
Fix textview htest.
terryjreedy Jul 27, 2019
52cfa62
Restore coverage, reduced in prior issue, to 100%.
terryjreedy Jul 27, 2019
2cbf9e5
Double space between classes.
terryjreedy Jul 27, 2019
59eba51
Spacing
terryjreedy Jul 27, 2019
f6ac725
Combine configuration.
terryjreedy Jul 27, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 15 additions & 9 deletions Lib/idlelib/configdialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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(
Expand All @@ -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.
Expand All @@ -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)
Expand Down Expand Up @@ -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')
Expand All @@ -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')
Expand Down Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion Lib/idlelib/idle_test/htest.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
60 changes: 56 additions & 4 deletions Lib/idlelib/idle_test/test_textview.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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):
Expand Down
93 changes: 56 additions & 37 deletions Lib/idlelib/textview.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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')
Expand All @@ -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.

Expand All @@ -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.
Expand All @@ -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)
Expand All @@ -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',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Settings dialog no longer expands with font size.