Tk Source Code

View Ticket
Login
Ticket UUID: 7185d26cf4121c526718459f5fd239e6d777fdee
Title: macOS menu items in special menus can be disabled incorrectly.
Type: Bug Version: 8.5.19, 8.6.10
Submitter: anonymous Created on: 2020-10-01 00:06:21
Subsystem: 11. Aqua Menus Assigned To: marc_culler
Priority: 5 Medium Severity: Important
Status: Closed Last Modified: 2020-11-20 14:48:15
Resolution: Fixed Closed By: marc_culler
    Closed on: 2020-11-20 14:48:15
Description:
Using Wish 8.5.19 and 8.6.10, but NOT using system wish 8.5.9 on OSX 10.14.6 the following occurs:
Any window that isn't the most recent toplevel created gets items in its associated "Help" menu automatically disabled whenever the item is before a separator. Here is a .tcl to illustrate:

menu .menubar
proc menu_about {} {}
proc menu_objectlist {} {}
proc myapp_info {} {}
proc menu_report {} {}
menu .menubar.help
.menubar add cascade -label "Help" -underline 0 -menu .menubar.help -state normal
. configure -menu .menubar
.menubar.help add command -label "About Myapp" -command {menu_about} -state normal
.menubar.help add separator
.menubar.help add command -label "List of objects" -command {menu_objectlist} -state normal
.menubar.help add separator
.menubar.help add command -label "myapp.info" -command {myapp_info} -state normal
.menubar.help add separator
.menubar.help add command -label "Report a Bug" -command {menu_report} -state normal
toplevel .window1 -menu .menubar
toplevel .window2 -menu .menubar

edit: it seems like the solution for now is to just set the state after each time a window is configured.
User Comments: marc_culler (claiming to be Marc Culler) added on 2020-11-20 14:48:15:
Closing this ticket once more, hopefully for the last time.

marc_culler (claiming to be Marc Culler) added on 2020-11-20 14:31:04:
Yes, you are right.  That should have been submePtr.

Thank you very much!  (Both for the testing and the fix.)

I have added your correction to the bugfix branch and will merge it shortly.

anonymous added on 2020-11-20 04:20:37:
it seems like it works right line 790 is NSMenuItem *item = (NSMenuItem *) submePtr->platformEntryData
instead of NSMenuItem *item = (NSMenuItem *) mePtr->platformEntryData

anonymous added on 2020-11-20 01:56:58:
unfortunately after further testing I did find a bug as a result:
If the last item of a menu is disabled before a window that uses it is created, the entire menu will be disabled. (I assume this has something to do with cloning again..)

menu .menubar
proc menu_about {} {}
proc menu_report {} {}
menu .menubar.help
.menubar add cascade -label "Help" -underline 0 -menu .menubar.help -state normal
. configure -menu .menubar
.menubar.help add command -label "About Myapp" -command {menu_about} -state normal
.menubar.help add command -label "Report a Bug" -command {menu_report} -state disabled
toplevel .window1 -menu .menubar

this seems to be true of non-special menus as well

marc_culler (claiming to be Marc Culler) added on 2020-11-19 16:21:57:
Dealing with the other special menus turned out to be easy.  It was just a
matter of iterating over Tk menu entries rather than NSMenuItems, so only
NSMenuItems which are associated with Tk menu entries have their disabled
property changed.

I am going to close this ticket and merge the fix.

marc_culler (claiming to be Marc Culler) added on 2020-11-19 13:53:35:
Thanks for testing, and thanks for the report!

Unfortunately, though, I don't think this is finished yet.  It is OK for
the Help Menu, but there are other menus that get system-added items such
as the so-called Apple menu, the Window menu and the Edit menu.  For those
it is not as simple as just adding one item at the top.  So I need to do
something more complicated to handle those correctly.

anonymous added on 2020-11-19 07:44:33:
looks like it's working here (using the original .tcl test)

anonymous added on 2020-11-19 07:13:18:
Great! Thanks a lot! I was trying to look through the code yesterday and suspected it might be something having to do with the cloning..

