diff options
Diffstat (limited to 'src/plugins/platforms/cocoa')
-rw-r--r-- | src/plugins/platforms/cocoa/qcocoaapplicationdelegate.mm | 5 | ||||
-rw-r--r-- | src/plugins/platforms/cocoa/qcocoacolordialoghelper.mm | 12 | ||||
-rw-r--r-- | src/plugins/platforms/cocoa/qcocoadrag.mm | 6 | ||||
-rw-r--r-- | src/plugins/platforms/cocoa/qcocoafontdialoghelper.mm | 12 | ||||
-rw-r--r-- | src/plugins/platforms/cocoa/qcocoahelpers.h | 2 | ||||
-rw-r--r-- | src/plugins/platforms/cocoa/qcocoamenu.mm | 10 | ||||
-rw-r--r-- | src/plugins/platforms/cocoa/qcocoamenubar.mm | 81 | ||||
-rw-r--r-- | src/plugins/platforms/cocoa/qcocoascreen.mm | 4 | ||||
-rw-r--r-- | src/plugins/platforms/cocoa/qcocoasystemtrayicon.h | 3 | ||||
-rw-r--r-- | src/plugins/platforms/cocoa/qcocoasystemtrayicon.mm | 36 | ||||
-rw-r--r-- | src/plugins/platforms/cocoa/qcocoatheme.mm | 65 | ||||
-rw-r--r-- | src/plugins/platforms/cocoa/qcocoawindow.h | 9 | ||||
-rw-r--r-- | src/plugins/platforms/cocoa/qcocoawindow.mm | 120 | ||||
-rw-r--r-- | src/plugins/platforms/cocoa/qnsview.mm | 1 | ||||
-rw-r--r-- | src/plugins/platforms/cocoa/qnsview_complextext.mm | 25 | ||||
-rw-r--r-- | src/plugins/platforms/cocoa/qnsview_keys.mm | 53 | ||||
-rw-r--r-- | src/plugins/platforms/cocoa/qnswindow.mm | 9 |
17 files changed, 322 insertions, 131 deletions
diff --git a/src/plugins/platforms/cocoa/qcocoaapplicationdelegate.mm b/src/plugins/platforms/cocoa/qcocoaapplicationdelegate.mm index 07ac6023c0..b13ec3bee8 100644 --- a/src/plugins/platforms/cocoa/qcocoaapplicationdelegate.mm +++ b/src/plugins/platforms/cocoa/qcocoaapplicationdelegate.mm @@ -232,6 +232,11 @@ QT_USE_NAMESPACE - (void)applicationDidBecomeActive:(NSNotification *)notification { + if (QCocoaWindow::s_applicationActivationObserver) { + [[[NSWorkspace sharedWorkspace] notificationCenter] removeObserver:QCocoaWindow::s_applicationActivationObserver]; + QCocoaWindow::s_applicationActivationObserver = nil; + } + if ([reflectionDelegate respondsToSelector:_cmd]) [reflectionDelegate applicationDidBecomeActive:notification]; diff --git a/src/plugins/platforms/cocoa/qcocoacolordialoghelper.mm b/src/plugins/platforms/cocoa/qcocoacolordialoghelper.mm index 777c3d65b6..b58f58caeb 100644 --- a/src/plugins/platforms/cocoa/qcocoacolordialoghelper.mm +++ b/src/plugins/platforms/cocoa/qcocoacolordialoghelper.mm @@ -182,7 +182,17 @@ QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSColorPanelDelegate); mDialogIsExecuting = false; mResultSet = false; mClosingDueToKnownButton = false; - [mColorPanel makeKeyAndOrderFront:mColorPanel]; + // Make this an asynchronous call, so the panel is made key only + // in the next event loop run. This is to make sure that by + // the time the modal loop is run in runModalForWindow below, + // which internally also sets the panel to key window, + // the panel is not yet key, and the NSApp still has the right + // reference to the _previousKeyWindow. Otherwise both NSApp.key + // and NSApp._previousKeyWindow would wrongly point to the panel, + // loosing any reference to the window that was key before. + dispatch_async(dispatch_get_main_queue(), ^{ + [mColorPanel makeKeyAndOrderFront:mColorPanel]; + }); } - (BOOL)runApplicationModalPanel diff --git a/src/plugins/platforms/cocoa/qcocoadrag.mm b/src/plugins/platforms/cocoa/qcocoadrag.mm index 50f094716f..b6c2e15ee2 100644 --- a/src/plugins/platforms/cocoa/qcocoadrag.mm +++ b/src/plugins/platforms/cocoa/qcocoadrag.mm @@ -184,6 +184,12 @@ bool QCocoaDrag::maybeDragMultipleItems() // contains a combined picture for all urls we drag. auto imageOrNil = dragImage; for (const auto &qtUrl : qtUrls) { + if (!qtUrl.isValid()) + continue; + + if (qtUrl.isRelative()) // NSPasteboardWriting rejects such items. + continue; + NSURL *nsUrl = qtUrl.toNSURL(); auto *newItem = [[[NSDraggingItem alloc] initWithPasteboardWriter:nsUrl] autorelease]; const NSRect itemFrame = NSMakeRect(itemLocation.x, itemLocation.y, diff --git a/src/plugins/platforms/cocoa/qcocoafontdialoghelper.mm b/src/plugins/platforms/cocoa/qcocoafontdialoghelper.mm index ac5d784b79..5cdf6bf02c 100644 --- a/src/plugins/platforms/cocoa/qcocoafontdialoghelper.mm +++ b/src/plugins/platforms/cocoa/qcocoafontdialoghelper.mm @@ -166,7 +166,17 @@ QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSFontPanelDelegate); { mDialogIsExecuting = false; mResultSet = false; - [mFontPanel makeKeyAndOrderFront:mFontPanel]; + // Make this an asynchronous call, so the panel is made key only + // in the next event loop run. This is to make sure that by + // the time the modal loop is run in runModalForWindow below, + // which internally also sets the panel to key window, + // the panel is not yet key, and the NSApp still has the right + // reference to the _previousKeyWindow. Otherwise both NSApp.key + // and NSApp._previousKeyWindow would wrongly point to the panel, + // loosing any reference to the window that was key before. + dispatch_async(dispatch_get_main_queue(), ^{ + [mFontPanel makeKeyAndOrderFront:mFontPanel]; + }); } - (BOOL)runApplicationModalPanel diff --git a/src/plugins/platforms/cocoa/qcocoahelpers.h b/src/plugins/platforms/cocoa/qcocoahelpers.h index 869b245911..1964dcf59d 100644 --- a/src/plugins/platforms/cocoa/qcocoahelpers.h +++ b/src/plugins/platforms/cocoa/qcocoahelpers.h @@ -313,7 +313,7 @@ QSendSuperHelper<Args...> qt_objcDynamicSuperHelper(id receiver, SEL selector, A // ------------------------------------------------------------------------- -struct InputMethodQueryResult : public QHash<Qt::InputMethodQuery, QVariant> +struct InputMethodQueryResult : public QHash<int, QVariant> { operator bool() { return !isEmpty(); } }; diff --git a/src/plugins/platforms/cocoa/qcocoamenu.mm b/src/plugins/platforms/cocoa/qcocoamenu.mm index 8a3d259b3e..6ef98f4af4 100644 --- a/src/plugins/platforms/cocoa/qcocoamenu.mm +++ b/src/plugins/platforms/cocoa/qcocoamenu.mm @@ -35,7 +35,7 @@ QCocoaMenu::QCocoaMenu() : QCocoaMenu::~QCocoaMenu() { - for (auto *item : qAsConst(m_menuItems)) { + for (auto *item : std::as_const(m_menuItems)) { if (item->menuParent() == this) item->setMenuParent(nullptr); } @@ -284,7 +284,7 @@ void QCocoaMenu::syncSeparatorsCollapsible(bool enable) if (lastVisibleItem && lastVisibleItem.separatorItem) lastVisibleItem.hidden = YES; } else { - for (auto *item : qAsConst(m_menuItems)) { + for (auto *item : std::as_const(m_menuItems)) { if (!item->isSeparator()) continue; @@ -423,7 +423,7 @@ QPlatformMenuItem *QCocoaMenu::menuItemAt(int position) const QPlatformMenuItem *QCocoaMenu::menuItemForTag(quintptr tag) const { - for (auto *item : qAsConst(m_menuItems)) { + for (auto *item : std::as_const(m_menuItems)) { if (item->tag() == tag) return item; } @@ -439,7 +439,7 @@ QList<QCocoaMenuItem *> QCocoaMenu::items() const QList<QCocoaMenuItem *> QCocoaMenu::merged() const { QList<QCocoaMenuItem *> result; - for (auto *item : qAsConst(m_menuItems)) { + for (auto *item : std::as_const(m_menuItems)) { if (item->menu()) { // recurse into submenus result.append(item->menu()->merged()); continue; @@ -460,7 +460,7 @@ void QCocoaMenu::propagateEnabledState(bool enabled) if (!m_enabled && enabled) // Some ancestor was enabled, but this menu is not return; - for (auto *item : qAsConst(m_menuItems)) { + for (auto *item : std::as_const(m_menuItems)) { if (QCocoaMenu *menu = item->menu()) menu->propagateEnabledState(enabled); else diff --git a/src/plugins/platforms/cocoa/qcocoamenubar.mm b/src/plugins/platforms/cocoa/qcocoamenubar.mm index 23aae466df..5298997271 100644 --- a/src/plugins/platforms/cocoa/qcocoamenubar.mm +++ b/src/plugins/platforms/cocoa/qcocoamenubar.mm @@ -13,6 +13,8 @@ #include <QtGui/QGuiApplication> #include <QtCore/QDebug> +#include <QtGui/private/qguiapplication_p.h> + QT_BEGIN_NAMESPACE static QList<QCocoaMenuBar*> static_menubars; @@ -21,6 +23,11 @@ QCocoaMenuBar::QCocoaMenuBar() { static_menubars.append(this); + // clicks into the menu bar should close all popup windows + static QMacNotificationObserver menuBarClickObserver(nil, NSMenuDidBeginTrackingNotification, ^{ + QGuiApplicationPrivate::instance()->closeAllPopups(); + }); + m_nativeMenu = [[NSMenu alloc] init]; #ifdef QT_COCOA_ENABLE_MENU_DEBUG qDebug() << "Construct QCocoaMenuBar" << this << m_nativeMenu; @@ -32,7 +39,7 @@ QCocoaMenuBar::~QCocoaMenuBar() #ifdef QT_COCOA_ENABLE_MENU_DEBUG qDebug() << "~QCocoaMenuBar" << this; #endif - for (auto menu : qAsConst(m_menus)) { + for (auto menu : std::as_const(m_menus)) { if (!menu) continue; NSMenuItem *item = nativeItemForMenu(menu); @@ -158,18 +165,6 @@ void QCocoaMenuBar::syncMenu_helper(QPlatformMenu *menu, bool menubarUpdate) for (QCocoaMenuItem *item : cocoaMenu->items()) cocoaMenu->syncMenuItem_helper(item, menubarUpdate); - const QString captionNoAmpersand = QString::fromNSString(cocoaMenu->nsMenu().title) - .remove(u'&'); - if (captionNoAmpersand == QCoreApplication::translate("QCocoaMenu", "Edit")) { - // prevent recursion from QCocoaMenu::insertMenuItem - when the menu is visible - // it calls syncMenu again. QCocoaMenu::setVisible just sets the bool, which then - // gets evaluated in the code after this block. - const bool wasVisible = cocoaMenu->isVisible(); - cocoaMenu->setVisible(false); - insertDefaultEditItems(cocoaMenu); - cocoaMenu->setVisible(wasVisible); - } - BOOL shouldHide = YES; if (cocoaMenu->isVisible()) { // If the NSMenu has no visible items, or only separators, we should hide it @@ -233,7 +228,7 @@ QCocoaWindow *QCocoaMenuBar::findWindowForMenubar() QCocoaMenuBar *QCocoaMenuBar::findGlobalMenubar() { - for (auto *menubar : qAsConst(static_menubars)) { + for (auto *menubar : std::as_const(static_menubars)) { if (menubar->m_window.isNull()) return menubar; } @@ -274,7 +269,7 @@ void QCocoaMenuBar::updateMenuBarImmediately() #endif bool disableForModal = mb->shouldDisable(cw); - for (auto menu : qAsConst(mb->m_menus)) { + for (auto menu : std::as_const(mb->m_menus)) { if (!menu) continue; NSMenuItem *item = mb->nativeItemForMenu(menu); @@ -306,6 +301,22 @@ void QCocoaMenuBar::updateMenuBarImmediately() [NSApp setMainMenu:mb->nsMenu()]; insertWindowMenu(); [loader qtTranslateApplicationMenu]; + + for (auto menu : std::as_const(mb->m_menus)) { + if (!menu) + continue; + + const QString captionNoAmpersand = QString::fromNSString(menu->nsMenu().title).remove(u'&'); + if (captionNoAmpersand != QCoreApplication::translate("QCocoaMenu", "Edit")) + continue; + + NSMenuItem *item = mb->nativeItemForMenu(menu); + auto *nsMenu = item.submenu; + if ([nsMenu indexOfItemWithTarget:NSApp andAction:@selector(startDictation:)] == -1) { + // AppKit was not able to recognize the special role of this menu item. + mb->insertDefaultEditItems(menu); + } + } } void QCocoaMenuBar::insertWindowMenu() @@ -327,18 +338,27 @@ void QCocoaMenuBar::insertWindowMenu() [mainMenu insertItem:winMenuItem atIndex:mainMenu.itemArray.count]; app.windowsMenu = winMenuItem.submenu; - // Windows, created and 'ordered front' before, will not be in this menu: + // Windows that have already been ordered in at this point have already been + // evaluated by AppKit via _addToWindowsMenuIfNecessary and added to the menu, + // but since the menu didn't exist at that point the addition was a noop. + // Instead of trying to duplicate the logic AppKit uses for deciding if + // a window should be part of the Window menu we toggle one of the settings + // that definitely will affect this, which results in AppKit reevaluating the + // situation and adding the window to the menu if necessary. for (NSWindow *win in app.windows) { - if (win.title && ![win.title isEqualToString:@""]) - [app addWindowsItem:win title:win.title filename:NO]; + win.excludedFromWindowsMenu = !win.excludedFromWindowsMenu; + win.excludedFromWindowsMenu = !win.excludedFromWindowsMenu; } } QList<QCocoaMenuItem*> QCocoaMenuBar::merged() const { QList<QCocoaMenuItem*> r; - for (auto menu : qAsConst(m_menus)) + for (auto menu : std::as_const(m_menus)) { + if (!menu) + continue; r.append(menu->merged()); + } return r; } @@ -357,10 +377,10 @@ bool QCocoaMenuBar::shouldDisable(QCocoaWindow *active) const // When there is an application modal window on screen, the entries of // the menubar should be disabled. The exception in Qt is that if the // modal window is the only window on screen, then we enable the menu bar. - for (auto *window : qAsConst(topWindows)) { + for (auto *window : std::as_const(topWindows)) { if (window->isVisible() && window->modality() == Qt::ApplicationModal) { // check for other visible windows - for (auto *other : qAsConst(topWindows)) { + for (auto *other : std::as_const(topWindows)) { if ((window != other) && (other->isVisible())) { // INVARIANT: we found another visible window // on screen other than our modalWidget. We therefore @@ -381,8 +401,8 @@ bool QCocoaMenuBar::shouldDisable(QCocoaWindow *active) const QPlatformMenu *QCocoaMenuBar::menuForTag(quintptr tag) const { - for (auto menu : qAsConst(m_menus)) - if (menu->tag() == tag) + for (auto menu : std::as_const(m_menus)) + if (menu && menu->tag() == tag) return menu; return nullptr; @@ -390,10 +410,13 @@ QPlatformMenu *QCocoaMenuBar::menuForTag(quintptr tag) const NSMenuItem *QCocoaMenuBar::itemForRole(QPlatformMenuItem::MenuRole role) { - for (auto menu : qAsConst(m_menus)) - for (auto *item : menu->items()) - if (item->effectiveRole() == role) - return item->nsItem(); + for (auto menu : std::as_const(m_menus)) { + if (menu) { + for (auto *item : menu->items()) + if (item->effectiveRole() == role) + return item->nsItem(); + } + } return nil; } @@ -411,7 +434,7 @@ void QCocoaMenuBar::insertDefaultEditItems(QCocoaMenu *menu) NSMenu *nsEditMenu = menu->nsMenu(); if ([nsEditMenu itemAtIndex:nsEditMenu.numberOfItems - 1].action == @selector(orderFrontCharacterPalette:)) { - for (auto defaultEditMenuItem : qAsConst(m_defaultEditMenuItems)) { + for (auto defaultEditMenuItem : std::as_const(m_defaultEditMenuItems)) { if (menu->items().contains(defaultEditMenuItem)) menu->removeMenuItem(defaultEditMenuItem); } @@ -437,7 +460,7 @@ void QCocoaMenuBar::insertDefaultEditItems(QCocoaMenu *menu) m_defaultEditMenuItems << separator << dictationItem << emojiItem; } - for (auto defaultEditMenuItem : qAsConst(m_defaultEditMenuItems)) { + for (auto defaultEditMenuItem : std::as_const(m_defaultEditMenuItems)) { if (menu->items().contains(defaultEditMenuItem)) menu->removeMenuItem(defaultEditMenuItem); menu->insertMenuItem(defaultEditMenuItem, nullptr); diff --git a/src/plugins/platforms/cocoa/qcocoascreen.mm b/src/plugins/platforms/cocoa/qcocoascreen.mm index a8778ebe1d..2de2076279 100644 --- a/src/plugins/platforms/cocoa/qcocoascreen.mm +++ b/src/plugins/platforms/cocoa/qcocoascreen.mm @@ -258,7 +258,9 @@ void QCocoaScreen::update(CGDirectDisplayID displayId) m_depth = NSBitsPerPixelFromDepth(nsScreen.depth); m_colorSpace = QColorSpace::fromIccProfile(QByteArray::fromNSData(nsScreen.colorSpace.ICCProfileData)); if (!m_colorSpace.isValid()) { - qWarning() << "macOS generated a color-profile Qt couldn't parse. This shouldn't happen."; + qCWarning(lcQpaScreen) << "Failed to parse ICC profile for" << nsScreen.colorSpace + << "with ICC data" << nsScreen.colorSpace.ICCProfileData + << "- Falling back to sRGB"; m_colorSpace = QColorSpace::SRgb; } diff --git a/src/plugins/platforms/cocoa/qcocoasystemtrayicon.h b/src/plugins/platforms/cocoa/qcocoasystemtrayicon.h index 414560e119..75c33cc5a3 100644 --- a/src/plugins/platforms/cocoa/qcocoasystemtrayicon.h +++ b/src/plugins/platforms/cocoa/qcocoasystemtrayicon.h @@ -45,12 +45,11 @@ public: bool isSystemTrayAvailable() const override; bool supportsMessages() const override; - void statusItemClicked(); + void emitActivated(); private: NSStatusItem *m_statusItem = nullptr; QStatusItemDelegate *m_delegate = nullptr; - QCocoaMenu *m_menu = nullptr; }; QT_END_NAMESPACE diff --git a/src/plugins/platforms/cocoa/qcocoasystemtrayicon.mm b/src/plugins/platforms/cocoa/qcocoasystemtrayicon.mm index c004cd69b5..2f7f73b481 100644 --- a/src/plugins/platforms/cocoa/qcocoasystemtrayicon.mm +++ b/src/plugins/platforms/cocoa/qcocoasystemtrayicon.mm @@ -64,6 +64,8 @@ void QCocoaSystemTrayIcon::init() m_delegate = [[QStatusItemDelegate alloc] initWithSysTray:this]; + // In case the status item does not have a menu assigned to it + // we fall back to the item's button to detect activation. m_statusItem.button.target = m_delegate; m_statusItem.button.action = @selector(statusItemClicked); [m_statusItem.button sendActionOn:NSEventMaskLeftMouseDown | NSEventMaskRightMouseDown | NSEventMaskOtherMouseDown]; @@ -81,8 +83,6 @@ void QCocoaSystemTrayIcon::cleanup() [m_delegate release]; m_delegate = nil; - - m_menu = nullptr; } QRect QCocoaSystemTrayIcon::geometry() const @@ -178,12 +178,20 @@ void QCocoaSystemTrayIcon::updateIcon(const QIcon &icon) void QCocoaSystemTrayIcon::updateMenu(QPlatformMenu *menu) { - // We don't set the menu property of the NSStatusItem here, - // as that would prevent us from receiving the action for the - // click, and we wouldn't be able to emit the activated signal. - // Instead we show the menu manually when the status item is - // clicked. - m_menu = static_cast<QCocoaMenu *>(menu); + m_statusItem.menu = menu ? static_cast<QCocoaMenu *>(menu)->nsMenu() : nil; + + if (m_statusItem.menu) { + // When a menu is assigned, NSStatusBarButtonCell will intercept the mouse + // down to pop up the menu, and we never see the NSStatusBarButton action. + // To ensure we emit the 'activated' signal in both cases we detect when + // menu starts tracking, which happens before the menu delegate is sent + // the menuWillOpen callback we use to emit aboutToShow for the menu. + [NSNotificationCenter.defaultCenter addObserver:m_delegate + selector:@selector(statusItemMenuBeganTracking:) + name:NSMenuDidBeginTrackingNotification + object:m_statusItem.menu + ]; + } } void QCocoaSystemTrayIcon::updateToolTip(const QString &toolTip) @@ -226,7 +234,7 @@ void QCocoaSystemTrayIcon::showMessage(const QString &title, const QString &mess } } -void QCocoaSystemTrayIcon::statusItemClicked() +void QCocoaSystemTrayIcon::emitActivated() { auto *mouseEvent = NSApp.currentEvent; @@ -245,9 +253,6 @@ void QCocoaSystemTrayIcon::statusItemClicked() } emit activated(activationReason); - - if (NSMenu *menu = m_menu ? m_menu->nsMenu() : nil) - QT_IGNORE_DEPRECATIONS([m_statusItem popUpStatusItemMenu:menu]); } QT_END_NAMESPACE @@ -270,7 +275,12 @@ QT_END_NAMESPACE - (void)statusItemClicked { - self.platformSystemTray->statusItemClicked(); + self.platformSystemTray->emitActivated(); +} + +- (void)statusItemMenuBeganTracking:(NSNotification*)notification +{ + self.platformSystemTray->emitActivated(); } - (BOOL)userNotificationCenter:(NSUserNotificationCenter *)center shouldPresentNotification:(NSUserNotification *)notification diff --git a/src/plugins/platforms/cocoa/qcocoatheme.mm b/src/plugins/platforms/cocoa/qcocoatheme.mm index 408c9ac403..b836b816cd 100644 --- a/src/plugins/platforms/cocoa/qcocoatheme.mm +++ b/src/plugins/platforms/cocoa/qcocoatheme.mm @@ -33,18 +33,6 @@ #include <CoreServices/CoreServices.h> -#if !QT_MACOS_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_14) -@interface NSColor (MojaveForwardDeclarations) -@property (class, strong, readonly) NSColor *selectedContentBackgroundColor NS_AVAILABLE_MAC(10_14); -@property (class, strong, readonly) NSColor *unemphasizedSelectedTextBackgroundColor NS_AVAILABLE_MAC(10_14); -@property (class, strong, readonly) NSColor *unemphasizedSelectedTextColor NS_AVAILABLE_MAC(10_14); -@property (class, strong, readonly) NSColor *unemphasizedSelectedContentBackgroundColor NS_AVAILABLE_MAC(10_14); -@property (class, strong, readonly) NSArray<NSColor *> *alternatingContentBackgroundColors NS_AVAILABLE_MAC(10_14); -// Missing from non-Mojave SDKs, even if introduced in 10.10 -@property (class, strong, readonly) NSColor *linkColor NS_AVAILABLE_MAC(10_10); -@end -#endif - QT_BEGIN_NAMESPACE static QPalette *qt_mac_createSystemPalette() @@ -74,14 +62,9 @@ static QPalette *qt_mac_createSystemPalette() // System palette initialization: QBrush br = qt_mac_toQBrush([NSColor selectedControlColor]); palette->setBrush(QPalette::Active, QPalette::Highlight, br); - if (__builtin_available(macOS 10.14, *)) { - const auto inactiveHighlight = qt_mac_toQBrush([NSColor unemphasizedSelectedContentBackgroundColor]); - palette->setBrush(QPalette::Inactive, QPalette::Highlight, inactiveHighlight); - palette->setBrush(QPalette::Disabled, QPalette::Highlight, inactiveHighlight); - } else { - palette->setBrush(QPalette::Inactive, QPalette::Highlight, br); - palette->setBrush(QPalette::Disabled, QPalette::Highlight, br); - } + const auto inactiveHighlight = qt_mac_toQBrush([NSColor unemphasizedSelectedContentBackgroundColor]); + palette->setBrush(QPalette::Inactive, QPalette::Highlight, inactiveHighlight); + palette->setBrush(QPalette::Disabled, QPalette::Highlight, inactiveHighlight); palette->setBrush(QPalette::Shadow, qt_mac_toQColor([NSColor shadowColor])); @@ -166,17 +149,8 @@ static QHash<QPlatformTheme::Palette, QPalette*> qt_mac_createRolePalettes() } if (mac_widget_colors[i].paletteRole == QPlatformTheme::MenuPalette || mac_widget_colors[i].paletteRole == QPlatformTheme::MenuBarPalette) { - NSColor *selectedMenuItemColor = nil; - if (__builtin_available(macOS 10.14, *)) { - // Cheap approximation for NSVisualEffectView (see deprecation note for selectedMenuItemTextColor) - selectedMenuItemColor = [[NSColor controlAccentColor] highlightWithLevel:0.3]; - } else { - // selectedMenuItemColor would presumably be the correct color to use as the background - // for selected menu items. But that color is always blue, and doesn't follow the - // appearance color in system preferences. So we therefore deliberately choose to use - // keyboardFocusIndicatorColor instead, which appears to have the same color value. - selectedMenuItemColor = [NSColor keyboardFocusIndicatorColor]; - } + // Cheap approximation for NSVisualEffectView (see deprecation note for selectedMenuItemTextColor) + auto selectedMenuItemColor = [[NSColor controlAccentColor] highlightWithLevel:0.3]; pal.setBrush(QPalette::Highlight, qt_mac_toQColor(selectedMenuItemColor)); qc = qt_mac_toQColor([NSColor labelColor]); pal.setBrush(QPalette::ButtonText, qc); @@ -197,17 +171,10 @@ static QHash<QPlatformTheme::Palette, QPalette*> qt_mac_createRolePalettes() } else if (mac_widget_colors[i].paletteRole == QPlatformTheme::ItemViewPalette) { NSArray<NSColor *> *baseColors = nil; NSColor *activeHighlightColor = nil; - if (__builtin_available(macOS 10.14, *)) { - baseColors = [NSColor alternatingContentBackgroundColors]; - activeHighlightColor = [NSColor selectedContentBackgroundColor]; - pal.setBrush(QPalette::Inactive, QPalette::HighlightedText, - qt_mac_toQBrush([NSColor unemphasizedSelectedTextColor])); - } else { - baseColors = [NSColor controlAlternatingRowBackgroundColors]; - activeHighlightColor = [NSColor alternateSelectedControlColor]; - pal.setBrush(QPalette::Inactive, QPalette::HighlightedText, - pal.brush(QPalette::Active, QPalette::Text)); - } + baseColors = [NSColor alternatingContentBackgroundColors]; + activeHighlightColor = [NSColor selectedContentBackgroundColor]; + pal.setBrush(QPalette::Inactive, QPalette::HighlightedText, + qt_mac_toQBrush([NSColor unemphasizedSelectedTextColor])); pal.setBrush(QPalette::Base, qt_mac_toQBrush(baseColors[0])); pal.setBrush(QPalette::AlternateBase, qt_mac_toQBrush(baseColors[1])); pal.setBrush(QPalette::Active, QPalette::Highlight, @@ -241,16 +208,12 @@ const char *QCocoaTheme::name = "cocoa"; QCocoaTheme::QCocoaTheme() : m_systemPalette(nullptr) { -#if QT_MACOS_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_14) if (QOperatingSystemVersion::current() >= QOperatingSystemVersion::MacOSMojave) { m_appearanceObserver = QMacKeyValueObserver(NSApp, @"effectiveAppearance", [this] { - if (__builtin_available(macOS 10.14, *)) - NSAppearance.currentAppearance = NSApp.effectiveAppearance; - + NSAppearance.currentAppearance = NSApp.effectiveAppearance; handleSystemThemeChange(); }); } -#endif m_systemColorObserver = QMacNotificationObserver(nil, NSSystemColorsDidChangeNotification, [this] { @@ -416,7 +379,7 @@ QPixmap QCocoaTheme::standardPixmap(StandardPixmap sp, const QSizeF &size) const if (icon) { pixmap = qt_mac_convert_iconref(icon, size.width(), size.height()); - ReleaseIconRef(icon); + QT_IGNORE_DEPRECATIONS(ReleaseIconRef(icon)); } return pixmap; @@ -489,6 +452,12 @@ QVariant QCocoaTheme::themeHint(ThemeHint hint) const return !NSScreen.screensHaveSeparateSpaces; case QPlatformTheme::ShowDirectoriesFirst: return false; + case QPlatformTheme::MouseDoubleClickInterval: + return NSEvent.doubleClickInterval * 1000; + case QPlatformTheme::KeyboardInputInterval: + return NSEvent.keyRepeatDelay * 1000; + case QPlatformTheme::KeyboardAutoRepeatRate: + return 1.0 / NSEvent.keyRepeatInterval; default: break; } diff --git a/src/plugins/platforms/cocoa/qcocoawindow.h b/src/plugins/platforms/cocoa/qcocoawindow.h index 67fabadb01..750e3a0648 100644 --- a/src/plugins/platforms/cocoa/qcocoawindow.h +++ b/src/plugins/platforms/cocoa/qcocoawindow.h @@ -189,7 +189,7 @@ protected: void recreateWindowIfNeeded(); QCocoaNSWindow *createNSWindow(bool shouldBePanel); - Qt::WindowState windowState() const; + Qt::WindowStates windowState() const; void applyWindowState(Qt::WindowStates newState); void toggleMaximized(); void toggleFullScreen(); @@ -215,6 +215,10 @@ public: // for QNSView void handleWindowStateChanged(HandleFlags flags = NoHandleFlags); void handleExposeEvent(const QRegion ®ion); + static void closeAllPopups(); + static void setupPopupMonitor(); + static void removePopupMonitor(); + NSView *m_view; QCocoaNSWindow *m_nsWindow; @@ -254,6 +258,9 @@ public: // for QNSView QHash<quintptr, BorderRange> m_contentBorderAreas; // identifier -> uppper/lower QHash<quintptr, bool> m_enabledContentBorderAreas; // identifier -> enabled state (true/false) + static inline id s_globalMouseMonitor = 0; + static inline id s_applicationActivationObserver = 0; + #if QT_CONFIG(vulkan) VkSurfaceKHR m_vulkanSurface = nullptr; #endif diff --git a/src/plugins/platforms/cocoa/qcocoawindow.mm b/src/plugins/platforms/cocoa/qcocoawindow.mm index ef94b49736..8a1aee3ad4 100644 --- a/src/plugins/platforms/cocoa/qcocoawindow.mm +++ b/src/plugins/platforms/cocoa/qcocoawindow.mm @@ -526,9 +526,11 @@ NSUInteger QCocoaWindow::windowStyleMask(Qt::WindowFlags flags) // working (for example minimizing frameless windows, or resizing // windows that don't have zoom or fullscreen titlebar buttons). styleMask |= NSWindowStyleMaskClosable - | NSWindowStyleMaskResizable | NSWindowStyleMaskMiniaturizable; + if (type != Qt::Popup) // We only care about popups exactly. + styleMask |= NSWindowStyleMaskResizable; + if (type == Qt::Tool) styleMask |= NSWindowStyleMaskUtilityWindow; @@ -658,7 +660,7 @@ void QCocoaWindow::applyWindowState(Qt::WindowStates requestedState) if (!isContentView()) return; - const Qt::WindowState currentState = windowState(); + const Qt::WindowState currentState = QWindowPrivate::effectiveState(windowState()); const Qt::WindowState newState = QWindowPrivate::effectiveState(requestedState); if (newState == currentState) @@ -690,9 +692,10 @@ void QCocoaWindow::applyWindowState(Qt::WindowStates requestedState) switch (currentState) { case Qt::WindowMinimized: [nsWindow deminiaturize:sender]; - Q_ASSERT_X(windowState() != Qt::WindowMinimized, "QCocoaWindow", - "[NSWindow deminiaturize:] is synchronous"); - break; + // Deminiaturizing is not synchronous, so we need to wait for the + // NSWindowDidMiniaturizeNotification before continuing to apply + // the new state. + return; case Qt::WindowFullScreen: { toggleFullScreen(); // Exiting fullscreen is not synchronous, so we need to wait for the @@ -726,23 +729,27 @@ void QCocoaWindow::applyWindowState(Qt::WindowStates requestedState) } } -Qt::WindowState QCocoaWindow::windowState() const +Qt::WindowStates QCocoaWindow::windowState() const { - // FIXME: Support compound states (Qt::WindowStates) - + Qt::WindowStates states = Qt::WindowNoState; NSWindow *window = m_view.window; + if (window.miniaturized) - return Qt::WindowMinimized; - if (window.qt_fullScreen) - return Qt::WindowFullScreen; - if ((window.zoomed && !isTransitioningToFullScreen()) - || (m_lastReportedWindowState == Qt::WindowMaximized && isTransitioningToFullScreen())) - return Qt::WindowMaximized; + states |= Qt::WindowMinimized; + + // Full screen and maximized are mutually exclusive, as macOS + // will report a full screen window as zoomed. + if (window.qt_fullScreen) { + states |= Qt::WindowFullScreen; + } else if ((window.zoomed && !isTransitioningToFullScreen()) + || (m_lastReportedWindowState == Qt::WindowMaximized && isTransitioningToFullScreen())) { + states |= Qt::WindowMaximized; + } // Note: We do not report Qt::WindowActive, even if isActive() // is true, as QtGui does not expect this window state to be set. - return Qt::WindowNoState; + return states; } void QCocoaWindow::toggleMaximized() @@ -858,12 +865,20 @@ void QCocoaWindow::windowDidDeminiaturize() if (!isContentView()) return; + Qt::WindowState requestedState = window()->windowState(); + handleWindowStateChanged(); + + if (requestedState != windowState() && requestedState != Qt::WindowMinimized) { + // We were only going out of minimized as an intermediate step before + // progressing into the final step, so re-sync the desired state. + applyWindowState(requestedState); + } } void QCocoaWindow::handleWindowStateChanged(HandleFlags flags) { - Qt::WindowState currentState = windowState(); + Qt::WindowStates currentState = windowState(); if (!(flags & HandleUnconditionally) && currentState == m_lastReportedWindowState) return; @@ -1226,7 +1241,7 @@ void QCocoaWindow::windowDidResignKey() // Make sure popups are closed before we deliver activation changes, which are // otherwise ignored by QApplication. - QGuiApplicationPrivate::instance()->closeAllPopups(); + closeAllPopups(); // The current key window will be non-nil if another window became key. If that // window is a Qt window, we delay the window activation event until the didBecomeKey @@ -1573,6 +1588,75 @@ void QCocoaWindow::requestActivateWindow() [m_view.window makeKeyWindow]; } +/* + Closes all popups, and removes observers and monitors. +*/ +void QCocoaWindow::closeAllPopups() +{ + QGuiApplicationPrivate::instance()->closeAllPopups(); + + removePopupMonitor(); +} + +void QCocoaWindow::removePopupMonitor() +{ + if (s_globalMouseMonitor) { + [NSEvent removeMonitor:s_globalMouseMonitor]; + s_globalMouseMonitor = nil; + } + if (s_applicationActivationObserver) { + [[[NSWorkspace sharedWorkspace] notificationCenter] removeObserver:s_applicationActivationObserver]; + s_applicationActivationObserver = nil; + } +} + +void QCocoaWindow::setupPopupMonitor() +{ + // we open a popup window while we are not active. None of our existing event + // handlers will get called if the user now clicks anywhere outside the application + // or activates another window. Use a global event monitor to watch for mouse + // presses, and close popups. We also want mouse tracking in the popup to work, so + // also watch for MouseMoved. + if (!s_globalMouseMonitor) { + // we only get LeftMouseDown events when we also set LeftMouseUp. + constexpr NSEventMask mouseButtonMask = NSEventTypeLeftMouseDown | NSEventTypeLeftMouseUp + | NSEventMaskRightMouseDown | NSEventMaskOtherMouseDown + | NSEventMaskMouseMoved; + s_globalMouseMonitor = [NSEvent addGlobalMonitorForEventsMatchingMask:mouseButtonMask + handler:^(NSEvent *e){ + if (!QGuiApplicationPrivate::instance()->popupActive()) { + removePopupMonitor(); + return; + } + const auto eventType = cocoaEvent2QtMouseEvent(e); + if (eventType == QEvent::MouseMove) { + if (s_windowUnderMouse) { + QWindow *window = s_windowUnderMouse->window(); + const auto button = cocoaButton2QtButton(e); + const auto buttons = currentlyPressedMouseButtons(); + const auto globalPoint = QCocoaScreen::mapFromNative(NSEvent.mouseLocation); + const auto localPoint = window->mapFromGlobal(globalPoint.toPoint()); + QWindowSystemInterface::handleMouseEvent(window, localPoint, globalPoint, + buttons, button, eventType); + } + } else { + closeAllPopups(); + } + }]; + } + // The activation observer also gets called when we become active because the user clicks + // into the popup. This should not close the popup, so QCocoaApplicationDelegate's + // applicationDidBecomeActive implementation removes this observer. + if (!s_applicationActivationObserver) { + s_applicationActivationObserver = [[[NSWorkspace sharedWorkspace] notificationCenter] + addObserverForName:NSWorkspaceDidActivateApplicationNotification + object:nil queue:nil + usingBlock:^(NSNotification *){ + closeAllPopups(); + }]; + } +} + QCocoaNSWindow *QCocoaWindow::createNSWindow(bool shouldBePanel) { QMacAutoReleasePool pool; @@ -1678,6 +1762,8 @@ QCocoaNSWindow *QCocoaWindow::createNSWindow(bool shouldBePanel) if ((type & Qt::Popup) == Qt::Popup) { nsWindow.hasShadow = YES; nsWindow.animationBehavior = NSWindowAnimationBehaviorUtilityWindow; + if (QGuiApplication::applicationState() != Qt::ApplicationActive) + setupPopupMonitor(); } } diff --git a/src/plugins/platforms/cocoa/qnsview.mm b/src/plugins/platforms/cocoa/qnsview.mm index dd0f1d6511..c45d906926 100644 --- a/src/plugins/platforms/cocoa/qnsview.mm +++ b/src/plugins/platforms/cocoa/qnsview.mm @@ -100,6 +100,7 @@ QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSViewMouseMoveHelper); // Keys bool m_lastKeyDead; bool m_sendKeyEvent; + bool m_sendKeyEventWithoutText; NSEvent *m_currentlyInterpretedKeyEvent; QSet<quint32> m_acceptedKeyDowns; diff --git a/src/plugins/platforms/cocoa/qnsview_complextext.mm b/src/plugins/platforms/cocoa/qnsview_complextext.mm index 4be8126299..cdeb7154fe 100644 --- a/src/plugins/platforms/cocoa/qnsview_complextext.mm +++ b/src/plugins/platforms/cocoa/qnsview_complextext.mm @@ -112,9 +112,14 @@ KeyEvent newlineEvent(m_currentlyInterpretedKeyEvent ? m_currentlyInterpretedKeyEvent : NSApp.currentEvent); newlineEvent.type = QEvent::KeyPress; - newlineEvent.key = Qt::Key_Return; - newlineEvent.text = QLatin1Char(kReturnCharCode); - newlineEvent.nativeVirtualKey = kVK_Return; + + const bool isEnter = newlineEvent.modifiers & Qt::KeypadModifier; + newlineEvent.key = isEnter ? Qt::Key_Enter : Qt::Key_Return; + newlineEvent.text = isEnter ? QLatin1Char(kEnterCharCode) + : QLatin1Char(kReturnCharCode); + newlineEvent.nativeVirtualKey = isEnter ? kVK_ANSI_KeypadEnter + : kVK_Return; + qCDebug(lcQpaKeys) << "Inserting newline via" << newlineEvent; newlineEvent.sendWindowSystemEvent(m_platformWindow->window()); } @@ -362,8 +367,20 @@ // pass the originating key event up the responder chain if applicable. qCDebug(lcQpaKeys) << "Trying to perform command" << selector; - if (![self tryToPerform:selector with:self]) + if (![self tryToPerform:selector with:self]) { m_sendKeyEvent = true; + + if (![NSStringFromSelector(selector) hasPrefix:@"insert"]) { + // The text input system determined that the key event was not + // meant for text insertion, and instead asked us to treat it + // as a (possibly noop) command. This typically happens for key + // events with either ⌘ or ⌃, function keys such as F1-F35, + // arrow keys, etc. We reflect that when sending the key event + // later on, by removing the text from the event, so that the + // event does not result in text insertion on the client side. + m_sendKeyEventWithoutText = true; + } + } } // ------------- Various text properties ------------- diff --git a/src/plugins/platforms/cocoa/qnsview_keys.mm b/src/plugins/platforms/cocoa/qnsview_keys.mm index dc669d6f3b..7619ecbc4b 100644 --- a/src/plugins/platforms/cocoa/qnsview_keys.mm +++ b/src/plugins/platforms/cocoa/qnsview_keys.mm @@ -3,6 +3,33 @@ // This file is included from qnsview.mm, and only used to organize the code +/* + Determines if the text represents one of the "special keys" on macOS + + As a legacy from OpenStep, macOS reserves the range 0xF700-0xF8FF of the + Unicode private use area for representing function keys on the keyboard: + + http://www.unicode.org/Public/MAPPINGS/VENDORS/APPLE/CORPCHAR.TXT + + https://developer.apple.com/documentation/appkit/nsevent/specialkey + + These code points are not supposed to have any glyphs associated with them, + but since we can't guarantee that the system doesn't have a font that does + provide glyphs for this range (Arial Unicode MS e.g.) we need to filter + the text of our key events up front. +*/ +static bool isSpecialKey(const QString &text) +{ + if (text.length() != 1) + return false; + + const char16_t unicode = text.at(0).unicode(); + if (unicode >= 0xF700 && unicode <= 0xF8FF) + return true; + + return false; +} + @implementation QNSView (Keys) - (bool)handleKeyEvent:(NSEvent *)nsevent @@ -16,6 +43,12 @@ // We will send a key event unless the input method handles it QBoolBlocker sendKeyEventGuard(m_sendKeyEvent, true); + // Assume we should send key events with text, unless told + // otherwise by doCommandBySelector. + m_sendKeyEventWithoutText = false; + + bool didInterpretKeyEvent = false; + if (keyEvent.type == QEvent::KeyPress) { if (m_composingText.isEmpty()) { @@ -63,6 +96,7 @@ m_currentlyInterpretedKeyEvent = nsevent; [self interpretKeyEvents:@[nsevent]]; m_currentlyInterpretedKeyEvent = 0; + didInterpretKeyEvent = true; // If the last key we sent was dead, then pass the next // key to the IM as well to complete composition. @@ -76,6 +110,10 @@ bool accepted = true; if (m_sendKeyEvent && m_composingText.isEmpty()) { KeyEvent keyEvent(nsevent); + // Trust text input system on whether to send the event with text or not, + // or otherwise apply heuristics to filter out private use symbols. + if (didInterpretKeyEvent ? m_sendKeyEventWithoutText : isSpecialKey(keyEvent.text)) + keyEvent.text = {}; qCDebug(lcQpaKeys) << "Sending as" << keyEvent; accepted = keyEvent.sendWindowSystemEvent(window); } @@ -207,9 +245,16 @@ KeyEvent::KeyEvent(NSEvent *nsevent) default: break; // Must be manually set } - if (nsevent.type == NSEventTypeKeyDown || nsevent.type == NSEventTypeKeyUp) { + switch (nsevent.type) { + case NSEventTypeKeyDown: + case NSEventTypeKeyUp: + case NSEventTypeFlagsChanged: nativeVirtualKey = nsevent.keyCode; + default: + break; + } + if (nsevent.type == NSEventTypeKeyDown || nsevent.type == NSEventTypeKeyUp) { NSString *charactersIgnoringModifiers = nsevent.charactersIgnoringModifiers; NSString *characters = nsevent.characters; @@ -229,11 +274,7 @@ KeyEvent::KeyEvent(NSEvent *nsevent) key = QAppleKeyMapper::fromCocoaKey(character); } - // Ignore text for the U+F700-U+F8FF range. This is used by Cocoa when - // delivering function keys (e.g. arrow keys, backspace, F1-F35, etc.) - if (!(modifiers & (Qt::ControlModifier | Qt::MetaModifier)) - && (character.unicode() < 0xf700 || character.unicode() > 0xf8ff)) - text = QString::fromNSString(characters); + text = QString::fromNSString(characters); isRepeat = nsevent.ARepeat; } diff --git a/src/plugins/platforms/cocoa/qnswindow.mm b/src/plugins/platforms/cocoa/qnswindow.mm index cce76e4b20..8d4a0617de 100644 --- a/src/plugins/platforms/cocoa/qnswindow.mm +++ b/src/plugins/platforms/cocoa/qnswindow.mm @@ -296,8 +296,13 @@ OSStatus CGSClearWindowTags(const CGSConnectionID, const CGSWindowID, int *, int // we assume that if you have translucent content, without a // frame then you intend to do all background drawing yourself. const QWindow *window = m_platformWindow ? m_platformWindow->window() : nullptr; - if (!self.opaque && window && window->flags().testFlag(Qt::FramelessWindowHint)) - return [NSColor clearColor]; + if (!self.opaque && window) { + // Qt::Popup also requires clearColor - in qmacstyle + // we fill background using a special path with rounded corners. + if (window->flags().testFlag(Qt::FramelessWindowHint) + || (window->flags() & Qt::WindowType_Mask) == Qt::Popup) + return [NSColor clearColor]; + } // This still allows you to have translucent content with a frame, // where the system background (or color set via NSWindow) will |