Cocoa Menus: Add support for menu items in window-less apps
Since we moved the menu items validation and target/action to QNSView (thus relying on the responder chain), we need to take care of case when the applications that doesn't have any window open. By adding similar methods to QCocoaApplicationDelegate, the last responder, we ensure the menu items will be validated and will trigger properly. This is particularly necessary for dock menu items, which live separately from any top-level widget. Dock menu added to Menurama which won't quit when its last window is closed. This way we can test that dock menu items will trigger in the absence of any window. Change-Id: I56d864eb9da1f8dd5adb2a3b6c3dd5304c723117 Reviewed-by: Morten Johan Sørvig <morten.sorvig@qt.io>
This commit is contained in:
parent
bd74b624d5
commit
595c1ae9e7
@ -1,6 +1,6 @@
|
|||||||
/****************************************************************************
|
/****************************************************************************
|
||||||
**
|
**
|
||||||
** Copyright (C) 2016 The Qt Company Ltd.
|
** Copyright (C) 2018 The Qt Company Ltd.
|
||||||
** Contact: https://www.qt.io/licensing/
|
** Contact: https://www.qt.io/licensing/
|
||||||
**
|
**
|
||||||
** This file is part of the plugins of the Qt Toolkit.
|
** This file is part of the plugins of the Qt Toolkit.
|
||||||
@ -90,14 +90,18 @@
|
|||||||
#include <qglobal.h>
|
#include <qglobal.h>
|
||||||
#include <private/qcore_mac_p.h>
|
#include <private/qcore_mac_p.h>
|
||||||
|
|
||||||
@class QT_MANGLE_NAMESPACE(QCocoaMenuLoader);
|
Q_FORWARD_DECLARE_OBJC_CLASS(QT_MANGLE_NAMESPACE(QCocoaNSMenuItem));
|
||||||
|
|
||||||
@interface QT_MANGLE_NAMESPACE(QCocoaApplicationDelegate) : NSObject <NSApplicationDelegate>
|
@interface QT_MANGLE_NAMESPACE(QCocoaApplicationDelegate) : NSObject <NSApplicationDelegate>
|
||||||
|
@property (nonatomic, retain) NSMenu *dockMenu;
|
||||||
+ (instancetype)sharedDelegate;
|
+ (instancetype)sharedDelegate;
|
||||||
- (void)setDockMenu:(NSMenu *)newMenu;
|
|
||||||
- (void)setReflectionDelegate:(NSObject<NSApplicationDelegate> *)oldDelegate;
|
- (void)setReflectionDelegate:(NSObject<NSApplicationDelegate> *)oldDelegate;
|
||||||
- (void)removeAppleEventHandlers;
|
- (void)removeAppleEventHandlers;
|
||||||
- (bool)inLaunch;
|
- (bool)inLaunch;
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
@interface QT_MANGLE_NAMESPACE(QCocoaApplicationDelegate) (MenuAPI)
|
||||||
|
- (void)qt_itemFired:(QT_MANGLE_NAMESPACE(QCocoaNSMenuItem) *)item;
|
||||||
|
@end
|
||||||
|
|
||||||
QT_NAMESPACE_ALIAS_OBJC_CLASS(QCocoaApplicationDelegate);
|
QT_NAMESPACE_ALIAS_OBJC_CLASS(QCocoaApplicationDelegate);
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
/****************************************************************************
|
/****************************************************************************
|
||||||
**
|
**
|
||||||
** Copyright (C) 2016 The Qt Company Ltd.
|
** Copyright (C) 2018 The Qt Company Ltd.
|
||||||
** Contact: https://www.qt.io/licensing/
|
** Contact: https://www.qt.io/licensing/
|
||||||
**
|
**
|
||||||
** This file is part of the plugins of the Qt Toolkit.
|
** This file is part of the plugins of the Qt Toolkit.
|
||||||
@ -73,8 +73,12 @@
|
|||||||
|
|
||||||
|
|
||||||
#import "qcocoaapplicationdelegate.h"
|
#import "qcocoaapplicationdelegate.h"
|
||||||
#import "qcocoamenuloader.h"
|
|
||||||
#include "qcocoaintegration.h"
|
#include "qcocoaintegration.h"
|
||||||
|
#include "qcocoamenu.h"
|
||||||
|
#include "qcocoamenuloader.h"
|
||||||
|
#include "qcocoamenuitem.h"
|
||||||
|
#include "qcocoansmenu.h"
|
||||||
|
|
||||||
#include <qevent.h>
|
#include <qevent.h>
|
||||||
#include <qurl.h>
|
#include <qurl.h>
|
||||||
#include <qdebug.h>
|
#include <qdebug.h>
|
||||||
@ -87,7 +91,6 @@ QT_USE_NAMESPACE
|
|||||||
|
|
||||||
@implementation QCocoaApplicationDelegate {
|
@implementation QCocoaApplicationDelegate {
|
||||||
bool startedQuit;
|
bool startedQuit;
|
||||||
NSMenu *dockMenu;
|
|
||||||
NSObject <NSApplicationDelegate> *reflectionDelegate;
|
NSObject <NSApplicationDelegate> *reflectionDelegate;
|
||||||
bool inLaunch;
|
bool inLaunch;
|
||||||
}
|
}
|
||||||
@ -129,7 +132,7 @@ QT_USE_NAMESPACE
|
|||||||
|
|
||||||
- (void)dealloc
|
- (void)dealloc
|
||||||
{
|
{
|
||||||
[dockMenu release];
|
[_dockMenu release];
|
||||||
if (reflectionDelegate) {
|
if (reflectionDelegate) {
|
||||||
[[NSApplication sharedApplication] setDelegate:reflectionDelegate];
|
[[NSApplication sharedApplication] setDelegate:reflectionDelegate];
|
||||||
[reflectionDelegate release];
|
[reflectionDelegate release];
|
||||||
@ -139,20 +142,13 @@ QT_USE_NAMESPACE
|
|||||||
[super dealloc];
|
[super dealloc];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)setDockMenu:(NSMenu*)newMenu
|
|
||||||
{
|
|
||||||
[newMenu retain];
|
|
||||||
[dockMenu release];
|
|
||||||
dockMenu = newMenu;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSMenu *)applicationDockMenu:(NSApplication *)sender
|
- (NSMenu *)applicationDockMenu:(NSApplication *)sender
|
||||||
{
|
{
|
||||||
Q_UNUSED(sender);
|
Q_UNUSED(sender);
|
||||||
// Manually invoke the delegate's -menuWillOpen: method.
|
// Manually invoke the delegate's -menuWillOpen: method.
|
||||||
// See QTBUG-39604 (and its fix) for details.
|
// See QTBUG-39604 (and its fix) for details.
|
||||||
[[dockMenu delegate] menuWillOpen:dockMenu];
|
[self.dockMenu.delegate menuWillOpen:self.dockMenu];
|
||||||
return [[dockMenu retain] autorelease];
|
return [[self.dockMenu retain] autorelease];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)canQuit
|
- (BOOL)canQuit
|
||||||
@ -444,3 +440,48 @@ QT_USE_NAMESPACE
|
|||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
@implementation QCocoaApplicationDelegate (Menus)
|
||||||
|
|
||||||
|
- (BOOL)validateMenuItem:(NSMenuItem*)item
|
||||||
|
{
|
||||||
|
auto *nativeItem = qt_objc_cast<QCocoaNSMenuItem *>(item);
|
||||||
|
if (!nativeItem)
|
||||||
|
return item.enabled; // FIXME Test with with Qt as plugin or embedded QWindow.
|
||||||
|
|
||||||
|
auto *platformItem = nativeItem.platformMenuItem;
|
||||||
|
if (!platformItem) // Try a bit harder with orphan menu itens
|
||||||
|
return item.hasSubmenu || (item.enabled && (item.action != @selector(qt_itemFired:)));
|
||||||
|
|
||||||
|
// Menu-holding items are always enabled, as it's conventional in Cocoa
|
||||||
|
if (platformItem->menu())
|
||||||
|
return YES;
|
||||||
|
|
||||||
|
return platformItem->isEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation QCocoaApplicationDelegate (MenuAPI)
|
||||||
|
|
||||||
|
- (void)qt_itemFired:(QCocoaNSMenuItem *)item
|
||||||
|
{
|
||||||
|
if (item.hasSubmenu)
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto *nativeItem = qt_objc_cast<QCocoaNSMenuItem *>(item);
|
||||||
|
Q_ASSERT_X(nativeItem, qPrintable(__FUNCTION__), "Triggered menu item is not a QCocoaNSMenuItem.");
|
||||||
|
auto *platformItem = nativeItem.platformMenuItem;
|
||||||
|
// Menu-holding items also get a target to play nicely
|
||||||
|
// with NSMenuValidation but should not trigger.
|
||||||
|
if (!platformItem || platformItem->menu())
|
||||||
|
return;
|
||||||
|
|
||||||
|
QScopedScopeLevelCounter scopeLevelCounter(QGuiApplicationPrivate::instance()->threadData);
|
||||||
|
QGuiApplicationPrivate::modifier_buttons = [QNSView convertKeyModifiers:[NSEvent modifierFlags]];
|
||||||
|
|
||||||
|
static QMetaMethod activatedSignal = QMetaMethod::fromSignal(&QCocoaMenuItem::activated);
|
||||||
|
activatedSignal.invoke(platformItem, Qt::QueuedConnection);
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
|
@ -265,7 +265,7 @@ void QCocoaNativeInterface::setDockMenu(QPlatformMenu *platformMenu)
|
|||||||
QMacAutoReleasePool pool;
|
QMacAutoReleasePool pool;
|
||||||
QCocoaMenu *cocoaPlatformMenu = static_cast<QCocoaMenu *>(platformMenu);
|
QCocoaMenu *cocoaPlatformMenu = static_cast<QCocoaMenu *>(platformMenu);
|
||||||
NSMenu *menu = cocoaPlatformMenu->nsMenu();
|
NSMenu *menu = cocoaPlatformMenu->nsMenu();
|
||||||
[[QCocoaApplicationDelegate sharedDelegate] setDockMenu:menu];
|
[QCocoaApplicationDelegate sharedDelegate].dockMenu = menu;
|
||||||
}
|
}
|
||||||
|
|
||||||
void *QCocoaNativeInterface::qMenuToNSMenu(QPlatformMenu *platformMenu)
|
void *QCocoaNativeInterface::qMenuToNSMenu(QPlatformMenu *platformMenu)
|
||||||
|
@ -39,6 +39,7 @@
|
|||||||
|
|
||||||
// This file is included from qnsview.mm, and only used to organize the code
|
// This file is included from qnsview.mm, and only used to organize the code
|
||||||
|
|
||||||
|
#include <qcocoaapplicationdelegate.h>
|
||||||
#include <qcocoansmenu.h>
|
#include <qcocoansmenu.h>
|
||||||
#include <qcocoamenuitem.h>
|
#include <qcocoamenuitem.h>
|
||||||
#include <qcocoamenu.h>
|
#include <qcocoamenu.h>
|
||||||
@ -101,19 +102,8 @@ static bool selectorIsCutCopyPaste(SEL selector)
|
|||||||
|
|
||||||
- (void)qt_itemFired:(QCocoaNSMenuItem *)item
|
- (void)qt_itemFired:(QCocoaNSMenuItem *)item
|
||||||
{
|
{
|
||||||
auto *nativeItem = qt_objc_cast<QCocoaNSMenuItem *>(item);
|
auto *appDelegate = [QCocoaApplicationDelegate sharedDelegate];
|
||||||
Q_ASSERT_X(nativeItem, qPrintable(__FUNCTION__), "Triggered menu item is not a QCocoaNSMenuItem.");
|
[appDelegate qt_itemFired:item];
|
||||||
auto *platformItem = nativeItem.platformMenuItem;
|
|
||||||
// Menu-holding items also get a target to play nicely
|
|
||||||
// with NSMenuValidation but should not trigger.
|
|
||||||
if (!platformItem || platformItem->menu())
|
|
||||||
return;
|
|
||||||
|
|
||||||
QScopedScopeLevelCounter scopeLevelCounter(QGuiApplicationPrivate::instance()->threadData);
|
|
||||||
QGuiApplicationPrivate::modifier_buttons = [QNSView convertKeyModifiers:[NSEvent modifierFlags]];
|
|
||||||
|
|
||||||
static QMetaMethod activatedSignal = QMetaMethod::fromSignal(&QCocoaMenuItem::activated);
|
|
||||||
activatedSignal.invoke(platformItem, Qt::QueuedConnection);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector
|
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector
|
||||||
|
@ -29,9 +29,46 @@
|
|||||||
#include "mainwindow.h"
|
#include "mainwindow.h"
|
||||||
#include "menuramaapplication.h"
|
#include "menuramaapplication.h"
|
||||||
|
|
||||||
|
#include <QtWidgets/QAction>
|
||||||
|
#include <QtWidgets/QMenu>
|
||||||
|
|
||||||
int main(int argc, char *argv[])
|
int main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
MenuramaApplication a(argc, argv);
|
MenuramaApplication a(argc, argv);
|
||||||
|
a.setQuitOnLastWindowClosed(false);
|
||||||
|
|
||||||
|
auto *dockMenu = new QMenu();
|
||||||
|
dockMenu->setAsDockMenu();
|
||||||
|
dockMenu->addAction(QLatin1String("New Window"), [=] {
|
||||||
|
auto *w = new MainWindow;
|
||||||
|
w->setAttribute(Qt::WA_DeleteOnClose, true);
|
||||||
|
w->show();
|
||||||
|
});
|
||||||
|
auto *disabledAction = dockMenu->addAction(QLatin1String("Disabled Item"), [=] {
|
||||||
|
qDebug() << "Should not happen!";
|
||||||
|
Q_UNREACHABLE();
|
||||||
|
});
|
||||||
|
disabledAction->setEnabled(false);
|
||||||
|
dockMenu->addAction(QLatin1String("Last Item Before Separator"), [=] {
|
||||||
|
qDebug() << "Last Item triggered";
|
||||||
|
});
|
||||||
|
auto *hiddenAction = dockMenu->addAction(QLatin1String("Invisible Item (FIXME rdar:39615815)"), [=] {
|
||||||
|
qDebug() << "Should not happen!";
|
||||||
|
Q_UNREACHABLE();
|
||||||
|
});
|
||||||
|
hiddenAction->setVisible(false);
|
||||||
|
dockMenu->addSeparator();
|
||||||
|
auto *toolsMenu = dockMenu->addMenu(QLatin1String("Menurama Tools"));
|
||||||
|
toolsMenu->addAction(QLatin1String("Hammer"), [=] {
|
||||||
|
qDebug() << "Bang! Bang!";
|
||||||
|
});
|
||||||
|
toolsMenu->addAction(QLatin1String("Wrench"), [=] {
|
||||||
|
qDebug() << "Clang! Clang!";
|
||||||
|
});
|
||||||
|
toolsMenu->addAction(QLatin1String("Screwdriver"), [=] {
|
||||||
|
qDebug() << "Squeak! Squeak!";
|
||||||
|
});
|
||||||
|
|
||||||
MainWindow w;
|
MainWindow w;
|
||||||
w.show();
|
w.show();
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>486</width>
|
<width>486</width>
|
||||||
<height>288</height>
|
<height>376</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<property name="windowTitle">
|
<property name="windowTitle">
|
||||||
@ -58,7 +58,7 @@ Click on "Dynamic Stuff" then move left and right to other menus. Disa
|
|||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||||
<item>
|
<item alignment="Qt::AlignTop">
|
||||||
<widget class="QPushButton" name="addManyButton">
|
<widget class="QPushButton" name="addManyButton">
|
||||||
<property name="sizePolicy">
|
<property name="sizePolicy">
|
||||||
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
|
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
|
||||||
@ -71,7 +71,7 @@ Click on "Dynamic Stuff" then move left and right to other menus. Disa
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item alignment="Qt::AlignTop">
|
||||||
<widget class="QLabel" name="label_2">
|
<widget class="QLabel" name="label_2">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Adding hundreds of items should not block the UI for noticeable periods of time. Odd numbered items should be disabled, those with 2nd LSB on should be hidden.</string>
|
<string>Adding hundreds of items should not block the UI for noticeable periods of time. Odd numbered items should be disabled, those with 2nd LSB on should be hidden.</string>
|
||||||
@ -83,6 +83,16 @@ Click on "Dynamic Stuff" then move left and right to other menus. Disa
|
|||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
|
<item alignment="Qt::AlignTop">
|
||||||
|
<widget class="QLabel" name="label_3">
|
||||||
|
<property name="text">
|
||||||
|
<string>Check out the dock menu. You can close this window to verify that its actions will trigger in the absence of any window. And you can add more windows from there too.</string>
|
||||||
|
</property>
|
||||||
|
<property name="wordWrap">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
<widget class="QMenuBar" name="menuBar">
|
<widget class="QMenuBar" name="menuBar">
|
||||||
|
Loading…
x
Reference in New Issue
Block a user