From 066bcb72124bfbf0e1f1b494d34a940dde592fb1 Mon Sep 17 00:00:00 2001 From: Cheryl Sabella Date: Mon, 30 Apr 2018 19:41:34 -0400 Subject: [PATCH 01/15] bpo-25198: IDLE Help: Get font size from config and dynamically change font size --- Lib/idlelib/help.py | 82 +++++++++++++++---- Lib/idlelib/idle_test/test_help.py | 78 ++++++++++++++++++ .../2018-04-30-19-11-09.bpo-25198.yzJ3SL.rst | 2 + 3 files changed, 148 insertions(+), 14 deletions(-) create mode 100644 Misc/NEWS.d/next/IDLE/2018-04-30-19-11-09.bpo-25198.yzJ3SL.rst diff --git a/Lib/idlelib/help.py b/Lib/idlelib/help.py index 9f63ea0d3990e6..f8a67a4d50d7f7 100644 --- a/Lib/idlelib/help.py +++ b/Lib/idlelib/help.py @@ -175,26 +175,33 @@ def __init__(self, parent, filename): Text.__init__(self, parent, wrap='word', highlightthickness=0, padx=5, borderwidth=0, width=uwide, height=uhigh) - normalfont = self.findfont(['TkDefaultFont', 'arial', 'helvetica']) - fixedfont = self.findfont(['TkFixedFont', 'monaco', 'courier']) - self['font'] = (normalfont, 12) - self.tag_configure('em', font=(normalfont, 12, 'italic')) - self.tag_configure('h1', font=(normalfont, 20, 'bold')) - self.tag_configure('h2', font=(normalfont, 18, 'bold')) - self.tag_configure('h3', font=(normalfont, 15, 'bold')) - self.tag_configure('pre', font=(fixedfont, 12), background='#f6f6ff') - self.tag_configure('preblock', font=(fixedfont, 10), lmargin1=25, - borderwidth=1, relief='solid', background='#eeffcc') - self.tag_configure('l1', lmargin1=25, lmargin2=25) - self.tag_configure('l2', lmargin1=50, lmargin2=50) - self.tag_configure('l3', lmargin1=75, lmargin2=75) - self.tag_configure('l4', lmargin1=100, lmargin2=100) + self.create_fonts() + self.configure_tags() self.parser = HelpParser(self) with open(filename, encoding='utf-8') as f: contents = f.read() self.parser.feed(contents) + + self.bind_events() self['state'] = 'disabled' + self.focus_set() + + def create_fonts(self): + "Create fonts to be used with tags." + self.base_size = idleConf.GetOption('main', 'EditorWindow', + 'font-size', type='int') + normalfont = self.findfont(['TkDefaultFont', 'arial', 'helvetica']) + fixedfont = self.findfont(['TkFixedFont', 'monaco', 'courier']) + + self.fonts = fonts = {} + fonts['normal'] = tkfont.Font(self, family=normalfont) + fonts['em'] = tkfont.Font(self, family=normalfont, slant='italic') + for tag in ('h3', 'h2', 'h1'): + fonts[tag] = tkfont.Font(self, family=normalfont, weight='bold') + for tag in ('pre', 'preblock'): + fonts[tag] = tkfont.Font(self, family=fixedfont) + self.scale_fontsize(base=self.base_size) def findfont(self, names): "Return name of first font family derived from names." @@ -206,6 +213,52 @@ def findfont(self, names): for x in tkfont.families(root=self)): return name + def configure_tags(self): + "Configure tags used in parsing." + self['font'] = self.fonts['normal'] + for tag in ('em', 'h1', 'h2', 'h3', 'pre', 'preblock'): + self.tag_configure(tag, font=self.fonts[tag]) + self.tag_configure('pre', background='#f6f6ff') + self.tag_configure('preblock', lmargin1=25, borderwidth=1, + relief='solid', background='#eeffcc') + self.tag_configure('l1', lmargin1=25, lmargin2=25) + self.tag_configure('l2', lmargin1=50, lmargin2=50) + self.tag_configure('l3', lmargin1=75, lmargin2=75) + self.tag_configure('l4', lmargin1=100, lmargin2=100) + + def bind_events(self): + "Bind events." + # Zoom out works with or without shift. + self.event_add('<>', + *['', '']) + self.bind('<>', lambda e: self.zoom(e, 'out')) + + self.event_add('<>', '') + self.bind('<>', lambda e: self.zoom(e, 'in')) + + # Windows and Mac use MouseWheel. + self.bind('', lambda e: self.zoom(e, 'wheel')) + # Linux uses Button 4 (scroll down) and Button 5 (scroll up). + self.bind('', lambda e: self.zoom(e, 'wheel')) + self.bind('', lambda e: self.zoom(e, 'wheel')) + + def scale_fontsize(self, base=12): + "Scale tag sizes based on the size of normal text." + # Scale percentages are from Sphinx classic.css. + scale = {'normal': 1.0, 'h6': 1.0, 'h5': 1.1, 'h4': 1.2, 'h3': 1.4, + 'h2': 1.6, 'h1': 2.0, 'em': 1.0, 'pre': 1.0, 'preblock': 0.9} + for tag in self.fonts: + self.fonts[tag]['size'] = int(base * scale[tag]) + + def zoom(self, event, how='out'): + "Handle zooming text in/out." + if how == 'wheel': + # Button 5 or negative delta is when mouse wheel is scrolled down. + how = 'in' if event.num == 5 or event.delta < 0 else 'out' + factor = 1.1 if how == 'out' else 0.9 + self.base_size = round(self.base_size * factor) + self.scale_fontsize(base=self.base_size) + class HelpFrame(Frame): "Display html text, scrollbar, and toc." @@ -213,6 +266,7 @@ def __init__(self, parent, filename): Frame.__init__(self, parent) self.text = text = HelpText(self, filename) self['background'] = text['background'] + self.toc = toc = self.toc_menu(text) self.scroll = scroll = Scrollbar(self, command=text.yview) text['yscrollcommand'] = scroll.set diff --git a/Lib/idlelib/idle_test/test_help.py b/Lib/idlelib/idle_test/test_help.py index b542659981894d..04c526045aee0b 100644 --- a/Lib/idlelib/idle_test/test_help.py +++ b/Lib/idlelib/idle_test/test_help.py @@ -6,7 +6,16 @@ requires('gui') from os.path import abspath, dirname, join from tkinter import Tk +from tkinter import font as tkfont +from idlelib import config +usercfg = help.idleConf.userCfg +testcfg = { + 'main': config.IdleUserConfParser(''), + 'highlight': config.IdleUserConfParser(''), + 'keys': config.IdleUserConfParser(''), + 'extensions': config.IdleUserConfParser(''), +} class HelpFrameTest(unittest.TestCase): @@ -30,5 +39,74 @@ def test_line1(self): self.assertEqual(text.get('1.0', '1.end'), ' IDLE ') +class HelpTestTest(unittest.TestCase): + + @classmethod + def setUpClass(cls): + cls.root = root = Tk() + root.withdraw() + helpfile = join(dirname(dirname(abspath(__file__))), 'help.html') + cls.text = help.HelpText(root, helpfile) + help.idleConf.userCfg = testcfg + + @classmethod + def tearDownClass(cls): + help.idleConf.userCfg = usercfg + del cls.text + cls.root.update_idletasks() + cls.root.destroy() + del cls.root + + def test_scale_fontsize(self): + text = self.text + eq = self.assertEqual + tags = ('normal', 'h3', 'h2', 'h1', 'em', 'pre', 'preblock') + save_fonts = text.fonts + text.fonts = {tag: tkfont.Font(text, family='courier') for tag in tags} + + text.scale_fontsize() + sizes = [text.fonts[tag]['size'] for tag in tags] + eq(sizes, [12, 16, 19, 24, 12, 12, 10]) + + text.scale_fontsize(21) + sizes = [text.fonts[tag]['size'] for tag in tags] + eq(sizes, [21, 29, 33, 42, 21, 21, 18]) + + text.fonts = save_fonts + + def test_zoom(self): + text = self.text + eq = self.assertEqual + + tags = ('normal', 'h3', 'h2', 'h1', 'em', 'pre', 'preblock') + base = [12, 16, 19, 24, 12, 12, 10] + zoomout = [13, 18, 20, 26, 13, 13, 11] + zoomin = [11, 15, 17, 22, 11, 11, 9] + + tests = (('<>', 0, zoomout), + ('<>', 0, base), + ('<>', 0, zoomin), + ('', 0, base), + ('', -120, zoomin), + ('', 120, base), + ('', 0, zoomout), + ('', 0, base)) + + # Base size starts at 12. + sizes = [text.fonts[tag]['size'] for tag in tags] + eq(text.base_size, base[0]) + eq(sizes, base) + + for event, delta, result in tests: + with self.subTest(event=event): + if event == '': + text.event_generate(event, delta=delta) + else: + text.event_generate(event) + sizes = [text.fonts[tag]['size'] for tag in tags] + eq(text.base_size, result[0]) + eq(sizes, result) + + if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/Misc/NEWS.d/next/IDLE/2018-04-30-19-11-09.bpo-25198.yzJ3SL.rst b/Misc/NEWS.d/next/IDLE/2018-04-30-19-11-09.bpo-25198.yzJ3SL.rst new file mode 100644 index 00000000000000..d804af09c6278a --- /dev/null +++ b/Misc/NEWS.d/next/IDLE/2018-04-30-19-11-09.bpo-25198.yzJ3SL.rst @@ -0,0 +1,2 @@ +Use user preferences to set font size in IDLE Help and allow dynamic font +sizing using keybindings or mouse wheel. From 42a1a74b84ef1537db149228a17c83228687af63 Mon Sep 17 00:00:00 2001 From: Cheryl Sabella Date: Wed, 2 May 2018 14:08:15 -0400 Subject: [PATCH 02/15] Extract FontSizer class and fix tests. --- Lib/idlelib/help.py | 105 +++++++++++++++-------- Lib/idlelib/idle_test/test_help.py | 132 ++++++++++++++++++++--------- 2 files changed, 161 insertions(+), 76 deletions(-) diff --git a/Lib/idlelib/help.py b/Lib/idlelib/help.py index f8a67a4d50d7f7..b34fbe5ba7613c 100644 --- a/Lib/idlelib/help.py +++ b/Lib/idlelib/help.py @@ -24,11 +24,13 @@ show_idlehelp - Create HelpWindow. Called in EditorWindow.help_dialog. """ +import sys + from html.parser import HTMLParser from os.path import abspath, dirname, isfile, join from platform import python_version -from tkinter import Toplevel, Frame, Text, Menu +from tkinter import Toplevel, Frame, Text, Menu, EventType from tkinter.ttk import Menubutton, Scrollbar from tkinter import font as tkfont @@ -39,6 +41,11 @@ ## IDLE Help ## +darwin = sys.platform == 'darwin' +MINIMUM_FONT_SIZE = 6 +MAXIMUM_FONT_SIZE = 100 + + class HelpParser(HTMLParser): """Render help.html into a text widget. @@ -165,6 +172,57 @@ def handle_data(self, data): self.text.insert('end', d, (self.tags, self.chartags)) +class FontSizer: + "Support dynamic widget font resizing." + def __init__(self, widget, callback=None): + """"Add font resizing functionality to widget. + + Args: + widget: Tk widget with font attribute to size. + callback: Function to call for additional font resizing + based on widget's font attribute. + """ + self.widget = widget + self.callback = callback + self.bind_events() + + def bind_events(self): + "Bind events to the widget." + shortcut = 'Command' if darwin else 'Control' + # Zoom out works with or without shift. + self.widget.event_add( + '<>', + *[f'<{shortcut}-Key-equal>', f'<{shortcut}-Key-plus>']) + self.widget.bind('<>', self.zoom) + + self.widget.event_add( + '<>', + *[f'<{shortcut}-Key-minus>', f'<{shortcut}-Key-underscore>']) + self.widget.bind('<>', self.zoom) + + # Windows and Mac use MouseWheel. + self.widget.bind('', self.zoom) + # Linux uses Button 4 (scroll down) and Button 5 (scroll up). + self.widget.bind('', self.zoom) + self.widget.bind('', self.zoom) + + def zoom(self, event): + "Handle zooming in/out." + if event.type == EventType.KeyPress: + increase = event.keysym in {'plus', 'equal'} + elif event.type == EventType.MouseWheel: + increase = event.delta >= 0 == darwin + elif event.type == EventType.Button: + increase = event.num == 4 + + font = tkfont.Font(self.widget, name=self.widget['font'], exists=True) + new_size = (min(font['size'] + 1, MAXIMUM_FONT_SIZE) if increase + else max(font['size'] - 1, MINIMUM_FONT_SIZE)) + font['size'] = new_size + if self.callback: + self.callback(new_size) + + class HelpText(Text): "Display help.html." def __init__(self, parent, filename): @@ -183,25 +241,28 @@ def __init__(self, parent, filename): contents = f.read() self.parser.feed(contents) - self.bind_events() self['state'] = 'disabled' self.focus_set() def create_fonts(self): "Create fonts to be used with tags." - self.base_size = idleConf.GetOption('main', 'EditorWindow', - 'font-size', type='int') + base_size = idleConf.GetOption('main', 'EditorWindow', + 'font-size', type='int') normalfont = self.findfont(['TkDefaultFont', 'arial', 'helvetica']) fixedfont = self.findfont(['TkFixedFont', 'monaco', 'courier']) + self.base_font = tkfont.Font(self, (normalfont, base_size)) + self['font'] = self.base_font + self.fonts = fonts = {} - fonts['normal'] = tkfont.Font(self, family=normalfont) fonts['em'] = tkfont.Font(self, family=normalfont, slant='italic') for tag in ('h3', 'h2', 'h1'): fonts[tag] = tkfont.Font(self, family=normalfont, weight='bold') for tag in ('pre', 'preblock'): fonts[tag] = tkfont.Font(self, family=fixedfont) - self.scale_fontsize(base=self.base_size) + self.scale_tagfonts(base_size) + + FontSizer(self, self.scale_tagfonts) def findfont(self, names): "Return name of first font family derived from names." @@ -215,7 +276,6 @@ def findfont(self, names): def configure_tags(self): "Configure tags used in parsing." - self['font'] = self.fonts['normal'] for tag in ('em', 'h1', 'h2', 'h3', 'pre', 'preblock'): self.tag_configure(tag, font=self.fonts[tag]) self.tag_configure('pre', background='#f6f6ff') @@ -226,39 +286,14 @@ def configure_tags(self): self.tag_configure('l3', lmargin1=75, lmargin2=75) self.tag_configure('l4', lmargin1=100, lmargin2=100) - def bind_events(self): - "Bind events." - # Zoom out works with or without shift. - self.event_add('<>', - *['', '']) - self.bind('<>', lambda e: self.zoom(e, 'out')) - - self.event_add('<>', '') - self.bind('<>', lambda e: self.zoom(e, 'in')) - - # Windows and Mac use MouseWheel. - self.bind('', lambda e: self.zoom(e, 'wheel')) - # Linux uses Button 4 (scroll down) and Button 5 (scroll up). - self.bind('', lambda e: self.zoom(e, 'wheel')) - self.bind('', lambda e: self.zoom(e, 'wheel')) - - def scale_fontsize(self, base=12): + def scale_tagfonts(self, base): "Scale tag sizes based on the size of normal text." # Scale percentages are from Sphinx classic.css. - scale = {'normal': 1.0, 'h6': 1.0, 'h5': 1.1, 'h4': 1.2, 'h3': 1.4, - 'h2': 1.6, 'h1': 2.0, 'em': 1.0, 'pre': 1.0, 'preblock': 0.9} + scale = {'h3': 1.2, 'h2': 1.4, 'h1': 1.6, + 'em': 1.0, 'pre': 1.0, 'preblock': 0.9} for tag in self.fonts: self.fonts[tag]['size'] = int(base * scale[tag]) - def zoom(self, event, how='out'): - "Handle zooming text in/out." - if how == 'wheel': - # Button 5 or negative delta is when mouse wheel is scrolled down. - how = 'in' if event.num == 5 or event.delta < 0 else 'out' - factor = 1.1 if how == 'out' else 0.9 - self.base_size = round(self.base_size * factor) - self.scale_fontsize(base=self.base_size) - class HelpFrame(Frame): "Display html text, scrollbar, and toc." diff --git a/Lib/idlelib/idle_test/test_help.py b/Lib/idlelib/idle_test/test_help.py index 04c526045aee0b..693a49868be17c 100644 --- a/Lib/idlelib/idle_test/test_help.py +++ b/Lib/idlelib/idle_test/test_help.py @@ -1,14 +1,17 @@ "Test help, coverage 87%." +import sys + from idlelib import help import unittest from test.support import requires requires('gui') from os.path import abspath, dirname, join -from tkinter import Tk +from tkinter import Tk, Text, TclError from tkinter import font as tkfont from idlelib import config +darwin = sys.platform == 'darwin' usercfg = help.idleConf.userCfg testcfg = { 'main': config.IdleUserConfParser(''), @@ -17,6 +20,7 @@ 'extensions': config.IdleUserConfParser(''), } + class HelpFrameTest(unittest.TestCase): @classmethod @@ -39,73 +43,119 @@ def test_line1(self): self.assertEqual(text.get('1.0', '1.end'), ' IDLE ') -class HelpTestTest(unittest.TestCase): +class FontSizerTest(unittest.TestCase): + + @classmethod + def setUpClass(cls): + cls.root = root = Tk() + root.withdraw() + cls.text = Text(root) + + @classmethod + def tearDownClass(cls): + del cls.text + cls.root.update_idletasks() + cls.root.destroy() + del cls.root + + def setUp(self): + text = self.text + self.font = font = tkfont.Font(text, ('courier', 30)) + text['font'] = font + text.insert('end', 'Test Text') + self.sizer = help.FontSizer(text) + + def tearDown(self): + del self.sizer, self.font + + def test_zoom(self): + text = self.text + font = tkfont.Font(name=text['font'], exists=True, root=text) + eq = self.assertEqual + text.focus_set() + + wheel = '' + button4 = '' + button5 = '' + + tests = ((wheel, {}, 31, None), + (wheel, {'delta': 1 if darwin else -120}, 30, None), + (button5, {}, 29, ''), + (wheel, {'delta': -1 if darwin else 120}, 30, None), + (button4, {}, 31, '')) + + eq(font['size'], 30) + + for event, kw, result, after in tests: + with self.subTest(event=event): + text.event_generate(event, **kw) + eq(font['size'], result) + if after: + text.event_generate(after) + +class HelpTextTest(unittest.TestCase): @classmethod def setUpClass(cls): + help.idleConf.userCfg = testcfg + testcfg['main'].SetOption('EditorWindow', 'font-size', '12') cls.root = root = Tk() root.withdraw() helpfile = join(dirname(dirname(abspath(__file__))), 'help.html') cls.text = help.HelpText(root, helpfile) - help.idleConf.userCfg = testcfg + cls.tags = ('h3', 'h2', 'h1', 'em', 'pre', 'preblock') @classmethod def tearDownClass(cls): help.idleConf.userCfg = usercfg - del cls.text + del cls.text, cls.tags cls.root.update_idletasks() cls.root.destroy() del cls.root - def test_scale_fontsize(self): + def get_sizes(self): + return [self.text.fonts[tag]['size'] for tag in self.tags] + + def test_scale_tagfonts(self): text = self.text eq = self.assertEqual - tags = ('normal', 'h3', 'h2', 'h1', 'em', 'pre', 'preblock') + save_fonts = text.fonts - text.fonts = {tag: tkfont.Font(text, family='courier') for tag in tags} + text.fonts = {tag: tkfont.Font(text, family='courier') for tag in self.tags} - text.scale_fontsize() - sizes = [text.fonts[tag]['size'] for tag in tags] - eq(sizes, [12, 16, 19, 24, 12, 12, 10]) + text.scale_tagfonts(12) + eq(self.get_sizes(), [14, 16, 19, 12, 12, 10]) - text.scale_fontsize(21) - sizes = [text.fonts[tag]['size'] for tag in tags] - eq(sizes, [21, 29, 33, 42, 21, 21, 18]) + text.scale_tagfonts(21) + eq(self.get_sizes(), [25, 29, 33, 21, 21, 18]) text.fonts = save_fonts - def test_zoom(self): + def test_font_sizing(self): text = self.text eq = self.assertEqual - tags = ('normal', 'h3', 'h2', 'h1', 'em', 'pre', 'preblock') - base = [12, 16, 19, 24, 12, 12, 10] - zoomout = [13, 18, 20, 26, 13, 13, 11] - zoomin = [11, 15, 17, 22, 11, 11, 9] - - tests = (('<>', 0, zoomout), - ('<>', 0, base), - ('<>', 0, zoomin), - ('', 0, base), - ('', -120, zoomin), - ('', 120, base), - ('', 0, zoomout), - ('', 0, base)) - - # Base size starts at 12. - sizes = [text.fonts[tag]['size'] for tag in tags] - eq(text.base_size, base[0]) - eq(sizes, base) - - for event, delta, result in tests: + base = [14, 16, 19, 12, 12, 10] + zoomout = [15, 18, 20, 13, 13, 11] + zoomin = [13, 15, 17, 11, 11, 9] + wheel = '' + button4 = '' + button5 = '' + + tests = ((wheel, {}, zoomout, None), + (wheel, {'delta': 1 if darwin else -120}, base, None), + (button5, {}, zoomin, ''), + (wheel, {'delta': -1 if darwin else 120}, base, None), + (button4, {}, zoomout, '')) + + eq(self.get_sizes(), base) + + for event, kw, result, after in tests: with self.subTest(event=event): - if event == '': - text.event_generate(event, delta=delta) - else: - text.event_generate(event) - sizes = [text.fonts[tag]['size'] for tag in tags] - eq(text.base_size, result[0]) - eq(sizes, result) + text.event_generate(event, **kw) + eq(self.get_sizes(), result) + if after: + text.event_generate(after) if __name__ == '__main__': From d25899d1fc94ffdff65a702a42c16320da59cb40 Mon Sep 17 00:00:00 2001 From: Cheryl Sabella Date: Wed, 2 May 2018 14:21:23 -0400 Subject: [PATCH 03/15] Add return break to event handler. --- Lib/idlelib/help.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Lib/idlelib/help.py b/Lib/idlelib/help.py index b34fbe5ba7613c..17f6a583484b4e 100644 --- a/Lib/idlelib/help.py +++ b/Lib/idlelib/help.py @@ -221,6 +221,7 @@ def zoom(self, event): font['size'] = new_size if self.callback: self.callback(new_size) + return 'break' class HelpText(Text): From 2fd78203c6209abe0392fd09997f6423654fb72d Mon Sep 17 00:00:00 2001 From: Cheryl Sabella Date: Mon, 3 Sep 2018 18:42:42 -0400 Subject: [PATCH 04/15] Separate event handlers from text changes. --- Lib/idlelib/help.py | 41 ++++++---- Lib/idlelib/idle_test/test_help.py | 125 +++++++++++++++++++++++------ 2 files changed, 127 insertions(+), 39 deletions(-) diff --git a/Lib/idlelib/help.py b/Lib/idlelib/help.py index 17f6a583484b4e..ec4168eb397ec1 100644 --- a/Lib/idlelib/help.py +++ b/Lib/idlelib/help.py @@ -191,31 +191,39 @@ def bind_events(self): shortcut = 'Command' if darwin else 'Control' # Zoom out works with or without shift. self.widget.event_add( - '<>', + '<>', *[f'<{shortcut}-Key-equal>', f'<{shortcut}-Key-plus>']) - self.widget.bind('<>', self.zoom) + self.widget.bind('<>', self.zoom_in) self.widget.event_add( - '<>', + '<>', *[f'<{shortcut}-Key-minus>', f'<{shortcut}-Key-underscore>']) - self.widget.bind('<>', self.zoom) + self.widget.bind('<>', self.zoom_out) # Windows and Mac use MouseWheel. - self.widget.bind('', self.zoom) + self.widget.bind('', self.zoom_mousewheel) # Linux uses Button 4 (scroll down) and Button 5 (scroll up). - self.widget.bind('', self.zoom) - self.widget.bind('', self.zoom) - - def zoom(self, event): - "Handle zooming in/out." - if event.type == EventType.KeyPress: - increase = event.keysym in {'plus', 'equal'} - elif event.type == EventType.MouseWheel: - increase = event.delta >= 0 == darwin - elif event.type == EventType.Button: - increase = event.num == 4 + self.widget.bind('', self.zoom_mousewheel) + self.widget.bind('', self.zoom_mousewheel) + + def zoom_in(self, event): + "Make font size larger." + self.zoom(increase=True) + + def zoom_out(self, event): + "Make font size smaller." + self.zoom(increase=False) + + def zoom_mousewheel(self, event): + "Adjust font size based on mouse wheel direction." + wheel_up = {EventType.MouseWheel: event.delta > 0, + EventType.Button: event.num == 4} + self.zoom(wheel_up[event.type]) + def zoom(self, increase): + "Adjust font size." font = tkfont.Font(self.widget, name=self.widget['font'], exists=True) + # Increase or decrease font size within defined range. new_size = (min(font['size'] + 1, MAXIMUM_FONT_SIZE) if increase else max(font['size'] - 1, MINIMUM_FONT_SIZE)) font['size'] = new_size @@ -255,6 +263,7 @@ def create_fonts(self): self.base_font = tkfont.Font(self, (normalfont, base_size)) self['font'] = self.base_font + # Define styling for each font tag used in html. self.fonts = fonts = {} fonts['em'] = tkfont.Font(self, family=normalfont, slant='italic') for tag in ('h3', 'h2', 'h1'): diff --git a/Lib/idlelib/idle_test/test_help.py b/Lib/idlelib/idle_test/test_help.py index 693a49868be17c..9347bae5ccbd23 100644 --- a/Lib/idlelib/idle_test/test_help.py +++ b/Lib/idlelib/idle_test/test_help.py @@ -1,4 +1,4 @@ -"Test help, coverage 87%." +"Test help, coverage 90%." import sys @@ -68,9 +68,33 @@ def setUp(self): def tearDown(self): del self.sizer, self.font - def test_zoom(self): + def test_zoom_in(self): + text = self.text + font = self.font + eq = self.assertEqual + text.focus_set() + + eq(font['size'], 30) + text.event_generate('<>') + eq(font['size'], 31) + text.event_generate('<>') + eq(font['size'], 32) + + def test_zoom_out(self): + text = self.text + font = self.font + eq = self.assertEqual + text.focus_set() + + eq(font['size'], 30) + text.event_generate('<>') + eq(font['size'], 29) + text.event_generate('<>') + eq(font['size'], 28) + + def test_mousewheel(self): text = self.text - font = tkfont.Font(name=text['font'], exists=True, root=text) + font = self.font eq = self.assertEqual text.focus_set() @@ -78,14 +102,13 @@ def test_zoom(self): button4 = '' button5 = '' - tests = ((wheel, {}, 31, None), - (wheel, {'delta': 1 if darwin else -120}, 30, None), + tests = ((wheel, {}, 29, None), + (wheel, {'delta': 10}, 30, None), (button5, {}, 29, ''), - (wheel, {'delta': -1 if darwin else 120}, 30, None), - (button4, {}, 31, '')) + (wheel, {'delta': -10}, 28, None), + (button4, {}, 29, '')) eq(font['size'], 30) - for event, kw, result, after in tests: with self.subTest(event=event): text.event_generate(event, **kw) @@ -93,6 +116,63 @@ def test_zoom(self): if after: text.event_generate(after) + def test_zoom(self): + text = self.text + font = self.font + eq = self.assertEqual + text.focus_set() + + tests = (('zoom out', False, 29), + ('zoom in', True, 30), + ('zoom out', False, 29), + ('zoom out', False, 28), + ('zoom in', True, 29)) + + eq(font['size'], 30) + for event, increase, result in tests: + with self.subTest(event=event): + self.sizer.zoom(increase) + eq(font['size'], result) + + def test_zoom_lower_boundary(self): + text = self.text + font = self.font + font['size'] = 7 + eq = self.assertEqual + text.focus_set() + + tests = (('zoom out', False, 6), + ('zoom out at limit (no change)', False, 6), + ('zoom in', True, 7), + ('zoom in', True, 8), + ('zoom out', False, 7)) + + eq(font['size'], 7) + for event, increase, result in tests: + with self.subTest(event=event): + self.sizer.zoom(increase) + eq(font['size'], result) + + def test_zoom_upper_boundary(self): + text = self.text + font = self.font + font['size'] = 99 + eq = self.assertEqual + text.focus_set() + + tests = (('zoom in', True, 100), + ('zoom in at limit (no change)', True, 100), + ('zoom out', False, 99), + ('zoom out', False, 98), + ('zoom in', True, 99)) + + eq(font['size'], 99) + for event, increase, result in tests: + with self.subTest(event=event): + self.sizer.zoom(increase) + eq(font['size'], result) + + class HelpTextTest(unittest.TestCase): @classmethod @@ -101,18 +181,22 @@ def setUpClass(cls): testcfg['main'].SetOption('EditorWindow', 'font-size', '12') cls.root = root = Tk() root.withdraw() - helpfile = join(dirname(dirname(abspath(__file__))), 'help.html') - cls.text = help.HelpText(root, helpfile) - cls.tags = ('h3', 'h2', 'h1', 'em', 'pre', 'preblock') @classmethod def tearDownClass(cls): help.idleConf.userCfg = usercfg - del cls.text, cls.tags cls.root.update_idletasks() cls.root.destroy() del cls.root + def setUp(self): + helpfile = join(dirname(dirname(abspath(__file__))), 'help.html') + self.text = help.HelpText(self.root, helpfile) + self.tags = ('h3', 'h2', 'h1', 'em', 'pre', 'preblock') + + def tearDown(self): + del self.text, self.tags + def get_sizes(self): return [self.text.fonts[tag]['size'] for tag in self.tags] @@ -120,33 +204,28 @@ def test_scale_tagfonts(self): text = self.text eq = self.assertEqual - save_fonts = text.fonts - text.fonts = {tag: tkfont.Font(text, family='courier') for tag in self.tags} - text.scale_tagfonts(12) eq(self.get_sizes(), [14, 16, 19, 12, 12, 10]) text.scale_tagfonts(21) eq(self.get_sizes(), [25, 29, 33, 21, 21, 18]) - text.fonts = save_fonts - def test_font_sizing(self): text = self.text eq = self.assertEqual base = [14, 16, 19, 12, 12, 10] - zoomout = [15, 18, 20, 13, 13, 11] - zoomin = [13, 15, 17, 11, 11, 9] + zoomin = [15, 18, 20, 13, 13, 11] + zoomout = [13, 15, 17, 11, 11, 9] wheel = '' button4 = '' button5 = '' tests = ((wheel, {}, zoomout, None), - (wheel, {'delta': 1 if darwin else -120}, base, None), - (button5, {}, zoomin, ''), - (wheel, {'delta': -1 if darwin else 120}, base, None), - (button4, {}, zoomout, '')) + (wheel, {'delta': 10}, base, None), + (button4, {}, zoomin, ''), + (wheel, {'delta': -10}, base, None), + (button5, {}, zoomout, '')) eq(self.get_sizes(), base) From ff08ebfbf72c1371ea6de561ddaf17ad83207b44 Mon Sep 17 00:00:00 2001 From: Cheryl Sabella Date: Fri, 7 Sep 2018 08:50:13 -0400 Subject: [PATCH 05/15] Update event bindings, event handlers, and tests. --- Lib/idlelib/help.py | 69 ++++++++--------- Lib/idlelib/idle_test/test_help.py | 118 ++++------------------------- 2 files changed, 51 insertions(+), 136 deletions(-) diff --git a/Lib/idlelib/help.py b/Lib/idlelib/help.py index ec4168eb397ec1..e9a06437381d82 100644 --- a/Lib/idlelib/help.py +++ b/Lib/idlelib/help.py @@ -74,7 +74,7 @@ def __init__(self, text): def indent(self, amt=1): "Change indent (+1, 0, -1) and tags." self.level += amt - self.tags = '' if self.level == 0 else 'l'+str(self.level) + self.tags = '' if self.level == 0 else f'l{self.level}' def handle_starttag(self, tag, attrs): "Handle starttags in help.html." @@ -189,47 +189,50 @@ def __init__(self, widget, callback=None): def bind_events(self): "Bind events to the widget." shortcut = 'Command' if darwin else 'Control' - # Zoom out works with or without shift. + # Bind to keys with or without shift. self.widget.event_add( - '<>', - *[f'<{shortcut}-Key-equal>', f'<{shortcut}-Key-plus>']) - self.widget.bind('<>', self.zoom_in) + '<>', + f'<{shortcut}-Key-equal>', f'<{shortcut}-Key-plus>') + self.widget.bind('<>', self.increase_font_size) self.widget.event_add( - '<>', - *[f'<{shortcut}-Key-minus>', f'<{shortcut}-Key-underscore>']) - self.widget.bind('<>', self.zoom_out) + '<>', + f'<{shortcut}-Key-minus>', f'<{shortcut}-Key-underscore>') + self.widget.bind('<>', self.decrease_font_size) # Windows and Mac use MouseWheel. - self.widget.bind('', self.zoom_mousewheel) + self.widget.bind('', self.update_mousewheel) # Linux uses Button 4 (scroll down) and Button 5 (scroll up). - self.widget.bind('', self.zoom_mousewheel) - self.widget.bind('', self.zoom_mousewheel) + self.widget.bind('', self.update_mousewheel) + self.widget.bind('', self.update_mousewheel) - def zoom_in(self, event): + def set_text_size(self, new_size): + "Set the font size for this widget." + font = tkfont.Font(self.widget, name=self.widget['font'], exists=True) + size = new_size(font) + font['size'] = size + if self.callback: + self.callback(size) + return 'break' + + def increase_font_size(self, event=None): "Make font size larger." - self.zoom(increase=True) + def new_size(font): + return min(font['size'] + 1, MAXIMUM_FONT_SIZE) + return self.set_text_size(new_size) - def zoom_out(self, event): + def decrease_font_size(self, event=None): "Make font size smaller." - self.zoom(increase=False) + def new_size(font): + return max(font['size'] - 1, MINIMUM_FONT_SIZE) + return self.set_text_size(new_size) - def zoom_mousewheel(self, event): + def update_mousewheel(self, event): "Adjust font size based on mouse wheel direction." - wheel_up = {EventType.MouseWheel: event.delta > 0, - EventType.Button: event.num == 4} - self.zoom(wheel_up[event.type]) - - def zoom(self, increase): - "Adjust font size." - font = tkfont.Font(self.widget, name=self.widget['font'], exists=True) - # Increase or decrease font size within defined range. - new_size = (min(font['size'] + 1, MAXIMUM_FONT_SIZE) if increase - else max(font['size'] - 1, MINIMUM_FONT_SIZE)) - font['size'] = new_size - if self.callback: - self.callback(new_size) - return 'break' + if (event.delta < 0) == (not darwin): + return self.decrease_font_size() + else: + return self.increase_font_size() class HelpText(Text): @@ -291,10 +294,8 @@ def configure_tags(self): self.tag_configure('pre', background='#f6f6ff') self.tag_configure('preblock', lmargin1=25, borderwidth=1, relief='solid', background='#eeffcc') - self.tag_configure('l1', lmargin1=25, lmargin2=25) - self.tag_configure('l2', lmargin1=50, lmargin2=50) - self.tag_configure('l3', lmargin1=75, lmargin2=75) - self.tag_configure('l4', lmargin1=100, lmargin2=100) + for level in range(1, 5): + self.tag_configure(f'l{level}', lmargin1=25*level, lmargin2=25*level) def scale_tagfonts(self, base): "Scale tag sizes based on the size of normal text." diff --git a/Lib/idlelib/idle_test/test_help.py b/Lib/idlelib/idle_test/test_help.py index 9347bae5ccbd23..31b55b0cf474cb 100644 --- a/Lib/idlelib/idle_test/test_help.py +++ b/Lib/idlelib/idle_test/test_help.py @@ -68,110 +68,30 @@ def setUp(self): def tearDown(self): del self.sizer, self.font - def test_zoom_in(self): + def test_increase_font_size(self): text = self.text font = self.font eq = self.assertEqual text.focus_set() eq(font['size'], 30) - text.event_generate('<>') + text.event_generate('<>') eq(font['size'], 31) - text.event_generate('<>') + text.event_generate('<>') eq(font['size'], 32) - def test_zoom_out(self): + def test_decrease_font_size(self): text = self.text font = self.font eq = self.assertEqual text.focus_set() eq(font['size'], 30) - text.event_generate('<>') + text.event_generate('<>') eq(font['size'], 29) - text.event_generate('<>') + text.event_generate('<>') eq(font['size'], 28) - def test_mousewheel(self): - text = self.text - font = self.font - eq = self.assertEqual - text.focus_set() - - wheel = '' - button4 = '' - button5 = '' - - tests = ((wheel, {}, 29, None), - (wheel, {'delta': 10}, 30, None), - (button5, {}, 29, ''), - (wheel, {'delta': -10}, 28, None), - (button4, {}, 29, '')) - - eq(font['size'], 30) - for event, kw, result, after in tests: - with self.subTest(event=event): - text.event_generate(event, **kw) - eq(font['size'], result) - if after: - text.event_generate(after) - - def test_zoom(self): - text = self.text - font = self.font - eq = self.assertEqual - text.focus_set() - - tests = (('zoom out', False, 29), - ('zoom in', True, 30), - ('zoom out', False, 29), - ('zoom out', False, 28), - ('zoom in', True, 29)) - - eq(font['size'], 30) - for event, increase, result in tests: - with self.subTest(event=event): - self.sizer.zoom(increase) - eq(font['size'], result) - - def test_zoom_lower_boundary(self): - text = self.text - font = self.font - font['size'] = 7 - eq = self.assertEqual - text.focus_set() - - tests = (('zoom out', False, 6), - ('zoom out at limit (no change)', False, 6), - ('zoom in', True, 7), - ('zoom in', True, 8), - ('zoom out', False, 7)) - - eq(font['size'], 7) - for event, increase, result in tests: - with self.subTest(event=event): - self.sizer.zoom(increase) - eq(font['size'], result) - - def test_zoom_upper_boundary(self): - text = self.text - font = self.font - font['size'] = 99 - eq = self.assertEqual - text.focus_set() - - tests = (('zoom in', True, 100), - ('zoom in at limit (no change)', True, 100), - ('zoom out', False, 99), - ('zoom out', False, 98), - ('zoom in', True, 99)) - - eq(font['size'], 99) - for event, increase, result in tests: - with self.subTest(event=event): - self.sizer.zoom(increase) - eq(font['size'], result) - class HelpTextTest(unittest.TestCase): @@ -210,31 +130,25 @@ def test_scale_tagfonts(self): text.scale_tagfonts(21) eq(self.get_sizes(), [25, 29, 33, 21, 21, 18]) - def test_font_sizing(self): + def test_resizing_callback(self): text = self.text eq = self.assertEqual base = [14, 16, 19, 12, 12, 10] - zoomin = [15, 18, 20, 13, 13, 11] - zoomout = [13, 15, 17, 11, 11, 9] - wheel = '' - button4 = '' - button5 = '' - - tests = ((wheel, {}, zoomout, None), - (wheel, {'delta': 10}, base, None), - (button4, {}, zoomin, ''), - (wheel, {'delta': -10}, base, None), - (button5, {}, zoomout, '')) + larger = [15, 18, 20, 13, 13, 11] + smaller = [13, 15, 17, 11, 11, 9] + + tests = (('<>', larger), + ('<>', base), + ('<>', smaller), + ('<>', base)) eq(self.get_sizes(), base) - for event, kw, result, after in tests: + for event, result in tests: with self.subTest(event=event): - text.event_generate(event, **kw) + text.event_generate(event) eq(self.get_sizes(), result) - if after: - text.event_generate(after) if __name__ == '__main__': From a23cb1cd69a72675552e51fbc4527c83520646b5 Mon Sep 17 00:00:00 2001 From: Cheryl Sabella Date: Thu, 27 Sep 2018 20:54:43 -0400 Subject: [PATCH 06/15] Move FontSizer to textview. --- Lib/idlelib/help.py | 70 +------------------------- Lib/idlelib/idle_test/test_help.py | 53 +------------------ Lib/idlelib/idle_test/test_textview.py | 48 +++++++++++++++++- Lib/idlelib/textview.py | 69 +++++++++++++++++++++++++ 4 files changed, 118 insertions(+), 122 deletions(-) diff --git a/Lib/idlelib/help.py b/Lib/idlelib/help.py index e9a06437381d82..402538df0199ec 100644 --- a/Lib/idlelib/help.py +++ b/Lib/idlelib/help.py @@ -24,8 +24,6 @@ show_idlehelp - Create HelpWindow. Called in EditorWindow.help_dialog. """ -import sys - from html.parser import HTMLParser from os.path import abspath, dirname, isfile, join from platform import python_version @@ -35,16 +33,13 @@ from tkinter import font as tkfont from idlelib.config import idleConf +from idlelib.textview import FontSizer ## About IDLE ## ## IDLE Help ## -darwin = sys.platform == 'darwin' -MINIMUM_FONT_SIZE = 6 -MAXIMUM_FONT_SIZE = 100 - class HelpParser(HTMLParser): """Render help.html into a text widget. @@ -172,69 +167,6 @@ def handle_data(self, data): self.text.insert('end', d, (self.tags, self.chartags)) -class FontSizer: - "Support dynamic widget font resizing." - def __init__(self, widget, callback=None): - """"Add font resizing functionality to widget. - - Args: - widget: Tk widget with font attribute to size. - callback: Function to call for additional font resizing - based on widget's font attribute. - """ - self.widget = widget - self.callback = callback - self.bind_events() - - def bind_events(self): - "Bind events to the widget." - shortcut = 'Command' if darwin else 'Control' - # Bind to keys with or without shift. - self.widget.event_add( - '<>', - f'<{shortcut}-Key-equal>', f'<{shortcut}-Key-plus>') - self.widget.bind('<>', self.increase_font_size) - - self.widget.event_add( - '<>', - f'<{shortcut}-Key-minus>', f'<{shortcut}-Key-underscore>') - self.widget.bind('<>', self.decrease_font_size) - - # Windows and Mac use MouseWheel. - self.widget.bind('', self.update_mousewheel) - # Linux uses Button 4 (scroll down) and Button 5 (scroll up). - self.widget.bind('', self.update_mousewheel) - self.widget.bind('', self.update_mousewheel) - - def set_text_size(self, new_size): - "Set the font size for this widget." - font = tkfont.Font(self.widget, name=self.widget['font'], exists=True) - size = new_size(font) - font['size'] = size - if self.callback: - self.callback(size) - return 'break' - - def increase_font_size(self, event=None): - "Make font size larger." - def new_size(font): - return min(font['size'] + 1, MAXIMUM_FONT_SIZE) - return self.set_text_size(new_size) - - def decrease_font_size(self, event=None): - "Make font size smaller." - def new_size(font): - return max(font['size'] - 1, MINIMUM_FONT_SIZE) - return self.set_text_size(new_size) - - def update_mousewheel(self, event): - "Adjust font size based on mouse wheel direction." - if (event.delta < 0) == (not darwin): - return self.decrease_font_size() - else: - return self.increase_font_size() - - class HelpText(Text): "Display help.html." def __init__(self, parent, filename): diff --git a/Lib/idlelib/idle_test/test_help.py b/Lib/idlelib/idle_test/test_help.py index 31b55b0cf474cb..b74f2c8cf37f13 100644 --- a/Lib/idlelib/idle_test/test_help.py +++ b/Lib/idlelib/idle_test/test_help.py @@ -7,8 +7,7 @@ from test.support import requires requires('gui') from os.path import abspath, dirname, join -from tkinter import Tk, Text, TclError -from tkinter import font as tkfont +from tkinter import Tk from idlelib import config darwin = sys.platform == 'darwin' @@ -43,56 +42,6 @@ def test_line1(self): self.assertEqual(text.get('1.0', '1.end'), ' IDLE ') -class FontSizerTest(unittest.TestCase): - - @classmethod - def setUpClass(cls): - cls.root = root = Tk() - root.withdraw() - cls.text = Text(root) - - @classmethod - def tearDownClass(cls): - del cls.text - cls.root.update_idletasks() - cls.root.destroy() - del cls.root - - def setUp(self): - text = self.text - self.font = font = tkfont.Font(text, ('courier', 30)) - text['font'] = font - text.insert('end', 'Test Text') - self.sizer = help.FontSizer(text) - - def tearDown(self): - del self.sizer, self.font - - def test_increase_font_size(self): - text = self.text - font = self.font - eq = self.assertEqual - text.focus_set() - - eq(font['size'], 30) - text.event_generate('<>') - eq(font['size'], 31) - text.event_generate('<>') - eq(font['size'], 32) - - def test_decrease_font_size(self): - text = self.text - font = self.font - eq = self.assertEqual - text.focus_set() - - eq(font['size'], 30) - text.event_generate('<>') - eq(font['size'], 29) - text.event_generate('<>') - eq(font['size'], 28) - - class HelpTextTest(unittest.TestCase): @classmethod diff --git a/Lib/idlelib/idle_test/test_textview.py b/Lib/idlelib/idle_test/test_textview.py index 7189378ab3dd61..6f249beb080ac3 100644 --- a/Lib/idlelib/idle_test/test_textview.py +++ b/Lib/idlelib/idle_test/test_textview.py @@ -11,8 +11,9 @@ import os import unittest -from tkinter import Tk, TclError, CHAR, NONE, WORD +from tkinter import Tk, Text, TclError, CHAR, NONE, WORD from tkinter.ttk import Button +from tkinter import font as tkfont from idlelib.idle_test.mock_idle import Func from idlelib.idle_test.mock_tk import Mbox_func @@ -229,5 +230,50 @@ def _command(): self.assertEqual(get('3.0', '3.end'), f.readline().strip()) +class FontSizerTest(unittest.TestCase): + + @classmethod + def setUpClass(cls): + cls.text = Text(root) + + @classmethod + def tearDownClass(cls): + del cls.text + + def setUp(self): + text = self.text + self.font = font = tkfont.Font(text, ('courier', 30)) + text['font'] = font + text.insert('end', 'Test Text') + self.sizer = tv.FontSizer(text) + + def tearDown(self): + del self.sizer, self.font + + def test_increase_font_size(self): + text = self.text + font = self.font + eq = self.assertEqual + text.focus_set() + + eq(font['size'], 30) + text.event_generate('<>') + eq(font['size'], 31) + text.event_generate('<>') + eq(font['size'], 32) + + def test_decrease_font_size(self): + text = self.text + font = self.font + eq = self.assertEqual + text.focus_set() + + eq(font['size'], 30) + text.event_generate('<>') + eq(font['size'], 29) + text.event_generate('<>') + eq(font['size'], 28) + + if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/Lib/idlelib/textview.py b/Lib/idlelib/textview.py index a66c1a4309a617..0635b2216330d1 100644 --- a/Lib/idlelib/textview.py +++ b/Lib/idlelib/textview.py @@ -1,13 +1,82 @@ """Simple text browser for IDLE """ +import sys from tkinter import Toplevel, Text, TclError,\ HORIZONTAL, VERTICAL, NS, EW, NSEW, NONE, WORD, SUNKEN +from tkinter.font import Font from tkinter.ttk import Frame, Scrollbar, Button from tkinter.messagebox import showerror from idlelib.colorizer import color_config +darwin = sys.platform == 'darwin' +MINIMUM_FONT_SIZE = 6 +MAXIMUM_FONT_SIZE = 100 + + +class FontSizer: + "Support dynamic text font resizing." + def __init__(self, text, callback=None): + """"Add font resizing functionality to text widget. + + Args: + text: Tk widget with font attribute to size. + callback: Function to call for additional font resizing + based on widget's font attribute. + """ + self.text = text + self.callback = callback + self.bind_events() + + def bind_events(self): + "Bind events to the widget." + shortcut = 'Command' if darwin else 'Control' + # Bind to keys with or without shift. + self.text.event_add( + '<>', + f'<{shortcut}-Key-equal>', f'<{shortcut}-Key-plus>') + self.text.bind('<>', self.increase_font_size) + + self.text.event_add( + '<>', + f'<{shortcut}-Key-minus>', f'<{shortcut}-Key-underscore>') + self.text.bind('<>', self.decrease_font_size) + + # Windows and Mac use MouseWheel. + self.text.bind('', self.update_mousewheel) + # Linux uses Button 4 and Button 5 for the mousewheel. + self.text.bind('', self.decrease_font_size) + self.text.bind('', self.increase_font_size) + + def set_text_size(self, new_size): + "Set the font size for this widget." + font = Font(self.text, name=self.text['font'], exists=True) + size = new_size(font) + font['size'] = size + if self.callback: + self.callback(size) + return 'break' + + def increase_font_size(self, event=None): + "Make font size larger." + def new_size(font): + return min(font['size'] + 1, MAXIMUM_FONT_SIZE) + return self.set_text_size(new_size) + + def decrease_font_size(self, event=None): + "Make font size smaller." + def new_size(font): + return max(font['size'] - 1, MINIMUM_FONT_SIZE) + return self.set_text_size(new_size) + + def update_mousewheel(self, event): + "Adjust font size based on mouse wheel direction." + if (event.delta < 0) == (not darwin): + return self.decrease_font_size() + else: + return self.increase_font_size() + class AutoHideScrollbar(Scrollbar): """A scrollbar that is automatically hidden when not needed. From e88f488046340d62094420f5aed5c0b9f7d12332 Mon Sep 17 00:00:00 2001 From: Cheryl Sabella Date: Sat, 15 Dec 2018 18:26:36 -0500 Subject: [PATCH 07/15] Retain Close button when increasing font size. --- Lib/idlelib/textview.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/idlelib/textview.py b/Lib/idlelib/textview.py index 0635b2216330d1..8fbbcfd40ec386 100644 --- a/Lib/idlelib/textview.py +++ b/Lib/idlelib/textview.py @@ -163,8 +163,8 @@ def __init__(self, parent, contents, wrap='word'): self.button_ok = button_ok = Button( self, text='Close', command=self.ok, takefocus=False) - self.textframe.pack(side='top', expand=True, fill='both') button_ok.pack(side='bottom') + self.textframe.pack(side='top', expand=True, fill='both') def ok(self, event=None): """Dismiss text viewer dialog.""" From 90cb041063eb54af702c6f42f743e3ba6dca5b1e Mon Sep 17 00:00:00 2001 From: Cheryl Sabella Date: Sat, 2 Feb 2019 12:34:59 -0500 Subject: [PATCH 08/15] Use decorator for changing text widget --- Lib/idlelib/textview.py | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/Lib/idlelib/textview.py b/Lib/idlelib/textview.py index 8fbbcfd40ec386..1429bb79d6d844 100644 --- a/Lib/idlelib/textview.py +++ b/Lib/idlelib/textview.py @@ -49,26 +49,26 @@ def bind_events(self): self.text.bind('', self.decrease_font_size) self.text.bind('', self.increase_font_size) - def set_text_size(self, new_size): + def set_text_fontsize(new_size): "Set the font size for this widget." - font = Font(self.text, name=self.text['font'], exists=True) - size = new_size(font) - font['size'] = size - if self.callback: - self.callback(size) - return 'break' - - def increase_font_size(self, event=None): + def sizer(self, event=None): + font = Font(self.text, name=self.text['font'], exists=True) + size = new_size(font['size']) + font['size'] = size + if self.callback: + self.callback(size) + return 'break' + return sizer + + @set_text_fontsize + def increase_font_size(fontsize): "Make font size larger." - def new_size(font): - return min(font['size'] + 1, MAXIMUM_FONT_SIZE) - return self.set_text_size(new_size) + return min(fontsize + 1, MAXIMUM_FONT_SIZE) - def decrease_font_size(self, event=None): + @set_text_fontsize + def decrease_font_size(fontsize): "Make font size smaller." - def new_size(font): - return max(font['size'] - 1, MINIMUM_FONT_SIZE) - return self.set_text_size(new_size) + return max(fontsize - 1, MINIMUM_FONT_SIZE) def update_mousewheel(self, event): "Adjust font size based on mouse wheel direction." From e03e4b27e787d957ebb483c0963253c21c128b1a Mon Sep 17 00:00:00 2001 From: Cheryl Sabella Date: Sat, 2 Feb 2019 12:54:17 -0500 Subject: [PATCH 09/15] Adjust font tag sizes within FontSizer. * Remove use of callback to adjust fonts in tags. * Adjust tag font sizes the same as the main font. * TODO: Tags don't retain ratio, so after they all go to min size, they don't recover original ratios. --- Lib/idlelib/help.py | 2 +- Lib/idlelib/idle_test/test_help.py | 4 ++-- Lib/idlelib/textview.py | 21 +++++++++++---------- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/Lib/idlelib/help.py b/Lib/idlelib/help.py index 402538df0199ec..f24bef367ad38e 100644 --- a/Lib/idlelib/help.py +++ b/Lib/idlelib/help.py @@ -207,7 +207,7 @@ def create_fonts(self): fonts[tag] = tkfont.Font(self, family=fixedfont) self.scale_tagfonts(base_size) - FontSizer(self, self.scale_tagfonts) + FontSizer(self) def findfont(self, names): "Return name of first font family derived from names." diff --git a/Lib/idlelib/idle_test/test_help.py b/Lib/idlelib/idle_test/test_help.py index b74f2c8cf37f13..d676d95b79ff60 100644 --- a/Lib/idlelib/idle_test/test_help.py +++ b/Lib/idlelib/idle_test/test_help.py @@ -84,8 +84,8 @@ def test_resizing_callback(self): eq = self.assertEqual base = [14, 16, 19, 12, 12, 10] - larger = [15, 18, 20, 13, 13, 11] - smaller = [13, 15, 17, 11, 11, 9] + larger = [15, 17, 20, 13, 13, 11] + smaller = [13, 15, 18, 11, 11, 9] tests = (('<>', larger), ('<>', base), diff --git a/Lib/idlelib/textview.py b/Lib/idlelib/textview.py index 1429bb79d6d844..ea5730c119b252 100644 --- a/Lib/idlelib/textview.py +++ b/Lib/idlelib/textview.py @@ -17,16 +17,13 @@ class FontSizer: "Support dynamic text font resizing." - def __init__(self, text, callback=None): + def __init__(self, text): """"Add font resizing functionality to text widget. Args: text: Tk widget with font attribute to size. - callback: Function to call for additional font resizing - based on widget's font attribute. """ self.text = text - self.callback = callback self.bind_events() def bind_events(self): @@ -50,13 +47,17 @@ def bind_events(self): self.text.bind('', self.increase_font_size) def set_text_fontsize(new_size): - "Set the font size for this widget." def sizer(self, event=None): - font = Font(self.text, name=self.text['font'], exists=True) - size = new_size(font['size']) - font['size'] = size - if self.callback: - self.callback(size) + "Set the font size for this widget and its tags." + def resize(fontname): + font = Font(self.text, name=fontname, exists=True) + font['size'] = new_size(font['size']) + + resize(self.text['font']) + for tag in self.text.tag_names(): + tag_font = self.text.tag_cget(tag, 'font') + if tag_font: + resize(tag_font) return 'break' return sizer From 645a0e0cedf96f4d70c69a5f158270edad304816 Mon Sep 17 00:00:00 2001 From: Cheryl Sabella Date: Sat, 2 Feb 2019 19:10:44 -0500 Subject: [PATCH 10/15] Allow for sizing fonts defined in different ways. * Originally resized Font objects, but now can resize fonts defined as tuples. --- Lib/idlelib/editor.py | 2 ++ Lib/idlelib/textview.py | 17 ++++++++++++----- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/Lib/idlelib/editor.py b/Lib/idlelib/editor.py index a178eaf93c013a..b361572dd8ff2e 100644 --- a/Lib/idlelib/editor.py +++ b/Lib/idlelib/editor.py @@ -28,6 +28,7 @@ from idlelib import search from idlelib.tree import wheel_event from idlelib import window +from idlelib.textview import FontSizer # The default tab setting for a Text widget, in average-width characters. TK_TABWIDTH_DEFAULT = 8 @@ -134,6 +135,7 @@ def __init__(self, flist=None, filename=None, key=None, root=None): 'main', 'EditorWindow', 'height', type='int'), } self.text = text = MultiCallCreator(Text)(text_frame, **text_options) + FontSizer(text) self.top.focused_widget = self.text self.createmenubar() diff --git a/Lib/idlelib/textview.py b/Lib/idlelib/textview.py index ea5730c119b252..5075602cd0b502 100644 --- a/Lib/idlelib/textview.py +++ b/Lib/idlelib/textview.py @@ -50,14 +50,21 @@ def set_text_fontsize(new_size): def sizer(self, event=None): "Set the font size for this widget and its tags." def resize(fontname): - font = Font(self.text, name=fontname, exists=True) - font['size'] = new_size(font['size']) - - resize(self.text['font']) + try: + font = Font(self.text, name=fontname, exists=True) + font['size'] = new_size(font['size']) + return font + except TclError: + font = self.text.tk.split(fontname) + if len(font) < 2: + return font + return (font[0], new_size(int(font[1])), font[2:]) + + self.text['font'] = resize(self.text['font']) for tag in self.text.tag_names(): tag_font = self.text.tag_cget(tag, 'font') if tag_font: - resize(tag_font) + tag_font = resize(tag_font) return 'break' return sizer From 5c1d58ab8cac608b4df63f1e2704bffe0fff5a95 Mon Sep 17 00:00:00 2001 From: Cheryl Sabella Date: Sat, 2 Feb 2019 19:48:03 -0500 Subject: [PATCH 11/15] Add tests for fonts defined as tuples. --- Lib/idlelib/idle_test/test_textview.py | 36 ++++++++++++++++++++++---- Lib/idlelib/textview.py | 5 ++-- 2 files changed, 34 insertions(+), 7 deletions(-) diff --git a/Lib/idlelib/idle_test/test_textview.py b/Lib/idlelib/idle_test/test_textview.py index 6f249beb080ac3..8cbde2f343f7c8 100644 --- a/Lib/idlelib/idle_test/test_textview.py +++ b/Lib/idlelib/idle_test/test_textview.py @@ -242,17 +242,16 @@ def tearDownClass(cls): def setUp(self): text = self.text - self.font = font = tkfont.Font(text, ('courier', 30)) - text['font'] = font text.insert('end', 'Test Text') self.sizer = tv.FontSizer(text) def tearDown(self): - del self.sizer, self.font + del self.sizer def test_increase_font_size(self): text = self.text - font = self.font + font = tkfont.Font(text, ('courier', 30)) + text['font'] = font eq = self.assertEqual text.focus_set() @@ -264,7 +263,8 @@ def test_increase_font_size(self): def test_decrease_font_size(self): text = self.text - font = self.font + font = tkfont.Font(text, ('courier', 30)) + text['font'] = font eq = self.assertEqual text.focus_set() @@ -274,6 +274,32 @@ def test_decrease_font_size(self): text.event_generate('<>') eq(font['size'], 28) + def test_increase_font_size_tuple(self): + text = self.text + font = ('Arial', 45, 'bold italic') + text['font'] = font + eq = self.assertEqual + text.focus_set() + + eq(text.tk.split(text['font'])[1], '45') + text.event_generate('<>') + eq(text.tk.split(text['font'])[1], '46') + text.event_generate('<>') + eq(text.tk.split(text['font'])[1], '47') + + def test_decrease_font_size_tuple(self): + text = self.text + font = ('Arial', 45) + text['font'] = font + eq = self.assertEqual + text.focus_set() + + eq(text.tk.split(text['font'])[1], '45') + text.event_generate('<>') + eq(text.tk.split(text['font'])[1], '44') + text.event_generate('<>') + eq(text.tk.split(text['font'])[1], '43') + if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/Lib/idlelib/textview.py b/Lib/idlelib/textview.py index 5075602cd0b502..acd1bf229642d4 100644 --- a/Lib/idlelib/textview.py +++ b/Lib/idlelib/textview.py @@ -55,10 +55,11 @@ def resize(fontname): font['size'] = new_size(font['size']) return font except TclError: - font = self.text.tk.split(fontname) + font = list(self.text.tk.split(fontname)) if len(font) < 2: return font - return (font[0], new_size(int(font[1])), font[2:]) + font[1] = new_size(int(font[1])) + return font self.text['font'] = resize(self.text['font']) for tag in self.text.tag_names(): From 98295cb77fa00278fac6debe73bcb5a30b86fa41 Mon Sep 17 00:00:00 2001 From: Cheryl Sabella Date: Sat, 2 Feb 2019 19:59:41 -0500 Subject: [PATCH 12/15] Refactor return values --- Lib/idlelib/textview.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/Lib/idlelib/textview.py b/Lib/idlelib/textview.py index acd1bf229642d4..3f8eb5ab355382 100644 --- a/Lib/idlelib/textview.py +++ b/Lib/idlelib/textview.py @@ -53,13 +53,11 @@ def resize(fontname): try: font = Font(self.text, name=fontname, exists=True) font['size'] = new_size(font['size']) - return font except TclError: font = list(self.text.tk.split(fontname)) - if len(font) < 2: - return font - font[1] = new_size(int(font[1])) - return font + if len(font) > 1: + font[1] = new_size(int(font[1])) + return font self.text['font'] = resize(self.text['font']) for tag in self.text.tag_names(): From d6f80e81820e9c00f69aebd870b1574d9e6e3c2d Mon Sep 17 00:00:00 2001 From: Cheryl Sabella Date: Sun, 9 Feb 2020 14:19:01 -0500 Subject: [PATCH 13/15] Update split to splitlist --- Lib/idlelib/idle_test/test_textview.py | 12 ++++++------ Lib/idlelib/textview.py | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Lib/idlelib/idle_test/test_textview.py b/Lib/idlelib/idle_test/test_textview.py index 8cbde2f343f7c8..89a89056e17f66 100644 --- a/Lib/idlelib/idle_test/test_textview.py +++ b/Lib/idlelib/idle_test/test_textview.py @@ -281,11 +281,11 @@ def test_increase_font_size_tuple(self): eq = self.assertEqual text.focus_set() - eq(text.tk.split(text['font'])[1], '45') + eq(text.tk.splitlist(text['font'])[1], '45') text.event_generate('<>') - eq(text.tk.split(text['font'])[1], '46') + eq(text.tk.splitlist(text['font'])[1], '46') text.event_generate('<>') - eq(text.tk.split(text['font'])[1], '47') + eq(text.tk.splitlist(text['font'])[1], '47') def test_decrease_font_size_tuple(self): text = self.text @@ -294,11 +294,11 @@ def test_decrease_font_size_tuple(self): eq = self.assertEqual text.focus_set() - eq(text.tk.split(text['font'])[1], '45') + eq(text.tk.splitlist(text['font'])[1], '45') text.event_generate('<>') - eq(text.tk.split(text['font'])[1], '44') + eq(text.tk.splitlist(text['font'])[1], '44') text.event_generate('<>') - eq(text.tk.split(text['font'])[1], '43') + eq(text.tk.splitlist(text['font'])[1], '43') if __name__ == '__main__': diff --git a/Lib/idlelib/textview.py b/Lib/idlelib/textview.py index 3f8eb5ab355382..0bdffded61ebfd 100644 --- a/Lib/idlelib/textview.py +++ b/Lib/idlelib/textview.py @@ -54,7 +54,7 @@ def resize(fontname): font = Font(self.text, name=fontname, exists=True) font['size'] = new_size(font['size']) except TclError: - font = list(self.text.tk.split(fontname)) + font = list(self.text.tk.splitlist(fontname)) if len(font) > 1: font[1] = new_size(int(font[1])) return font From dab3eb55e46fd139c08105742b914df13e9f3eaa Mon Sep 17 00:00:00 2001 From: Cheryl Sabella Date: Sun, 9 Feb 2020 14:26:53 -0500 Subject: [PATCH 14/15] Remove unrelated changes to editor.py --- Lib/idlelib/editor.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/Lib/idlelib/editor.py b/Lib/idlelib/editor.py index b361572dd8ff2e..a178eaf93c013a 100644 --- a/Lib/idlelib/editor.py +++ b/Lib/idlelib/editor.py @@ -28,7 +28,6 @@ from idlelib import search from idlelib.tree import wheel_event from idlelib import window -from idlelib.textview import FontSizer # The default tab setting for a Text widget, in average-width characters. TK_TABWIDTH_DEFAULT = 8 @@ -135,7 +134,6 @@ def __init__(self, flist=None, filename=None, key=None, root=None): 'main', 'EditorWindow', 'height', type='int'), } self.text = text = MultiCallCreator(Text)(text_frame, **text_options) - FontSizer(text) self.top.focused_widget = self.text self.createmenubar() From 26b3342d3bbc26c7e8acb7178786e4b50f674c5f Mon Sep 17 00:00:00 2001 From: Terry Jan Reedy Date: Mon, 21 Sep 2020 08:37:12 -0400 Subject: [PATCH 15/15] Update 2018-04-30-19-11-09.bpo-25198.yzJ3SL.rst --- Misc/NEWS.d/next/IDLE/2018-04-30-19-11-09.bpo-25198.yzJ3SL.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/IDLE/2018-04-30-19-11-09.bpo-25198.yzJ3SL.rst b/Misc/NEWS.d/next/IDLE/2018-04-30-19-11-09.bpo-25198.yzJ3SL.rst index d804af09c6278a..c29acc97c38e5e 100644 --- a/Misc/NEWS.d/next/IDLE/2018-04-30-19-11-09.bpo-25198.yzJ3SL.rst +++ b/Misc/NEWS.d/next/IDLE/2018-04-30-19-11-09.bpo-25198.yzJ3SL.rst @@ -1,2 +1,2 @@ -Use user preferences to set font size in IDLE Help and allow dynamic font +Use user preferences to set font size in IDLE Textview and Help and allow dynamic font sizing using keybindings or mouse wheel.