diff options
author | Gabriel de Dietrich <gabriel.dedietrich@qt.io> | 2018-03-28 18:07:30 -0700 |
---|---|---|
committer | Gabriel de Dietrich <gabriel.dedietrich@qt.io> | 2018-04-19 22:19:46 +0000 |
commit | b7be91b5f28cd42e6f9898f480fa12a0cd359a84 (patch) | |
tree | 78a8d28dd227d12afbd95677e8a4932ed1ca319a /src/plugins/platforms/cocoa/qnsview_menus.mm | |
parent | 0b9a301e89c6473091a9b80552d6e4058d35bbe6 (diff) |
Cocoa Menus: Use the responder chain for menu items target/action
We start by setting the menu item target to nil.
Then, -[qt_itemFired:] action is now in QNSView, which itself is
naturally inserted in the responder chain. This removes the need
to track and change the menu item's target/action when we're
displaying a native dialog. Part of this is possible because we
now derive our own QCocoaNSMenuItem class from NSMenuItem.
We use -[respondsToSelector:] to decide whether the QNSView in
the responder chain should respond to cut:, copy:, etc. And we
only return YES when the view is first responder. The invocation
to these action is forwarded to the same views' -[qt_itemFired:].
Message forwarding is done via forwardInvocation:, but experiments
have shown that it can be done by the sole means of respondsToSelector:
and direct invocation from cut:, copy:, etc. See the usage of the
macro QT_COCOA_DYNAMIC_MENU_ITEM_ACTION.
Menu validation also happens in QNSView and looks for modal windows.
Therefore, -[worksWhenModal] is no longer necessary. Also, since the
target is no longer set, the logic as documented in NSMenuItem.target
won't work anymore.
Most items from QCocoaMenuLoader also become QCocoaNSMenuItem and
get the same target/action, which removes a bit of duplicated (and
outdated) code. A particular case is the Quit item, which gets the
terminate: action set until an actual menu item is added.
Tested with texedit and standard dialogs examples together with
menus, menurama and bigmenucreator manual tests.
We also renamed some functions and variables to reflect common
naming practices.
Change-Id: I9b51d3be3467a666d8c3dcf8585edbc821e0282e
Reviewed-by: Tor Arne Vestbø <tor.arne.vestbo@qt.io>
Diffstat (limited to 'src/plugins/platforms/cocoa/qnsview_menus.mm')
-rw-r--r-- | src/plugins/platforms/cocoa/qnsview_menus.mm | 141 |
1 files changed, 141 insertions, 0 deletions
diff --git a/src/plugins/platforms/cocoa/qnsview_menus.mm b/src/plugins/platforms/cocoa/qnsview_menus.mm new file mode 100644 index 0000000000..15c14a1236 --- /dev/null +++ b/src/plugins/platforms/cocoa/qnsview_menus.mm @@ -0,0 +1,141 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +// This file is included from qnsview.mm, and only used to organize the code + +#include <qcocoansmenu.h> +#include <qcocoamenuitem.h> +#include <qcocoamenu.h> +#include <qcocoamenubar.h> + +static bool selectorIsCutCopyPaste(SEL selector) +{ + return (selector == @selector(cut:) + || selector == @selector(copy:) + || selector == @selector(paste:) + || selector == @selector(selectAll:)); +} + +@interface QT_MANGLE_NAMESPACE(QNSView) (Menus) +- (void)qt_itemFired:(QCocoaNSMenuItem *)item; +@end + +@implementation QT_MANGLE_NAMESPACE(QNSView) (Menus) + +- (BOOL)validateMenuItem:(NSMenuItem*)item +{ + if (![item isMemberOfClass:[QCocoaNSMenuItem class]]) + return item.enabled; // FIXME Test with with Qt as plugin or embedded QWindow. + + auto *platformItem = static_cast<QCocoaNSMenuItem *>(item).platformMenuItem; + if (!platformItem) + return NO; + + // Menu-holding items are always enabled, as it's conventional in Cocoa + if (platformItem->menu()) + return YES; + + // Check if a modal dialog is active. Validate only menu + // items belonging to this view's window own menu bar. + if (QGuiApplication::modalWindow()) { + QCocoaMenuBar *menubar = nullptr; + + QObject *menuParent = platformItem->menuParent(); + while (menuParent && !(menubar = qobject_cast<QCocoaMenuBar *>(menuParent))) { + auto *menuObject = dynamic_cast<QCocoaMenuObject *>(menuParent); + menuParent = menuObject->menuParent(); + } + + if (menubar && menubar->cocoaWindow() != self.platformWindow) + return NO; + } + + return platformItem->isEnabled(); +} + +- (BOOL)respondsToSelector:(SEL)selector +{ + // Not exactly true. Both copy: and selectAll: can work on non key views. + if (selectorIsCutCopyPaste(selector)) + return ([NSApp keyWindow] == self.window) && (self.window.firstResponder == self); + + return [super respondsToSelector:selector]; +} + +- (void)qt_itemFired:(QCocoaNSMenuItem *)item +{ + Q_ASSERT([item isMemberOfClass:[QCocoaNSMenuItem class]]); + auto *platformItem = static_cast<QCocoaNSMenuItem *>(item).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 +{ + if (selectorIsCutCopyPaste(selector)) { + NSMethodSignature *itemFiredSign = [super methodSignatureForSelector:@selector(qt_itemFired:)]; + return itemFiredSign; + } + + return [super methodSignatureForSelector:selector]; +} + +- (void)forwardInvocation:(NSInvocation *)invocation +{ + if (selectorIsCutCopyPaste(invocation.selector)) { + NSObject *sender; + [invocation getArgument:&sender atIndex:2]; + if ([sender isMemberOfClass:[QCocoaNSMenuItem class]]) { + [self qt_itemFired:static_cast<QCocoaNSMenuItem *>(sender)]; + return; + } + } + + [super forwardInvocation:invocation]; +} + +@end |