diff options
Diffstat (limited to 'Lib/tkinter')
-rw-r--r-- | Lib/tkinter/__init__.py | 155 | ||||
-rw-r--r-- | Lib/tkinter/_fix.py | 4 | ||||
-rw-r--r-- | Lib/tkinter/filedialog.py | 2 | ||||
-rw-r--r-- | Lib/tkinter/font.py | 63 | ||||
-rw-r--r-- | Lib/tkinter/test/test_tkinter/test_variables.py | 165 |
5 files changed, 344 insertions, 45 deletions
diff --git a/Lib/tkinter/__init__.py b/Lib/tkinter/__init__.py index a6ad888..ea23705 100644 --- a/Lib/tkinter/__init__.py +++ b/Lib/tkinter/__init__.py @@ -30,17 +30,19 @@ button.pack(side=BOTTOM) tk.mainloop() """ -__version__ = "$Revision$" - import sys if sys.platform == "win32": # Attempt to configure Tcl/Tk without requiring PATH from tkinter import _fix + +import warnings + import _tkinter # If this fails your Python may not be configured for Tk TclError = _tkinter.TclError from tkinter.constants import * import re + wantobjects = 1 TkVersion = float(_tkinter.TK_VERSION) @@ -190,6 +192,7 @@ class Variable: Subclasses StringVar, IntVar, DoubleVar, BooleanVar are specializations that constrain the type of the value returned from get().""" _default = "" + _tk = None def __init__(self, master=None, value=None, name=None): """Construct a variable @@ -200,6 +203,11 @@ class Variable: If NAME matches an existing variable and VALUE is omitted then the existing value is retained. """ + # check for type of NAME parameter to override weird error message + # raised from Modules/_tkinter.c:SetVar like: + # TypeError: setvar() takes exactly 3 arguments (2 given) + if name is not None and not isinstance(name, str): + raise TypeError("name must be a string") global _varnum if not master: master = _default_root @@ -211,18 +219,21 @@ class Variable: self._name = 'PY_VAR' + repr(_varnum) _varnum += 1 if value is not None: - self.set(value) + self.initialize(value) elif not self._tk.call("info", "exists", self._name): - self.set(self._default) + self.initialize(self._default) def __del__(self): """Unset the variable in Tcl.""" - self._tk.globalunsetvar(self._name) + if (self._tk is not None and self._tk.call("info", "exists", + self._name)): + self._tk.globalunsetvar(self._name) def __str__(self): """Return the name of the variable in Tcl.""" return self._name def set(self, value): """Set the variable to VALUE.""" return self._tk.globalsetvar(self._name, value) + initialize = set def get(self): """Return value of variable.""" return self._tk.globalgetvar(self._name) @@ -297,12 +308,6 @@ class IntVar(Variable): """ Variable.__init__(self, master, value, name) - def set(self, value): - """Set the variable to value, converting booleans to integers.""" - if isinstance(value, bool): - value = int(value) - return Variable.set(self, value) - def get(self): """Return the value of the variable as an integer.""" return getint(self._tk.globalgetvar(self._name)) @@ -343,7 +348,10 @@ class BooleanVar(Variable): def get(self): """Return the value of the variable as a bool.""" - return self._tk.getboolean(self._tk.globalgetvar(self._name)) + try: + return self._tk.getboolean(self._tk.globalgetvar(self._name)) + except TclError: + raise ValueError("invalid literal for getboolean()") def mainloop(n=0): """Run the main loop of Tcl.""" @@ -355,7 +363,10 @@ getdouble = float def getboolean(s): """Convert true and false to integer values 1 and 0.""" - return _default_root.tk.getboolean(s) + try: + return _default_root.tk.getboolean(s) + except TclError: + raise ValueError("invalid literal for getboolean()") # Methods defined on both toplevel and interior widgets class Misc: @@ -445,7 +456,10 @@ class Misc: getdouble = float def getboolean(self, s): """Return a boolean value for Tcl boolean values true and false given as parameter.""" - return self.tk.getboolean(s) + try: + return self.tk.getboolean(s) + except TclError: + raise ValueError("invalid literal for getboolean()") def focus_set(self): """Direct input focus to this widget. @@ -1218,7 +1232,6 @@ class Misc: return (e,) def _report_exception(self): """Internal function.""" - import sys exc, val, tb = sys.exc_info() root = self._root() root.report_callback_exception(exc, val, tb) @@ -1293,6 +1306,13 @@ class Misc: self.tk.call( 'place', 'slaves', self._w))] # Grid methods that apply to the master + def grid_anchor(self, anchor=None): # new in Tk 8.5 + """The anchor value controls how to place the grid within the + master when no row/column has any weight. + + The default anchor is nw.""" + self.tk.call('grid', 'anchor', self._w, anchor) + anchor = grid_anchor def grid_bbox(self, column=None, row=None, col2=None, row2=None): """Return a tuple of integer coordinates for the bounding box of this widget controlled by the geometry manager grid. @@ -1311,7 +1331,6 @@ class Misc: if col2 is not None and row2 is not None: args = args + (col2, row2) return self._getints(self.tk.call(*args)) or None - bbox = grid_bbox def _grid_configure(self, command, index, cnf, kw): """Internal function.""" @@ -1570,6 +1589,14 @@ class Wm: the focus. Return current focus model if MODEL is None.""" return self.tk.call('wm', 'focusmodel', self._w, model) focusmodel = wm_focusmodel + def wm_forget(self, window): # new in Tk 8.5 + """The window will be unmappend from the screen and will no longer + be managed by wm. toplevel windows will be treated like frame + windows once they are no longer managed by wm, however, the menu + option configuration will be remembered and the menus will return + once the widget is managed again.""" + self.tk.call('wm', 'forget', window) + forget = wm_forget def wm_frame(self): """Return identifier for decorative frame of this widget if present.""" return self.tk.call('wm', 'frame', self._w) @@ -1623,6 +1650,31 @@ class Wm: None is given.""" return self.tk.call('wm', 'iconname', self._w, newName) iconname = wm_iconname + def wm_iconphoto(self, default=False, *args): # new in Tk 8.5 + """Sets the titlebar icon for this window based on the named photo + images passed through args. If default is True, this is applied to + all future created toplevels as well. + + The data in the images is taken as a snapshot at the time of + invocation. If the images are later changed, this is not reflected + to the titlebar icons. Multiple images are accepted to allow + different images sizes to be provided. The window manager may scale + provided icons to an appropriate size. + + On Windows, the images are packed into a Windows icon structure. + This will override an icon specified to wm_iconbitmap, and vice + versa. + + On X, the images are arranged into the _NET_WM_ICON X property, + which most modern window managers support. An icon specified by + wm_iconbitmap may exist simuultaneously. + + On Macintosh, this currently does nothing.""" + if default: + self.tk.call('wm', 'iconphoto', self._w, "-default", *args) + else: + self.tk.call('wm', 'iconphoto', self._w, *args) + iconphoto = wm_iconphoto def wm_iconposition(self, x=None, y=None): """Set the position of the icon of this widget to X and Y. Return a tuple of the current values of X and X if None is given.""" @@ -1634,6 +1686,12 @@ class Wm: value if None is given.""" return self.tk.call('wm', 'iconwindow', self._w, pathName) iconwindow = wm_iconwindow + def wm_manage(self, widget): # new in Tk 8.5 + """The widget specified will become a stand alone top-level window. + The window will be decorated with the window managers title bar, + etc.""" + self.tk.call('wm', 'manage', widget) + manage = wm_manage def wm_maxsize(self, width=None, height=None): """Set max WIDTH and HEIGHT for this widget. If the window is gridded the values are given in grid units. Return the current values if None @@ -1722,7 +1780,7 @@ class Tk(Misc, Wm): # ensure that self.tk is always _something_. self.tk = None if baseName is None: - import sys, os + import os baseName = os.path.basename(sys.argv[0]) baseName, ext = os.path.splitext(baseName) if ext not in ('.py', '.pyc', '.pyo'): @@ -1798,7 +1856,7 @@ class Tk(Misc, Wm): exec(open(base_py).read(), dir) def report_callback_exception(self, exc, val, tb): """Internal function. It reports exception on sys.stderr.""" - import traceback, sys + import traceback sys.stderr.write("Exception in Tkinter callback\n") sys.last_type = exc sys.last_value = val @@ -2123,25 +2181,45 @@ class Button(Widget): """ return self.tk.call(self._w, 'invoke') + # Indices: # XXX I don't like these -- take them away def AtEnd(): + warnings.warn("tkinter.AtEnd will be removed in 3.4", + DeprecationWarning, stacklevel=2) return 'end' + + def AtInsert(*args): + warnings.warn("tkinter.AtInsert will be removed in 3.4", + DeprecationWarning, stacklevel=2) s = 'insert' for a in args: if a: s = s + (' ' + a) return s + + def AtSelFirst(): + warnings.warn("tkinter.AtSelFirst will be removed in 3.4", + DeprecationWarning, stacklevel=2) return 'sel.first' + + def AtSelLast(): + warnings.warn("tkinter.AtSelLast will be removed in 3.4", + DeprecationWarning, stacklevel=2) return 'sel.last' + + def At(x, y=None): + warnings.warn("tkinter.At will be removed in 3.4", + DeprecationWarning, stacklevel=2) if y is None: return '@%r' % (x,) else: return '@%r,%r' % (x, y) + class Canvas(Widget, XView, YView): """Canvas widget to display graphical elements like lines or text.""" def __init__(self, master=None, cnf={}, **kw): @@ -2727,6 +2805,10 @@ class Menu(Widget): def unpost(self): """Unmap a menu.""" self.tk.call(self._w, 'unpost') + def xposition(self, index): # new in Tk 8.5 + """Return the x-position of the leftmost pixel of the menu item + at INDEX.""" + return getint(self.tk.call(self._w, 'xposition', index)) def yposition(self, index): """Return the y-position of the topmost pixel of the menu item at INDEX.""" return getint(self.tk.call( @@ -2886,6 +2968,25 @@ class Text(Widget, XView, YView): relation OP is satisfied. OP is one of <, <=, ==, >=, >, or !=.""" return self.tk.getboolean(self.tk.call( self._w, 'compare', index1, op, index2)) + def count(self, index1, index2, *args): # new in Tk 8.5 + """Counts the number of relevant things between the two indices. + If index1 is after index2, the result will be a negative number + (and this holds for each of the possible options). + + The actual items which are counted depends on the options given by + args. The result is a list of integers, one for the result of each + counting option given. Valid counting options are "chars", + "displaychars", "displayindices", "displaylines", "indices", + "lines", "xpixels" and "ypixels". There is an additional possible + option "update", which if given then all subsequent options ensure + that any possible out of date information is recalculated.""" + args = ['-%s' % arg for arg in args if not arg.startswith('-')] + args += [index1, index2] + res = self.tk.call(self._w, 'count', *args) or None + if res is not None and len(args) <= 3: + return (res, ) + else: + return res def debug(self, boolean=None): """Turn on the internal consistency checks of the B-Tree inside the text widget according to BOOLEAN.""" @@ -3048,6 +3149,24 @@ class Text(Widget, XView, YView): def mark_previous(self, index): """Return the name of the previous mark before INDEX.""" return self.tk.call(self._w, 'mark', 'previous', index) or None + def peer_create(self, newPathName, cnf={}, **kw): # new in Tk 8.5 + """Creates a peer text widget with the given newPathName, and any + optional standard configuration options. By default the peer will + have the same start and and end line as the parent widget, but + these can be overriden with the standard configuration options.""" + self.tk.call(self._w, 'peer', 'create', newPathName, + *self._options(cnf, kw)) + def peer_names(self): # new in Tk 8.5 + """Returns a list of peers of this widget (this does not include + the widget itself).""" + return self.tk.splitlist(self.tk.call(self._w, 'peer', 'names')) + def replace(self, index1, index2, chars, *args): # new in Tk 8.5 + """Replaces the range of characters between index1 and index2 with + the given characters and tags specified by args. + + See the method insert for some more information about args, and the + method delete for information about the indices.""" + self.tk.call(self._w, 'replace', index1, index2, chars, *args) def scan_mark(self, x, y): """Remember the current X, Y coordinates.""" self.tk.call(self._w, 'scan', 'mark', x, y) diff --git a/Lib/tkinter/_fix.py b/Lib/tkinter/_fix.py index 5a69d89..5f32d25 100644 --- a/Lib/tkinter/_fix.py +++ b/Lib/tkinter/_fix.py @@ -46,10 +46,10 @@ else: s = "\\" + s[3:] return s -prefix = os.path.join(sys.prefix,"tcl") +prefix = os.path.join(sys.base_prefix,"tcl") if not os.path.exists(prefix): # devdir/../tcltk/lib - prefix = os.path.join(sys.prefix, os.path.pardir, "tcltk", "lib") + prefix = os.path.join(sys.base_prefix, os.path.pardir, "tcltk", "lib") prefix = os.path.abspath(prefix) # if this does not exist, no further search is needed if os.path.exists(prefix): diff --git a/Lib/tkinter/filedialog.py b/Lib/tkinter/filedialog.py index 98d2d5c..3ffb252 100644 --- a/Lib/tkinter/filedialog.py +++ b/Lib/tkinter/filedialog.py @@ -306,7 +306,6 @@ class _Dialog(commondialog.Dialog): def _fixresult(self, widget, result): if result: # keep directory and filename until next time - import os # convert Tcl path objects to strings try: result = result.string @@ -333,7 +332,6 @@ class Open(_Dialog): # multiple results: result = tuple([getattr(r, "string", r) for r in result]) if result: - import os path, file = os.path.split(result[0]) self.options["initialdir"] = path # don't set initialfile or filename, as we have multiple of these diff --git a/Lib/tkinter/font.py b/Lib/tkinter/font.py index 5425b06..4929241 100644 --- a/Lib/tkinter/font.py +++ b/Lib/tkinter/font.py @@ -2,27 +2,27 @@ # # written by Fredrik Lundh, February 1998 # -# FIXME: should add 'displayof' option where relevant (actual, families, -# measure, and metrics) -# __version__ = "0.9" +import itertools import tkinter + # weight/slant NORMAL = "normal" ROMAN = "roman" BOLD = "bold" ITALIC = "italic" + def nametofont(name): """Given the name of a tk named font, returns a Font representation. """ return Font(name=name, exists=True) -class Font: +class Font: """Represents a named font. Constructor options are: @@ -44,6 +44,8 @@ class Font: """ + counter = itertools.count(1) + def _set(self, kw): options = [] for k, v in kw.items(): @@ -63,7 +65,8 @@ class Font: options[args[i][1:]] = args[i+1] return options - def __init__(self, root=None, font=None, name=None, exists=False, **options): + def __init__(self, root=None, font=None, name=None, exists=False, + **options): if not root: root = tkinter._default_root if font: @@ -72,7 +75,7 @@ class Font: else: font = self._set(options) if not name: - name = "font" + str(id(self)) + name = "font" + str(next(self.counter)) self.name = name if exists: @@ -118,14 +121,17 @@ class Font: "Return a distinct copy of the current font" return Font(self._root, **self.actual()) - def actual(self, option=None): + def actual(self, option=None, displayof=None): "Return actual font attributes" + args = () + if displayof: + args = ('-displayof', displayof) if option: - return self._call("font", "actual", self.name, "-"+option) + args = args + ('-' + option, ) + return self._call("font", "actual", self.name, *args) else: return self._mkdict( - self._split(self._call("font", "actual", self.name)) - ) + self._split(self._call("font", "actual", self.name, *args))) def cget(self, option): "Get font attribute" @@ -138,37 +144,47 @@ class Font: *self._set(options)) else: return self._mkdict( - self._split(self._call("font", "config", self.name)) - ) + self._split(self._call("font", "config", self.name))) configure = config - def measure(self, text): + def measure(self, text, displayof=None): "Return text width" - return int(self._call("font", "measure", self.name, text)) + args = (text,) + if displayof: + args = ('-displayof', displayof, text) + return int(self._call("font", "measure", self.name, *args)) - def metrics(self, *options): + def metrics(self, *options, **kw): """Return font metrics. For best performance, create a dummy widget using this font before calling this method.""" - + args = () + displayof = kw.pop('displayof', None) + if displayof: + args = ('-displayof', displayof) if options: + args = args + self._get(options) return int( - self._call("font", "metrics", self.name, self._get(options)) - ) + self._call("font", "metrics", self.name, *args)) else: - res = self._split(self._call("font", "metrics", self.name)) + res = self._split(self._call("font", "metrics", self.name, *args)) options = {} for i in range(0, len(res), 2): options[res[i][1:]] = int(res[i+1]) return options -def families(root=None): + +def families(root=None, displayof=None): "Get font families (as a tuple)" if not root: root = tkinter._default_root - return root.tk.splitlist(root.tk.call("font", "families")) + args = () + if displayof: + args = ('-displayof', displayof) + return root.tk.splitlist(root.tk.call("font", "families", *args)) + def names(root=None): "Get names of defined fonts (as a tuple)" @@ -176,6 +192,7 @@ def names(root=None): root = tkinter._default_root return root.tk.splitlist(root.tk.call("font", "names")) + # -------------------------------------------------------------------- # test stuff @@ -198,10 +215,10 @@ if __name__ == "__main__": print(f.measure("hello"), f.metrics("linespace")) - print(f.metrics()) + print(f.metrics(displayof=root)) f = Font(font=("Courier", 20, "bold")) - print(f.measure("hello"), f.metrics("linespace")) + print(f.measure("hello"), f.metrics("linespace", displayof=root)) w = tkinter.Label(root, text="Hello, world", font=f) w.pack() diff --git a/Lib/tkinter/test/test_tkinter/test_variables.py b/Lib/tkinter/test/test_tkinter/test_variables.py new file mode 100644 index 0000000..378cc92 --- /dev/null +++ b/Lib/tkinter/test/test_tkinter/test_variables.py @@ -0,0 +1,165 @@ +import unittest + +from tkinter import Variable, StringVar, IntVar, DoubleVar, BooleanVar, Tk + + +class Var(Variable): + + _default = "default" + side_effect = False + + def set(self, value): + self.side_effect = True + super().set(value) + + +class TestBase(unittest.TestCase): + + def setUp(self): + self.root = Tk() + + def tearDown(self): + self.root.destroy() + + +class TestVariable(TestBase): + + def test_default(self): + v = Variable(self.root) + self.assertEqual("", v.get()) + self.assertRegex(str(v), r"^PY_VAR(\d+)$") + + def test_name_and_value(self): + v = Variable(self.root, "sample string", "varname") + self.assertEqual("sample string", v.get()) + self.assertEqual("varname", str(v)) + + def test___del__(self): + self.assertFalse(self.root.call("info", "exists", "varname")) + v = Variable(self.root, "sample string", "varname") + self.assertTrue(self.root.call("info", "exists", "varname")) + del v + self.assertFalse(self.root.call("info", "exists", "varname")) + + def test_dont_unset_not_existing(self): + self.assertFalse(self.root.call("info", "exists", "varname")) + v1 = Variable(self.root, name="name") + v2 = Variable(self.root, name="name") + del v1 + self.assertFalse(self.root.call("info", "exists", "name")) + # shouldn't raise exception + del v2 + self.assertFalse(self.root.call("info", "exists", "name")) + + def test___eq__(self): + # values doesn't matter, only class and name are checked + v1 = Variable(self.root, name="abc") + v2 = Variable(self.root, name="abc") + self.assertEqual(v1, v2) + + v3 = Variable(self.root, name="abc") + v4 = StringVar(self.root, name="abc") + self.assertNotEqual(v3, v4) + + def test_invalid_name(self): + with self.assertRaises(TypeError): + Variable(self.root, name=123) + + def test_initialize(self): + v = Var() + self.assertFalse(v.side_effect) + v.set("value") + self.assertTrue(v.side_effect) + + +class TestStringVar(TestBase): + + def test_default(self): + v = StringVar(self.root) + self.assertEqual("", v.get()) + + def test_get(self): + v = StringVar(self.root, "abc", "name") + self.assertEqual("abc", v.get()) + self.root.globalsetvar("name", "value") + self.assertEqual("value", v.get()) + + +class TestIntVar(TestBase): + + def test_default(self): + v = IntVar(self.root) + self.assertEqual(0, v.get()) + + def test_get(self): + v = IntVar(self.root, 123, "name") + self.assertEqual(123, v.get()) + self.root.globalsetvar("name", "345") + self.assertEqual(345, v.get()) + + def test_invalid_value(self): + v = IntVar(self.root, name="name") + self.root.globalsetvar("name", "value") + with self.assertRaises(ValueError): + v.get() + self.root.globalsetvar("name", "345.0") + with self.assertRaises(ValueError): + v.get() + + +class TestDoubleVar(TestBase): + + def test_default(self): + v = DoubleVar(self.root) + self.assertEqual(0.0, v.get()) + + def test_get(self): + v = DoubleVar(self.root, 1.23, "name") + self.assertAlmostEqual(1.23, v.get()) + self.root.globalsetvar("name", "3.45") + self.assertAlmostEqual(3.45, v.get()) + + def test_get_from_int(self): + v = DoubleVar(self.root, 1.23, "name") + self.assertAlmostEqual(1.23, v.get()) + self.root.globalsetvar("name", "3.45") + self.assertAlmostEqual(3.45, v.get()) + self.root.globalsetvar("name", "456") + self.assertAlmostEqual(456, v.get()) + + def test_invalid_value(self): + v = DoubleVar(self.root, name="name") + self.root.globalsetvar("name", "value") + with self.assertRaises(ValueError): + v.get() + + +class TestBooleanVar(TestBase): + + def test_default(self): + v = BooleanVar(self.root) + self.assertEqual(False, v.get()) + + def test_get(self): + v = BooleanVar(self.root, True, "name") + self.assertAlmostEqual(True, v.get()) + self.root.globalsetvar("name", "0") + self.assertAlmostEqual(False, v.get()) + + def test_invalid_value_domain(self): + v = BooleanVar(self.root, name="name") + self.root.globalsetvar("name", "value") + with self.assertRaises(ValueError): + v.get() + self.root.globalsetvar("name", "1.0") + with self.assertRaises(ValueError): + v.get() + + +tests_gui = (TestVariable, TestStringVar, TestIntVar, + TestDoubleVar, TestBooleanVar) + + +if __name__ == "__main__": + from test.support import run_unittest + run_unittest(*tests_gui) |