| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -33,6 +33,7 @@ alternative `GUI frameworks and tools <https://wiki.python.org/moin/GuiProgrammi | |||
| 33 | 33 | tkinter.rst | |
| 34 | 34 | tkinter.colorchooser.rst | |
| 35 | 35 | tkinter.font.rst | |
| 36 | + tkinter.fontchooser.rst | ||
| 36 | 37 | dialog.rst | |
| 37 | 38 | tkinter.messagebox.rst | |
| 38 | 39 | tkinter.scrolledtext.rst | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,90 @@ | |||
| 1 | + :mod:`!tkinter.fontchooser` --- Font selection dialog | ||
| 2 | + ===================================================== | ||
| 3 | + | ||
| 4 | + .. module:: tkinter.fontchooser | ||
| 5 | + :synopsis: Font selection dialog | ||
| 6 | + | ||
| 7 | + .. versionadded:: next | ||
| 8 | + | ||
| 9 | + **Source code:** :source:`Lib/tkinter/fontchooser.py` | ||
| 10 | + | ||
| 11 | + -------------- | ||
| 12 | + | ||
| 13 | + The :mod:`!tkinter.fontchooser` module provides the :class:`FontChooser` class | ||
| 14 | + as an interface to the native font selection dialog. | ||
| 15 | + | ||
| 16 | + The font dialog is application-global: | ||
| 17 | + there is a single font dialog per Tcl interpreter, | ||
| 18 | + and all :class:`FontChooser` instances configure the same dialog. | ||
| 19 | + | ||
| 20 | + Depending on the platform, the dialog may be modal or modeless, | ||
| 21 | + so :meth:`~FontChooser.show` may return immediately. | ||
| 22 | + The selected font is not returned: | ||
| 23 | + it is passed as a :class:`~tkinter.font.Font` object to the callback | ||
| 24 | + specified with the *command* option. | ||
| 25 | + | ||
| 26 | + The dialog also generates two virtual events on the parent window | ||
| 27 | + (see the *parent* option): | ||
| 28 | + | ||
| 29 | + ``<<TkFontchooserVisibility>>`` | ||
| 30 | + Generated when the dialog is shown or hidden. | ||
| 31 | + Query the *visible* option to tell which. | ||
| 32 | + | ||
| 33 | + ``<<TkFontchooserFontChanged>>`` | ||
| 34 | + Generated when the selected font changes. | ||
| 35 | + | ||
| 36 | + .. note:: | ||
| 37 | + | ||
| 38 | + The *command* callback is the only reliable way to obtain the selected font. | ||
| 39 | + On some platforms the *font* option is not updated to the user's choice. | ||
| 40 | + | ||
| 41 | + .. class:: FontChooser(master=None, **options) | ||
| 42 | + | ||
| 43 | + The class implementing the font selection dialog. | ||
| 44 | + | ||
| 45 | + *master* is the widget whose Tcl interpreter owns the dialog. | ||
| 46 | + If omitted, it defaults to *parent* if that is given, | ||
| 47 | + or to the default root window otherwise. | ||
| 48 | + | ||
| 49 | + The supported configuration options are: | ||
| 50 | + | ||
| 51 | + * *parent* --- the window to which the dialog and its virtual events are related. | ||
| 52 | + It defaults to the main window; | ||
| 53 | + on macOS the dialog is shown as a sheet attached to it, | ||
| 54 | + rather than as a free-standing panel. | ||
| 55 | + * *title* --- the title of the dialog. | ||
| 56 | + * *font* --- the font that is currently selected in the dialog. | ||
| 57 | + * *command* --- a callback that is called | ||
| 58 | + with a :class:`~tkinter.font.Font` object wrapping the selected font | ||
| 59 | + when the user selects a font. | ||
| 60 | + * *visible* --- whether the dialog is currently displayed (read-only). | ||
| 61 | + | ||
| 62 | + The *font* option accepts the forms supported by :class:`tkinter.font.Font`. | ||
| 63 | + | ||
| 64 | + .. method:: configure(**options) | ||
| 65 | + config(**options) | ||
| 66 | + | ||
| 67 | + Query or modify the options of the font dialog. | ||
| 68 | + With no arguments, return a dict of all option values. | ||
| 69 | + With a string argument, return the value of that option. | ||
| 70 | + Otherwise, set the given options. | ||
| 71 | + | ||
| 72 | + .. method:: cget(option) | ||
| 73 | + | ||
| 74 | + Return the value of the given option of the font dialog. | ||
| 75 | + | ||
| 76 | + .. method:: show() | ||
| 77 | + | ||
| 78 | + Display the font dialog. | ||
| 79 | + Depending on the platform, this method may return immediately | ||
| 80 | + or only once the dialog has been withdrawn. | ||
| 81 | + | ||
| 82 | + .. method:: hide() | ||
| 83 | + | ||
| 84 | + Hide the font dialog if it is displayed. | ||
| 85 | + | ||
| 86 | + | ||
| 87 | + .. seealso:: | ||
| 88 | + | ||
| 89 | + Module :mod:`tkinter.font` | ||
| 90 | + Tkinter font-handling utilities | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -146,6 +146,9 @@ The modules that provide Tk support include: | |||
| 146 | 146 | :mod:`tkinter.font` | |
| 147 | 147 | Utilities to help work with fonts. | |
| 148 | 148 | ||
| 149 | + :mod:`tkinter.fontchooser` | ||
| 150 | + Dialog to let the user choose a font. | ||
| 151 | + | ||
| 149 | 152 | :mod:`tkinter.messagebox` | |
| 150 | 153 | Access to standard Tk dialog boxes. | |
| 151 | 154 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -478,6 +478,11 @@ tkinter | |||
| 478 | 478 | :meth:`~tkinter.font.Font.measure` and :meth:`~tkinter.font.Font.metrics`. | |
| 479 | 479 | (Contributed by Serhiy Storchaka in :gh:`143990`.) | |
| 480 | 480 | ||
| 481 | + * Added the :mod:`tkinter.fontchooser` module which provides the | ||
| 482 | + :class:`~tkinter.fontchooser.FontChooser` class as an interface to the | ||
| 483 | + native font selection dialog. | ||
| 484 | + (Contributed by Serhiy Storchaka in :gh:`72880`.) | ||
| 485 | + | ||
| 481 | 486 | * Values of several Tcl object types returned by :mod:`tkinter` are now | |
| 482 | 487 | converted to the corresponding Python type instead of being wrapped in a | |
| 483 | 488 | :class:`!_tkinter.Tcl_Obj`: ``index``, ``window``, ``nsName`` and | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,145 @@ | |||
| 1 | + import time | ||
| 2 | + import unittest | ||
| 3 | + import tkinter | ||
| 4 | + from tkinter import font | ||
| 5 | + from tkinter.fontchooser import FontChooser | ||
| 6 | + from test import support | ||
| 7 | + from test.support import requires | ||
| 8 | + from test.test_tkinter.support import (AbstractTkTest, | ||
| 9 | + AbstractDefaultRootTest, | ||
| 10 | + setUpModule) # noqa: F401 | ||
| 11 | + | ||
| 12 | + requires('gui') | ||
| 13 | + | ||
| 14 | + | ||
| 15 | + class FontChooserTest(AbstractTkTest, unittest.TestCase): | ||
| 16 | + | ||
| 17 | + def setUp(self): | ||
| 18 | + super().setUp() | ||
| 19 | + self.fc = FontChooser(self.root) | ||
| 20 | + self.addCleanup(self._reset) | ||
| 21 | + | ||
| 22 | + def _reset(self): | ||
| 23 | + # The font dialog is global for the interpreter, so restore | ||
| 24 | + # a clean state for the next test. | ||
| 25 | + try: | ||
| 26 | + self.fc.configure(parent=self.root, title='', command=None) | ||
| 27 | + self.fc.hide() | ||
| 28 | + except tkinter.TclError: | ||
| 29 | + pass | ||
| 30 | + | ||
| 31 | + def _visible(self): | ||
| 32 | + return self.root.tk.getboolean(self.fc.cget('visible')) | ||
| 33 | + | ||
| 34 | + def _wait_visibility(self, expected, timeout=None): | ||
| 35 | + # Bounded wait until the dialog visibility matches expected. | ||
| 36 | + if timeout is None: | ||
| 37 | + timeout = support.LOOPBACK_TIMEOUT | ||
| 38 | + deadline = time.monotonic() + timeout | ||
| 39 | + while time.monotonic() < deadline: | ||
| 40 | + self.root.update() | ||
| 41 | + if self._visible() == expected: | ||
| 42 | + return True | ||
| 43 | + time.sleep(0.01) | ||
| 44 | + return False | ||
| 45 | + | ||
| 46 | + def test_configure_query(self): | ||
| 47 | + options = self.fc.configure() | ||
| 48 | + self.assertIsInstance(options, dict) | ||
| 49 | + self.assertLessEqual({'parent', 'title', 'font', 'command', 'visible'}, | ||
| 50 | + options.keys()) | ||
| 51 | + | ||
| 52 | + def test_configure(self): | ||
| 53 | + self.fc.configure(title='Pick a font') | ||
| 54 | + if self.root._windowingsystem == 'x11': | ||
| 55 | + self.assertEqual(self.fc.cget('title'), 'Pick a font') | ||
| 56 | + self.fc.configure(font='Courier 10') | ||
| 57 | + self.assertTrue(self.fc.cget('font')) | ||
| 58 | + if self.root._windowingsystem == 'x11': | ||
| 59 | + self.assertEqual(str(self.fc.cget('font')), 'Courier 10') | ||
| 60 | + | ||
| 61 | + def test_parent(self): | ||
| 62 | + top = tkinter.Toplevel(self.root) | ||
| 63 | + self.addCleanup(top.destroy) | ||
| 64 | + # The constructor does not force -parent to the master: it stays | ||
| 65 | + # the main window even when the master is another widget. | ||
| 66 | + FontChooser(top) | ||
| 67 | + self.assertEqual(self.fc.cget('parent'), str(self.root)) | ||
| 68 | + # -parent can be set explicitly. | ||
| 69 | + self.fc.configure(parent=top) | ||
| 70 | + self.assertEqual(self.fc.cget('parent'), str(top)) | ||
| 71 | + | ||
| 72 | + def test_configure_font_instance(self): | ||
| 73 | + # A Font instance can be passed as the font, both to the | ||
| 74 | + # constructor and to configure(). The dialog may store it as | ||
| 75 | + # the font name or as a resolved description depending on the | ||
| 76 | + # platform, so compare the actual attributes. | ||
| 77 | + def actual(spec): | ||
| 78 | + return font.Font(self.root, spec, exists=True).actual() | ||
| 79 | + f = font.Font(self.root, family='Courier', size=14, weight='bold') | ||
| 80 | + fc = FontChooser(self.root, font=f) | ||
| 81 | + self.assertEqual(actual(fc.cget('font')), f.actual()) | ||
| 82 | + f2 = font.Font(self.root, family='Times', size=11) | ||
| 83 | + fc.configure(font=f2) | ||
| 84 | + self.assertEqual(actual(fc.cget('font')), f2.actual()) | ||
| 85 | + | ||
| 86 | + def test_configure_visible_readonly(self): | ||
| 87 | + with self.assertRaises(tkinter.TclError): | ||
| 88 | + self.fc.configure(visible=True) | ||
| 89 | + | ||
| 90 | + def test_cget_visible(self): | ||
| 91 | + self.assertFalse(self._visible()) | ||
| 92 | + | ||
| 93 | + def test_command(self): | ||
| 94 | + result = [] | ||
| 95 | + self.fc.configure(command=result.append) | ||
| 96 | + name = self.fc._command_name | ||
| 97 | + self.assertTrue(name) | ||
| 98 | + # The Tcl command is named after the wrapped callback. | ||
| 99 | + self.assertTrue(name.endswith('append'), name) | ||
| 100 | + # The callback receives a Font wrapping the selected font. | ||
| 101 | + self.root.tk.call(name, 'Courier 10') | ||
| 102 | + self.assertEqual(len(result), 1) | ||
| 103 | + selected = result[0] | ||
| 104 | + self.assertIsInstance(selected, font.Font) | ||
| 105 | + # The description is wrapped without creating a named font. | ||
| 106 | + self.assertEqual(str(selected), 'Courier 10') | ||
| 107 | + self.assertFalse(selected.delete_font) | ||
| 108 | + self.assertEqual(int(selected.actual('size')), 10) | ||
| 109 | + # Replacing the callback deletes the old Tcl command. | ||
| 110 | + self.fc.configure(command=lambda font: None) | ||
| 111 | + self.assertNotEqual(self.fc._command_name, name) | ||
| 112 | + self.assertFalse(self.root.tk.call('info', 'commands', name)) | ||
| 113 | + # Removing the callback deletes the Tcl command. | ||
| 114 | + name = self.fc._command_name | ||
| 115 | + self.fc.configure(command=None) | ||
| 116 | + self.assertIsNone(self.fc._command_name) | ||
| 117 | + self.assertFalse(self.root.tk.call('info', 'commands', name)) | ||
| 118 | + self.assertEqual(self.fc.cget('command'), '') | ||
| 119 | + | ||
| 120 | + def test_show_hide(self): | ||
| 121 | + if self.root._windowingsystem != 'x11': | ||
| 122 | + self.skipTest('cannot safely drive the native font dialog') | ||
| 123 | + events = [] | ||
| 124 | + self.root.bind('<<TkFontchooserVisibility>>', events.append) | ||
| 125 | + self.fc.show() | ||
| 126 | + if not self._wait_visibility(True): | ||
| 127 | + self.skipTest('the font dialog was not mapped') | ||
| 128 | + self.fc.hide() | ||
| 129 | + self.assertTrue(self._wait_visibility(False)) | ||
| 130 | + self.assertTrue(events) | ||
| 131 | + | ||
| 132 | + | ||
| 133 | + class DefaultRootTest(AbstractDefaultRootTest, unittest.TestCase): | ||
| 134 | + | ||
| 135 | + def test_fontchooser(self): | ||
| 136 | + root = tkinter.Tk() | ||
| 137 | + fc = FontChooser() | ||
| 138 | + self.assertIs(fc.master, root) | ||
| 139 | + root.destroy() | ||
| 140 | + tkinter.NoDefaultRoot() | ||
| 141 | + self.assertRaises(RuntimeError, FontChooser) | ||
| 142 | + | ||
| 143 | + | ||
| 144 | + if __name__ == "__main__": | ||
| 145 | + unittest.main() | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,108 @@ | |||
| 1 | + """Interface to the native font selection dialog. | ||
| 2 | + | ||
| 3 | + The FontChooser class gives access to the "tk fontchooser" command. | ||
| 4 | + The dialog is application-global: all instances configure the single | ||
| 5 | + dialog of the Tcl interpreter. | ||
| 6 | + | ||
| 7 | + The dialog may be modeless, so show() may return immediately. | ||
| 8 | + """ | ||
| 9 | + | ||
| 10 | + import tkinter | ||
| 11 | + from tkinter.font import Font | ||
| 12 | + | ||
| 13 | + __all__ = ["FontChooser"] | ||
| 14 | + | ||
| 15 | + | ||
| 16 | + class FontChooser: | ||
| 17 | + """The font selection dialog. | ||
| 18 | + | ||
| 19 | + Supported configuration options are: | ||
| 20 | + | ||
| 21 | + parent: the window to which the dialog and its virtual | ||
| 22 | + events are related (the main window by default) | ||
| 23 | + title: the title of the dialog | ||
| 24 | + font: the font that is currently selected in the dialog | ||
| 25 | + command: a callback that is called with a tkinter.font.Font | ||
| 26 | + object wrapping the selected font when the user | ||
| 27 | + selects a font | ||
| 28 | + visible: whether the dialog is currently displayed | ||
| 29 | + (read-only) | ||
| 30 | + | ||
| 31 | + The "font" option accepts the forms supported by | ||
| 32 | + tkinter.font.Font(font=...). | ||
| 33 | + | ||
| 34 | + The "command" callback is the only reliable way to obtain the | ||
| 35 | + selected font. On some platforms the "font" option is not updated | ||
| 36 | + to the user's choice. | ||
| 37 | + """ | ||
| 38 | + | ||
| 39 | + def __init__(self, master=None, **options): | ||
| 40 | + if master is None: | ||
| 41 | + master = options.get('parent') | ||
| 42 | + if master is None: | ||
| 43 | + master = tkinter._get_default_root('create a font chooser') | ||
| 44 | + self.master = master | ||
| 45 | + self._command_name = None | ||
| 46 | + if options: | ||
| 47 | + self.configure(options) | ||
| 48 | + | ||
| 49 | + def configure(self, cnf=None, **kw): | ||
| 50 | + """Query or modify the options of the font dialog. | ||
| 51 | + | ||
| 52 | + With no arguments, return a dict of all option values. With a | ||
| 53 | + string argument, return the value of that option. Otherwise, | ||
| 54 | + set the given options. The "visible" option is read-only. | ||
| 55 | + """ | ||
| 56 | + if kw: | ||
| 57 | + cnf = tkinter._cnfmerge((cnf, kw)) | ||
| 58 | + elif cnf: | ||
| 59 | + cnf = tkinter._cnfmerge(cnf) | ||
| 60 | + master = self.master | ||
| 61 | + if cnf is None: | ||
| 62 | + items = master.tk.splitlist(master.tk.call( | ||
| 63 | + 'tk', 'fontchooser', 'configure')) | ||
| 64 | + return {items[i][1:]: items[i+1] for i in range(0, len(items), 2)} | ||
| 65 | + if isinstance(cnf, str): | ||
| 66 | + return master.tk.call('tk', 'fontchooser', 'configure', '-' + cnf) | ||
| 67 | + cnf = dict(cnf) | ||
| 68 | + new_name = None | ||
| 69 | + if 'command' in cnf: | ||
| 70 | + command = cnf['command'] | ||
| 71 | + if command is None: | ||
| 72 | + cnf['command'] = '' | ||
| 73 | + elif callable(command): | ||
| 74 | + # Pass the selected font to the callback as a Font object. | ||
| 75 | + def callback(description): | ||
| 76 | + command(Font(self.master, font=description, exists=True)) | ||
| 77 | + # Name the Tcl command after the callback. | ||
| 78 | + callback.__func__ = command | ||
| 79 | + new_name = cnf['command'] = master._register(callback) | ||
| 80 | + try: | ||
| 81 | + master.tk.call('tk', 'fontchooser', 'configure', | ||
| 82 | + *master._options(cnf)) | ||
| 83 | + except tkinter.TclError: | ||
| 84 | + if new_name is not None: | ||
| 85 | + master.deletecommand(new_name) | ||
| 86 | + raise | ||
| 87 | + if 'command' in cnf: | ||
| 88 | + if self._command_name is not None: | ||
| 89 | + master.deletecommand(self._command_name) | ||
| 90 | + self._command_name = new_name | ||
| 91 | + config = configure | ||
| 92 | + | ||
| 93 | + def cget(self, option): | ||
| 94 | + """Return the value of the given option of the font dialog.""" | ||
| 95 | + return self.master.tk.call('tk', 'fontchooser', 'configure', | ||
| 96 | + '-' + option) | ||
| 97 | + | ||
| 98 | + def show(self): | ||
| 99 | + """Display the font dialog. | ||
| 100 | + | ||
| 101 | + Depending on the platform, may return immediately or only | ||
| 102 | + once the dialog has been withdrawn. | ||
| 103 | + """ | ||
| 104 | + self.master.tk.call('tk', 'fontchooser', 'show') | ||
| 105 | + | ||
| 106 | + def hide(self): | ||
| 107 | + """Hide the font dialog if it is displayed.""" | ||
| 108 | + self.master.tk.call('tk', 'fontchooser', 'hide') | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,3 @@ | |||
| 1 | + Added the :mod:`tkinter.fontchooser` module which provides the | ||
| 2 | + :class:`~tkinter.fontchooser.FontChooser` class as an interface to the | ||
| 3 | + native font selection dialog. | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments