@@ -357,6 +357,19 @@ def setUp(self):
357357 def create (self , ** kwargs ):
358358 return ttk .Entry (self .root , ** kwargs )
359359
360+ def _arrow_x (self , widget , y , arrow ):
361+ # Return an x coordinate on the up/down *arrow* element in row y,
362+ # found by scanning inward from the right edge of the widget. A
363+ # fixed inset such as width - 5 lands in the arrow element's
364+ # right-hand border, which scales with the display scaling, so at a
365+ # high DPI identify() there returns 'field' instead of the arrow.
366+ self .assertTrue (wait_until_mapped (widget , full_size = True ))
367+ width = widget .winfo_width ()
368+ for x in range (width - 1 , width // 2 , - 1 ):
369+ if widget .identify (x , y ).endswith (arrow ):
370+ return x
371+ self .fail (f'no { arrow } found in row { y } ' )
372+
360373 def test_configure_invalidcommand (self ):
361374 widget = self .create ()
362375 self .checkCommandParam (widget , 'invalidcommand' )
@@ -392,7 +405,12 @@ def test_identify(self):
392405 # Identifying the element under a point requires the widget to be
393406 # mapped with a real size; the rest of the checks do not.
394407 if wait_until_mapped (self .entry ):
395- self .assertIn (self .entry .identify (5 , 5 ), self .IDENTIFY_AS )
408+ # Probe the centre of the widget: a fixed pixel such as (5, 5)
409+ # lands in the field at a normal scaling but in the surrounding
410+ # padding at a high DPI.
411+ x = self .entry .winfo_width () // 2
412+ y = self .entry .winfo_height () // 2
413+ self .assertIn (self .entry .identify (x , y ), self .IDENTIFY_AS )
396414 self .assertEqual (self .entry .identify (- 1 , - 1 ), "" )
397415
398416 self .assertRaises (tkinter .TclError , self .entry .identify , None , 5 )
@@ -493,10 +511,11 @@ def test_configure_height(self):
493511 self .checkParams (widget , 'height' , 100 , 101.2 , 102.6 , - 100 , 0 , '1i' )
494512
495513 def _show_drop_down_listbox (self ):
496- width = self .combo .winfo_width ()
497- x , y = width - 5 , 5
514+ y = 5
498515 if sys .platform != 'darwin' : # there's no down arrow on macOS
499- self .assertRegex (self .combo .identify (x , y ), r'.*downarrow\Z' )
516+ x = self ._arrow_x (self .combo , y , 'downarrow' )
517+ else :
518+ x = self .combo .winfo_width () - 5
500519 self .combo .event_generate ('<Button-1>' , x = x , y = y )
501520 self .combo .event_generate ('<ButtonRelease-1>' , x = x , y = y )
502521
@@ -1069,21 +1088,15 @@ def test_tab_identifiers(self):
10691088
10701089 self .nb .pack ()
10711090 self .nb .update ()
1072- if sys .platform == 'darwin' :
1073- tb_idx = "@20,5"
1074- else :
1075- tb_idx = "@5,5"
1076- self .assertEqual (self .nb .tab (tb_idx ), self .nb .tab ('current' ))
1077-
1078- for i in range (5 , 100 , 5 ):
1079- try :
1080- if self .nb .tab ('@%d, 5' % i , text = None ) == 'a' :
1081- break
1082- except tkinter .TclError :
1083- pass
1091+ # Address a tab by a point taken from Tk's own geometry rather than a
1092+ # fixed pixel, which can land on a different tab or miss the tabs
1093+ # entirely (e.g. on Aqua the tabs are inset) as the display scaling
1094+ # changes.
1095+ x , y = self ._tab_point (self .nb .index ('current' ))
1096+ self .assertEqual (self .nb .tab (f'@{ x } ,{ y } ' ), self .nb .tab ('current' ))
10841097
1085- else :
1086- self .fail ( "Tab with text 'a' not found" )
1098+ x , y = self . _tab_point ( self . nb . index ( self . child1 ))
1099+ self .assertEqual ( self . nb . tab ( f'@ { x } , { y } ' , text = None ), 'a' )
10871100
10881101 def test_add_and_hidden (self ):
10891102 self .assertRaises (tkinter .TclError , self .nb .hide , - 1 )
@@ -1213,23 +1226,43 @@ def test_configure_tabs(self):
12131226
12141227 self .assertEqual (self .nb .tabs (), ())
12151228
1229+ def _tab_point (self , index ):
1230+ # Return a window coordinate that really lies on tab *index*, taken
1231+ # from Tk's own geometry (ttk::notebook index @x,y), so that it is
1232+ # correct regardless of the theme, platform and display scaling. A
1233+ # fixed pixel such as (5, 5) can land on a different tab element
1234+ # depending on the DPI, or miss the tabs entirely (e.g. on Aqua the
1235+ # tabs are inset), which would make a click there test nothing.
1236+ self .assertTrue (wait_until_mapped (self .nb , full_size = True ))
1237+ w , h = self .nb .winfo_width (), self .nb .winfo_height ()
1238+ # The selected pane fills the widget except for the tab strip, so skip
1239+ # that large tab-free region instead of querying every pixel in it.
1240+ pane = self .nb .nametowidget (self .nb .select ())
1241+ px , py = pane .winfo_x (), pane .winfo_y ()
1242+ pw , ph = pane .winfo_width (), pane .winfo_height ()
1243+ for y in range (h ):
1244+ inside_y = py <= y < py + ph
1245+ for x in range (w ):
1246+ if inside_y and px <= x < px + pw :
1247+ continue
1248+ try :
1249+ if self .nb .index (f'@{ x } ,{ y } ' ) == index :
1250+ return x , y
1251+ except TclError :
1252+ continue
1253+ self .fail (f'no point found on tab { index } ' )
1254+
12161255 def test_traversal (self ):
12171256 self .nb .pack ()
12181257 self .nb .update ()
12191258
1220- self .nb .select (0 )
1259+ # A mouse click selects the tab it lands on.
1260+ self .nb .select (1 )
1261+ self .assertEqual (self .nb .select (), str (self .child2 ))
1262+ simulate_mouse_click (self .nb , * self ._tab_point (0 ))
1263+ self .assertEqual (self .nb .select (), str (self .child1 ))
12211264
1222- if sys .platform == 'darwin' :
1223- focus_identify_as = ''
1224- elif sys .platform == 'win32' :
1225- focus_identify_as = 'focus'
1226- else :
1227- focus_identify_as = 'focus' if tk_version < (8 , 7 ) else 'padding'
1228- # identify() at (5, 5) needs the tab realized there; under focus
1229- # contention the mapped size can lag, so wait for the full size.
1230- if wait_until_mapped (self .nb , full_size = True ):
1231- self .assertEqual (self .nb .identify (5 , 5 ), focus_identify_as )
1232- simulate_mouse_click (self .nb , 5 , 5 )
1265+ # Control-Tab and Shift-Control-Tab traverse the tabs.
12331266 self .nb .focus_force ()
12341267 self .nb .event_generate ('<Control-Tab>' )
12351268 self .assertEqual (self .nb .select (), str (self .child2 ))
@@ -1244,21 +1277,25 @@ def test_traversal(self):
12441277 self .nb .tab (self .child2 , text = 'e' , underline = 0 )
12451278 self .nb .enable_traversal ()
12461279 self .nb .focus_force ()
1247- if wait_until_mapped (self .nb , full_size = True ):
1248- self .assertEqual (self .nb .identify (5 , 5 ), focus_identify_as )
1249- simulate_mouse_click (self .nb , 5 , 5 )
1250- # on macOS Emacs-style keyboard shortcuts are region-dependent;
1251- # let's use the regular arrow keys instead
1280+
1281+ # A click still selects the tab it lands on after enable_traversal().
1282+ self .nb .select (1 )
1283+ self .assertEqual (self .nb .select (), str (self .child2 ))
1284+ simulate_mouse_click (self .nb , * self ._tab_point (0 ))
1285+ self .assertEqual (self .nb .select (), str (self .child1 ))
1286+
1287+ # Mnemonics traverse the tabs (macOS uses region-dependent
1288+ # Emacs-style shortcuts, so use the regular arrow keys there).
12521289 if sys .platform == 'darwin' :
12531290 begin = '<Left>'
12541291 end = '<Right>'
12551292 else :
12561293 begin = '<Alt-a>'
12571294 end = '<Alt-e>'
1258- self .nb .event_generate (begin )
1259- self .assertEqual (self .nb .select (), str (self .child1 ))
12601295 self .nb .event_generate (end )
12611296 self .assertEqual (self .nb .select (), str (self .child2 ))
1297+ self .nb .event_generate (begin )
1298+ self .assertEqual (self .nb .select (), str (self .child1 ))
12621299
12631300
12641301@add_configure_tests (IntegerSizeTests , StandardTtkOptionsTests )
@@ -1284,22 +1321,16 @@ def create(self, **kwargs):
12841321
12851322 def _click_increment_arrow (self ):
12861323 self .require_mapped (self .spin )
1287- width = self .spin .winfo_width ()
1288- height = self .spin .winfo_height ()
1289- x = width - 5
1290- y = height // 2 - 5
1291- self .assertRegex (self .spin .identify (x , y ), r'.*uparrow\Z' )
1324+ y = self .spin .winfo_height ()// 2 - 5
1325+ x = self ._arrow_x (self .spin , y , 'uparrow' )
12921326 self .spin .event_generate ('<ButtonPress-1>' , x = x , y = y )
12931327 self .spin .event_generate ('<ButtonRelease-1>' , x = x , y = y )
12941328 self .spin .update_idletasks ()
12951329
12961330 def _click_decrement_arrow (self ):
12971331 self .require_mapped (self .spin )
1298- width = self .spin .winfo_width ()
1299- height = self .spin .winfo_height ()
1300- x = width - 5
1301- y = height // 2 + 4
1302- self .assertRegex (self .spin .identify (x , y ), r'.*downarrow\Z' )
1332+ y = self .spin .winfo_height ()// 2 + 4
1333+ x = self ._arrow_x (self .spin , y , 'downarrow' )
13031334 self .spin .event_generate ('<ButtonPress-1>' , x = x , y = y )
13041335 self .spin .event_generate ('<ButtonRelease-1>' , x = x , y = y )
13051336 self .spin .update_idletasks ()
@@ -2069,14 +2100,17 @@ def test_tag_bind(self):
20692100 self .tv .pack ()
20702101 self .tv .update ()
20712102
2103+ # Find the y coordinate of each item. Scan the whole height of the
2104+ # widget rather than a fixed pixel range, since the row height grows
2105+ # with the display scaling.
20722106 pos_y = set ()
20732107 found = set ()
2074- for i in range (0 , 100 , 10 ):
2108+ for y in range (self . tv . winfo_height () ):
20752109 if len (found ) == 2 : # item1 and item2 already found
20762110 break
2077- item_id = self .tv .identify_row (i )
2111+ item_id = self .tv .identify_row (y )
20782112 if item_id and item_id not in found :
2079- pos_y .add (i )
2113+ pos_y .add (y )
20802114 found .add (item_id )
20812115
20822116 self .assertEqual (len (pos_y ), 2 ) # item1 and item2 y pos
0 commit comments