diff options
Diffstat (limited to 'src/plugins/platforms/cocoa/qcocoamenu.mm')
-rw-r--r-- | src/plugins/platforms/cocoa/qcocoamenu.mm | 365 |
1 files changed, 190 insertions, 175 deletions
diff --git a/src/plugins/platforms/cocoa/qcocoamenu.mm b/src/plugins/platforms/cocoa/qcocoamenu.mm index 00bbf49cca..9e466deb9a 100644 --- a/src/plugins/platforms/cocoa/qcocoamenu.mm +++ b/src/plugins/platforms/cocoa/qcocoamenu.mm @@ -1,9 +1,9 @@ /**************************************************************************** ** -** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** Copyright (C) 2012 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author James Turner <james.turner@kdab.com> ** Contact: http://www.qt-project.org/ ** -** This file is part of the QtGui module of the Qt Toolkit. +** This file is part of the plugins of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** GNU Lesser General Public License Usage @@ -39,229 +39,244 @@ ** ****************************************************************************/ -#include "qapplication.h" -#include "qvarlengtharray.h" -#import "qcocoamenu.h" -#import "qcocoamenuloader.h" -#import "qcocoaapplication.h" +#include "qcocoamenu.h" + #include "qcocoahelpers.h" -#include <private/qapplication_p.h> -#include <private/qaction_p.h> +#include "qcocoaautoreleasepool.h" -QT_FORWARD_DECLARE_CLASS(QAction) -QT_FORWARD_DECLARE_CLASS(QWidget) -QT_FORWARD_DECLARE_CLASS(QApplication) -QT_FORWARD_DECLARE_CLASS(QCoreApplication) -QT_FORWARD_DECLARE_CLASS(QApplicationPrivate) -QT_FORWARD_DECLARE_CLASS(QKeyEvent) -QT_FORWARD_DECLARE_CLASS(QEvent) +@interface QT_MANGLE_NAMESPACE(QCocoaMenuDelegate) : NSObject <NSMenuDelegate> { + QCocoaMenu *m_menu; +} -QT_BEGIN_NAMESPACE -extern void qt_mac_menu_collapseSeparators(NSMenu *menu, bool collapse); -void qt_mac_clear_status_text(QAction *action); -extern void qt_mac_emit_menuSignals(QMenu *menu, bool show); -extern void qt_mac_menu_emit_hovered(QMenu *menu, QAction *action); -QT_END_NAMESPACE +- (id) initWithMenu:(QCocoaMenu*) m; -QT_USE_NAMESPACE +@end -@implementation QT_MANGLE_NAMESPACE(QNativeCocoaMenu) +@implementation QT_MANGLE_NAMESPACE(QCocoaMenuDelegate) -- (id)initWithQMenu:(QMenu*)menu +- (id) initWithMenu:(QCocoaMenu*) m { - self = [super init]; - if (self) { - qmenu = menu; - previousAction = 0; - [self setAutoenablesItems:NO]; - [self setDelegate:self]; - } + if ((self = [super init])) + m_menu = m; + return self; } -- (void)menu:(NSMenu*)menu willHighlightItem:(NSMenuItem*)item +- (void) menuWillOpen:(NSMenu*)m { - Q_UNUSED(menu); + Q_UNUSED(m); + emit m_menu->aboutToShow(); +} - if (!item) { - if (previousAction) { - qt_mac_clear_status_text(previousAction); - previousAction = 0; - } - return; - } +- (void) menuDidClose:(NSMenu*)m +{ + Q_UNUSED(m); + // wrong, but it's the best we can do + emit m_menu->aboutToHide(); +} - if (QAction *action = reinterpret_cast<QAction *>([item tag])) { - QMenu *qtmenu = static_cast<QT_MANGLE_NAMESPACE(QNativeCocoaMenu) *>(menu)->qmenu; - previousAction = action; - action->activate(QAction::Hover); - qt_mac_menu_emit_hovered(qtmenu, action); - action->showStatusText(0); // 0 widget -> action's parent - } +- (void) itemFired:(NSMenuItem*) item +{ + QCocoaMenuItem *cocoaItem = reinterpret_cast<QCocoaMenuItem *>([item tag]); + cocoaItem->activated(); } -- (void)menuWillOpen:(NSMenu*)menu +- (BOOL)validateMenuItem:(NSMenuItem*)menuItem { - while (QWidget *popup - = QApplication::activePopupWidget()) - popup->close(); - QMenu *qtmenu = static_cast<QT_MANGLE_NAMESPACE(QNativeCocoaMenu) *>(menu)->qmenu; - qt_mac_emit_menuSignals(qtmenu, true); - qt_mac_menu_collapseSeparators(menu, qtmenu->separatorsCollapsible()); + if (![menuItem tag]) + return YES; + + + QCocoaMenuItem* cocoaItem = reinterpret_cast<QCocoaMenuItem *>([menuItem tag]); + return cocoaItem->isEnabled(); } -- (void)menuDidClose:(NSMenu*)menu +@end + +QCocoaMenu::QCocoaMenu() : + m_enabled(true), + m_tag(0) { - qt_mac_emit_menuSignals(((QT_MANGLE_NAMESPACE(QNativeCocoaMenu) *)menu)->qmenu, false); - if (previousAction) { - qt_mac_clear_status_text(previousAction); - previousAction = 0; + m_delegate = [[QCocoaMenuDelegate alloc] initWithMenu:this]; + m_nativeItem = [[NSMenuItem alloc] initWithTitle:@"" action:nil keyEquivalent:@""]; + m_nativeMenu = [[NSMenu alloc] initWithTitle:@"Untitled"]; + [m_nativeMenu setAutoenablesItems:YES]; + m_nativeMenu.delegate = (QCocoaMenuDelegate *) m_delegate; + [m_nativeItem setSubmenu:m_nativeMenu]; +} + +void QCocoaMenu::setText(const QString &text) +{ + QString stripped = qt_mac_removeAmpersandEscapes(text); + [m_nativeMenu setTitle:QCFString::toNSString(stripped)]; + [m_nativeItem setTitle:QCFString::toNSString(stripped)]; +} + +void QCocoaMenu::insertMenuItem(QPlatformMenuItem *menuItem, QPlatformMenuItem *before) +{ + QCocoaMenuItem *cocoaItem = static_cast<QCocoaMenuItem *>(menuItem); + QCocoaMenuItem *beforeItem = static_cast<QCocoaMenuItem *>(before); + + cocoaItem->sync(); + if (beforeItem) { + int index = m_menuItems.indexOf(beforeItem); + // if a before item is supplied, it should be in the menu + Q_ASSERT(index >= 0); + m_menuItems.insert(index, cocoaItem); + } else { + m_menuItems.append(cocoaItem); } + + insertNative(cocoaItem, beforeItem); } -- (BOOL)hasShortcut:(NSMenu *)menu forKey:(NSString *)key forModifiers:(NSUInteger)modifier - whichItem:(NSMenuItem**)outItem +void QCocoaMenu::insertNative(QCocoaMenuItem *item, QCocoaMenuItem *beforeItem) { - for (NSMenuItem *item in [menu itemArray]) { - if (![item isEnabled] || [item isHidden] || [item isSeparatorItem]) - continue; - if ([item hasSubmenu]) { - if ([self hasShortcut:[item submenu] - forKey:key - forModifiers:modifier whichItem:outItem]) { - if (outItem) - *outItem = item; - return YES; - } - } - NSString *menuKey = [item keyEquivalent]; - if (menuKey && NSOrderedSame == [menuKey compare:key] - && (modifier == [item keyEquivalentModifierMask])) { - if (outItem) - *outItem = item; - return YES; - } + [item->nsItem() setTarget:m_delegate]; + [item->nsItem() setAction:@selector(itemFired:)]; + + if (item->isMerged()) + return; + + // if the item we're inserting before is merged, skip along until + // we find a non-merged real item to insert ahead of. + while (beforeItem && beforeItem->isMerged()) { + beforeItem = itemOrNull(m_menuItems.indexOf(beforeItem) + 1); + } + + if (beforeItem) { + Q_ASSERT(!beforeItem->isMerged()); + NSUInteger nativeIndex = [m_nativeMenu indexOfItem:beforeItem->nsItem()]; + [m_nativeMenu insertItem: item->nsItem() atIndex: nativeIndex]; + } else { + [m_nativeMenu addItem: item->nsItem()]; } - if (outItem) - *outItem = 0; - return NO; } -NSString *qt_mac_removePrivateUnicode(NSString* string) +void QCocoaMenu::removeMenuItem(QPlatformMenuItem *menuItem) { - int len = [string length]; - if (len) { - QVarLengthArray <unichar, 10> characters(len); - bool changed = false; - for (int i = 0; i<len; i++) { - characters[i] = [string characterAtIndex:i]; - // check if they belong to key codes in private unicode range - // currently we need to handle only the NSDeleteFunctionKey - if (characters[i] == NSDeleteFunctionKey) { - characters[i] = NSDeleteCharacter; - changed = true; - } - } - if (changed) - return [NSString stringWithCharacters:characters.data() length:len]; + QCocoaMenuItem *cocoaItem = static_cast<QCocoaMenuItem *>(menuItem); + Q_ASSERT(m_menuItems.contains(cocoaItem)); + m_menuItems.removeOne(cocoaItem); + if (!cocoaItem->isMerged()) { + [m_nativeMenu removeItem: cocoaItem->nsItem()]; } - return string; } -- (BOOL)menuHasKeyEquivalent:(NSMenu *)menu forEvent:(NSEvent *)event target:(id *)target action:(SEL *)action +QCocoaMenuItem *QCocoaMenu::itemOrNull(int index) const +{ + if ((index < 0) || (index >= m_menuItems.size())) + return 0; + + return m_menuItems.at(index); +} + +void QCocoaMenu::syncMenuItem(QPlatformMenuItem *menuItem) { - // Check if the menu actually has a keysequence defined for this key event. - // If it does, then we will first send the key sequence to the QWidget that has focus - // since (in Qt's eyes) it needs to a chance at the key event first. If the widget - // accepts the key event, we then return YES, but set the target and action to be nil, - // which means that the action should not be triggered, and instead dispatch the event ourselves. - // In every other case we return NO, which means that Cocoa can do as it pleases - // (i.e., fire the menu action). - NSMenuItem *whichItem; - // Change the private unicode keys to the ones used in setting the "Key Equivalents" - NSString *characters = qt_mac_removePrivateUnicode([event characters]); - if ([self hasShortcut:menu - forKey:characters - // Interested only in Shift, Cmd, Ctrl & Alt Keys, so ignoring masks like, Caps lock, Num Lock ... - forModifiers:([event modifierFlags] & (NSShiftKeyMask | NSControlKeyMask | NSCommandKeyMask | NSAlternateKeyMask)) - whichItem:&whichItem]) { - QWidget *widget = 0; - QAction *qaction = 0; - if (whichItem && [whichItem tag]) { - qaction = reinterpret_cast<QAction *>([whichItem tag]); + QCocoaMenuItem *cocoaItem = static_cast<QCocoaMenuItem *>(menuItem); + Q_ASSERT(m_menuItems.contains(cocoaItem)); + + bool wasMerged = cocoaItem->isMerged(); + NSMenuItem *oldItem = [m_nativeMenu itemWithTag:(NSInteger) cocoaItem]; + + if (cocoaItem->sync() != oldItem) { + // native item was changed for some reason + if (!wasMerged) { + [m_nativeMenu removeItem:oldItem]; } - if (qApp->activePopupWidget()) - widget = (qApp->activePopupWidget()->focusWidget() ? - qApp->activePopupWidget()->focusWidget() : qApp->activePopupWidget()); - else if (QApplicationPrivate::focus_widget) - widget = QApplicationPrivate::focus_widget; - // If we could not find any receivers, pass it to the active window - if (!widget) - widget = qApp->activeWindow(); - if (qaction && widget) { - int key = qaction->shortcut(); - QKeyEvent accel_ev(QEvent::ShortcutOverride, (key & (~Qt::KeyboardModifierMask)), - Qt::KeyboardModifiers(key & Qt::KeyboardModifierMask)); - accel_ev.ignore(); - -// ### qt_sendSpontaneousEvent(widget, &accel_ev); - - if (accel_ev.isAccepted()) { - qWarning("Unimplemented: qt_dispatchKeyEvent"); -#if 0 - qt_dispatchKeyEvent(event, widget); -#endif - *target = nil; - *action = nil; - return YES; + + QCocoaMenuItem* beforeItem = itemOrNull(m_menuItems.indexOf(cocoaItem) + 1); + insertNative(cocoaItem, beforeItem); + } +} + +void QCocoaMenu::syncSeparatorsCollapsible(bool enable) +{ + QCocoaAutoReleasePool pool; + if (enable) { + bool previousIsSeparator = true; // setting to true kills all the separators placed at the top. + NSMenuItem *previousItem = nil; + + NSArray *itemArray = [m_nativeMenu itemArray]; + for (unsigned int i = 0; i < [itemArray count]; ++i) { + NSMenuItem *item = reinterpret_cast<NSMenuItem *>([itemArray objectAtIndex:i]); + if ([item isSeparatorItem]) + [item setHidden:previousIsSeparator]; + + if (![item isHidden]) { + previousItem = item; + previousIsSeparator = ([previousItem isSeparatorItem]); } } + + // We now need to check the final item since we don't want any separators at the end of the list. + if (previousItem && previousIsSeparator) + [previousItem setHidden:YES]; + } else { + foreach (QCocoaMenuItem *item, m_menuItems) { + if (!item->isSeparator()) + continue; + + // sync the visiblity directly + item->sync(); + } } - return NO; } -- (NSInteger)indexOfItemWithTarget:(id)anObject andAction:(SEL)actionSelector +void QCocoaMenu::setParentItem(QCocoaMenuItem *item) { - NSInteger index = [super indexOfItemWithTarget:anObject andAction:actionSelector]; - static SEL selForOFCP = NSSelectorFromString(@"orderFrontCharacterPalette:"); - if (index == -1 && selForOFCP == actionSelector) { - // Check if the 'orderFrontCharacterPalette' SEL exists for QNativeCocoaMenuLoader object - QT_MANGLE_NAMESPACE(QCocoaMenuLoader) *loader = [NSApp QT_MANGLE_NAMESPACE(qt_qcocoamenuLoader)]; - return [super indexOfItemWithTarget:loader andAction:actionSelector]; - } - return index; + Q_UNUSED(item); } -@end +void QCocoaMenu::setEnabled(bool enabled) +{ + m_enabled = enabled; +} -QT_BEGIN_NAMESPACE -extern int qt_mac_menus_open_count; // qmenu_mac.mm +QPlatformMenuItem *QCocoaMenu::menuItemAt(int position) const +{ + return m_menuItems.at(position); +} -void qt_mac_emit_menuSignals(QMenu *menu, bool show) +QPlatformMenuItem *QCocoaMenu::menuItemForTag(quintptr tag) const { - if (!menu) - return; - int delta; - if (show) { - emit menu->aboutToShow(); - delta = 1; - } else { - emit menu->aboutToHide(); - delta = -1; + foreach (QCocoaMenuItem *item, m_menuItems) { + if (item->tag() == tag) + return item; } - qt_mac_menus_open_count += delta; + + return 0; } -void qt_mac_clear_status_text(QAction *action) +QList<QCocoaMenuItem *> QCocoaMenu::merged() const { - action->d_func()->showStatusText(0, QString()); + QList<QCocoaMenuItem *> result; + foreach (QCocoaMenuItem *item, m_menuItems) { + if (item->menu()) { // recurse into submenus + result.append(item->menu()->merged()); + continue; + } + + if (item->isMerged()) + result.append(item); + } + + return result; } -void qt_mac_menu_emit_hovered(QMenu *menu, QAction *action) +void QCocoaMenu::syncModalState(bool modal) { - emit menu->hovered(action); -} + if (!m_enabled) + modal = true; + [m_nativeItem setEnabled:!modal]; -QT_END_NAMESPACE + foreach (QCocoaMenuItem *item, m_menuItems) { + if (item->menu()) { // recurse into submenus + item->menu()->syncModalState(modal); + continue; + } + item->syncModalState(modal); + } +} |