Qt Mac multiple menubars/modifiable menubar - qt

I have an application that shows multiple subpanels & the client wants to show different menus for each subpanel.
Mac apps can only have one menuBar per system window, apparently, and it's minimally modifiable (if at all.) I need to remove/add or enable/disable menus on the menubar.
I've thought about making each of the subpanels a system window and attaching a menubar to each, but I don't see any provision for switching to a window's menubar. Besides, I suspect that doing so would create a state/positioning mess for the subpanels.
What I've Found
I've found that if I create the actions as children of the main window, I can add and remove them at will from the menus themselves. So, I can modify the menu contents, but I can't modify the menubar contents.
I've found I can also change the title of the menu to anything at any time. So, if I clear the contents and set the title to an empty string, it has the apparent effect of removing the menu (although it's still there and still highlights).
Barring another solution, have to do that, for now.
Is this possible at all on Mac? If I went down into Cocoa (don't know Cocoa), would I be able to maybe set up multiple menubars, or at least modify the menubar when the subpanel changes?

I was searching something else but as precisely I've just been working this one, what I do is
- delete the current menuBar if there's one
- menuBar=new QMenuBar(0);
- menuBar->setNativeMenuBar(true);
And it seems to work fine. Just for what it's worth.

A Cocoa application has only one menubar active at any given time, and you can modify and replace it. For instance, the (Cocoa) code below adds a new menu (with three items) to the menubar. It is also possible to edit and remove menus as well as menu items.
NSMenu *menubar = [NSApp mainMenu];
NSMenuItem *newBarMenuItem = [[[NSMenuItem alloc] initWithTitle:#"" action:NULL keyEquivalent:#""] autorelease];
NSMenu *newMenu = [[[NSMenu alloc] initWithTitle:#"New Menu"] autorelease];
NSMenuItem *menuItem1 = [[[NSMenuItem alloc] initWithTitle:#"Action 1" action:#selector(action1:) keyEquivalent:#""] autorelease];
NSMenuItem *menuItem2 = [[[NSMenuItem alloc] initWithTitle:#"Action 2" action:#selector(action2:) keyEquivalent:#""] autorelease];
[newMenu addItem:menuItem1];
[newMenu addItem:[NSMenuItem separatorItem]];
[newMenu addItem:menuItem2];
[menubar addItem:newBarMenuItem];
[menubar setSubmenu:newMenu forItem:newBarMenuItem];
[NSApp mainMenu] returns the application menu. A new menu item is added to the main menu/menubar, representing a submenu that contains three items, one of them being a separator.
It is also possible to replace the menubar by crafting an appropriate menu and sending [NSApp setMainMenu:menubarReplacement].

Yes, this is possible in a Qt app, and fairly common. :)
In your app you probably have code to build your menus, and install them into the menubar (using QMenuBar) in the first place. As Juan correctly points out, to alter the menu bar, you can delete that instance and regenerate a new menubar and its menus as needed.
In my own code, I just keep my original QMenuBar around, and call QMenuBar::clear() on the instance. This is an alternative to the delete/re-instantiate that Juan recommends, although either approach is likely valid. Then I repopulate the menubar with the currently needed menus.
I typically only rebuild the whole QMenuBar when the set of top-level menus, or the title of a top-level menu needs to change. More commonly, I am dynamically regenerating the actual menu items (QActions) and/or their state (like their text, whether they are enabled or not, checked or not, etc) within a given menu.
To dynamically regenerate a single menu's contents only, you can connect a method callback to that particular QMenu's aboutToShow signal, and rebuild the menu's items dynamically within that callback (don't forget to start with QMenu::clear() on the instance or you may end up with duplicate items in the menu!). When the QMenu pops up, it will show your dynamically rebuilt items/states. This method also works for dynamic regeneration of popup/context menus.

Related

How to disable the context menu of a QDockWidget title bar

I have a couple of QDockWidgets that are all not closabale (using Qt 5.6). Therefore, the context menu that is displayed when right-clicking a title bar of one of them only has disabled entries, and I would like to disable the whole context menu.
I tried to set the contextMenuPolicy to NoContextMenu without success.
I then tried to use a subclass of QDockWidget, override the ContextMenuEvent and ignore it. The menu is still displayed.
I then tried to install an event filter to catch the ContextMenuEvent, but it did not catch any, just PaintEvents, ResizeEvents etc.
I'm out of ideas … any help would be greatly appreciated!
As per the comments it is necessary to set the context menu policy on the QDockWidget to Qt::PreventContextMenu...
dock_widget->setContextMenuPolicy(Qt::PreventContextMenu);
rather than simply Qt::NoContextMenu. From the documentation Qt::NoContextMenu simply defers the context menu handling to the parent widget rather than preventing it entirely.

How do I get rid of this anonymous draggable bar in qtcreator?

I wrote a program in qt creator, but it includes this anonymous draggable toolbar that I would like to get rid of. It does not show up in design mode. If I right click on it, it gives me a menu with a checkbox, which I can select to get rid of it while the program is running. What is this bar? How do I get rid of it? If I knew what it was, I could just programmatically tell it to hide when the program loads.
Here are some screenshots demonstrating this weird problem. In the first screenshot, the mouse is pointed at the bar. The second one demonstrates the right-click context menu. The third one shows the program without the bar. The box can also be dragged out of the program's window entirely. I can also upload a picture of that, if necessary.
(mouse pointing at bar)
(bar has a right-click menu!)
(bar is gone)
First things first, make sure you haven't added the toolbar yourself in code (the Designer form is nothing magical, it just generates normal Qt code to ui_XXXX.h, which you should totally study, so you understand what is really happening "under the hood").
Second, perhaps that is the main menu bar, not the main tool bar.
Then, on to remove the nasty bar. On the upper right corner of Design view, you have a tree of all items in your UI. Find the QToolBar there. It is probably called mainToolBar. Right click it and remove it there. Or, if you actually have menu bar, then find QMenuBar, probably called menuBar, and remove it instead. Remember to make version control commit (or other backup) before doing this, so you can easily revert changes.
If this fails for whatever reason, and you just want to get it to work, you can always find and delete (be careful about the dangling pointers left behind, set them to nullptr if possible):
ui->setupUi(this);
// a hack: we want to get rid of the toolbar and the menubar,
// but can't remove it from the .ui, so we delete them like this.
delete findChild<QToolBar *>(); // NULL return value is ok for delete
delete findChild<QMenuBar *>(); // NULL return value is ok for delete
Doc links for findChild and alternative findChildren.
What you have there is probably the mainToolBar QToolBar object you get with every QDesigner-created QMainWindow .ui file.
If you want to get rid of it, locate it in your .ui file and just delete it.
But better think twice: you might later decide you want a QToolBar object and (at least AFAIK) there is no way to recreate it in QDesigner...
you must use this code in your constructor.
ui->mainToolBar->toggleViewAction()->setVisible(false);
mainToolBar is my toolbar name.

Is there a way of disabling a WKInterfaceMenu?

I have a WKInterfaceMenu added to the storyboard which is working great. However, once used, I don't want the menu to appear for a second time.
Is there a way of preventing the menu from appearing?
You can definitely do this, but you need to do things a bit differently.
First off, don't set up the context menu in the storyboard. Instead, set up all the menu items programmatically. You can add and remove menu items using the addMenuItemWithImageNamed and clearAllMenuItems methods on your WKInterfaceController instance.
You want to set up all your menu items initially, then remove them in all your callback methods for each menu item. That way, they'll be there at first, and will be removed once you select one of them.

How to update a QLayout and get the new dimensions before returning?

This is driving me nuts. I have a custom menu class that, when set visible, shows a list of items located in a particular folder. When a hardware button is pressed, my application gets the latest list of items, populates the menu with them, and returns.
The menu displaying these items uses a QListWidget filled with custom widgets. Each of the widgets contains one or more QLabels in a horizontal layout, and is created at the time the menu is shown. In order to adjust the text displayed based on the menu width available, I need to get the size of the QLabel AFTER it has been resized according to the layout, but before the menu becomes visible to the user. The problem is, my layout does not get updated until all of the functions constructing my list return.
I have tried QApplication::ProcessEvents() and the layout update functions, but none of them have updated the values of my QLabels before returning. I can set a QTimer when the button is initially pressed, and have it show the menu, update the items, and stop itself, but that seems like a terrible solution.
Any help would really be appreciated! I've spent most of a day on this.
Marlon
I had this exact problem and could not find an answer anywhere on the Internet. Calling Layout.update(), Layout.activate(), or widget.adjustSize() (all suggested in various places) all did not work.
I had a widget with a vertical layout that I wanted to add a QLabel to and then immediately use the size of the QLabel.
The only thing that worked reliably was
layout->addWidget(myLabel);
myLabel->show();
size = myLabel->size();
It would seem that layouts will just not recalculate until you either return from a function and allow the Qt event loop to progress or manually call show() yourself.
How to update a QLayout and get the new dimensions before returning?
Don't. You're not meant to do that. It'll drive you "nuts" because you're doing it backwards. Layout updates are handled asynchronously from the event loop. Instead of getting layout dimensions right away, set yourself up to be part of the system. Some options are:
Implement a custom widget that will interact properly with the layout, growing to fill the available width of the layout. Perhaps all you need is a size policy and a way to elide text?
Make a custom layout that takes the special properties of your use case into account.
You want to call QWidget::adjustSize() on your parent widget. This will force the layout recalculations.
Have you tried using layout()->update(); ?
I've tried many but nothing works for me on Qt 5.15.
Only invented little patch - create timer and get size after 20 msec:
QTimer::singleShot(20, this, [this]
{
const auto height = myLayout->contentsRect().height();
// ...
});

QSystemTray problems

I am trying to use QSystemTrayIcon for my application and i am facing some problems.
It is the first time i use qt so i am not really used to it.
I followed this tutorial to make a system tray icon but i fail to customize it.
I want to have a button show/hide and not 3 show, hide, restore. These actions are really confusing for a newbie and i dont know what to do and what to connect.
I tried some things but with no luck.
Also when the system tray menu appears if you click somewhere else, the menu stays open.
Any way to solve this thing too?
If you want to remove one of the menu items, modify the createTrayIcon function so that it only adds the actions you need (and clean up the unused members once you get it to work). It's that simple.
If you want a single menu item or button to toggle between visible and hidden, you'll need to create a custom slot that calls show() or hide() (or setVisible(bool)) depending on whether the widget is hidden or not (use isVisible() for that for example). Then connect your action to that slot.
Read the Signals and Slots documentation and examples for information about how to create a new slot.

Resources