diff --git a/Lib/test/test_tkinter/support.py b/Lib/test/test_tkinter/support.py index ebb9e00ff91bf0..0617820b707126 100644 --- a/Lib/test/test_tkinter/support.py +++ b/Lib/test/test_tkinter/support.py @@ -1,6 +1,19 @@ import functools +import re import tkinter +messages_v1 = { + 'no_busy': 'can\'t find busy window.*', + 'no_font': 'font "{}" doesn\'t exist', + 'no_image': 'image "{}" doesn\'t exist', + } + +messages_v2 = { + 'no_busy': 'cannot find busy window', + 'no_font': 'font "{}" does not exist', + 'no_image': 'image "{}" does not exist', + } + class AbstractTkTest: @classmethod @@ -112,6 +125,10 @@ def get_tk_patchlevel(root): def pixels_conv(value): return float(value[:-1]) * units[value[-1:]] +pix_re = re.compile(r'[0-9]*\.?[0-9]*[cimp]{1}') +def is_pixel_str(x): + return pix_re.fullmatch(x) != None + def tcl_obj_eq(actual, expected): if actual == expected: return True diff --git a/Lib/test/test_tkinter/test_misc.py b/Lib/test/test_tkinter/test_misc.py index b0b9ed60040443..05bca28c948995 100644 --- a/Lib/test/test_tkinter/test_misc.py +++ b/Lib/test/test_tkinter/test_misc.py @@ -4,10 +4,15 @@ from tkinter import TclError import enum from test import support -from test.test_tkinter.support import AbstractTkTest, AbstractDefaultRootTest, requires_tk +from test.test_tkinter.support import AbstractTkTest, AbstractDefaultRootTest, requires_tk, tk_version support.requires('gui') +if tk_version < (9,0): + from test.test_tkinter.support import messages_v1 as messages +else: + from test.test_tkinter.support import messages_v2 as messages + class MiscTest(AbstractTkTest, unittest.TestCase): def test_all(self): @@ -66,9 +71,9 @@ def test_tk_busy(self): f.tk_busy_forget() self.assertFalse(f.tk_busy_status()) self.assertFalse(f.tk_busy_current()) - with self.assertRaisesRegex(TclError, "can't find busy window"): + with self.assertRaisesRegex(TclError, messages['no_busy']): f.tk_busy_configure() - with self.assertRaisesRegex(TclError, "can't find busy window"): + with self.assertRaisesRegex(TclError, messages['no_busy']): f.tk_busy_forget() @requires_tk(8, 6, 6) @@ -87,7 +92,7 @@ def test_tk_busy_with_cursor(self): self.assertEqual(f.tk_busy_configure('cursor')[4], 'heart') f.tk_busy_forget() - with self.assertRaisesRegex(TclError, "can't find busy window"): + with self.assertRaisesRegex(TclError, messages["no_busy"]): f.tk_busy_cget('cursor') def test_tk_setPalette(self): diff --git a/Lib/test/test_tkinter/test_widgets.py b/Lib/test/test_tkinter/test_widgets.py index 9ea764ca2a39d8..7e90f274ee826a 100644 --- a/Lib/test/test_tkinter/test_widgets.py +++ b/Lib/test/test_tkinter/test_widgets.py @@ -7,9 +7,15 @@ from test.test_tkinter.support import (requires_tk, tk_version, get_tk_patchlevel, widget_eq, AbstractDefaultRootTest) +if tk_version < (9, 0): + from test.test_tkinter.support import messages_v1 as messages, is_pixel_str +else: + from test.test_tkinter.support import messages_v2 as messages, is_pixel_str from test.test_tkinter.widget_tests import ( - add_standard_options, - AbstractWidgetTest, StandardOptionsTests, IntegerSizeTests, PixelSizeTests) + add_configure_tests, + AbstractWidgetTest, + StandardOptionsTests, + IntegerSizeTests, PixelSizeTests) requires('gui') @@ -20,9 +26,14 @@ def float_round(x): return float(round(x)) - class AbstractToplevelTest(AbstractWidgetTest, PixelSizeTests): - _conv_pad_pixels = False + _no_round = {'padx', 'pady'} if tk_version < (9, 0) else { + 'borderwidth', 'height', 'highlightthickness', 'padx', 'pady', + 'width'} + _clipped = { + 'highlightthickness'} if tk_version < (9, 0) else { + 'borderwidth', 'height', 'highlightthickness', 'padx', 'pady', + 'width'} def test_configure_class(self): widget = self.create() @@ -58,7 +69,7 @@ def test_configure_visual(self): self.assertEqual(widget2['visual'], 'default') -@add_standard_options(StandardOptionsTests) +@add_configure_tests(StandardOptionsTests) class ToplevelTest(AbstractToplevelTest, unittest.TestCase): OPTIONS = ( 'background', 'backgroundimage', 'borderwidth', @@ -101,7 +112,7 @@ def test_configure_use(self): self.assertEqual(widget2['use'], wid) -@add_standard_options(StandardOptionsTests) +@add_configure_tests(StandardOptionsTests) class FrameTest(AbstractToplevelTest, unittest.TestCase): OPTIONS = ( 'background', 'backgroundimage', 'borderwidth', @@ -109,12 +120,15 @@ class FrameTest(AbstractToplevelTest, unittest.TestCase): 'highlightbackground', 'highlightcolor', 'highlightthickness', 'padx', 'pady', 'relief', 'takefocus', 'tile', 'visual', 'width', ) + _no_round = {'padx', 'pady'} if tk_version < (9, 0) else { + 'borderwidth', 'height', 'highlightthickness', 'padx', 'pady', + 'width'} def create(self, **kwargs): return tkinter.Frame(self.root, **kwargs) -@add_standard_options(StandardOptionsTests) +@add_configure_tests(StandardOptionsTests) class LabelFrameTest(AbstractToplevelTest, unittest.TestCase): OPTIONS = ( 'background', 'borderwidth', @@ -124,6 +138,9 @@ class LabelFrameTest(AbstractToplevelTest, unittest.TestCase): 'labelanchor', 'labelwidget', 'padx', 'pady', 'relief', 'takefocus', 'text', 'visual', 'width', ) + _no_round = {'padx', 'pady'} if tk_version < (9, 0) else { + 'borderwidth', 'height', 'highlightthickness', 'padx', 'pady', + 'width'} def create(self, **kwargs): return tkinter.LabelFrame(self.root, **kwargs) @@ -141,15 +158,14 @@ def test_configure_labelwidget(self): self.checkParam(widget, 'labelwidget', label, expected='.foo') label.destroy() - +# Label, Button, Checkbutton, Radiobutton, MenuButton class AbstractLabelTest(AbstractWidgetTest, IntegerSizeTests): - _conv_pixels = False - _clip_highlightthickness = tk_version >= (8, 7) - _clip_pad = tk_version >= (8, 7) - _clip_borderwidth = tk_version >= (8, 7) - + _rounds_pixels = False + _clipped = {} if tk_version < (9, 0) else { + 'borderwidth', 'insertborderwidth', 'highlightthickness', + 'padx', 'pady'} -@add_standard_options(StandardOptionsTests) +@add_configure_tests(StandardOptionsTests) class LabelTest(AbstractLabelTest, unittest.TestCase): OPTIONS = ( 'activebackground', 'activeforeground', 'anchor', @@ -165,7 +181,7 @@ def create(self, **kwargs): return tkinter.Label(self.root, **kwargs) -@add_standard_options(StandardOptionsTests) +@add_configure_tests(StandardOptionsTests) class ButtonTest(AbstractLabelTest, unittest.TestCase): OPTIONS = ( 'activebackground', 'activeforeground', 'anchor', @@ -186,7 +202,7 @@ def test_configure_default(self): self.checkEnumParam(widget, 'default', 'active', 'disabled', 'normal') -@add_standard_options(StandardOptionsTests) +@add_configure_tests(StandardOptionsTests) class CheckbuttonTest(AbstractLabelTest, unittest.TestCase): OPTIONS = ( 'activebackground', 'activeforeground', 'anchor', @@ -240,8 +256,7 @@ def test_same_name(self): b2.deselect() self.assertEqual(v.get(), 0) - -@add_standard_options(StandardOptionsTests) +@add_configure_tests(StandardOptionsTests) class RadiobuttonTest(AbstractLabelTest, unittest.TestCase): OPTIONS = ( 'activebackground', 'activeforeground', 'anchor', @@ -264,7 +279,7 @@ def test_configure_value(self): self.checkParams(widget, 'value', 1, 2.3, '', 'any string') -@add_standard_options(StandardOptionsTests) +@add_configure_tests(StandardOptionsTests) class MenubuttonTest(AbstractLabelTest, unittest.TestCase): OPTIONS = ( 'activebackground', 'activeforeground', 'anchor', @@ -277,10 +292,10 @@ class MenubuttonTest(AbstractLabelTest, unittest.TestCase): 'takefocus', 'text', 'textvariable', 'underline', 'width', 'wraplength', ) - _conv_pixels = round - _clip_highlightthickness = True - _clip_pad = True - _clip_borderwidth = False + _rounds_pixels = tk_version < (9, 0) + _clipped = {'highlightthickness', 'padx', 'pady' + } if tk_version < (9, 0) else { 'insertborderwidth', + 'highlightthickness', 'padx', 'pady'} def create(self, **kwargs): return tkinter.Menubutton(self.root, **kwargs) @@ -298,7 +313,7 @@ def test_configure_image(self): widget = self.create() image = tkinter.PhotoImage(master=self.root, name='image1') self.checkParam(widget, 'image', image, conv=str) - errmsg = 'image "spam" doesn\'t exist' + errmsg = messages['no_image'].format('spam') with self.assertRaises(tkinter.TclError) as cm: widget['image'] = 'spam' if errmsg is not None: @@ -328,9 +343,13 @@ def test_bad_kwarg(self): with self.assertRaisesRegex(TclError, r"^unknown option -image$"): tkinter.OptionMenu(self.root, None, 'b', image='') - -@add_standard_options(IntegerSizeTests, StandardOptionsTests) +@add_configure_tests(IntegerSizeTests, StandardOptionsTests) class EntryTest(AbstractWidgetTest, unittest.TestCase): + _rounds_pixels = (tk_version < (9, 0)) + _clipped = {'highlightthickness'} if tk_version < (9, 0) else { + 'highlightthickness', 'borderwidth', 'insertborderwidth', + 'selectborderwidth'} + OPTIONS = ( 'background', 'borderwidth', 'cursor', 'disabledbackground', 'disabledforeground', @@ -355,16 +374,23 @@ def test_configure_disabledbackground(self): def test_configure_insertborderwidth(self): widget = self.create(insertwidth=100) self.checkPixelsParam(widget, 'insertborderwidth', - 0, 1.3, 2.6, 6, -2, '10p') + 0, 1.3, 2.6, 6, '10p') + self.checkParam(widget, 'insertborderwidth', -2) # insertborderwidth is bounded above by a half of insertwidth. - self.checkParam(widget, 'insertborderwidth', 60, expected=100//2) + expected = 100 // 2 if tk_version < (9, 0) else 60 + self.checkParam(widget, 'insertborderwidth', 60, expected=expected) def test_configure_insertwidth(self): widget = self.create() self.checkPixelsParam(widget, 'insertwidth', 1.3, 3.6, '10p') - self.checkParam(widget, 'insertwidth', 0.1, expected=2) - self.checkParam(widget, 'insertwidth', -2, expected=2) - self.checkParam(widget, 'insertwidth', 0.9, expected=1) + if tk_version < (9, 0): + self.checkParam(widget, 'insertwidth', 0.1, expected=2) + self.checkParam(widget, 'insertwidth', -2, expected=2) + self.checkParam(widget, 'insertwidth', 0.9, expected=1) + else: + self.checkParam(widget, 'insertwidth', 0.1) + self.checkParam(widget, 'insertwidth', -2, expected=0) + self.checkParam(widget, 'insertwidth', 0.9) def test_configure_invalidcommand(self): widget = self.create() @@ -422,7 +448,7 @@ def test_selection_methods(self): widget.selection_adjust(0) -@add_standard_options(StandardOptionsTests) +@add_configure_tests(StandardOptionsTests) class SpinboxTest(EntryTest, unittest.TestCase): OPTIONS = ( 'activebackground', 'background', 'borderwidth', @@ -559,7 +585,7 @@ def test_selection_element(self): self.assertEqual(widget.selection_element(), "buttondown") -@add_standard_options(StandardOptionsTests) +@add_configure_tests(StandardOptionsTests) class TextTest(AbstractWidgetTest, unittest.TestCase): OPTIONS = ( 'autoseparators', 'background', 'blockcursor', 'borderwidth', @@ -574,6 +600,9 @@ class TextTest(AbstractWidgetTest, unittest.TestCase): 'tabs', 'tabstyle', 'takefocus', 'undo', 'width', 'wrap', 'xscrollcommand', 'yscrollcommand', ) + _rounds_pixels = (tk_version < (9, 0)) + _no_round = {'selectborderwidth'} + _clipped = {'highlightthickness'} def create(self, **kwargs): return tkinter.Text(self.root, **kwargs) @@ -602,8 +631,10 @@ def test_configure_endline(self): def test_configure_height(self): widget = self.create() self.checkPixelsParam(widget, 'height', 100, 101.2, 102.6, '3c') - self.checkParam(widget, 'height', -100, expected=1) - self.checkParam(widget, 'height', 0, expected=1) + self.checkParam(widget, 'height', -100, + expected=1 if tk_version < (9, 0) else -100) + self.checkParam(widget, 'height', 0, + expected=1 if tk_version < (9, 0) else 0 ) def test_configure_maxundo(self): widget = self.create() @@ -696,7 +727,7 @@ def test_bbox(self): self.assertRaises(TypeError, widget.bbox, '1.1', 'end') -@add_standard_options(PixelSizeTests, StandardOptionsTests) +@add_configure_tests(PixelSizeTests, StandardOptionsTests) class CanvasTest(AbstractWidgetTest, unittest.TestCase): OPTIONS = ( 'background', 'borderwidth', @@ -710,8 +741,7 @@ class CanvasTest(AbstractWidgetTest, unittest.TestCase): 'xscrollcommand', 'xscrollincrement', 'yscrollcommand', 'yscrollincrement', 'width', ) - - _conv_pixels = round + _clipped = {'highlightthickness'} _stringify = True def create(self, **kwargs): @@ -953,7 +983,7 @@ def test_moveto(self): self.assertEqual(y2_2 - y1_2, y2_3 - y1_3) -@add_standard_options(IntegerSizeTests, StandardOptionsTests) +@add_configure_tests(IntegerSizeTests, StandardOptionsTests) class ListboxTest(AbstractWidgetTest, unittest.TestCase): OPTIONS = ( 'activestyle', 'background', 'borderwidth', 'cursor', @@ -965,6 +995,10 @@ class ListboxTest(AbstractWidgetTest, unittest.TestCase): 'selectmode', 'setgrid', 'state', 'takefocus', 'width', 'xscrollcommand', 'yscrollcommand', ) + _rounds_pixels = (tk_version < (9, 0)) + _clipped = {'highlightthickness' + } if tk_version < (9, 0) else { 'borderwidth', + 'highlightthickness', 'selectborderwidth'} def create(self, **kwargs): return tkinter.Listbox(self.root, **kwargs) @@ -1091,7 +1125,7 @@ def test_get(self): self.assertRaises(TclError, lb.get, 2.4) -@add_standard_options(PixelSizeTests, StandardOptionsTests) +@add_configure_tests(PixelSizeTests, StandardOptionsTests) class ScaleTest(AbstractWidgetTest, unittest.TestCase): OPTIONS = ( 'activebackground', 'background', 'bigincrement', 'borderwidth', @@ -1102,6 +1136,8 @@ class ScaleTest(AbstractWidgetTest, unittest.TestCase): 'resolution', 'showvalue', 'sliderlength', 'sliderrelief', 'state', 'takefocus', 'tickinterval', 'to', 'troughcolor', 'variable', 'width', ) + _rounds_pixels = (tk_version < (9, 0)) + _clipped = {'highlightthickness'} default_orient = 'vertical' def create(self, **kwargs): @@ -1159,7 +1195,7 @@ def test_configure_to(self): conv=float_round) -@add_standard_options(PixelSizeTests, StandardOptionsTests) +@add_configure_tests(PixelSizeTests, StandardOptionsTests) class ScrollbarTest(AbstractWidgetTest, unittest.TestCase): OPTIONS = ( 'activebackground', 'activerelief', @@ -1170,7 +1206,9 @@ class ScrollbarTest(AbstractWidgetTest, unittest.TestCase): 'repeatdelay', 'repeatinterval', 'takefocus', 'troughcolor', 'width', ) - _conv_pixels = round + _rounds_pixels = True + _clipped = {'highlightthickness'} if tk_version < (9, 0) else{ + 'borderwidth', 'highlightthickness'} _stringify = True default_orient = 'vertical' @@ -1208,7 +1246,7 @@ def test_set(self): self.assertRaises(TypeError, sb.set, 0.6, 0.7, 0.8) -@add_standard_options(StandardOptionsTests) +@add_configure_tests(StandardOptionsTests) class PanedWindowTest(AbstractWidgetTest, unittest.TestCase): OPTIONS = ( 'background', 'borderwidth', 'cursor', @@ -1219,6 +1257,14 @@ class PanedWindowTest(AbstractWidgetTest, unittest.TestCase): 'sashcursor', 'sashpad', 'sashrelief', 'sashwidth', 'showhandle', 'width', ) + _rounds_pixels = True + _no_round = {'handlesize', 'height', 'proxyborderwidth', + 'sashwidth', 'selectborderwidth', 'width' + } if tk_version < (9, 0) else {'borderwidth', + 'handlepad', 'handlesize', + 'height', 'proxyborderwidth', 'sashpad', + 'sashwidth', 'selectborderwidth', 'width'} + _clipped = {} default_orient = 'horizontal' def create(self, **kwargs): @@ -1347,13 +1393,13 @@ def test_paneconfigure_minsize(self): def test_paneconfigure_padx(self): p, b, c = self.create2() - self.check_paneconfigure(p, b, 'padx', 1.3, 1) + self.check_paneconfigure(p, b, 'padx', 1.3, 1 if tk_version < (9, 0) else 1.3) self.check_paneconfigure_bad(p, b, 'padx', EXPECTED_SCREEN_DISTANCE_ERRMSG.format('badValue')) def test_paneconfigure_pady(self): p, b, c = self.create2() - self.check_paneconfigure(p, b, 'pady', 1.3, 1) + self.check_paneconfigure(p, b, 'pady', 1.3, 1 if tk_version < (9, 0) else 1.3) self.check_paneconfigure_bad(p, b, 'pady', EXPECTED_SCREEN_DISTANCE_ERRMSG.format('badValue')) @@ -1379,17 +1425,17 @@ def test_paneconfigure_width(self): EXPECTED_SCREEN_DISTANCE_OR_EMPTY_ERRMSG.format('badValue')) -@add_standard_options(StandardOptionsTests) +@add_configure_tests(StandardOptionsTests) class MenuTest(AbstractWidgetTest, unittest.TestCase): OPTIONS = ( 'activebackground', 'activeborderwidth', 'activeforeground', - 'activerelief', - 'background', 'borderwidth', 'cursor', + 'activerelief', 'background', 'borderwidth', 'cursor', 'disabledforeground', 'font', 'foreground', 'postcommand', 'relief', 'selectcolor', 'takefocus', 'tearoff', 'tearoffcommand', 'title', 'type', ) - _conv_pixels = False + _rounds_pixels = False + _clipped = {} def create(self, **kwargs): return tkinter.Menu(self.root, **kwargs) @@ -1458,7 +1504,7 @@ def test_entryconfigure_variable(self): self.assertEqual(str(m1.entrycget(1, 'variable')), str(v2)) -@add_standard_options(PixelSizeTests, StandardOptionsTests) +@add_configure_tests(PixelSizeTests, StandardOptionsTests) class MessageTest(AbstractWidgetTest, unittest.TestCase): OPTIONS = ( 'anchor', 'aspect', 'background', 'borderwidth', @@ -1467,11 +1513,10 @@ class MessageTest(AbstractWidgetTest, unittest.TestCase): 'justify', 'padx', 'pady', 'relief', 'takefocus', 'text', 'textvariable', 'width', ) - _conv_pad_pixels = False - if tk_version >= (8, 7): - _conv_pixels = False - _clip_pad = tk_version >= (8, 7) - _clip_borderwidth = tk_version >= (8, 7) + _rounds_pixels = (tk_version < (9, 0)) + _no_round = {'padx', 'pady'} + _clipped = {'highlightthickness'} if tk_version < (9, 0) else { + 'borderwidth', 'highlightthickness', 'padx', 'pady'} def create(self, **kwargs): return tkinter.Message(self.root, **kwargs) @@ -1482,16 +1527,14 @@ def test_configure_aspect(self): def test_configure_padx(self): widget = self.create() - self.checkPixelsParam(widget, 'padx', 3, 4.4, 5.6, '12m', - conv=self._conv_pad_pixels) - expected = self._default_pixels if self._clip_pad else -2 + self.checkPixelsParam(widget, 'padx', 3, 4.4, 5.6, '12m')#, + expected = -2 if tk_version < (9, 0) else self._default_pixels self.checkParam(widget, 'padx', -2, expected=expected) def test_configure_pady(self): widget = self.create() - self.checkPixelsParam(widget, 'pady', 3, 4.4, 5.6, '12m', - conv=self._conv_pad_pixels) - expected = self._default_pixels if self._clip_pad else -2 + self.checkPixelsParam(widget, 'pady', 3, 4.4, 5.6, '12m')#, + expected = -2 if tk_version < (9, 0) else self._default_pixels self.checkParam(widget, 'pady', -2, expected=expected) def test_configure_width(self): diff --git a/Lib/test/test_tkinter/widget_tests.py b/Lib/test/test_tkinter/widget_tests.py index 8ab2f74245095d..593cd9ec7df7f3 100644 --- a/Lib/test/test_tkinter/widget_tests.py +++ b/Lib/test/test_tkinter/widget_tests.py @@ -4,19 +4,22 @@ import tkinter from test.test_tkinter.support import (AbstractTkTest, requires_tk, tk_version, pixels_conv, tcl_obj_eq) +if tk_version < (9,0): + from test.test_tkinter.support import is_pixel_str, messages_v1 as messages +else: + from test.test_tkinter.support import is_pixel_str, messages_v2 as messages import test.support - _sentinel = object() +# Options which accept all values allowed by Tk_GetPixels +# borderwidth = bd + class AbstractWidgetTest(AbstractTkTest): - _default_pixels = '' if tk_version >= (9, 0) else -1 if tk_version >= (8, 7) else '' - _conv_pixels = round - _conv_pad_pixels = None - _stringify = False - _clip_highlightthickness = True - _clip_pad = False - _clip_borderwidth = False + _default_pixels = '' # Value for unset pixel options. + _rounds_pixels = True # True if some pixel options are rounded. + _no_round = {} # Pixel options which are not rounded nonetheless + _stringify = False # Whether to convert tuples to strings _allow_empty_justify = False @property @@ -44,6 +47,9 @@ def checkParam(self, widget, name, value, *, expected=_sentinel, widget[name] = value if expected is _sentinel: expected = value + if name in self._clipped: + if not isinstance(expected, str): + expected = max(expected, 0) if conv: expected = conv(expected) if self._stringify or not self.wantobjects: @@ -140,10 +146,11 @@ def checkEnumParam(self, widget, name, *values, errmsg = 'bad' + errmsg2 self.checkInvalidParam(widget, name, 'spam', errmsg=errmsg) - def checkPixelsParam(self, widget, name, *values, - conv=None, **kwargs): - if conv is None: - conv = self._conv_pixels + def checkPixelsParam(self, widget, name, *values, conv=None, **kwargs): + if not self._rounds_pixels or name in self._no_round: + conv = False + else: + conv = round for value in values: expected = _sentinel conv1 = conv @@ -173,7 +180,7 @@ def checkImageParam(self, widget, name): image = tkinter.PhotoImage(master=self.root, name='image1') self.checkParam(widget, name, image, conv=str) self.checkInvalidParam(widget, name, 'spam', - errmsg='image "spam" doesn\'t exist') + errmsg=messages['no_image'].format('spam')) widget[name] = '' def checkVariableParam(self, widget, name, var): @@ -215,31 +222,80 @@ def test_keys(self): print('%s.OPTIONS doesn\'t contain "%s"' % (self.__class__.__name__, k)) +class PixelOptionsTests: + """Standard options that accept all formats acceptable to Tk_GetPixels. -class StandardOptionsTests: - STANDARD_OPTIONS = ( - 'activebackground', 'activeborderwidth', 'activeforeground', 'anchor', - 'background', 'bitmap', 'borderwidth', 'compound', 'cursor', - 'disabledforeground', 'exportselection', 'font', 'foreground', - 'highlightbackground', 'highlightcolor', 'highlightthickness', - 'image', 'insertbackground', 'insertborderwidth', - 'insertofftime', 'insertontime', 'insertwidth', - 'jump', 'justify', 'orient', 'padx', 'pady', 'relief', - 'repeatdelay', 'repeatinterval', - 'selectbackground', 'selectborderwidth', 'selectforeground', - 'setgrid', 'takefocus', 'text', 'textvariable', 'troughcolor', - 'underline', 'wraplength', 'xscrollcommand', 'yscrollcommand', - ) - - def test_configure_activebackground(self): - widget = self.create() - self.checkColorParam(widget, 'activebackground') + In addition to numbers, these options can be set with distances + specified as a string consisting of a number followed by a single + character giving the unit of distance. The allowed units are: + millimeters ('m'), centimeters ('c'), inches ('i') or points ('p'). + In Tk 9 a cget call for one of these options returns a Tcl_Obj of + type "pixels", whose string representation is the distance string + passed to configure. + """ + PIXEL_OPTIONS = ('activeborderwidth', 'borderwidth', 'highlightthickness', + 'insertborderwidth', 'insertwidth', 'padx', 'pady', 'selectborderwidth') def test_configure_activeborderwidth(self): widget = self.create() self.checkPixelsParam(widget, 'activeborderwidth', 0, 1.3, 2.9, 6, -2, '10p') + def test_configure_borderwidth(self): + widget = self.create() + self.checkPixelsParam(widget, 'borderwidth', + 0, 1.3, 2.6, 6, '10p') + self.checkParam(widget, 'borderwidth', -2) + if 'bd' in self.OPTIONS: + self.checkPixelsParam(widget, 'bd', 0, 1.3, 2.6, 6, '10p') + self.checkParam(widget, 'bd', -2, expected=expected) + + def test_configure_highlightthickness(self): + widget = self.create() + self.checkPixelsParam(widget, 'highlightthickness', + 0, 1.3, 2.6, 6, '10p') + self.checkParam(widget, 'highlightthickness', -2) + + def test_configure_insertborderwidth(self): + widget = self.create() + self.checkPixelsParam(widget, 'insertborderwidth', + 0, 1.3, 2.6, 6, '10p') + self.checkParam(widget, 'insertborderwidth', -2) + + def test_configure_insertwidth(self): + widget = self.create() + self.checkPixelsParam(widget, 'insertwidth', 1.3, 2.6, -2, '10p') + + def test_configure_padx(self): + widget = self.create() + self.checkPixelsParam(widget, 'padx', 3, 4.4, 5.6, '12m') + self.checkParam(widget, 'padx', -2) + + def test_configure_pady(self): + widget = self.create() + self.checkPixelsParam(widget, 'pady', 3, 4.4, 5.6, '12m') + self.checkParam(widget, 'pady', -2) + + def test_configure_selectborderwidth(self): + widget = self.create() + self.checkPixelsParam(widget, 'selectborderwidth', 1.3, 2.6, -2, '10p') + +class StandardOptionsTests(PixelOptionsTests): + + STANDARD_OPTIONS = ( 'activebackground', 'activeforeground', + 'anchor', 'background', 'bitmap', 'compound', 'cursor', + 'disabledforeground', 'exportselection', 'font', 'foreground', + 'highlightbackground', 'highlightcolor', 'image', + 'insertbackground', 'insertofftime', 'insertontime', 'jump', + 'justify', 'orient', 'relief', 'repeatdelay', 'repeatinterval', + 'selectbackground', 'selectforeground', 'setgrid', 'takefocus', + 'text', 'textvariable', 'troughcolor', 'underline', 'wraplength', + 'xscrollcommand', 'yscrollcommand', ) + PixelOptionsTests.PIXEL_OPTIONS + + def test_configure_activebackground(self): + widget = self.create() + self.checkColorParam(widget, 'activebackground') + def test_configure_activeforeground(self): widget = self.create() self.checkColorParam(widget, 'activeforeground') @@ -277,18 +333,6 @@ def test_configure_bitmap(self): self.checkInvalidParam(widget, 'bitmap', 'spam', errmsg='bitmap "spam" not defined') - def test_configure_borderwidth(self): - widget = self.create() - self.checkPixelsParam(widget, 'borderwidth', - 0, 1.3, 2.6, 6, '10p') - expected = 0 if self._clip_borderwidth else -2 - self.checkParam(widget, 'borderwidth', -2, expected=expected, - conv=self._conv_pixels) - if 'bd' in self.OPTIONS: - self.checkPixelsParam(widget, 'bd', 0, 1.3, 2.6, 6, '10p') - self.checkParam(widget, 'bd', -2, expected=expected, - conv=self._conv_pixels) - def test_configure_compound(self): widget = self.create() self.checkEnumParam(widget, 'compound', @@ -313,7 +357,7 @@ def test_configure_font(self): is_ttk = widget.__class__.__module__ == 'tkinter.ttk' if not is_ttk: self.checkInvalidParam(widget, 'font', '', - errmsg='font "" doesn\'t exist') + errmsg=messages['no_font'].format('')) def test_configure_foreground(self): widget = self.create() @@ -329,14 +373,6 @@ def test_configure_highlightcolor(self): widget = self.create() self.checkColorParam(widget, 'highlightcolor') - def test_configure_highlightthickness(self): - widget = self.create() - self.checkPixelsParam(widget, 'highlightthickness', - 0, 1.3, 2.6, 6, '10p') - expected = 0 if self._clip_highlightthickness else -2 - self.checkParam(widget, 'highlightthickness', -2, expected=expected, - conv=self._conv_pixels) - def test_configure_image(self): widget = self.create() self.checkImageParam(widget, 'image') @@ -345,11 +381,6 @@ def test_configure_insertbackground(self): widget = self.create() self.checkColorParam(widget, 'insertbackground') - def test_configure_insertborderwidth(self): - widget = self.create() - self.checkPixelsParam(widget, 'insertborderwidth', - 0, 1.3, 2.6, 6, -2, '10p') - def test_configure_insertofftime(self): widget = self.create() self.checkIntegerParam(widget, 'insertofftime', 100) @@ -358,10 +389,6 @@ def test_configure_insertontime(self): widget = self.create() self.checkIntegerParam(widget, 'insertontime', 100) - def test_configure_insertwidth(self): - widget = self.create() - self.checkPixelsParam(widget, 'insertwidth', 1.3, 2.6, -2, '10p') - def test_configure_jump(self): widget = self.create() self.checkBooleanParam(widget, 'jump') @@ -379,22 +406,6 @@ def test_configure_orient(self): self.assertEqual(str(widget['orient']), self.default_orient) self.checkEnumParam(widget, 'orient', 'horizontal', 'vertical') - def test_configure_padx(self): - widget = self.create() - self.checkPixelsParam(widget, 'padx', 3, 4.4, 5.6, '12m', - conv=self._conv_pad_pixels) - expected = 0 if self._clip_pad else -2 - self.checkParam(widget, 'padx', -2, expected=expected, - conv=self._conv_pad_pixels) - - def test_configure_pady(self): - widget = self.create() - self.checkPixelsParam(widget, 'pady', 3, 4.4, 5.6, '12m', - conv=self._conv_pad_pixels) - expected = 0 if self._clip_pad else -2 - self.checkParam(widget, 'pady', -2, expected=expected, - conv=self._conv_pad_pixels) - @requires_tk(8, 7) def test_configure_placeholder(self): widget = self.create() @@ -421,10 +432,6 @@ def test_configure_selectbackground(self): widget = self.create() self.checkColorParam(widget, 'selectbackground') - def test_configure_selectborderwidth(self): - widget = self.create() - self.checkPixelsParam(widget, 'selectborderwidth', 1.3, 2.6, -2, '10p') - def test_configure_selectforeground(self): widget = self.create() self.checkColorParam(widget, 'selectforeground') @@ -534,6 +541,7 @@ def test_configure_variable(self): class IntegerSizeTests: + """ Tests widgets which only accept integral width and height.""" def test_configure_height(self): widget = self.create() self.checkIntegerParam(widget, 'height', 100, -100, 0) @@ -544,6 +552,7 @@ def test_configure_width(self): class PixelSizeTests: + """ Tests widgets which accept screen distances for width and height.""" def test_configure_height(self): widget = self.create() self.checkPixelsParam(widget, 'height', 100, 101.2, 102.6, -100, 0, '3c') @@ -553,7 +562,7 @@ def test_configure_width(self): self.checkPixelsParam(widget, 'width', 402, 403.4, 404.6, -402, 0, '5i') -def add_standard_options(*source_classes): +def add_configure_tests(*source_classes): # This decorator adds test_configure_xxx methods from source classes for # every xxx option in the OPTIONS class attribute if they are not defined # explicitly. diff --git a/Lib/test/test_ttk/test_style.py b/Lib/test/test_ttk/test_style.py index 9a04a95dc40d65..64c9647506748c 100644 --- a/Lib/test/test_ttk/test_style.py +++ b/Lib/test/test_ttk/test_style.py @@ -227,12 +227,12 @@ def test_element_create_image(self): foreground='blue', background='yellow') img3 = tkinter.BitmapImage(master=self.root, file=imgfile, foreground='white', background='black') - style.element_create('Button.button', 'image', + style.element_create('Button.testbutton', 'image', img1, ('pressed', img2), ('active', img3), border=(2, 4), sticky='we') - self.assertIn('Button.button', style.element_names()) + self.assertIn('Button.testbutton', style.element_names()) - style.layout('Button', [('Button.button', {'sticky': 'news'})]) + style.layout('Button', [('Button.testbutton', {'sticky': 'news'})]) b = ttk.Button(self.root, style='Button') b.pack(expand=True, fill='both') self.assertEqual(b.winfo_reqwidth(), 16) diff --git a/Lib/test/test_ttk/test_widgets.py b/Lib/test/test_ttk/test_widgets.py index cb210b7d2fc960..a8990110e69190 100644 --- a/Lib/test/test_ttk/test_widgets.py +++ b/Lib/test/test_ttk/test_widgets.py @@ -8,7 +8,7 @@ from test.test_tkinter.support import ( AbstractTkTest, requires_tk, tk_version, get_tk_patchlevel, simulate_mouse_click, AbstractDefaultRootTest) -from test.test_tkinter.widget_tests import (add_standard_options, +from test.test_tkinter.widget_tests import (add_configure_tests, AbstractWidgetTest, StandardOptionsTests, IntegerSizeTests, PixelSizeTests) requires('gui') @@ -125,10 +125,11 @@ def test_cb(arg1, **kw): class AbstractToplevelTest(AbstractWidgetTest, PixelSizeTests): - _conv_pixels = False + _rounds_pixels = False + _clipped = {} -@add_standard_options(StandardTtkOptionsTests) +@add_configure_tests(StandardTtkOptionsTests) class FrameTest(AbstractToplevelTest, unittest.TestCase): OPTIONS = ( 'borderwidth', 'class', 'cursor', 'height', @@ -140,7 +141,7 @@ def create(self, **kwargs): return ttk.Frame(self.root, **kwargs) -@add_standard_options(StandardTtkOptionsTests) +@add_configure_tests(StandardTtkOptionsTests) class LabelFrameTest(AbstractToplevelTest, unittest.TestCase): OPTIONS = ( 'borderwidth', 'class', 'cursor', 'height', @@ -168,6 +169,8 @@ def test_configure_labelwidget(self): class AbstractLabelTest(AbstractWidgetTest): _allow_empty_justify = True + _rounds_pixels = False + _clipped = {} def checkImageParam(self, widget, name): image = tkinter.PhotoImage(master=self.root, name='image1') @@ -196,7 +199,7 @@ def test_configure_width(self): self.checkParams(widget, 'width', 402, -402, 0) -@add_standard_options(StandardTtkOptionsTests) +@add_configure_tests(StandardTtkOptionsTests) class LabelTest(AbstractLabelTest, unittest.TestCase): OPTIONS = ( 'anchor', 'background', 'borderwidth', @@ -214,7 +217,7 @@ def create(self, **kwargs): test_configure_justify = StandardOptionsTests.test_configure_justify -@add_standard_options(StandardTtkOptionsTests) +@add_configure_tests(StandardTtkOptionsTests) class ButtonTest(AbstractLabelTest, unittest.TestCase): OPTIONS = ( 'class', 'command', 'compound', 'cursor', 'default', @@ -239,7 +242,7 @@ def test_invoke(self): self.assertTrue(success) -@add_standard_options(StandardTtkOptionsTests) +@add_configure_tests(StandardTtkOptionsTests) class CheckbuttonTest(AbstractLabelTest, unittest.TestCase): OPTIONS = ( 'class', 'command', 'compound', 'cursor', @@ -326,7 +329,7 @@ def test_unique_variables2(self): self.assertEqual(len(set(variables)), len(buttons), variables) -@add_standard_options(IntegerSizeTests, StandardTtkOptionsTests) +@add_configure_tests(IntegerSizeTests, StandardTtkOptionsTests) class EntryTest(AbstractWidgetTest, unittest.TestCase): OPTIONS = ( 'background', 'class', 'cursor', @@ -336,6 +339,8 @@ class EntryTest(AbstractWidgetTest, unittest.TestCase): 'show', 'state', 'style', 'takefocus', 'textvariable', 'validate', 'validatecommand', 'width', 'xscrollcommand', ) + _rounds_pixels = False + _clipped = {} IDENTIFY_AS = 'Entry.field' if sys.platform == 'darwin' else 'textarea' def setUp(self): @@ -450,7 +455,7 @@ def validate(content): self.assertEqual(self.entry.state(), ()) -@add_standard_options(IntegerSizeTests, StandardTtkOptionsTests) +@add_configure_tests(IntegerSizeTests, StandardTtkOptionsTests) class ComboboxTest(EntryTest, unittest.TestCase): OPTIONS = ( 'background', 'class', 'cursor', 'exportselection', @@ -461,7 +466,7 @@ class ComboboxTest(EntryTest, unittest.TestCase): 'validate', 'validatecommand', 'values', 'width', 'xscrollcommand', ) - IDENTIFY_AS = 'Combobox.button' if sys.platform == 'darwin' else 'textarea' + IDENTIFY_AS = 'textarea' def setUp(self): super().setUp() @@ -576,12 +581,14 @@ def check_get_current(getval, currval): combo2.destroy() -@add_standard_options(IntegerSizeTests, StandardTtkOptionsTests) +@add_configure_tests(IntegerSizeTests, StandardTtkOptionsTests) class PanedWindowTest(AbstractWidgetTest, unittest.TestCase): OPTIONS = ( 'class', 'cursor', 'height', 'orient', 'style', 'takefocus', 'width', ) + _rounds_pixels = False + _clipped = {} def setUp(self): super().setUp() @@ -712,7 +719,7 @@ def test_sashpos(self): self.assertIsInstance(self.paned.sashpos(0), int) -@add_standard_options(StandardTtkOptionsTests) +@add_configure_tests(StandardTtkOptionsTests) class RadiobuttonTest(AbstractLabelTest, unittest.TestCase): OPTIONS = ( 'class', 'command', 'compound', 'cursor', @@ -791,13 +798,14 @@ def test_configure_menu(self): menu.destroy() -@add_standard_options(StandardTtkOptionsTests) +@add_configure_tests(StandardTtkOptionsTests) class ScaleTest(AbstractWidgetTest, unittest.TestCase): OPTIONS = ( 'class', 'command', 'cursor', 'from', 'length', 'orient', 'state', 'style', 'takefocus', 'to', 'value', 'variable', ) - _conv_pixels = False + _rounds_pixels = False + _clipped = {} default_orient = 'horizontal' def setUp(self): @@ -899,7 +907,7 @@ def test_set(self): self.assertRaises(tkinter.TclError, self.scale.set, None) -@add_standard_options(StandardTtkOptionsTests) +@add_configure_tests(StandardTtkOptionsTests) class ProgressbarTest(AbstractWidgetTest, unittest.TestCase): OPTIONS = ( 'anchor', 'class', 'cursor', 'font', 'foreground', 'justify', @@ -907,7 +915,8 @@ class ProgressbarTest(AbstractWidgetTest, unittest.TestCase): 'mode', 'maximum', 'phase', 'text', 'wraplength', 'style', 'takefocus', 'value', 'variable', ) - _conv_pixels = False + _rounds_pixels = False + _clipped = {} _allow_empty_justify = True default_orient = 'horizontal' @@ -952,25 +961,26 @@ def test_configure_value(self): @unittest.skipIf(sys.platform == 'darwin', 'ttk.Scrollbar is special on MacOSX') -@add_standard_options(StandardTtkOptionsTests) +@add_configure_tests(StandardTtkOptionsTests) class ScrollbarTest(AbstractWidgetTest, unittest.TestCase): OPTIONS = ( 'class', 'command', 'cursor', 'orient', 'style', 'takefocus', ) + _rounds_pixels = False + _clipped = {} default_orient = 'vertical' def create(self, **kwargs): return ttk.Scrollbar(self.root, **kwargs) - -@add_standard_options(PixelSizeTests if tk_version >= (8, 7) else IntegerSizeTests, +@add_configure_tests(PixelSizeTests if tk_version >= (8, 7) else IntegerSizeTests, StandardTtkOptionsTests) class NotebookTest(AbstractWidgetTest, unittest.TestCase): OPTIONS = ( 'class', 'cursor', 'height', 'padding', 'style', 'takefocus', 'width', ) - if tk_version >= (8, 7): - _conv_pixels = False + _rounds_pixels = (tk_version < (9,0)) + _clipped = {} def setUp(self): super().setUp() @@ -983,6 +993,14 @@ def setUp(self): def create(self, **kwargs): return ttk.Notebook(self.root, **kwargs) + def test_configure_height(self): + widget = self.create() + self.checkPixelsParam(widget, 'height', 100, -100, 0) + + def test_configure_width(self): + widget = self.create() + self.checkPixelsParam(widget, 'width', 402, -402, 0) + def test_tab_identifiers(self): self.nb.forget(0) self.nb.hide(self.child2) @@ -1180,7 +1198,7 @@ def test_traversal(self): self.assertEqual(self.nb.select(), str(self.child2)) -@add_standard_options(IntegerSizeTests, StandardTtkOptionsTests) +@add_configure_tests(IntegerSizeTests, StandardTtkOptionsTests) class SpinboxTest(EntryTest, unittest.TestCase): OPTIONS = ( 'background', 'class', 'command', 'cursor', 'exportselection', @@ -1357,7 +1375,7 @@ def test_configure_values(self): spin2.destroy() -@add_standard_options(StandardTtkOptionsTests) +@add_configure_tests(StandardTtkOptionsTests) class TreeviewTest(AbstractWidgetTest, unittest.TestCase): OPTIONS = ( 'class', 'columns', 'cursor', 'displaycolumns', @@ -1365,6 +1383,8 @@ class TreeviewTest(AbstractWidgetTest, unittest.TestCase): 'style', 'takefocus', 'titlecolumns', 'titleitems', 'xscrollcommand', 'yscrollcommand', ) + _rounds_pixels = False + _clipped = {} def setUp(self): super().setUp() @@ -1923,24 +1943,28 @@ def test_tag_has(self): self.assertEqual(self.tv.tag_has('tag3'), ()) -@add_standard_options(StandardTtkOptionsTests) +@add_configure_tests(StandardTtkOptionsTests) class SeparatorTest(AbstractWidgetTest, unittest.TestCase): OPTIONS = ( 'class', 'cursor', 'orient', 'style', 'takefocus', # 'state'? ) + _rounds_pixels = False + _clipped = {} default_orient = 'horizontal' def create(self, **kwargs): return ttk.Separator(self.root, **kwargs) -@add_standard_options(StandardTtkOptionsTests) +@add_configure_tests(StandardTtkOptionsTests) class SizegripTest(AbstractWidgetTest, unittest.TestCase): OPTIONS = ( 'class', 'cursor', 'style', 'takefocus', # 'state'? ) + _rounds_pixels = False + _clipped = {} def create(self, **kwargs): return ttk.Sizegrip(self.root, **kwargs) diff --git a/Modules/_tkinter.c b/Modules/_tkinter.c index cd3722f54c24ce..25eb9e9a2f5193 100644 --- a/Modules/_tkinter.c +++ b/Modules/_tkinter.c @@ -325,6 +325,7 @@ typedef struct { const Tcl_ObjType *ListType; const Tcl_ObjType *StringType; const Tcl_ObjType *UTF32StringType; + const Tcl_ObjType *PixelType; } TkappObject; #define Tkapp_Interp(v) (((TkappObject *) (v))->interp) @@ -637,6 +638,7 @@ Tkapp_New(const char *screenName, const char *className, v->ListType = Tcl_GetObjType("list"); v->StringType = Tcl_GetObjType("string"); v->UTF32StringType = Tcl_GetObjType("utf32string"); + v->PixelType = Tcl_GetObjType("pixel"); /* Delete the 'exit' command, which can screw things up */ Tcl_DeleteCommand(v->interp, "exit"); @@ -1236,7 +1238,8 @@ FromObj(TkappObject *tkapp, Tcl_Obj *value) } if (value->typePtr == tkapp->StringType || - value->typePtr == tkapp->UTF32StringType) + value->typePtr == tkapp->UTF32StringType || + value->typePtr == tkapp->PixelType) { return unicodeFromTclObj(tkapp, value); }