summaryrefslogtreecommitdiffstats
path: root/src/plugins/platforms/cocoa/qcocoasystemtrayicon.mm
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/platforms/cocoa/qcocoasystemtrayicon.mm')
-rw-r--r--src/plugins/platforms/cocoa/qcocoasystemtrayicon.mm354
1 files changed, 112 insertions, 242 deletions
diff --git a/src/plugins/platforms/cocoa/qcocoasystemtrayicon.mm b/src/plugins/platforms/cocoa/qcocoasystemtrayicon.mm
index 559188aa5f..cec8301cf6 100644
--- a/src/plugins/platforms/cocoa/qcocoasystemtrayicon.mm
+++ b/src/plugins/platforms/cocoa/qcocoasystemtrayicon.mm
@@ -1,42 +1,6 @@
-/****************************************************************************
-**
-** Copyright (C) 2016 The Qt Company Ltd.
-** Copyright (C) 2012 Klaralvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Christoph Schleifenbaum <christoph.schleifenbaum@kdab.com>
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of the plugins of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:LGPL$
-** Commercial License Usage
-** Licensees holding valid commercial Qt licenses may use this file in
-** accordance with the commercial license agreement provided with the
-** Software or, alternatively, in accordance with the terms contained in
-** a written agreement between you and The Qt Company. For licensing terms
-** and conditions see https://www.qt.io/terms-conditions. For further
-** information use the contact form at https://www.qt.io/contact-us.
-**
-** GNU Lesser General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU Lesser
-** General Public License version 3 as published by the Free Software
-** Foundation and appearing in the file LICENSE.LGPL3 included in the
-** packaging of this file. Please review the following information to
-** ensure the GNU Lesser General Public License version 3 requirements
-** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 2.0 or (at your option) the GNU General
-** Public license version 3 or any later version approved by the KDE Free
-** Qt Foundation. The licenses are as published by the Free Software
-** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
-** included in the packaging of this file. Please review the following
-** information to ensure the GNU General Public License requirements will
-** be met: https://www.gnu.org/licenses/gpl-2.0.html and
-** https://www.gnu.org/licenses/gpl-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
+// Copyright (C) 2016 The Qt Company Ltd.
+// Copyright (C) 2012 Klaralvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Christoph Schleifenbaum <christoph.schleifenbaum@kdab.com>
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
/****************************************************************************
**
@@ -72,6 +36,8 @@
**
****************************************************************************/
+#include <AppKit/AppKit.h>
+
#include "qcocoasystemtrayicon.h"
#ifndef QT_NO_SYSTEMTRAYICON
@@ -83,76 +49,59 @@
#include <QtCore/private/qcore_mac_p.h>
#include "qcocoamenu.h"
+#include "qcocoansmenu.h"
-#include "qt_mac_p.h"
#include "qcocoahelpers.h"
#include "qcocoaintegration.h"
#include "qcocoascreen.h"
#include <QtGui/private/qcoregraphics_p.h>
-#import <AppKit/AppKit.h>
-
-QT_USE_NAMESPACE
+#warning NSUserNotification was deprecated in macOS 11. \
+We should be using UserNotifications.framework instead. \
+See QTBUG-110998 for more information.
+#define NSUserNotificationCenter QT_IGNORE_DEPRECATIONS(NSUserNotificationCenter)
+#define NSUserNotification QT_IGNORE_DEPRECATIONS(NSUserNotification)
-@interface QT_MANGLE_NAMESPACE(QNSStatusItem) : NSObject <NSUserNotificationCenterDelegate>
-@property (nonatomic, assign) QCocoaMenu *menu;
-@property (nonatomic, assign) QIcon icon;
-@property (nonatomic, readonly) NSStatusItem *item;
-@property (nonatomic, readonly) QRectF geometry;
-- (instancetype)initWithSysTray:(QCocoaSystemTrayIcon *)systray;
-- (void)triggerSelector:(id)sender button:(Qt::MouseButton)mouseButton;
-- (void)doubleClickSelector:(id)sender;
-@end
+QT_BEGIN_NAMESPACE
-QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSStatusItem);
+void QCocoaSystemTrayIcon::init()
+{
+ m_statusItem = [[NSStatusBar.systemStatusBar statusItemWithLength:NSSquareStatusItemLength] retain];
-@interface QT_MANGLE_NAMESPACE(QNSImageView) : NSImageView
-@property (nonatomic, assign) BOOL down;
-@property (nonatomic, assign) QNSStatusItem *parent;
-@end
+ m_delegate = [[QStatusItemDelegate alloc] initWithSysTray:this];
-QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSImageView);
+ // 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];
+}
-QT_BEGIN_NAMESPACE
-class QSystemTrayIconSys
+void QCocoaSystemTrayIcon::cleanup()
{
-public:
- QSystemTrayIconSys(QCocoaSystemTrayIcon *sys) {
- item = [[QNSStatusItem alloc] initWithSysTray:sys];
- NSUserNotificationCenter.defaultUserNotificationCenter.delegate = item;
- }
- ~QSystemTrayIconSys() {
- [[[item item] view] setHidden: YES];
- NSUserNotificationCenter *center = NSUserNotificationCenter.defaultUserNotificationCenter;
- if (center.delegate == item)
- center.delegate = nil;
- [item release];
- }
- QNSStatusItem *item;
-};
+ NSUserNotificationCenter *center = NSUserNotificationCenter.defaultUserNotificationCenter;
+ if (center.delegate == m_delegate)
+ center.delegate = nil;
-void QCocoaSystemTrayIcon::init()
-{
- if (!m_sys)
- m_sys = new QSystemTrayIconSys(this);
+ [NSStatusBar.systemStatusBar removeStatusItem:m_statusItem];
+ [m_statusItem release];
+ m_statusItem = nil;
+
+ [m_delegate release];
+ m_delegate = nil;
}
QRect QCocoaSystemTrayIcon::geometry() const
{
- if (!m_sys)
+ if (!m_statusItem)
return QRect();
- const QRectF geom = [m_sys->item geometry];
- if (!geom.isNull())
- return geom.toRect();
- else
- return QRect();
-}
+ if (NSWindow *window = m_statusItem.button.window) {
+ if (QCocoaScreen *screen = QCocoaScreen::get(window.screen))
+ return screen->mapFromNative(window.frame).toRect();
+ }
-void QCocoaSystemTrayIcon::cleanup()
-{
- delete m_sys;
- m_sys = nullptr;
+ return QRect();
}
static bool heightCompareFunction (QSize a, QSize b) { return (a.height() < b.height()); }
@@ -165,17 +114,15 @@ static QList<QSize> sortByHeight(const QList<QSize> &sizes)
void QCocoaSystemTrayIcon::updateIcon(const QIcon &icon)
{
- if (!m_sys)
+ if (!m_statusItem)
return;
- m_sys->item.icon = icon;
-
- // The reccomended maximum title bar icon height is 18 points
+ // The recommended maximum title bar icon height is 18 points
// (device independent pixels). The menu height on past and
// current OS X versions is 22 points. Provide some future-proofing
// by deriving the icon height from the menu height.
const int padding = 4;
- const int menuHeight = [[NSStatusBar systemStatusBar] thickness];
+ const int menuHeight = NSStatusBar.systemStatusBar.thickness;
const int maxImageHeight = menuHeight - padding;
// Select pixmap based on the device pixel height. Ideally we would use
@@ -185,7 +132,7 @@ void QCocoaSystemTrayIcon::updateIcon(const QIcon &icon)
qreal devicePixelRatio = qApp->devicePixelRatio();
const int maxPixmapHeight = maxImageHeight * devicePixelRatio;
QSize selectedSize;
- Q_FOREACH (const QSize& size, sortByHeight(icon.availableSizes())) {
+ for (const QSize& size : sortByHeight(icon.availableSizes())) {
// Select a pixmap based on the height. We want the largest pixmap
// with a height smaller or equal to maxPixmapHeight. The pixmap
// may rectangular; assume it has a reasonable size. If there is
@@ -227,31 +174,49 @@ void QCocoaSystemTrayIcon::updateIcon(const QIcon &icon)
r.moveCenter(fullHeightPixmap.rect().center());
p.drawPixmap(r, pixmap);
}
+ fullHeightPixmap.setDevicePixelRatio(devicePixelRatio);
- NSImage *nsimage = static_cast<NSImage *>(qt_mac_create_nsimage(fullHeightPixmap));
+ auto *nsimage = [NSImage imageFromQImage:fullHeightPixmap.toImage()];
[nsimage setTemplate:icon.isMask()];
- [(NSImageView*)[[m_sys->item item] view] setImage: nsimage];
- [nsimage release];
+ m_statusItem.button.image = nsimage;
+ m_statusItem.button.imageScaling = NSImageScaleProportionallyDown;
}
void QCocoaSystemTrayIcon::updateMenu(QPlatformMenu *menu)
{
- if (!m_sys)
+ auto *nsMenu = menu ? static_cast<QCocoaMenu *>(menu)->nsMenu() : nil;
+ if (m_statusItem.menu == nsMenu)
return;
- m_sys->item.menu = static_cast<QCocoaMenu *>(menu);
- if (menu && [m_sys->item.menu->nsMenu() numberOfItems] > 0) {
- [[m_sys->item item] setHighlightMode:YES];
- } else {
- [[m_sys->item item] setHighlightMode:NO];
+ if (m_statusItem.menu) {
+ [NSNotificationCenter.defaultCenter removeObserver:m_delegate
+ name:NSMenuDidBeginTrackingNotification
+ object:m_statusItem.menu
+ ];
+ }
+
+ m_statusItem.menu = nsMenu;
+
+ 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)
{
- if (!m_sys)
+ if (!m_statusItem)
return;
- [[[m_sys->item item] view] setToolTip:toolTip.toNSString()];
+
+ m_statusItem.button.toolTip = toolTip.toNSString();
}
bool QCocoaSystemTrayIcon::isSystemTrayAvailable() const
@@ -267,180 +232,85 @@ bool QCocoaSystemTrayIcon::supportsMessages() const
void QCocoaSystemTrayIcon::showMessage(const QString &title, const QString &message,
const QIcon& icon, MessageIcon, int msecs)
{
- if (!m_sys)
+ if (!m_statusItem)
return;
- NSUserNotification *notification = [[NSUserNotification alloc] init];
- notification.title = [NSString stringWithUTF8String:title.toUtf8().data()];
- notification.informativeText = [NSString stringWithUTF8String:message.toUtf8().data()];
-
- if (!icon.isNull()) {
- auto *nsimage = qt_mac_create_nsimage(icon);
- [nsimage setTemplate:icon.isMask()];
- notification.contentImage = [nsimage autorelease];
- }
+ auto *notification = [[NSUserNotification alloc] init];
+ notification.title = title.toNSString();
+ notification.informativeText = message.toNSString();
+ notification.contentImage = [NSImage imageFromQIcon:icon];
NSUserNotificationCenter *center = NSUserNotificationCenter.defaultUserNotificationCenter;
- center.delegate = m_sys->item;
- [center deliverNotification:notification];
+ center.delegate = m_delegate;
+
+ [center deliverNotification:[notification autorelease]];
+
if (msecs) {
NSTimeInterval timeout = msecs / 1000.0;
[center performSelector:@selector(removeDeliveredNotification:) withObject:notification afterDelay:timeout];
}
- [notification release];
-}
-QT_END_NAMESPACE
-
-@implementation NSStatusItem (Qt)
-@end
-
-@implementation QNSImageView
-- (instancetype)initWithParent:(QNSStatusItem *)myParent {
- self = [super init];
- self.parent = myParent;
- self.down = NO;
- return self;
}
-- (void)menuTrackingDone:(NSNotification *)__unused notification
+void QCocoaSystemTrayIcon::emitActivated()
{
- self.down = NO;
+ auto *mouseEvent = NSApp.currentEvent;
- [self setNeedsDisplay:YES];
-}
-
-- (void)mousePressed:(NSEvent *)mouseEvent
-{
- self.down = YES;
- int clickCount = [mouseEvent clickCount];
- [self setNeedsDisplay:YES];
+ auto activationReason = QPlatformSystemTrayIcon::Unknown;
- if (clickCount == 2) {
- [self menuTrackingDone:nil];
- [self.parent doubleClickSelector:self];
+ if (mouseEvent.clickCount == 2) {
+ activationReason = QPlatformSystemTrayIcon::DoubleClick;
} else {
- [self.parent triggerSelector:self button:cocoaButton2QtButton(mouseEvent)];
+ auto mouseButton = cocoaButton2QtButton(mouseEvent);
+ if (mouseButton == Qt::MiddleButton)
+ activationReason = QPlatformSystemTrayIcon::MiddleClick;
+ else if (mouseButton == Qt::RightButton)
+ activationReason = QPlatformSystemTrayIcon::Context;
+ else
+ activationReason = QPlatformSystemTrayIcon::Trigger;
}
-}
-
-- (void)mouseDown:(NSEvent *)mouseEvent
-{
- [self mousePressed:mouseEvent];
-}
-
-- (void)mouseUp:(NSEvent *)mouseEvent
-{
- Q_UNUSED(mouseEvent);
- [self menuTrackingDone:nil];
-}
-- (void)rightMouseDown:(NSEvent *)mouseEvent
-{
- [self mousePressed:mouseEvent];
+ emit activated(activationReason);
}
-- (void)rightMouseUp:(NSEvent *)mouseEvent
-{
- Q_UNUSED(mouseEvent);
- [self menuTrackingDone:nil];
-}
+QT_END_NAMESPACE
-- (void)otherMouseDown:(NSEvent *)mouseEvent
-{
- [self mousePressed:mouseEvent];
-}
+@implementation QStatusItemDelegate
-- (void)otherMouseUp:(NSEvent *)mouseEvent
+- (instancetype)initWithSysTray:(QCocoaSystemTrayIcon *)platformSystemTray
{
- Q_UNUSED(mouseEvent);
- [self menuTrackingDone:nil];
-}
+ if ((self = [super init]))
+ self.platformSystemTray = platformSystemTray;
-- (void)drawRect:(NSRect)rect {
- [[self.parent item] drawStatusBarBackgroundInRect:rect withHighlight:self.down];
- [super drawRect:rect];
-}
-@end
-
-@implementation QNSStatusItem {
- QCocoaSystemTrayIcon *systray;
- NSStatusItem *item;
- QNSImageView *imageCell;
-}
-
-@synthesize menu = menu;
-@synthesize icon = icon;
-
-- (instancetype)initWithSysTray:(QCocoaSystemTrayIcon *)sys
-{
- self = [super init];
- if (self) {
- item = [[[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength] retain];
- menu = nullptr;
- systray = sys;
- imageCell = [[QNSImageView alloc] initWithParent:self];
- [item setView: imageCell];
- }
return self;
}
-- (void)dealloc {
- [[NSStatusBar systemStatusBar] removeStatusItem:item];
- [[NSNotificationCenter defaultCenter] removeObserver:imageCell];
- imageCell.parent = nil;
- [imageCell release];
- [item release];
+- (void)dealloc
+{
+ self.platformSystemTray = nullptr;
[super dealloc];
}
-- (NSStatusItem *)item {
- return item;
-}
-
-- (QRectF)geometry {
- if (NSWindow *window = item.view.window) {
- if (QCocoaScreen *screen = QCocoaScreen::get(window.screen))
- return screen->mapFromNative(window.frame);
- }
- return QRectF();
-}
-
-- (void)triggerSelector:(id)sender button:(Qt::MouseButton)mouseButton {
- Q_UNUSED(sender);
- if (!systray)
- return;
-
- if (mouseButton == Qt::MidButton)
- emit systray->activated(QPlatformSystemTrayIcon::MiddleClick);
- else
- emit systray->activated(QPlatformSystemTrayIcon::Trigger);
-
- if (menu) {
- NSMenu *m = menu->nsMenu();
- [[NSNotificationCenter defaultCenter] addObserver:imageCell
- selector:@selector(menuTrackingDone:)
- name:NSMenuDidEndTrackingNotification
- object:m];
- [item popUpStatusItemMenu: m];
- }
+- (void)statusItemClicked
+{
+ self.platformSystemTray->emitActivated();
}
-- (void)doubleClickSelector:(id)sender {
- Q_UNUSED(sender);
- if (!systray)
- return;
- emit systray->activated(QPlatformSystemTrayIcon::DoubleClick);
+- (void)statusItemMenuBeganTracking:(NSNotification*)notification
+{
+ self.platformSystemTray->emitActivated();
}
-- (BOOL)userNotificationCenter:(NSUserNotificationCenter *)center shouldPresentNotification:(NSUserNotification *)notification {
+- (BOOL)userNotificationCenter:(NSUserNotificationCenter *)center shouldPresentNotification:(NSUserNotification *)notification
+{
Q_UNUSED(center);
Q_UNUSED(notification);
return YES;
}
-- (void)userNotificationCenter:(NSUserNotificationCenter *)center didActivateNotification:(NSUserNotification *)notification {
+- (void)userNotificationCenter:(NSUserNotificationCenter *)center didActivateNotification:(NSUserNotification *)notification
+{
[center removeDeliveredNotification:notification];
- emit systray->messageClicked();
+ emit self.platformSystemTray->messageClicked();
}
@end