@@ -351,6 +351,19 @@ def setUp(self):
351351 def create (self , ** kwargs ):
352352 return ttk .Entry (self .root , ** kwargs )
353353
354+ def _arrow_x (self , widget , y , arrow ):
355+ # Return an x coordinate on the up/down *arrow* element in row y,
356+ # found by scanning inward from the right edge of the widget. A
357+ # fixed inset such as width - 5 lands in the arrow element's
358+ # right-hand border, which scales with the display scaling, so at a
359+ # high DPI identify() there returns 'field' instead of the arrow.
360+ self .assertTrue (wait_until_mapped (widget , full_size = True ))
361+ width = widget .winfo_width ()
362+ for x in range (width - 1 , width // 2 , - 1 ):
363+ if widget .identify (x , y ).endswith (arrow ):
364+ return x
365+ self .fail (f'no { arrow } found in row { y } ' )
366+
354367 def test_configure_invalidcommand (self ):
355368 widget = self .create ()
356369 self .checkCommandParam (widget , 'invalidcommand' )
@@ -385,7 +398,12 @@ def test_identify(self):
385398 # Identifying the element under a point requires the widget to be
386399 # mapped with a real size; the rest of the checks do not.
387400 if wait_until_mapped (self .entry ):
388- self .assertIn (self .entry .identify (5 , 5 ), self .IDENTIFY_AS )
401+ # Probe the centre of the widget: a fixed pixel such as (5, 5)
402+ # lands in the field at a normal scaling but in the surrounding
403+ # padding at a high DPI.
404+ x = self .entry .winfo_width () // 2
405+ y = self .entry .winfo_height () // 2
406+ self .assertIn (self .entry .identify (x , y ), self .IDENTIFY_AS )
389407 self .assertEqual (self .entry .identify (- 1 , - 1 ), "" )
390408
391409 self .assertRaises (tkinter .TclError , self .entry .identify , None , 5 )
@@ -486,10 +504,11 @@ def test_configure_height(self):
486504 self .checkParams (widget , 'height' , 100 , 101.2 , 102.6 , - 100 , 0 , '1i' )
487505
488506 def _show_drop_down_listbox (self ):
489- width = self .combo .winfo_width ()
490- x , y = width - 5 , 5
507+ y = 5
491508 if sys .platform != 'darwin' : # there's no down arrow on macOS
492- self .assertRegex (self .combo .identify (x , y ), r'.*downarrow\z' )
509+ x = self ._arrow_x (self .combo , y , 'downarrow' )
510+ else :
511+ x = self .combo .winfo_width () - 5
493512 self .combo .event_generate ('<Button-1>' , x = x , y = y )
494513 self .combo .event_generate ('<ButtonRelease-1>' , x = x , y = y )
495514
@@ -1062,21 +1081,15 @@ def test_tab_identifiers(self):
10621081
10631082 self .nb .pack ()
10641083 self .nb .update ()
1065- if sys .platform == 'darwin' :
1066- tb_idx = "@20,5"
1067- else :
1068- tb_idx = "@5,5"
1069- self .assertEqual (self .nb .tab (tb_idx ), self .nb .tab ('current' ))
1070-
1071- for i in range (5 , 100 , 5 ):
1072- try :
1073- if self .nb .tab ('@%d, 5' % i , text = None ) == 'a' :
1074- break
1075- except tkinter .TclError :
1076- pass
1084+ # Address a tab by a point taken from Tk's own geometry rather than a
1085+ # fixed pixel, which can land on a different tab or miss the tabs
1086+ # entirely (e.g. on Aqua the tabs are inset) as the display scaling
1087+ # changes.
1088+ x , y = self ._tab_point (self .nb .index ('current' ))
1089+ self .assertEqual (self .nb .tab (f'@{ x } ,{ y } ' ), self .nb .tab ('current' ))
10771090
1078- else :
1079- self .fail ( "Tab with text 'a' not found" )
1091+ x , y = self . _tab_point ( self . nb . index ( self . child1 ))
1092+ self .assertEqual ( self . nb . tab ( f'@ { x } , { y } ' , text = None ), 'a' )
10801093
10811094 def test_add_and_hidden (self ):
10821095 self .assertRaises (tkinter .TclError , self .nb .hide , - 1 )
@@ -1206,23 +1219,43 @@ def test_configure_tabs(self):
12061219
12071220 self .assertEqual (self .nb .tabs (), ())
12081221
1222+ def _tab_point (self , index ):
1223+ # Return a window coordinate that really lies on tab *index*, taken
1224+ # from Tk's own geometry (ttk::notebook index @x,y), so that it is
1225+ # correct regardless of the theme, platform and display scaling. A
1226+ # fixed pixel such as (5, 5) can land on a different tab element
1227+ # depending on the DPI, or miss the tabs entirely (e.g. on Aqua the
1228+ # tabs are inset), which would make a click there test nothing.
1229+ self .assertTrue (wait_until_mapped (self .nb , full_size = True ))
1230+ w , h = self .nb .winfo_width (), self .nb .winfo_height ()
1231+ # The selected pane fills the widget except for the tab strip, so skip
1232+ # that large tab-free region instead of querying every pixel in it.
1233+ pane = self .nb .nametowidget (self .nb .select ())
1234+ px , py = pane .winfo_x (), pane .winfo_y ()
1235+ pw , ph = pane .winfo_width (), pane .winfo_height ()
1236+ for y in range (h ):
1237+ inside_y = py <= y < py + ph
1238+ for x in range (w ):
1239+ if inside_y and px <= x < px + pw :
1240+ continue
1241+ try :
1242+ if self .nb .index (f'@{ x } ,{ y } ' ) == index :
1243+ return x , y
1244+ except TclError :
1245+ continue
1246+ self .fail (f'no point found on tab { index } ' )
1247+
12091248 def test_traversal (self ):
12101249 self .nb .pack ()
12111250 self .nb .update ()
12121251
1213- self .nb .select (0 )
1252+ # A mouse click selects the tab it lands on.
1253+ self .nb .select (1 )
1254+ self .assertEqual (self .nb .select (), str (self .child2 ))
1255+ simulate_mouse_click (self .nb , * self ._tab_point (0 ))
1256+ self .assertEqual (self .nb .select (), str (self .child1 ))
12141257
1215- if sys .platform == 'darwin' :
1216- focus_identify_as = ''
1217- elif sys .platform == 'win32' :
1218- focus_identify_as = 'focus'
1219- else :
1220- focus_identify_as = 'focus' if tk_version < (8 , 7 ) else 'padding'
1221- # identify() at (5, 5) needs the tab realized there; under focus
1222- # contention the mapped size can lag, so wait for the full size.
1223- if wait_until_mapped (self .nb , full_size = True ):
1224- self .assertEqual (self .nb .identify (5 , 5 ), focus_identify_as )
1225- simulate_mouse_click (self .nb , 5 , 5 )
1258+ # Control-Tab and Shift-Control-Tab traverse the tabs.
12261259 self .nb .focus_force ()
12271260 self .nb .event_generate ('<Control-Tab>' )
12281261 self .assertEqual (self .nb .select (), str (self .child2 ))
@@ -1237,21 +1270,25 @@ def test_traversal(self):
12371270 self .nb .tab (self .child2 , text = 'e' , underline = 0 )
12381271 self .nb .enable_traversal ()
12391272 self .nb .focus_force ()
1240- if wait_until_mapped (self .nb , full_size = True ):
1241- self .assertEqual (self .nb .identify (5 , 5 ), focus_identify_as )
1242- simulate_mouse_click (self .nb , 5 , 5 )
1243- # on macOS Emacs-style keyboard shortcuts are region-dependent;
1244- # let's use the regular arrow keys instead
1273+
1274+ # A click still selects the tab it lands on after enable_traversal().
1275+ self .nb .select (1 )
1276+ self .assertEqual (self .nb .select (), str (self .child2 ))
1277+ simulate_mouse_click (self .nb , * self ._tab_point (0 ))
1278+ self .assertEqual (self .nb .select (), str (self .child1 ))
1279+
1280+ # Mnemonics traverse the tabs (macOS uses region-dependent
1281+ # Emacs-style shortcuts, so use the regular arrow keys there).
12451282 if sys .platform == 'darwin' :
12461283 begin = '<Left>'
12471284 end = '<Right>'
12481285 else :
12491286 begin = '<Alt-a>'
12501287 end = '<Alt-e>'
1251- self .nb .event_generate (begin )
1252- self .assertEqual (self .nb .select (), str (self .child1 ))
12531288 self .nb .event_generate (end )
12541289 self .assertEqual (self .nb .select (), str (self .child2 ))
1290+ self .nb .event_generate (begin )
1291+ self .assertEqual (self .nb .select (), str (self .child1 ))
12551292
12561293
12571294@add_configure_tests (IntegerSizeTests , StandardTtkOptionsTests )
@@ -1277,22 +1314,16 @@ def create(self, **kwargs):
12771314
12781315 def _click_increment_arrow (self ):
12791316 self .require_mapped (self .spin )
1280- width = self .spin .winfo_width ()
1281- height = self .spin .winfo_height ()
1282- x = width - 5
1283- y = height // 2 - 5
1284- self .assertRegex (self .spin .identify (x , y ), r'.*uparrow\z' )
1317+ y = self .spin .winfo_height ()// 2 - 5
1318+ x = self ._arrow_x (self .spin , y , 'uparrow' )
12851319 self .spin .event_generate ('<ButtonPress-1>' , x = x , y = y )
12861320 self .spin .event_generate ('<ButtonRelease-1>' , x = x , y = y )
12871321 self .spin .update_idletasks ()
12881322
12891323 def _click_decrement_arrow (self ):
12901324 self .require_mapped (self .spin )
1291- width = self .spin .winfo_width ()
1292- height = self .spin .winfo_height ()
1293- x = width - 5
1294- y = height // 2 + 4
1295- self .assertRegex (self .spin .identify (x , y ), r'.*downarrow\z' )
1325+ y = self .spin .winfo_height ()// 2 + 4
1326+ x = self ._arrow_x (self .spin , y , 'downarrow' )
12961327 self .spin .event_generate ('<ButtonPress-1>' , x = x , y = y )
12971328 self .spin .event_generate ('<ButtonRelease-1>' , x = x , y = y )
12981329 self .spin .update_idletasks ()
@@ -2062,14 +2093,17 @@ def test_tag_bind(self):
20622093 self .tv .pack ()
20632094 self .tv .update ()
20642095
2096+ # Find the y coordinate of each item. Scan the whole height of the
2097+ # widget rather than a fixed pixel range, since the row height grows
2098+ # with the display scaling.
20652099 pos_y = set ()
20662100 found = set ()
2067- for i in range (0 , 100 , 10 ):
2101+ for y in range (self . tv . winfo_height () ):
20682102 if len (found ) == 2 : # item1 and item2 already found
20692103 break
2070- item_id = self .tv .identify_row (i )
2104+ item_id = self .tv .identify_row (y )
20712105 if item_id and item_id not in found :
2072- pos_y .add (i )
2106+ pos_y .add (y )
20732107 found .add (item_id )
20742108
20752109 self .assertEqual (len (pos_y ), 2 ) # item1 and item2 y pos
0 commit comments