1import sys 2import unittest 3import tkinter 4from tkinter import ttk 5from test.support import requires, gc_collect 6from test.test_tkinter.support import AbstractTkTest, AbstractDefaultRootTest 7 8requires('gui') 9 10class LabeledScaleTest(AbstractTkTest, unittest.TestCase): 11 12 def tearDown(self): 13 self.root.update_idletasks() 14 super().tearDown() 15 16 def test_widget_destroy(self): 17 # automatically created variable 18 x = ttk.LabeledScale(self.root) 19 var = x._variable._name 20 x.destroy() 21 gc_collect() # For PyPy or other GCs. 22 self.assertRaises(tkinter.TclError, x.tk.globalgetvar, var) 23 24 # manually created variable 25 myvar = tkinter.DoubleVar(self.root) 26 name = myvar._name 27 x = ttk.LabeledScale(self.root, variable=myvar) 28 x.destroy() 29 if self.wantobjects: 30 self.assertEqual(x.tk.globalgetvar(name), myvar.get()) 31 else: 32 self.assertEqual(float(x.tk.globalgetvar(name)), myvar.get()) 33 del myvar 34 gc_collect() # For PyPy or other GCs. 35 self.assertRaises(tkinter.TclError, x.tk.globalgetvar, name) 36 37 # checking that the tracing callback is properly removed 38 myvar = tkinter.IntVar(self.root) 39 # LabeledScale will start tracing myvar 40 x = ttk.LabeledScale(self.root, variable=myvar) 41 x.destroy() 42 # Unless the tracing callback was removed, creating a new 43 # LabeledScale with the same var will cause an error now. This 44 # happens because the variable will be set to (possibly) a new 45 # value which causes the tracing callback to be called and then 46 # it tries calling instance attributes not yet defined. 47 ttk.LabeledScale(self.root, variable=myvar) 48 if hasattr(sys, 'last_exc'): 49 self.assertNotEqual(type(sys.last_exc), tkinter.TclError) 50 elif hasattr(sys, 'last_type'): 51 self.assertNotEqual(sys.last_type, tkinter.TclError) 52 53 def test_initialization(self): 54 # master passing 55 master = tkinter.Frame(self.root) 56 x = ttk.LabeledScale(master) 57 self.assertEqual(x.master, master) 58 x.destroy() 59 60 # variable initialization/passing 61 passed_expected = (('0', 0), (0, 0), (10, 10), 62 (-1, -1), (sys.maxsize + 1, sys.maxsize + 1), 63 (2.5, 2), ('2.5', 2)) 64 for pair in passed_expected: 65 x = ttk.LabeledScale(self.root, from_=pair[0]) 66 self.assertEqual(x.value, pair[1]) 67 x.destroy() 68 x = ttk.LabeledScale(self.root, from_=None) 69 self.assertRaises((ValueError, tkinter.TclError), x._variable.get) 70 x.destroy() 71 # variable should have its default value set to the from_ value 72 myvar = tkinter.DoubleVar(self.root, value=20) 73 x = ttk.LabeledScale(self.root, variable=myvar) 74 self.assertEqual(x.value, 0) 75 x.destroy() 76 # check that it is really using a DoubleVar 77 x = ttk.LabeledScale(self.root, variable=myvar, from_=0.5) 78 self.assertEqual(x.value, 0.5) 79 self.assertEqual(x._variable._name, myvar._name) 80 x.destroy() 81 82 # widget positionment 83 def check_positions(scale, scale_pos, label, label_pos): 84 self.assertEqual(scale.pack_info()['side'], scale_pos) 85 self.assertEqual(label.place_info()['anchor'], label_pos) 86 x = ttk.LabeledScale(self.root, compound='top') 87 check_positions(x.scale, 'bottom', x.label, 'n') 88 x.destroy() 89 x = ttk.LabeledScale(self.root, compound='bottom') 90 check_positions(x.scale, 'top', x.label, 's') 91 x.destroy() 92 # invert default positions 93 x = ttk.LabeledScale(self.root, compound='unknown') 94 check_positions(x.scale, 'top', x.label, 's') 95 x.destroy() 96 x = ttk.LabeledScale(self.root) # take default positions 97 check_positions(x.scale, 'bottom', x.label, 'n') 98 x.destroy() 99 100 # extra, and invalid, kwargs 101 self.assertRaises(tkinter.TclError, ttk.LabeledScale, master, a='b') 102 103 104 def test_horizontal_range(self): 105 lscale = ttk.LabeledScale(self.root, from_=0, to=10) 106 lscale.pack() 107 lscale.update() 108 109 linfo_1 = lscale.label.place_info() 110 prev_xcoord = lscale.scale.coords()[0] 111 self.assertEqual(prev_xcoord, int(linfo_1['x'])) 112 # change range to: from -5 to 5. This should change the x coord of 113 # the scale widget, since 0 is at the middle of the new 114 # range. 115 lscale.scale.configure(from_=-5, to=5) 116 # The following update is needed since the test doesn't use mainloop, 117 # at the same time this shouldn't affect test outcome 118 lscale.update() 119 curr_xcoord = lscale.scale.coords()[0] 120 self.assertNotEqual(prev_xcoord, curr_xcoord) 121 # the label widget should have been repositioned too 122 linfo_2 = lscale.label.place_info() 123 self.assertEqual(lscale.label['text'], 0 if self.wantobjects else '0') 124 self.assertEqual(curr_xcoord, int(linfo_2['x'])) 125 # change the range back 126 lscale.scale.configure(from_=0, to=10) 127 self.assertNotEqual(prev_xcoord, curr_xcoord) 128 self.assertEqual(prev_xcoord, int(linfo_1['x'])) 129 130 lscale.destroy() 131 132 133 def test_variable_change(self): 134 x = ttk.LabeledScale(self.root) 135 x.pack() 136 x.update() 137 138 curr_xcoord = x.scale.coords()[0] 139 newval = x.value + 1 140 x.value = newval 141 # The following update is needed since the test doesn't use mainloop, 142 # at the same time this shouldn't affect test outcome 143 x.update() 144 self.assertEqual(x.value, newval) 145 self.assertEqual(x.label['text'], 146 newval if self.wantobjects else str(newval)) 147 self.assertEqual(float(x.scale.get()), newval) 148 self.assertGreater(x.scale.coords()[0], curr_xcoord) 149 self.assertEqual(x.scale.coords()[0], 150 int(x.label.place_info()['x'])) 151 152 # value outside range 153 if self.wantobjects: 154 conv = lambda x: x 155 else: 156 conv = int 157 x.value = conv(x.scale['to']) + 1 # no changes shouldn't happen 158 x.update() 159 self.assertEqual(x.value, newval) 160 self.assertEqual(conv(x.label['text']), newval) 161 self.assertEqual(float(x.scale.get()), newval) 162 self.assertEqual(x.scale.coords()[0], 163 int(x.label.place_info()['x'])) 164 165 # non-integer value 166 x.value = newval = newval + 1.5 167 x.update() 168 self.assertEqual(x.value, int(newval)) 169 self.assertEqual(conv(x.label['text']), int(newval)) 170 self.assertEqual(float(x.scale.get()), newval) 171 172 x.destroy() 173 174 175 def test_resize(self): 176 x = ttk.LabeledScale(self.root) 177 x.pack(expand=True, fill='both') 178 gc_collect() # For PyPy or other GCs. 179 x.update() 180 181 width, height = x.master.winfo_width(), x.master.winfo_height() 182 width_new, height_new = width * 2, height * 2 183 184 x.value = 3 185 x.update() 186 x.master.wm_geometry("%dx%d" % (width_new, height_new)) 187 self.assertEqual(int(x.label.place_info()['x']), 188 x.scale.coords()[0]) 189 190 # Reset geometry 191 x.master.wm_geometry("%dx%d" % (width, height)) 192 x.destroy() 193 194 195class OptionMenuTest(AbstractTkTest, unittest.TestCase): 196 197 def setUp(self): 198 super().setUp() 199 self.textvar = tkinter.StringVar(self.root) 200 201 def tearDown(self): 202 del self.textvar 203 super().tearDown() 204 205 206 def test_widget_destroy(self): 207 var = tkinter.StringVar(self.root) 208 optmenu = ttk.OptionMenu(self.root, var) 209 name = var._name 210 optmenu.update_idletasks() 211 optmenu.destroy() 212 self.assertEqual(optmenu.tk.globalgetvar(name), var.get()) 213 del var 214 gc_collect() # For PyPy or other GCs. 215 self.assertRaises(tkinter.TclError, optmenu.tk.globalgetvar, name) 216 217 218 def test_initialization(self): 219 self.assertRaises(tkinter.TclError, 220 ttk.OptionMenu, self.root, self.textvar, invalid='thing') 221 222 optmenu = ttk.OptionMenu(self.root, self.textvar, 'b', 'a', 'b') 223 self.assertEqual(optmenu._variable.get(), 'b') 224 225 self.assertTrue(optmenu['menu']) 226 self.assertTrue(optmenu['textvariable']) 227 228 optmenu.destroy() 229 230 231 def test_menu(self): 232 items = ('a', 'b', 'c') 233 default = 'a' 234 optmenu = ttk.OptionMenu(self.root, self.textvar, default, *items) 235 found_default = False 236 for i in range(len(items)): 237 value = optmenu['menu'].entrycget(i, 'value') 238 self.assertEqual(value, items[i]) 239 if value == default: 240 found_default = True 241 self.assertTrue(found_default) 242 optmenu.destroy() 243 244 # default shouldn't be in menu if it is not part of values 245 default = 'd' 246 optmenu = ttk.OptionMenu(self.root, self.textvar, default, *items) 247 curr = None 248 i = 0 249 while True: 250 last, curr = curr, optmenu['menu'].entryconfigure(i, 'value') 251 if last == curr: 252 # no more menu entries 253 break 254 self.assertNotEqual(curr, default) 255 i += 1 256 self.assertEqual(i, len(items)) 257 258 # check that variable is updated correctly 259 optmenu.pack() 260 gc_collect() # For PyPy or other GCs. 261 optmenu['menu'].invoke(0) 262 self.assertEqual(optmenu._variable.get(), items[0]) 263 264 # changing to an invalid index shouldn't change the variable 265 self.assertRaises(tkinter.TclError, optmenu['menu'].invoke, -1) 266 self.assertEqual(optmenu._variable.get(), items[0]) 267 268 optmenu.destroy() 269 270 # specifying a callback 271 success = [] 272 def cb_test(item): 273 self.assertEqual(item, items[1]) 274 success.append(True) 275 optmenu = ttk.OptionMenu(self.root, self.textvar, 'a', command=cb_test, 276 *items) 277 optmenu['menu'].invoke(1) 278 if not success: 279 self.fail("Menu callback not invoked") 280 281 optmenu.destroy() 282 283 def test_unique_radiobuttons(self): 284 # check that radiobuttons are unique across instances (bpo25684) 285 items = ('a', 'b', 'c') 286 default = 'a' 287 optmenu = ttk.OptionMenu(self.root, self.textvar, default, *items) 288 textvar2 = tkinter.StringVar(self.root) 289 optmenu2 = ttk.OptionMenu(self.root, textvar2, default, *items) 290 optmenu.pack() 291 optmenu2.pack() 292 optmenu['menu'].invoke(1) 293 optmenu2['menu'].invoke(2) 294 optmenu_stringvar_name = optmenu['menu'].entrycget(0, 'variable') 295 optmenu2_stringvar_name = optmenu2['menu'].entrycget(0, 'variable') 296 self.assertNotEqual(optmenu_stringvar_name, 297 optmenu2_stringvar_name) 298 self.assertEqual(self.root.tk.globalgetvar(optmenu_stringvar_name), 299 items[1]) 300 self.assertEqual(self.root.tk.globalgetvar(optmenu2_stringvar_name), 301 items[2]) 302 303 optmenu.destroy() 304 optmenu2.destroy() 305 306 def test_trace_variable(self): 307 # prior to bpo45160, tracing a variable would cause the callback to be made twice 308 success = [] 309 items = ('a', 'b', 'c') 310 textvar = tkinter.StringVar(self.root) 311 def cb_test(*args): 312 success.append(textvar.get()) 313 optmenu = ttk.OptionMenu(self.root, textvar, "a", *items) 314 optmenu.pack() 315 cb_name = textvar.trace_add("write", cb_test) 316 optmenu['menu'].invoke(1) 317 self.assertEqual(success, ['b']) 318 self.assertEqual(textvar.get(), 'b') 319 textvar.trace_remove("write", cb_name) 320 optmenu.destroy() 321 322 323class DefaultRootTest(AbstractDefaultRootTest, unittest.TestCase): 324 325 def test_labeledscale(self): 326 self._test_widget(ttk.LabeledScale) 327 328 329if __name__ == "__main__": 330 unittest.main() 331