1import functools 2import re 3import tkinter 4import unittest 5 6class AbstractTkTest: 7 8 @classmethod 9 def setUpClass(cls): 10 cls._old_support_default_root = tkinter._support_default_root 11 destroy_default_root() 12 tkinter.NoDefaultRoot() 13 cls.root = tkinter.Tk() 14 cls.wantobjects = cls.root.wantobjects() 15 # De-maximize main window. 16 # Some window managers can maximize new windows. 17 cls.root.wm_state('normal') 18 try: 19 cls.root.wm_attributes('-zoomed', False) 20 except tkinter.TclError: 21 pass 22 23 @classmethod 24 def tearDownClass(cls): 25 cls.root.update_idletasks() 26 cls.root.destroy() 27 del cls.root 28 tkinter._default_root = None 29 tkinter._support_default_root = cls._old_support_default_root 30 31 def setUp(self): 32 self.root.deiconify() 33 34 def tearDown(self): 35 for w in self.root.winfo_children(): 36 w.destroy() 37 self.root.withdraw() 38 39def destroy_default_root(): 40 if getattr(tkinter, '_default_root', None): 41 tkinter._default_root.update_idletasks() 42 tkinter._default_root.destroy() 43 tkinter._default_root = None 44 45def simulate_mouse_click(widget, x, y): 46 """Generate proper events to click at the x, y position (tries to act 47 like an X server).""" 48 widget.event_generate('<Enter>', x=0, y=0) 49 widget.event_generate('<Motion>', x=x, y=y) 50 widget.event_generate('<ButtonPress-1>', x=x, y=y) 51 widget.event_generate('<ButtonRelease-1>', x=x, y=y) 52 53 54import _tkinter 55tcl_version = tuple(map(int, _tkinter.TCL_VERSION.split('.'))) 56 57def requires_tcl(*version): 58 if len(version) <= 2: 59 return unittest.skipUnless(tcl_version >= version, 60 'requires Tcl version >= ' + '.'.join(map(str, version))) 61 62 def deco(test): 63 @functools.wraps(test) 64 def newtest(self): 65 if get_tk_patchlevel() < version: 66 self.skipTest('requires Tcl version >= ' + 67 '.'.join(map(str, version))) 68 test(self) 69 return newtest 70 return deco 71 72_tk_patchlevel = None 73def get_tk_patchlevel(): 74 global _tk_patchlevel 75 if _tk_patchlevel is None: 76 tcl = tkinter.Tcl() 77 patchlevel = tcl.call('info', 'patchlevel') 78 m = re.fullmatch(r'(\d+)\.(\d+)([ab.])(\d+)', patchlevel) 79 major, minor, releaselevel, serial = m.groups() 80 major, minor, serial = int(major), int(minor), int(serial) 81 releaselevel = {'a': 'alpha', 'b': 'beta', '.': 'final'}[releaselevel] 82 if releaselevel == 'final': 83 _tk_patchlevel = major, minor, serial, releaselevel, 0 84 else: 85 _tk_patchlevel = major, minor, 0, releaselevel, serial 86 return _tk_patchlevel 87 88units = { 89 'c': 72 / 2.54, # centimeters 90 'i': 72, # inches 91 'm': 72 / 25.4, # millimeters 92 'p': 1, # points 93} 94 95def pixels_conv(value): 96 return float(value[:-1]) * units[value[-1:]] 97 98def tcl_obj_eq(actual, expected): 99 if actual == expected: 100 return True 101 if isinstance(actual, _tkinter.Tcl_Obj): 102 if isinstance(expected, str): 103 return str(actual) == expected 104 if isinstance(actual, tuple): 105 if isinstance(expected, tuple): 106 return (len(actual) == len(expected) and 107 all(tcl_obj_eq(act, exp) 108 for act, exp in zip(actual, expected))) 109 return False 110 111def widget_eq(actual, expected): 112 if actual == expected: 113 return True 114 if isinstance(actual, (str, tkinter.Widget)): 115 if isinstance(expected, (str, tkinter.Widget)): 116 return str(actual) == str(expected) 117 return False 118