marc_culler (claiming to be Marc Culler) added on 2020-11-18 23:04:53:
This is now fixed in the bug-7185d26cf4 branch.  When enabling/disabling
the menu items in a special menu whose first item was added by the system,
e.g. a menu named "Help", the first item is now skipped.

Maybe someone would test this before I merge the fix?

marc_culler (claiming to be Marc Culler) added on 2020-11-18 20:59:08:
I can now explain the mystery. If you change the script by changing the
pathname of the help menu from menubar.help to menubar.xyzw then there are
no disabled menu items.  Also, those menus will not contain an item named
"Wish Help" at the top.

The problems seems to be that when the last segment of the menu pathname
is .help the menu becomes an official "Help Menu", which causes the OS to
insert an item named "Wish Help" as the first item, before all of the
items that were added with Tcl code.  It seems that Tk is aware of this extra
item for a newly created menu.  But when an existing menu is cloned to create
a menu for a new toplevel, Tk loses track of that extra item and hence the
menu indexes are off by one.  This means that when Tk thinks it is marking
a separator as disabled it is actually marking the preceding item as disabled.

The core problem is that a Mac application has only one menubar shared by all
windows, namely only one at the top of the screen. The official "Help Menu"
is the menu in the shared menubar named "Help".  When a Tk menubar is cloned,
which Tk does whenever a menubar is assigned to a new toplevel, then the clone
becomes the shared menubar.  The "Wish Help" item which was added by the
system does not get removed.  But since the original menubar is no longer
the shared menubar, Tk does not expect it's Help Menu to contain the "Wish Help"
item and that causes its indexing to be off by one.

Needless to say, this problem can be avoided by assigning the menubar to only
one window in the application, such as a root window which gets immediately
withdrawn, as was recommended several times in this ticket.  Another way to
avoid it is to assign different menubars to different windows.  Cloning
menubars seems to be what causes the problem.

I am not sure what a fix would be.  The code for cloning a menubar is written
in Tcl and is part of the generic Tcl library. (It appears in tearoff.tcl - 
where else?) So there is no good way for the code in tkMacOSXMenu.c to be made
aware of whether a menubar which is being created is actually a clone.

marc_culler (claiming to be Marc Culler) added on 2020-11-18 14:35:07:
I now have some ideas about what might be going on here.

First of all, the menu items which are being incorrectly disabled are items
which immediately precede a separator. So it looks like there may be an
off-by-one error which causes the item preceding the separator to get disabled
when the intent was to disable the separator.  (A separator is an instance
of NSMenuItem, and hence has a disabled property, which should always be set
to YES, even though its appearance does not change if the property changes.)

Second, when a menubar is assigned to a toplevel, it gets cloned. So each
toplevel actually has its own copy of the menubar.  Any operation on the
menubar is supposed to be replicated on all of its clones.  But it would
appear that the off-by-one error must be getting introduced during the cloning
operation.  I don't know at this point how the various clones get assigned
to windows - specifically whether a newly created window gets the original
menu or a clone.  But it seems that there is one of the many copies of the
menubar which does not have the off-by-one error, while the others all do.

I recall that there used to be a consistent difference between menu item
indexes on macOS vs other platforms because of the tearoff entry that would
be inserted by default on other platforms but not on macOS, which obviously
could not support "tearing off" the application menubar at the top of the
screen.  But I have no idea whether this is related to the off-by-one error
which seems to be occurring here.

marc_culler (claiming to be Marc Culler) added on 2020-11-17 16:39:40:
To quote myself:

"None of this explains what the role of the separator is in this situation.
Maybe there is an answer somewhere in the NSMenu documentation."

I do consider the disabled menu items and the interaction with separators
to be a bug.  I have no ideas at the moment that could explain that behavior,
and have seen no indication that it is a Tk bug rather than an Apple bug,
but it is on my list of things to think about and watch for.

