@@ -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' )
@@ -391,7 +404,12 @@ def test_identify(self):
391404 # Identifying the element under a point requires the widget to be
392405 # mapped with a real size; the rest of the checks do not.
393406 if wait_until_mapped (self .entry ):
394- self .assertIn (self .entry .identify (5 , 5 ), self .IDENTIFY_AS )
407+ # Probe the centre of the widget: a fixed pixel such as (5, 5)
408+ # lands in the field at a normal scaling but in the surrounding
409+ # padding at a high DPI.
410+ x = self .entry .winfo_width () // 2
411+ y = self .entry .winfo_height () // 2
412+ self .assertIn (self .entry .identify (x , y ), self .IDENTIFY_AS )
395413 self .assertEqual (self .entry .identify (- 1 , - 1 ), "" )
396414
397415 self .assertRaises (tkinter .TclError , self .entry .identify , None , 5 )
@@ -492,10 +510,11 @@ def test_configure_height(self):
492510 self .checkParams (widget , 'height' , 100 , 101.2 , 102.6 , - 100 , 0 , '1i' )
493511
494512 def _show_drop_down_listbox (self ):
495- width = self .combo .winfo_width ()
496- x , y = width - 5 , 5
513+ y = 5
497514 if sys .platform != 'darwin' : # there's no down arrow on macOS
498- self .assertRegex (self .combo .identify (x , y ), r'.*downarrow\z' )
515+ x = self ._arrow_x (self .combo , y , 'downarrow' )
516+ else :
517+ x = self .combo .winfo_width () - 5
499518 self .combo .event_generate ('<Button-1>' , x = x , y = y )
500519 self .combo .event_generate ('<ButtonRelease-1>' , x = x , y = y )
501520
@@ -1068,21 +1087,15 @@ def test_tab_identifiers(self):
10681087
10691088 self .nb .pack ()
10701089 self .nb .update ()
1071- if sys .platform == 'darwin' :
1072- tb_idx = "@20,5"
1073- else :
1074- tb_idx = "@5,5"
1075- self .assertEqual (self .nb .tab (tb_idx ), self .nb .tab ('current' ))
1076-
1077- for i in range (5 , 100 , 5 ):
1078- try :
1079- if self .nb .tab ('@%d, 5' % i , text = None ) == 'a' :
1080- break
1081- except tkinter .TclError :
1082- pass
1090+ # Address a tab by a point taken from Tk's own geometry rather than a
1091+ # fixed pixel, which can land on a different tab or miss the tabs
1092+ # entirely (e.g. on Aqua the tabs are inset) as the display scaling
1093+ # changes.
1094+ x , y = self ._tab_point (self .nb .index ('current' ))
1095+ self .assertEqual (self .nb .tab (f'@{ x } ,{ y } ' ), self .nb .tab ('current' ))
10831096
1084- else :
1085- self .fail ( "Tab with text 'a' not found" )
1097+ x , y = self . _tab_point ( self . nb . index ( self . child1 ))
1098+ self .assertEqual ( self . nb . tab ( f'@ { x } , { y } ' , text = None ), 'a' )
10861099
10871100 def test_add_and_hidden (self ):
10881101 self .assertRaises (tkinter .TclError , self .nb .hide , - 1 )
@@ -1212,23 +1225,43 @@ def test_configure_tabs(self):
12121225
12131226 self .assertEqual (self .nb .tabs (), ())
12141227
1228+ def _tab_point (self , index ):
1229+ # Return a window coordinate that really lies on tab *index*, taken
1230+ # from Tk's own geometry (ttk::notebook index @x,y), so that it is
1231+ # correct regardless of the theme, platform and display scaling. A
1232+ # fixed pixel such as (5, 5) can land on a different tab element
1233+ # depending on the DPI, or miss the tabs entirely (e.g. on Aqua the
1234+ # tabs are inset), which would make a click there test nothing.
1235+ self .assertTrue (wait_until_mapped (self .nb , full_size = True ))
1236+ w , h = self .nb .winfo_width (), self .nb .winfo_height ()
1237+ # The selected pane fills the widget except for the tab strip, so skip
1238+ # that large tab-free region instead of querying every pixel in it.
1239+ pane = self .nb .nametowidget (self .nb .select ())
1240+ px , py = pane .winfo_x (), pane .winfo_y ()
1241+ pw , ph = pane .winfo_width (), pane .winfo_height ()
1242+ for y in range (h ):
1243+ inside_y = py <= y < py + ph
1244+ for x in range (w ):
1245+ if inside_y and px <= x < px + pw :
1246+ continue
1247+ try :
1248+ if self .nb .index (f'@{ x } ,{ y } ' ) == index :
1249+ return x , y
1250+ except TclError :
1251+ continue
1252+ self .fail (f'no point found on tab { index } ' )
1253+
12151254 def test_traversal (self ):
12161255 self .nb .pack ()
12171256 self .nb .update ()
12181257
1219- self .nb .select (0 )
1258+ # A mouse click selects the tab it lands on.
1259+ self .nb .select (1 )
1260+ self .assertEqual (self .nb .select (), str (self .child2 ))
1261+ simulate_mouse_click (self .nb , * self ._tab_point (0 ))
1262+ self .assertEqual (self .nb .select (), str (self .child1 ))
12201263
1221- if sys .platform == 'darwin' :
1222- focus_identify_as = ''
1223- elif sys .platform == 'win32' :
1224- focus_identify_as = 'focus'
1225- else :
1226- focus_identify_as = 'focus' if tk_version < (8 , 7 ) else 'padding'
1227- # identify() at (5, 5) needs the tab realized there; under focus
1228- # contention the mapped size can lag, so wait for the full size.
1229- if wait_until_mapped (self .nb , full_size = True ):
1230- self .assertEqual (self .nb .identify (5 , 5 ), focus_identify_as )
1231- simulate_mouse_click (self .nb , 5 , 5 )
1264+ # Control-Tab and Shift-Control-Tab traverse the tabs.
12321265 self .nb .focus_force ()
12331266 self .nb .event_generate ('<Control-Tab>' )
12341267 self .assertEqual (self .nb .select (), str (self .child2 ))
@@ -1243,21 +1276,25 @@ def test_traversal(self):
12431276 self .nb .tab (self .child2 , text = 'e' , underline = 0 )
12441277 self .nb .enable_traversal ()
12451278 self .nb .focus_force ()
1246- if wait_until_mapped (self .nb , full_size = True ):
1247- self .assertEqual (self .nb .identify (5 , 5 ), focus_identify_as )
1248- simulate_mouse_click (self .nb , 5 , 5 )
1249- # on macOS Emacs-style keyboard shortcuts are region-dependent;
1250- # let's use the regular arrow keys instead
1279+
1280+ # A click still selects the tab it lands on after enable_traversal().
1281+ self .nb .select (1 )
1282+ self .assertEqual (self .nb .select (), str (self .child2 ))
1283+ simulate_mouse_click (self .nb , * self ._tab_point (0 ))
1284+ self .assertEqual (self .nb .select (), str (self .child1 ))
1285+
1286+ # Mnemonics traverse the tabs (macOS uses region-dependent
1287+ # Emacs-style shortcuts, so use the regular arrow keys there).
12511288 if sys .platform == 'darwin' :
12521289 begin = '<Left>'
12531290 end = '<Right>'
12541291 else :
12551292 begin = '<Alt-a>'
12561293 end = '<Alt-e>'
1257- self .nb .event_generate (begin )
1258- self .assertEqual (self .nb .select (), str (self .child1 ))
12591294 self .nb .event_generate (end )
12601295 self .assertEqual (self .nb .select (), str (self .child2 ))
1296+ self .nb .event_generate (begin )
1297+ self .assertEqual (self .nb .select (), str (self .child1 ))
12611298
12621299
12631300@add_configure_tests (IntegerSizeTests , StandardTtkOptionsTests )
@@ -1283,22 +1320,16 @@ def create(self, **kwargs):
12831320
12841321 def _click_increment_arrow (self ):
12851322 self .require_mapped (self .spin )
1286- width = self .spin .winfo_width ()
1287- height = self .spin .winfo_height ()
1288- x = width - 5
1289- y = height // 2 - 5
1290- self .assertRegex (self .spin .identify (x , y ), r'.*uparrow\z' )
1323+ y = self .spin .winfo_height ()// 2 - 5
1324+ x = self ._arrow_x (self .spin , y , 'uparrow' )
12911325 self .spin .event_generate ('<ButtonPress-1>' , x = x , y = y )
12921326 self .spin .event_generate ('<ButtonRelease-1>' , x = x , y = y )
12931327 self .spin .update_idletasks ()
12941328
12951329 def _click_decrement_arrow (self ):
12961330 self .require_mapped (self .spin )
1297- width = self .spin .winfo_width ()
1298- height = self .spin .winfo_height ()
1299- x = width - 5
1300- y = height // 2 + 4
1301- self .assertRegex (self .spin .identify (x , y ), r'.*downarrow\z' )
1331+ y = self .spin .winfo_height ()// 2 + 4
1332+ x = self ._arrow_x (self .spin , y , 'downarrow' )
13021333 self .spin .event_generate ('<ButtonPress-1>' , x = x , y = y )
13031334 self .spin .event_generate ('<ButtonRelease-1>' , x = x , y = y )
13041335 self .spin .update_idletasks ()
@@ -2068,14 +2099,17 @@ def test_tag_bind(self):
20682099 self .tv .pack ()
20692100 self .tv .update ()
20702101
2102+ # Find the y coordinate of each item. Scan the whole height of the
2103+ # widget rather than a fixed pixel range, since the row height grows
2104+ # with the display scaling.
20712105 pos_y = set ()
20722106 found = set ()
2073- for i in range (0 , 100 , 10 ):
2107+ for y in range (self . tv . winfo_height () ):
20742108 if len (found ) == 2 : # item1 and item2 already found
20752109 break
2076- item_id = self .tv .identify_row (i )
2110+ item_id = self .tv .identify_row (y )
20772111 if item_id and item_id not in found :
2078- pos_y .add (i )
2112+ pos_y .add (y )
20792113 found .add (item_id )
20802114
20812115 self .assertEqual (len (pos_y ), 2 ) # item1 and item2 y pos
0 commit comments