summaryrefslogtreecommitdiffstats
path: root/src/plugins/platforms/cocoa
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/platforms/cocoa')
-rw-r--r--src/plugins/platforms/cocoa/qcocoaapplicationdelegate.mm5
-rw-r--r--src/plugins/platforms/cocoa/qcocoacolordialoghelper.mm12
-rw-r--r--src/plugins/platforms/cocoa/qcocoadrag.mm6
-rw-r--r--src/plugins/platforms/cocoa/qcocoafontdialoghelper.mm12
-rw-r--r--src/plugins/platforms/cocoa/qcocoahelpers.h2
-rw-r--r--src/plugins/platforms/cocoa/qcocoamenu.mm10
-rw-r--r--src/plugins/platforms/cocoa/qcocoamenubar.mm81
-rw-r--r--src/plugins/platforms/cocoa/qcocoascreen.mm4
-rw-r--r--src/plugins/platforms/cocoa/qcocoasystemtrayicon.h3
-rw-r--r--src/plugins/platforms/cocoa/qcocoasystemtrayicon.mm36
-rw-r--r--src/plugins/platforms/cocoa/qcocoatheme.mm65
-rw-r--r--src/plugins/platforms/cocoa/qcocoawindow.h9
-rw-r--r--src/plugins/platforms/cocoa/qcocoawindow.mm120
-rw-r--r--src/plugins/platforms/cocoa/qnsview.mm1
-rw-r--r--src/plugins/platforms/cocoa/qnsview_complextext.mm25
-rw-r--r--src/plugins/platforms/cocoa/qnsview_keys.mm53
-rw-r--r--src/plugins/platforms/cocoa/qnswindow.mm9
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 &region);
+ 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