That said, I don't think that solving your problem X depends on fixing that
bug Y.  Also, I don't think it is reasonable to expect Tk to somehow do away
with Apple's design of a menubar shared by all windows in an application.
That is what Brad was talking about as far as I can tell.

If you want your app to provide menus that are displayed in a window on all
platforms, and in the case of macOS independent from the common menubar,
then that is certainly doable.  That is what menu buttons are for.  Checkout
the menu button and ttk menu button items in the Tk widget demo.

anonymous added on 2020-11-17 04:11:55:
on top of that, all of the "workarounds" are just that, workarounds. And I still haven't seen one that solves the issue in a way that's immediately apparent or easy-to-workaround for our case (cross-platform, with people able to write tk extensions) yet.
Anyways it's kind of weird to me that people don't see this as a bug that should be resolved

anonymous added on 2020-11-17 04:04:20:
I want tk on macos to not disable help menu items randomly.

I'm not sure if I would characterize that as "forcing tk on mac os to do what I want"
I guess that's technically true, as I want tk to not "do" buggy behavior.
maybe I'll try asking in the group anyways, but this definitely doesn't seem like expected behavior.

bll added on 2020-11-17 02:04:37:
This is sounding like an XY problem 
( https://en.wikipedia.org/wiki/XY_problem ) .

Instead of trying to force Tk on Mac OS to do what you want,
I would go to the comp.lang.tcl newsgroup, outline what you
want to achieve, and ask for help finding a solution.

Mac OS menus are very different, and you either have to use them
as they are, or use your own menu system.

anonymous added on 2020-11-17 01:25:06:
that doesn't work in our case since it is possible for developers to specify their own toplevels that might use the -menu flag. (and it's a cross-platform app, so I think it is possible for different windows to have different menus on linux and windows). Also, someone reported that not using -menu for toplevels on linux didn't work.

I had just been setting the state of every help menu item to "normal" every time a window switched. However, this was not acceptable to the devs since there might be cases where help menu items should be explicitly disabled.

It seems like the only solution would be for the "plugin" devs to also set their specific "help" menu items to enabled whenever the window is switched as well. But this seems more like a hack than an actual fix.

bll added on 2020-11-17 00:17:07:
I believe that these were the recommendations:

a) Use case: same menu for all toplevels:

Do not specify -menu for the other toplevels.
Each toplevel will use the same menu.

b) Use case: different menus for different toplevels.

Re-configure the menu when switching focus to a
new toplevel.

Tk does not switch menus when switching toplevels.
That task is left to the application.

anonymous added on 2020-11-17 00:03:04:
what was the resolution for this?

anonymous added on 2020-11-06 23:56:53:
just to be clear, this issue isn't resolved simply by using the same root window since in our case it's a cross-platform app that might use "user" plugins that manipulate the state of the help menu. The devs wanted to keep the option (variable) for using other menubars "if the window-architecture is ever going to change". Hopefully there was some fix when this was closed

marc_culler (claiming to be Marc Culler) added on 2020-11-06 18:22:50:
I think this has been resolved, and the ticket can be closed.

marc_culler (claiming to be Marc Culler) added on 2020-10-07 22:36:10:
To expand on Kevin's comment, if you replace the last two lines with

toplevel .window1
toplevel .window2

then your script works the way you want it to. So I would say that the
workaround is not to set the state after a window is configured but rather
to assign the menubar to the root window and not to any other toplevels,
which will automatically use the same menu.

It is possible to change the menubar configuration depending on which
window has focus, but you appear to want the configuration to be the same
no matter which window has focus.

None of this explains what the role of the separator is in this situation.
Maybe there is an answer somewhere in the NSMenu documentation.

kevin_walzer added on 2020-10-01 01:34:20:
Why are you configuring the main menubar for anything except the root window?  On macOS all windows use the same menubar. This is redundant, in other words. Configuring a different menu makes sense, but I'm not following this use case. The bug can be avoided simply by following the standard app conventions of the platform.

bll added on 2020-10-01 00:44:30:
Confirmed with 8.6.10 on 10.15.7.
The disabled menu entries can be set to -state normal.

Attachments: