diff options
Diffstat (limited to 'src/plugins/platforms/cocoa')
75 files changed, 4867 insertions, 3829 deletions
diff --git a/src/plugins/platforms/cocoa/cocoa.pro b/src/plugins/platforms/cocoa/cocoa.pro index 2694cb3607..8d65cf328f 100644 --- a/src/plugins/platforms/cocoa/cocoa.pro +++ b/src/plugins/platforms/cocoa/cocoa.pro @@ -8,7 +8,6 @@ SOURCES += main.mm \ qcocoawindow.mm \ qnsview.mm \ qnswindow.mm \ - qnsviewaccessibility.mm \ qnswindowdelegate.mm \ qcocoanativeinterface.mm \ qcocoaeventdispatcher.mm \ @@ -72,19 +71,25 @@ HEADERS += qcocoaintegration.h \ qtConfig(opengl.*) { SOURCES += qcocoaglcontext.mm - HEADERS += qcocoaglcontext.h } +qtConfig(vulkan) { + SOURCES += qcocoavulkaninstance.mm + HEADERS += qcocoavulkaninstance.h +} + RESOURCES += qcocoaresources.qrc -LIBS += -framework AppKit -framework Carbon -framework IOKit -framework QuartzCore -lcups +LIBS += -framework AppKit -framework CoreServices -framework Carbon -framework IOKit -framework QuartzCore -framework CoreVideo -framework Metal -lcups QT += \ core-private gui-private \ accessibility_support-private clipboard_support-private theme_support-private \ fontdatabase_support-private graphics_support-private +qtConfig(vulkan): QT += vulkan_support-private + CONFIG += no_app_extension_api_only qtHaveModule(widgets) { @@ -122,11 +127,6 @@ qtHaveModule(widgets) { OTHER_FILES += cocoa.json -# Acccessibility debug support -# DEFINES += QT_COCOA_ENABLE_ACCESSIBILITY_INSPECTOR -# include ($$PWD/../../../../util/accessibilityinspector/accessibilityinspector.pri) - - PLUGIN_TYPE = platforms PLUGIN_CLASS_NAME = QCocoaIntegrationPlugin !equals(TARGET, $$QT_DEFAULT_QPA_PLUGIN): PLUGIN_EXTENDS = - diff --git a/src/plugins/platforms/cocoa/main.mm b/src/plugins/platforms/cocoa/main.mm index f9bfdc7dc7..89ecdf46f9 100644 --- a/src/plugins/platforms/cocoa/main.mm +++ b/src/plugins/platforms/cocoa/main.mm @@ -60,7 +60,7 @@ QPlatformIntegration * QCocoaIntegrationPlugin::create(const QString& system, co if (system.compare(QLatin1String("cocoa"), Qt::CaseInsensitive) == 0) return new QCocoaIntegration(paramList); - return 0; + return nullptr; } QT_END_NAMESPACE diff --git a/src/plugins/platforms/cocoa/messages.cpp b/src/plugins/platforms/cocoa/messages.cpp index 8eea1e654e..06e3dd454e 100644 --- a/src/plugins/platforms/cocoa/messages.cpp +++ b/src/plugins/platforms/cocoa/messages.cpp @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2018 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the plugins of the Qt Toolkit. @@ -39,7 +39,8 @@ #include "messages.h" -#include <QCoreApplication> +#include <QtCore/qcoreapplication.h> +#include <QtCore/qregularexpression.h> // Translatable messages should go into this .cpp file for them to be picked up by lupdate. @@ -52,13 +53,13 @@ QString msgAboutQt() static const char *application_menu_strings[] = { + QT_TRANSLATE_NOOP("MAC_APPLICATION_MENU","About %1"), + QT_TRANSLATE_NOOP("MAC_APPLICATION_MENU","Preferences..."), QT_TRANSLATE_NOOP("MAC_APPLICATION_MENU","Services"), QT_TRANSLATE_NOOP("MAC_APPLICATION_MENU","Hide %1"), QT_TRANSLATE_NOOP("MAC_APPLICATION_MENU","Hide Others"), QT_TRANSLATE_NOOP("MAC_APPLICATION_MENU","Show All"), - QT_TRANSLATE_NOOP("MAC_APPLICATION_MENU","Preferences..."), - QT_TRANSLATE_NOOP("MAC_APPLICATION_MENU","Quit %1"), - QT_TRANSLATE_NOOP("MAC_APPLICATION_MENU","About %1") + QT_TRANSLATE_NOOP("MAC_APPLICATION_MENU","Quit %1") }; QString qt_mac_applicationmenu_string(int type) @@ -77,8 +78,13 @@ QPlatformMenuItem::MenuRole detectMenuRole(const QString &caption) QString captionNoAmpersand(caption); captionNoAmpersand.remove(QLatin1Char('&')); const QString aboutString = QCoreApplication::translate("QCocoaMenuItem", "About"); - if (captionNoAmpersand.startsWith(aboutString, Qt::CaseInsensitive) || caption.endsWith(aboutString, Qt::CaseInsensitive)) + if (captionNoAmpersand.startsWith(aboutString, Qt::CaseInsensitive) + || captionNoAmpersand.endsWith(aboutString, Qt::CaseInsensitive)) { + static const QRegularExpression qtRegExp(QLatin1String("qt$"), QRegularExpression::CaseInsensitiveOption); + if (captionNoAmpersand.contains(qtRegExp)) + return QPlatformMenuItem::AboutQtRole; return QPlatformMenuItem::AboutRole; + } if (captionNoAmpersand.startsWith(QCoreApplication::translate("QCocoaMenuItem", "Config"), Qt::CaseInsensitive) || captionNoAmpersand.startsWith(QCoreApplication::translate("QCocoaMenuItem", "Preference"), Qt::CaseInsensitive) || captionNoAmpersand.startsWith(QCoreApplication::translate("QCocoaMenuItem", "Options"), Qt::CaseInsensitive) diff --git a/src/plugins/platforms/cocoa/messages.h b/src/plugins/platforms/cocoa/messages.h index e41898fe59..3a9eaf604e 100644 --- a/src/plugins/platforms/cocoa/messages.h +++ b/src/plugins/platforms/cocoa/messages.h @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2018 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the plugins of the Qt Toolkit. @@ -46,13 +46,13 @@ QT_BEGIN_NAMESPACE enum { - ServicesAppMenuItem = 0, + AboutAppMenuItem = 0, + PreferencesAppMenuItem, + ServicesAppMenuItem, HideAppMenuItem, HideOthersAppMenuItem, ShowAllAppMenuItem, - PreferencesAppMenuItem, - QuitAppMenuItem, - AboutAppMenuItem + QuitAppMenuItem }; diff --git a/src/plugins/platforms/cocoa/qcocoaaccessibility.h b/src/plugins/platforms/cocoa/qcocoaaccessibility.h index e456364555..457c158ddc 100644 --- a/src/plugins/platforms/cocoa/qcocoaaccessibility.h +++ b/src/plugins/platforms/cocoa/qcocoaaccessibility.h @@ -46,6 +46,8 @@ #ifndef QT_NO_ACCESSIBILITY +@class QT_MANGLE_NAMESPACE(QMacAccessibilityElement); + QT_BEGIN_NAMESPACE class QCocoaAccessibility : public QPlatformAccessibility @@ -82,9 +84,8 @@ namespace QCocoaAccessible { NSString *macRole(QAccessibleInterface *interface); NSString *macSubrole(QAccessibleInterface *interface); bool shouldBeIgnored(QAccessibleInterface *interface); -NSArray *unignoredChildren(QAccessibleInterface *interface); +NSArray<QT_MANGLE_NAMESPACE(QMacAccessibilityElement) *> *unignoredChildren(QAccessibleInterface *interface); NSString *getTranslatedAction(const QString &qtAction); -NSMutableArray *createTranslatedActionsList(const QStringList &qtActions); QString translateAction(NSString *nsAction, QAccessibleInterface *interface); bool hasValueAttribute(QAccessibleInterface *interface); id getValueAttribute(QAccessibleInterface *interface); diff --git a/src/plugins/platforms/cocoa/qcocoaaccessibility.mm b/src/plugins/platforms/cocoa/qcocoaaccessibility.mm index 654e6210c8..368cf56c80 100644 --- a/src/plugins/platforms/cocoa/qcocoaaccessibility.mm +++ b/src/plugins/platforms/cocoa/qcocoaaccessibility.mm @@ -256,12 +256,12 @@ bool shouldBeIgnored(QAccessibleInterface *interface) return false; } -NSArray *unignoredChildren(QAccessibleInterface *interface) +NSArray<QMacAccessibilityElement *> *unignoredChildren(QAccessibleInterface *interface) { int numKids = interface->childCount(); // qDebug() << "Children for: " << axid << iface << " are: " << numKids; - NSMutableArray *kids = [NSMutableArray arrayWithCapacity:numKids]; + NSMutableArray<QMacAccessibilityElement *> *kids = [NSMutableArray<QMacAccessibilityElement *> arrayWithCapacity:numKids]; for (int i = 0; i < numKids; ++i) { QAccessibleInterface *child = interface->child(i); if (!child || !child->isValid() || child->state().invalid || child->state().invisible) @@ -310,7 +310,7 @@ NSString *getTranslatedAction(const QString &qtAction) // NSAccessibilityCancelAction; // NSAccessibilityDeleteAction; - return 0; + return nil; } @@ -386,7 +386,7 @@ id getValueAttribute(QAccessibleInterface *interface) } if (interface->state().checkable) { - return [NSNumber numberWithInt: (interface->state().checked ? 1 : 0)]; + return interface->state().checked ? @(1) : @(0); } return nil; diff --git a/src/plugins/platforms/cocoa/qcocoaaccessibilityelement.h b/src/plugins/platforms/cocoa/qcocoaaccessibilityelement.h index 07f3f3a99e..914aaa2b1b 100644 --- a/src/plugins/platforms/cocoa/qcocoaaccessibilityelement.h +++ b/src/plugins/platforms/cocoa/qcocoaaccessibilityelement.h @@ -52,13 +52,10 @@ @class QT_MANGLE_NAMESPACE(QMacAccessibilityElement); -@interface QT_MANGLE_NAMESPACE(QMacAccessibilityElement) : NSObject { - NSString *role; - QAccessible::Id axid; -} +@interface QT_MANGLE_NAMESPACE(QMacAccessibilityElement) : NSObject -- (id)initWithId:(QAccessible::Id)anId; -+ (QT_MANGLE_NAMESPACE(QMacAccessibilityElement) *)elementWithId:(QAccessible::Id)anId; +- (instancetype)initWithId:(QAccessible::Id)anId; ++ (instancetype)elementWithId:(QAccessible::Id)anId; @end diff --git a/src/plugins/platforms/cocoa/qcocoaaccessibilityelement.mm b/src/plugins/platforms/cocoa/qcocoaaccessibilityelement.mm index df2336d08b..03dc895ffb 100644 --- a/src/plugins/platforms/cocoa/qcocoaaccessibilityelement.mm +++ b/src/plugins/platforms/cocoa/qcocoaaccessibilityelement.mm @@ -99,9 +99,12 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of *end = curEnd; } -@implementation QMacAccessibilityElement +@implementation QMacAccessibilityElement { + NSString *role; + QAccessible::Id axid; +} -- (id)initWithId:(QAccessible::Id)anId +- (instancetype)initWithId:(QAccessible::Id)anId { Q_ASSERT((int)anId < 0); self = [super init]; @@ -115,7 +118,7 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of return self; } -+ (id)elementWithId:(QAccessible::Id)anId ++ (instancetype)elementWithId:(QAccessible::Id)anId { Q_ASSERT(anId); if (!anId) @@ -168,22 +171,15 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of { QStringRef textBefore = QStringRef(&text, 0, index); int newlines = textBefore.count(QLatin1Char('\n')); - return [NSNumber numberWithInt: newlines]; + return @(newlines); } - (BOOL) accessibilityNotifiesWhenDestroyed { return YES; } -- (NSArray *)accessibilityAttributeNames { - static NSArray *defaultAttributes = nil; - - QAccessibleInterface *iface = QAccessible::accessibleInterface(axid); - if (!iface || !iface->isValid()) - return defaultAttributes; - - if (defaultAttributes == nil) { - defaultAttributes = [[NSArray alloc] initWithObjects: +- (NSArray<NSString *> *)accessibilityAttributeNames { + static NSArray<NSString *> *defaultAttributes = [@[ NSAccessibilityRoleAttribute, NSAccessibilityRoleDescriptionAttribute, NSAccessibilitySubroleAttribute, @@ -196,35 +192,36 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of NSAccessibilitySizeAttribute, NSAccessibilityTitleAttribute, NSAccessibilityDescriptionAttribute, - NSAccessibilityEnabledAttribute, - nil]; - } + NSAccessibilityEnabledAttribute + ] retain]; + + QAccessibleInterface *iface = QAccessible::accessibleInterface(axid); + if (!iface || !iface->isValid()) + return defaultAttributes; - NSMutableArray *attributes = [[NSMutableArray alloc] initWithCapacity : [defaultAttributes count]]; - [attributes addObjectsFromArray : defaultAttributes]; + NSMutableArray<NSString *> *attributes = [[NSMutableArray<NSString *> alloc] initWithCapacity:defaultAttributes.count]; + [attributes addObjectsFromArray:defaultAttributes]; if (QCocoaAccessible::hasValueAttribute(iface)) { - [attributes addObject : NSAccessibilityValueAttribute]; + [attributes addObject:NSAccessibilityValueAttribute]; } if (iface->textInterface()) { - [attributes addObjectsFromArray: [[NSArray alloc] initWithObjects: + [attributes addObjectsFromArray:@[ NSAccessibilityNumberOfCharactersAttribute, NSAccessibilitySelectedTextAttribute, NSAccessibilitySelectedTextRangeAttribute, NSAccessibilityVisibleCharacterRangeAttribute, - NSAccessibilityInsertionPointLineNumberAttribute, - nil + NSAccessibilityInsertionPointLineNumberAttribute ]]; // TODO: multi-selection: NSAccessibilitySelectedTextRangesAttribute, } if (iface->valueInterface()) { - [attributes addObjectsFromArray: [[NSArray alloc] initWithObjects: + [attributes addObjectsFromArray:@[ NSAccessibilityMinValueAttribute, - NSAccessibilityMaxValueAttribute, - nil + NSAccessibilityMaxValueAttribute ]]; } @@ -261,13 +258,13 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of - (id) minValueAttribute:(QAccessibleInterface*)iface { if (QAccessibleValueInterface *val = iface->valueInterface()) - return [NSNumber numberWithDouble: val->minimumValue().toDouble()]; + return @(val->minimumValue().toDouble()); return nil; } - (id) maxValueAttribute:(QAccessibleInterface*)iface { if (QAccessibleValueInterface *val = iface->valueInterface()) - return [NSNumber numberWithDouble: val->maximumValue().toDouble()]; + return @(val->maximumValue().toDouble()); return nil; } @@ -289,7 +286,7 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of } else if ([attribute isEqualToString:NSAccessibilityFocusedAttribute]) { // Just check if the app thinks we're focused. id focusedElement = [NSApp accessibilityAttributeValue:NSAccessibilityFocusedUIElementAttribute]; - return [NSNumber numberWithBool:[focusedElement isEqual:self]]; + return @([focusedElement isEqual:self]); } else if ([attribute isEqualToString:NSAccessibilityParentAttribute]) { return NSAccessibilityUnignoredAncestor([self parentElement]); } else if ([attribute isEqualToString:NSAccessibilityWindowAttribute]) { @@ -312,7 +309,7 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of } else if ([attribute isEqualToString:NSAccessibilityDescriptionAttribute]) { return iface->text(QAccessible::Description).toNSString(); } else if ([attribute isEqualToString:NSAccessibilityEnabledAttribute]) { - return [NSNumber numberWithBool:!iface->state().disabled]; + return @(!iface->state().disabled); } else if ([attribute isEqualToString:NSAccessibilityValueAttribute]) { // VoiceOver asks for the value attribute for all elements. Return nil // if we don't want the element to have a value attribute. @@ -323,7 +320,7 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of } else if ([attribute isEqualToString:NSAccessibilityNumberOfCharactersAttribute]) { if (QAccessibleTextInterface *text = iface->textInterface()) - return [NSNumber numberWithInt: text->characterCount()]; + return @(text->characterCount()); return nil; } else if ([attribute isEqualToString:NSAccessibilitySelectedTextAttribute]) { if (QAccessibleTextInterface *text = iface->textInterface()) { @@ -357,7 +354,7 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of int position = text->cursorPosition(); convertLineOffset(text, &line, &position); } - return [NSNumber numberWithInt: line]; + return @(line); } return nil; } else if ([attribute isEqualToString:NSAccessibilityMinValueAttribute]) { @@ -378,18 +375,17 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of } if (iface->textInterface()) { - return [[NSArray alloc] initWithObjects: - NSAccessibilityStringForRangeParameterizedAttribute, - NSAccessibilityLineForIndexParameterizedAttribute, - NSAccessibilityRangeForLineParameterizedAttribute, - NSAccessibilityRangeForPositionParameterizedAttribute, -// NSAccessibilityRangeForIndexParameterizedAttribute, - NSAccessibilityBoundsForRangeParameterizedAttribute, -// NSAccessibilityRTFForRangeParameterizedAttribute, - NSAccessibilityStyleRangeForIndexParameterizedAttribute, - NSAccessibilityAttributedStringForRangeParameterizedAttribute, - nil - ]; + return @[ + NSAccessibilityStringForRangeParameterizedAttribute, + NSAccessibilityLineForIndexParameterizedAttribute, + NSAccessibilityRangeForLineParameterizedAttribute, + NSAccessibilityRangeForPositionParameterizedAttribute, +// NSAccessibilityRangeForIndexParameterizedAttribute, + NSAccessibilityBoundsForRangeParameterizedAttribute, +// NSAccessibilityRTFForRangeParameterizedAttribute, + NSAccessibilityStyleRangeForIndexParameterizedAttribute, + NSAccessibilityAttributedStringForRangeParameterizedAttribute + ]; } return nil; @@ -416,7 +412,7 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of return nil; int line = -1; convertLineOffset(iface->textInterface(), &line, &index); - return [NSNumber numberWithInt:line]; + return @(line); } if ([attribute isEqualToString: NSAccessibilityRangeForLineParameterizedAttribute]) { int line = [parameter intValue]; @@ -573,7 +569,7 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of return NSAccessibilityUnignoredAncestor(self); // find the deepest child at the point - QAccessibleInterface *childOfChildInterface = 0; + QAccessibleInterface *childOfChildInterface = nullptr; do { childOfChildInterface = childInterface->childAt(screenPoint.x(), screenPoint.y()); if (childOfChildInterface && childOfChildInterface->isValid()) diff --git a/src/plugins/platforms/cocoa/qcocoaapplication.h b/src/plugins/platforms/cocoa/qcocoaapplication.h index 66a92686bc..15530d8281 100644 --- a/src/plugins/platforms/cocoa/qcocoaapplication.h +++ b/src/plugins/platforms/cocoa/qcocoaapplication.h @@ -89,8 +89,7 @@ #import <AppKit/AppKit.h> -@interface QT_MANGLE_NAMESPACE(QNSApplication) : NSApplication { -} +@interface QT_MANGLE_NAMESPACE(QNSApplication) : NSApplication @end QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSApplication); diff --git a/src/plugins/platforms/cocoa/qcocoaapplication.mm b/src/plugins/platforms/cocoa/qcocoaapplication.mm index d0f27795b6..340191622a 100644 --- a/src/plugins/platforms/cocoa/qcocoaapplication.mm +++ b/src/plugins/platforms/cocoa/qcocoaapplication.mm @@ -113,11 +113,11 @@ static const QByteArray q_macLocalEventType = QByteArrayLiteral("mac_generic_NSE static bool qt_filterEvent(NSEvent *event) { if (qApp && qApp->eventDispatcher()-> - filterNativeEvent(q_macLocalEventType, static_cast<void*>(event), 0)) + filterNativeEvent(q_macLocalEventType, static_cast<void*>(event), nullptr)) return true; - if ([event type] == NSApplicationDefined) { - switch (static_cast<short>([event subtype])) { + if (event.type == NSEventTypeApplicationDefined) { + switch (static_cast<short>(event.subtype)) { case QtCocoaEventSubTypePostMessage: qt_sendPostedMessage(event); return true; @@ -137,7 +137,7 @@ static void qt_maybeSendKeyEquivalentUpEvent(NSEvent *event) // and forward the key event to the key (focus) window. // However, non-Qt windows will not (and should not) get // any special treatment, only QWindow-owned NSWindows. - if (event.type == NSKeyUp && (event.modifierFlags & NSCommandKeyMask)) { + if (event.type == NSEventTypeKeyUp && (event.modifierFlags & NSEventModifierFlagCommand)) { NSWindow *targetWindow = event.window; if ([targetWindow.class conformsToProtocol:@protocol(QNSWindowProtocol)]) [targetWindow sendEvent:event]; diff --git a/src/plugins/platforms/cocoa/qcocoaapplicationdelegate.h b/src/plugins/platforms/cocoa/qcocoaapplicationdelegate.h index 59c71017e3..0816730c54 100644 --- a/src/plugins/platforms/cocoa/qcocoaapplicationdelegate.h +++ b/src/plugins/platforms/cocoa/qcocoaapplicationdelegate.h @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2018 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the plugins of the Qt Toolkit. @@ -90,20 +90,18 @@ #include <qglobal.h> #include <private/qcore_mac_p.h> -@class QT_MANGLE_NAMESPACE(QCocoaMenuLoader); +Q_FORWARD_DECLARE_OBJC_CLASS(QT_MANGLE_NAMESPACE(QCocoaNSMenuItem)); -@interface QT_MANGLE_NAMESPACE(QCocoaApplicationDelegate) : NSObject <NSApplicationDelegate> { - bool startedQuit; - NSMenu *dockMenu; - NSObject <NSApplicationDelegate> *reflectionDelegate; - bool inLaunch; -} -+ (QT_MANGLE_NAMESPACE(QCocoaApplicationDelegate)*)sharedDelegate; -- (void)setDockMenu:(NSMenu *)newMenu; -- (void)setReflectionDelegate:(NSObject <NSApplicationDelegate> *)oldDelegate; -- (void)getUrl:(NSAppleEventDescriptor *)event withReplyEvent:(NSAppleEventDescriptor *)replyEvent; -- (void) removeAppleEventHandlers; -- (bool) inLaunch; +@interface QT_MANGLE_NAMESPACE(QCocoaApplicationDelegate) : NSObject <NSApplicationDelegate> +@property (nonatomic, retain) NSMenu *dockMenu; ++ (instancetype)sharedDelegate; +- (void)setReflectionDelegate:(NSObject<NSApplicationDelegate> *)oldDelegate; +- (void)removeAppleEventHandlers; +- (bool)inLaunch; +@end + +@interface QT_MANGLE_NAMESPACE(QCocoaApplicationDelegate) (MenuAPI) +- (void)qt_itemFired:(QT_MANGLE_NAMESPACE(QCocoaNSMenuItem) *)item; @end QT_NAMESPACE_ALIAS_OBJC_CLASS(QCocoaApplicationDelegate); diff --git a/src/plugins/platforms/cocoa/qcocoaapplicationdelegate.mm b/src/plugins/platforms/cocoa/qcocoaapplicationdelegate.mm index a94e0dc517..221a8b0866 100644 --- a/src/plugins/platforms/cocoa/qcocoaapplicationdelegate.mm +++ b/src/plugins/platforms/cocoa/qcocoaapplicationdelegate.mm @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2018 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the plugins of the Qt Toolkit. @@ -73,9 +73,12 @@ #import "qcocoaapplicationdelegate.h" -#import "qnswindowdelegate.h" -#import "qcocoamenuloader.h" #include "qcocoaintegration.h" +#include "qcocoamenu.h" +#include "qcocoamenuloader.h" +#include "qcocoamenuitem.h" +#include "qcocoansmenu.h" + #include <qevent.h> #include <qurl.h> #include <qdebug.h> @@ -86,7 +89,11 @@ QT_USE_NAMESPACE -@implementation QCocoaApplicationDelegate +@implementation QCocoaApplicationDelegate { + bool startedQuit; + NSObject <NSApplicationDelegate> *reflectionDelegate; + bool inLaunch; +} + (instancetype)sharedDelegate { @@ -102,7 +109,7 @@ QT_USE_NAMESPACE return shared; } -- (id)init +- (instancetype)init { self = [super init]; if (self) { @@ -125,7 +132,7 @@ QT_USE_NAMESPACE - (void)dealloc { - [dockMenu release]; + [_dockMenu release]; if (reflectionDelegate) { [[NSApplication sharedApplication] setDelegate:reflectionDelegate]; [reflectionDelegate release]; @@ -135,23 +142,16 @@ QT_USE_NAMESPACE [super dealloc]; } -- (void)setDockMenu:(NSMenu*)newMenu -{ - [newMenu retain]; - [dockMenu release]; - dockMenu = newMenu; -} - - (NSMenu *)applicationDockMenu:(NSApplication *)sender { Q_UNUSED(sender); // Manually invoke the delegate's -menuWillOpen: method. // See QTBUG-39604 (and its fix) for details. - [[dockMenu delegate] menuWillOpen:dockMenu]; - return [[dockMenu retain] autorelease]; + [self.dockMenu.delegate menuWillOpen:self.dockMenu]; + return [[self.dockMenu retain] autorelease]; } -- (BOOL) canQuit +- (BOOL)canQuit { [[NSApp mainMenu] cancelTracking]; @@ -219,7 +219,7 @@ QT_USE_NAMESPACE return NSTerminateCancel; } -- (void) applicationWillFinishLaunching:(NSNotification *)notification +- (void)applicationWillFinishLaunching:(NSNotification *)notification { Q_UNUSED(notification); @@ -249,14 +249,14 @@ QT_USE_NAMESPACE } // called by QCocoaIntegration's destructor before resetting the application delegate to nil -- (void) removeAppleEventHandlers +- (void)removeAppleEventHandlers { NSAppleEventManager *eventManager = [NSAppleEventManager sharedAppleEventManager]; [eventManager removeEventHandlerForEventClass:kCoreEventClass andEventID:kAEQuitApplication]; [eventManager removeEventHandlerForEventClass:kInternetEventClass andEventID:kAEGetURL]; } -- (bool) inLaunch +- (bool)inLaunch { return inLaunch; } @@ -267,13 +267,11 @@ QT_USE_NAMESPACE inLaunch = false; if (qEnvironmentVariableIsEmpty("QT_MAC_DISABLE_FOREGROUND_APPLICATION_TRANSFORM")) { - if (__builtin_available(macOS 10.12, *)) { - // Move the application window to front to avoid launching behind the terminal. - // Ignoring other apps is necessary (we must ignore the terminal), but makes - // Qt apps play slightly less nice with other apps when lanching from Finder - // (See the activateIgnoringOtherApps docs.) - [[NSApplication sharedApplication] activateIgnoringOtherApps:YES]; - } + // Move the application window to front to avoid launching behind the terminal. + // Ignoring other apps is necessary (we must ignore the terminal), but makes + // Qt apps play slightly less nice with other apps when lanching from Finder + // (See the activateIgnoringOtherApps docs.) + [[NSApplication sharedApplication] activateIgnoringOtherApps:YES]; } } @@ -440,3 +438,48 @@ QT_USE_NAMESPACE } @end + +@implementation QCocoaApplicationDelegate (Menus) + +- (BOOL)validateMenuItem:(NSMenuItem*)item +{ + auto *nativeItem = qt_objc_cast<QCocoaNSMenuItem *>(item); + if (!nativeItem) + return item.enabled; // FIXME Test with with Qt as plugin or embedded QWindow. + + auto *platformItem = nativeItem.platformMenuItem; + if (!platformItem) // Try a bit harder with orphan menu itens + return item.hasSubmenu || (item.enabled && (item.action != @selector(qt_itemFired:))); + + // Menu-holding items are always enabled, as it's conventional in Cocoa + if (platformItem->menu()) + return YES; + + return platformItem->isEnabled(); +} + +@end + +@implementation QCocoaApplicationDelegate (MenuAPI) + +- (void)qt_itemFired:(QCocoaNSMenuItem *)item +{ + if (item.hasSubmenu) + return; + + auto *nativeItem = qt_objc_cast<QCocoaNSMenuItem *>(item); + Q_ASSERT_X(nativeItem, qPrintable(__FUNCTION__), "Triggered menu item is not a QCocoaNSMenuItem."); + auto *platformItem = nativeItem.platformMenuItem; + // Menu-holding items also get a target to play nicely + // with NSMenuValidation but should not trigger. + if (!platformItem || platformItem->menu()) + return; + + QScopedScopeLevelCounter scopeLevelCounter(QGuiApplicationPrivate::instance()->threadData); + QGuiApplicationPrivate::modifier_buttons = [QNSView convertKeyModifiers:[NSEvent modifierFlags]]; + + static QMetaMethod activatedSignal = QMetaMethod::fromSignal(&QCocoaMenuItem::activated); + activatedSignal.invoke(platformItem, Qt::QueuedConnection); +} + +@end diff --git a/src/plugins/platforms/cocoa/qcocoabackingstore.mm b/src/plugins/platforms/cocoa/qcocoabackingstore.mm index 1343fbc45a..81a0a7d040 100644 --- a/src/plugins/platforms/cocoa/qcocoabackingstore.mm +++ b/src/plugins/platforms/cocoa/qcocoabackingstore.mm @@ -44,8 +44,6 @@ QT_BEGIN_NAMESPACE -Q_LOGGING_CATEGORY(lcCocoaBackingStore, "qt.qpa.cocoa.backingstore"); - QCocoaBackingStore::QCocoaBackingStore(QWindow *window) : QRasterBackingStore(window) { @@ -69,11 +67,6 @@ QImage::Format QCocoaBackingStore::format() const return QRasterBackingStore::format(); } -#if !QT_MACOS_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_12) -static const NSCompositingOperation NSCompositingOperationCopy = NSCompositeCopy; -static const NSCompositingOperation NSCompositingOperationSourceOver = NSCompositeSourceOver; -#endif - /*! Flushes the given \a region from the specified \a window onto the screen. @@ -101,13 +94,13 @@ void QCocoaBackingStore::flush(QWindow *window, const QRegion ®ion, const QPo QNSView *topLevelView = qnsview_cast(static_cast<QCocoaWindow *>(topLevelWindow->handle())->view()); QNSView *view = qnsview_cast(static_cast<QCocoaWindow *>(window->handle())->view()); - if (lcCocoaBackingStore().isDebugEnabled()) { + if (lcQpaBackingStore().isDebugEnabled()) { QString targetViewDescription; if (view != topLevelView) { QDebug targetDebug(&targetViewDescription); targetDebug << "onto" << topLevelView << "at" << offset; } - qCDebug(lcCocoaBackingStore) << "Flushing" << region << "of" << view << qPrintable(targetViewDescription); + qCDebug(lcQpaBackingStore) << "Flushing" << region << "of" << view << qPrintable(targetViewDescription); } // Prevent potentially costly color conversion by assigning the display color space @@ -127,118 +120,120 @@ void QCocoaBackingStore::flush(QWindow *window, const QRegion ®ion, const QPo // FIXME: Figure out if there's a way to do partial updates view.layer.contents = (__bridge id)static_cast<CGImageRef>(cgImage); if (view != topLevelView) { + const CGSize topLevelSize = topLevelView.bounds.size; view.layer.contentsRect = CGRectApplyAffineTransform( [view convertRect:view.bounds toView:topLevelView], // The contentsRect is in unit coordinate system - CGAffineTransformMakeScale(1.0 / m_image.width(), 1.0 / m_image.height())); + CGAffineTransformMakeScale(1.0 / topLevelSize.width, 1.0 / topLevelSize.height)); + } + } else { + // Normally a NSView is drawn via drawRect, as part of the display cycle in the + // main runloop, via setNeedsDisplay and friends. AppKit will lock focus on each + // individual view, starting with the top level and then traversing any subviews, + // calling drawRect for each of them. This pull model results in expose events + // sent to Qt, which result in drawing to the backingstore and flushing it. + // Qt may also decide to paint and flush the backingstore via e.g. timers, + // or other events such as mouse events, in which case we're in a push model. + // If there is no focused view, it means we're in the latter case, and need + // to manually flush the NSWindow after drawing to its graphic context. + const bool drawingOutsideOfDisplayCycle = ![NSView focusView]; + + // We also need to ensure the flushed view has focus, so that the graphics + // context is set up correctly (coordinate system, clipping, etc). Outside + // of the normal display cycle there is no focused view, as explained above, + // so we have to handle it manually. There's also a corner case inside the + // normal display cycle due to way QWidgetBackingStore composits native child + // widgets, where we'll get a flush of a native child during the drawRect of + // its parent/ancestor, and the parent/ancestor being the one locked by AppKit. + // In this case we also need to lock and unlock focus manually. + const bool shouldHandleViewLockManually = [NSView focusView] != view; + if (shouldHandleViewLockManually && ![view lockFocusIfCanDraw]) { + qWarning() << "failed to lock focus of" << view; + return; } - return; - } - - // Normally a NSView is drawn via drawRect, as part of the display cycle in the - // main runloop, via setNeedsDisplay and friends. AppKit will lock focus on each - // individual view, starting with the top level and then traversing any subviews, - // calling drawRect for each of them. This pull model results in expose events - // sent to Qt, which result in drawing to the backingstore and flushing it. - // Qt may also decide to paint and flush the backingstore via e.g. timers, - // or other events such as mouse events, in which case we're in a push model. - // If there is no focused view, it means we're in the latter case, and need - // to manually flush the NSWindow after drawing to its graphic context. - const bool drawingOutsideOfDisplayCycle = ![NSView focusView]; - - // We also need to ensure the flushed view has focus, so that the graphics - // context is set up correctly (coordinate system, clipping, etc). Outside - // of the normal display cycle there is no focused view, as explained above, - // so we have to handle it manually. There's also a corner case inside the - // normal display cycle due to way QWidgetBackingStore composits native child - // widgets, where we'll get a flush of a native child during the drawRect of - // its parent/ancestor, and the parent/ancestor being the one locked by AppKit. - // In this case we also need to lock and unlock focus manually. - const bool shouldHandleViewLockManually = [NSView focusView] != view; - if (shouldHandleViewLockManually && ![view lockFocusIfCanDraw]) { - qWarning() << "failed to lock focus of" << view; - return; - } - const qreal devicePixelRatio = m_image.devicePixelRatio(); + const qreal devicePixelRatio = m_image.devicePixelRatio(); - // If the flushed window is a content view, and we're filling the drawn area - // completely, or it doesn't have a window background we need to preserve, - // we can get away with copying instead of blending the backing store. - QCocoaWindow *cocoaWindow = static_cast<QCocoaWindow *>(window->handle()); - const NSCompositingOperation compositingOperation = cocoaWindow->isContentView() - && (cocoaWindow->isOpaque() || view.window.backgroundColor == NSColor.clearColor) - ? NSCompositingOperationCopy : NSCompositingOperationSourceOver; + // If the flushed window is a content view, and we're filling the drawn area + // completely, or it doesn't have a window background we need to preserve, + // we can get away with copying instead of blending the backing store. + QCocoaWindow *cocoaWindow = static_cast<QCocoaWindow *>(window->handle()); + const NSCompositingOperation compositingOperation = cocoaWindow->isContentView() + && (cocoaWindow->isOpaque() || view.window.backgroundColor == NSColor.clearColor) + ? NSCompositingOperationCopy : NSCompositingOperationSourceOver; #ifdef QT_DEBUG - static bool debugBackingStoreFlush = [[NSUserDefaults standardUserDefaults] - boolForKey:@"QtCocoaDebugBackingStoreFlush"]; + static bool debugBackingStoreFlush = [[NSUserDefaults standardUserDefaults] + boolForKey:@"QtCocoaDebugBackingStoreFlush"]; #endif - // ------------------------------------------------------------------------- + // ------------------------------------------------------------------------- - // The current contexts is typically a NSWindowGraphicsContext, but can be - // NSBitmapGraphicsContext e.g. when debugging the view hierarchy in Xcode. - // If we need to distinguish things here in the future, we can use e.g. - // [NSGraphicsContext drawingToScreen], or the attributes of the context. - NSGraphicsContext *graphicsContext = [NSGraphicsContext currentContext]; - Q_ASSERT_X(graphicsContext, "QCocoaBackingStore", - "Focusing the view should give us a current graphics context"); + // The current contexts is typically a NSWindowGraphicsContext, but can be + // NSBitmapGraphicsContext e.g. when debugging the view hierarchy in Xcode. + // If we need to distinguish things here in the future, we can use e.g. + // [NSGraphicsContext drawingToScreen], or the attributes of the context. + NSGraphicsContext *graphicsContext = [NSGraphicsContext currentContext]; + Q_ASSERT_X(graphicsContext, "QCocoaBackingStore", + "Focusing the view should give us a current graphics context"); - // Create temporary image to use for blitting, without copying image data - NSImage *backingStoreImage = [[[NSImage alloc] initWithCGImage:cgImage size:NSZeroSize] autorelease]; + // Create temporary image to use for blitting, without copying image data + NSImage *backingStoreImage = [[[NSImage alloc] initWithCGImage:cgImage size:NSZeroSize] autorelease]; - QRegion clippedRegion = region; - for (QWindow *w = window; w; w = w->parent()) { - if (!w->mask().isEmpty()) { - clippedRegion &= w == window ? w->mask() - : w->mask().translated(window->mapFromGlobal(w->mapToGlobal(QPoint(0, 0)))); + QRegion clippedRegion = region; + for (QWindow *w = window; w; w = w->parent()) { + if (!w->mask().isEmpty()) { + clippedRegion &= w == window ? w->mask() + : w->mask().translated(window->mapFromGlobal(w->mapToGlobal(QPoint(0, 0)))); + } } - } - for (const QRect &viewLocalRect : clippedRegion) { - QPoint backingStoreOffset = viewLocalRect.topLeft() + offset; - QRect backingStoreRect(backingStoreOffset * devicePixelRatio, viewLocalRect.size() * devicePixelRatio); - if (graphicsContext.flipped) // Flip backingStoreRect to match graphics context - backingStoreRect.moveTop(m_image.height() - (backingStoreRect.y() + backingStoreRect.height())); + for (const QRect &viewLocalRect : clippedRegion) { + QPoint backingStoreOffset = viewLocalRect.topLeft() + offset; + QRect backingStoreRect(backingStoreOffset * devicePixelRatio, viewLocalRect.size() * devicePixelRatio); + if (graphicsContext.flipped) // Flip backingStoreRect to match graphics context + backingStoreRect.moveTop(m_image.height() - (backingStoreRect.y() + backingStoreRect.height())); - CGRect viewRect = viewLocalRect.toCGRect(); + CGRect viewRect = viewLocalRect.toCGRect(); - if (windowHasUnifiedToolbar()) - NSDrawWindowBackground(viewRect); + if (windowHasUnifiedToolbar()) + NSDrawWindowBackground(viewRect); - [backingStoreImage drawInRect:viewRect fromRect:backingStoreRect.toCGRect() - operation:compositingOperation fraction:1.0 respectFlipped:YES hints:nil]; + [backingStoreImage drawInRect:viewRect fromRect:backingStoreRect.toCGRect() + operation:compositingOperation fraction:1.0 respectFlipped:YES hints:nil]; #ifdef QT_DEBUG - if (Q_UNLIKELY(debugBackingStoreFlush)) { - [[NSColor colorWithCalibratedRed:drand48() green:drand48() blue:drand48() alpha:0.3] set]; - [NSBezierPath fillRect:viewRect]; - - if (drawingOutsideOfDisplayCycle) { - [[[NSColor magentaColor] colorWithAlphaComponent:0.5] set]; - [NSBezierPath strokeLineFromPoint:viewLocalRect.topLeft().toCGPoint() - toPoint:viewLocalRect.bottomRight().toCGPoint()]; + if (Q_UNLIKELY(debugBackingStoreFlush)) { + [[NSColor colorWithCalibratedRed:drand48() green:drand48() blue:drand48() alpha:0.3] set]; + [NSBezierPath fillRect:viewRect]; + + if (drawingOutsideOfDisplayCycle) { + [[[NSColor magentaColor] colorWithAlphaComponent:0.5] set]; + [NSBezierPath strokeLineFromPoint:viewLocalRect.topLeft().toCGPoint() + toPoint:viewLocalRect.bottomRight().toCGPoint()]; + } } - } #endif + } + + // ------------------------------------------------------------------------- + + if (shouldHandleViewLockManually) + [view unlockFocus]; + + if (drawingOutsideOfDisplayCycle) { + redrawRoundedBottomCorners([view convertRect:region.boundingRect().toCGRect() toView:nil]); + [view.window flushWindow]; + } } + // Done flushing to either CALayer or NSWindow backingstore + QCocoaWindow *topLevelCocoaWindow = static_cast<QCocoaWindow *>(topLevelWindow->handle()); if (Q_UNLIKELY(topLevelCocoaWindow->m_needsInvalidateShadow)) { [topLevelView.window invalidateShadow]; topLevelCocoaWindow->m_needsInvalidateShadow = false; } - - // ------------------------------------------------------------------------- - - if (shouldHandleViewLockManually) - [view unlockFocus]; - - if (drawingOutsideOfDisplayCycle) { - redrawRoundedBottomCorners([view convertRect:region.boundingRect().toCGRect() toView:nil]); - [view.window flushWindow]; - } } /* diff --git a/src/plugins/platforms/cocoa/qcocoaclipboard.mm b/src/plugins/platforms/cocoa/qcocoaclipboard.mm index b4745900dc..a35c153084 100644 --- a/src/plugins/platforms/cocoa/qcocoaclipboard.mm +++ b/src/plugins/platforms/cocoa/qcocoaclipboard.mm @@ -56,13 +56,13 @@ QMimeData *QCocoaClipboard::mimeData(QClipboard::Mode mode) pasteBoard->sync(); return pasteBoard->mimeData(); } - return 0; + return nullptr; } void QCocoaClipboard::setMimeData(QMimeData *data, QClipboard::Mode mode) { if (QMacPasteboard *pasteBoard = pasteboardForMode(mode)) { - if (data == 0) { + if (!data) { pasteBoard->clear(); } @@ -90,7 +90,7 @@ QMacPasteboard *QCocoaClipboard::pasteboardForMode(QClipboard::Mode mode) const else if (mode == QClipboard::FindBuffer) return m_find.data(); else - return 0; + return nullptr; } void QCocoaClipboard::handleApplicationStateChanged(Qt::ApplicationState state) diff --git a/src/plugins/platforms/cocoa/qcocoacolordialoghelper.mm b/src/plugins/platforms/cocoa/qcocoacolordialoghelper.mm index aa6124507d..d7850b1481 100644 --- a/src/plugins/platforms/cocoa/qcocoacolordialoghelper.mm +++ b/src/plugins/platforms/cocoa/qcocoacolordialoghelper.mm @@ -50,7 +50,14 @@ QT_USE_NAMESPACE @interface QT_MANGLE_NAMESPACE(QNSColorPanelDelegate) : NSObject<NSWindowDelegate, QNSPanelDelegate> -{ +- (void)restoreOriginalContentView; +- (void)updateQtColor; +- (void)finishOffWithCode:(NSInteger)code; +@end + +QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSColorPanelDelegate); + +@implementation QNSColorPanelDelegate { @public NSColorPanel *mColorPanel; QCocoaColorDialogHelper *mHelper; @@ -61,22 +68,14 @@ QT_USE_NAMESPACE BOOL mDialogIsExecuting; BOOL mResultSet; BOOL mClosingDueToKnownButton; -}; -- (void)restoreOriginalContentView; -- (void)updateQtColor; -- (void)finishOffWithCode:(NSInteger)code; -@end - -QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSColorPanelDelegate); - -@implementation QNSColorPanelDelegate +} -- (id)init +- (instancetype)init { self = [super init]; mColorPanel = [NSColorPanel sharedColorPanel]; - mHelper = 0; - mStolenContentView = 0; + mHelper = nullptr; + mStolenContentView = nil; mPanelButtons = nil; mResultCode = NSModalResponseCancel; mDialogIsExecuting = false; @@ -116,9 +115,9 @@ QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSColorPanelDelegate); [self restoreOriginalContentView]; } else if (!mStolenContentView) { // steal the color panel's contents view - mStolenContentView = [mColorPanel contentView]; + mStolenContentView = mColorPanel.contentView; [mStolenContentView retain]; - [mColorPanel setContentView:0]; + mColorPanel.contentView = nil; // create a new content view and add the stolen one as a subview mPanelButtons = [[QNSPanelContentsWrapper alloc] initWithPanelDelegate:self]; @@ -310,7 +309,7 @@ public: void cleanup(QCocoaColorDialogHelper *helper) { if (mDelegate->mHelper == helper) - mDelegate->mHelper = 0; + mDelegate->mHelper = nullptr; } bool exec() diff --git a/src/plugins/platforms/cocoa/qcocoacursor.mm b/src/plugins/platforms/cocoa/qcocoacursor.mm index 8c98dc69f7..87a57c78c8 100644 --- a/src/plugins/platforms/cocoa/qcocoacursor.mm +++ b/src/plugins/platforms/cocoa/qcocoacursor.mm @@ -80,15 +80,15 @@ void QCocoaCursor::setPos(const QPoint &position) pos.x = position.x(); pos.y = position.y(); - CGEventRef e = CGEventCreateMouseEvent(0, kCGEventMouseMoved, pos, kCGMouseButtonLeft); + CGEventRef e = CGEventCreateMouseEvent(nullptr, kCGEventMouseMoved, pos, kCGMouseButtonLeft); CGEventPost(kCGHIDEventTap, e); CFRelease(e); } NSCursor *QCocoaCursor::convertCursor(QCursor *cursor) { - if (cursor == nullptr) - return 0; + if (!cursor) + return nil; const Qt::CursorShape newShape = cursor->shape(); NSCursor *cocoaCursor; @@ -138,11 +138,11 @@ NSCursor *QCocoaCursor::convertCursor(QCursor *cursor) cocoaCursor = m_cursors.value(newShape); if (cocoaCursor && cursor->shape() == Qt::BitmapCursor) { [cocoaCursor release]; - cocoaCursor = 0; + cocoaCursor = nil; } - if (cocoaCursor == 0) { + if (!cocoaCursor) { cocoaCursor = createCursorData(cursor); - if (cocoaCursor == 0) + if (!cocoaCursor) return [NSCursor arrowCursor]; m_cursors.insert(newShape, cocoaCursor); @@ -211,8 +211,8 @@ NSCursor *QCocoaCursor::createCursorData(QCursor *cursor) 0x07, 0xf0, 0x0f, 0xf8, 0x0f, 0xf8, 0x01, 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01, 0xc0 }; #endif - const uchar *cursorData = 0; - const uchar *cursorMaskData = 0; + const uchar *cursorData = nullptr; + const uchar *cursorMaskData = nullptr; QPoint hotspot = cursor->hotSpot(); switch (cursor->shape()) { @@ -269,7 +269,7 @@ NSCursor *QCocoaCursor::createCursorData(QCursor *cursor) #endif default: qWarning("Qt: QCursor::update: Invalid cursor shape %d", cursor->shape()); - return 0; + return nil; } // Create an NSCursor from image data if this a self-provided cursor. @@ -279,7 +279,7 @@ NSCursor *QCocoaCursor::createCursorData(QCursor *cursor) return (createCursorFromBitmap(&bitmap, &mask, hotspot)); } - return 0; // should not happen, all cases covered above + return nil; // should not happen, all cases covered above } NSCursor *QCocoaCursor::createCursorFromBitmap(const QBitmap *bitmap, const QBitmap *mask, const QPoint hotspot) diff --git a/src/plugins/platforms/cocoa/qcocoadrag.mm b/src/plugins/platforms/cocoa/qcocoadrag.mm index e1f36b47a1..09433194a6 100644 --- a/src/plugins/platforms/cocoa/qcocoadrag.mm +++ b/src/plugins/platforms/cocoa/qcocoadrag.mm @@ -50,10 +50,10 @@ QT_BEGIN_NAMESPACE static const int dragImageMaxChars = 26; QCocoaDrag::QCocoaDrag() : - m_drag(0) + m_drag(nullptr) { - m_lastEvent = 0; - m_lastView = 0; + m_lastEvent = nil; + m_lastView = nil; } QCocoaDrag::~QCocoaDrag() @@ -73,7 +73,7 @@ QMimeData *QCocoaDrag::dragMimeData() if (m_drag) return m_drag->mimeData(); - return 0; + return nullptr; } Qt::DropAction QCocoaDrag::defaultAction(Qt::DropActions possibleActions, @@ -140,7 +140,7 @@ Qt::DropAction QCocoaDrag::drag(QDrag *o) NSPoint event_location = [m_lastEvent locationInWindow]; NSWindow *theWindow = [m_lastEvent window]; - Q_ASSERT(theWindow != nil); + Q_ASSERT(theWindow); event_location.x -= hotSpot.x(); CGFloat flippedY = pmDeviceIndependentSize.height() - hotSpot.y(); event_location.y -= flippedY; @@ -157,7 +157,7 @@ Qt::DropAction QCocoaDrag::drag(QDrag *o) [nsimage release]; - m_drag = 0; + m_drag = nullptr; return m_executed_drop_action; } diff --git a/src/plugins/platforms/cocoa/qcocoaeventdispatcher.h b/src/plugins/platforms/cocoa/qcocoaeventdispatcher.h index 2ffc1395ba..ebf33cf4e2 100644 --- a/src/plugins/platforms/cocoa/qcocoaeventdispatcher.h +++ b/src/plugins/platforms/cocoa/qcocoaeventdispatcher.h @@ -110,8 +110,8 @@ class QCocoaEventDispatcher : public QAbstractEventDispatcher Q_DECLARE_PRIVATE(QCocoaEventDispatcher) public: - QCocoaEventDispatcher(QAbstractEventDispatcherPrivate &priv, QObject *parent = 0); - explicit QCocoaEventDispatcher(QObject *parent = 0); + QCocoaEventDispatcher(QAbstractEventDispatcherPrivate &priv, QObject *parent = nullptr); + explicit QCocoaEventDispatcher(QObject *parent = nullptr); ~QCocoaEventDispatcher(); bool processEvents(QEventLoop::ProcessEventsFlags flags); @@ -153,6 +153,7 @@ public: void maybeStopCFRunLoopTimer(); static void runLoopTimerCallback(CFRunLoopTimerRef, void *info); static void activateTimersSourceCallback(void *info); + bool processTimers(); // Set 'blockSendPostedEvents' to true if you _really_ need // to make sure that qt events are not posted while calling diff --git a/src/plugins/platforms/cocoa/qcocoaeventdispatcher.mm b/src/plugins/platforms/cocoa/qcocoaeventdispatcher.mm index bf9ba4eccf..b0f2b6d940 100644 --- a/src/plugins/platforms/cocoa/qcocoaeventdispatcher.mm +++ b/src/plugins/platforms/cocoa/qcocoaeventdispatcher.mm @@ -120,20 +120,26 @@ void QCocoaEventDispatcherPrivate::runLoopTimerCallback(CFRunLoopTimerRef, void void QCocoaEventDispatcherPrivate::activateTimersSourceCallback(void *info) { QCocoaEventDispatcherPrivate *d = static_cast<QCocoaEventDispatcherPrivate *>(info); - (void) d->timerInfoList.activateTimers(); - d->maybeStartCFRunLoopTimer(); + d->processTimers(); d->maybeCancelWaitForMoreEvents(); } +bool QCocoaEventDispatcherPrivate::processTimers() +{ + int activated = timerInfoList.activateTimers(); + maybeStartCFRunLoopTimer(); + return activated > 0; +} + void QCocoaEventDispatcherPrivate::maybeStartCFRunLoopTimer() { if (timerInfoList.isEmpty()) { // no active timers, so the CFRunLoopTimerRef should not be active either - Q_ASSERT(runLoopTimerRef == 0); + Q_ASSERT(!runLoopTimerRef); return; } - if (runLoopTimerRef == 0) { + if (!runLoopTimerRef) { // start the CFRunLoopTimer CFAbsoluteTime ttf = CFAbsoluteTimeGetCurrent(); CFTimeInterval interval; @@ -150,11 +156,11 @@ void QCocoaEventDispatcherPrivate::maybeStartCFRunLoopTimer() } ttf += interval; - CFRunLoopTimerContext info = { 0, this, 0, 0, 0 }; + CFRunLoopTimerContext info = { 0, this, nullptr, nullptr, nullptr }; // create the timer with a large interval, as recommended by the CFRunLoopTimerSetNextFireDate() // documentation, since we will adjust the timer's time-to-fire as needed to keep Qt timers working - runLoopTimerRef = CFRunLoopTimerCreate(0, ttf, oneyear, 0, 0, QCocoaEventDispatcherPrivate::runLoopTimerCallback, &info); - Q_ASSERT(runLoopTimerRef != 0); + runLoopTimerRef = CFRunLoopTimerCreate(nullptr, ttf, oneyear, 0, 0, QCocoaEventDispatcherPrivate::runLoopTimerCallback, &info); + Q_ASSERT(runLoopTimerRef); CFRunLoopAddTimer(mainRunLoop(), runLoopTimerRef, kCFRunLoopCommonModes); } else { @@ -180,12 +186,12 @@ void QCocoaEventDispatcherPrivate::maybeStartCFRunLoopTimer() void QCocoaEventDispatcherPrivate::maybeStopCFRunLoopTimer() { - if (runLoopTimerRef == 0) + if (!runLoopTimerRef) return; CFRunLoopTimerInvalidate(runLoopTimerRef); CFRelease(runLoopTimerRef); - runLoopTimerRef = 0; + runLoopTimerRef = nullptr; } void QCocoaEventDispatcher::registerTimer(int timerId, int interval, Qt::TimerType timerType, QObject *obj) @@ -293,25 +299,25 @@ static bool IsMouseOrKeyEvent( NSEvent* event ) switch( [event type] ) { - case NSLeftMouseDown: - case NSLeftMouseUp: - case NSRightMouseDown: - case NSRightMouseUp: - case NSMouseMoved: // ?? - case NSLeftMouseDragged: - case NSRightMouseDragged: - case NSMouseEntered: - case NSMouseExited: - case NSKeyDown: - case NSKeyUp: - case NSFlagsChanged: // key modifiers changed? - case NSCursorUpdate: // ?? - case NSScrollWheel: - case NSTabletPoint: - case NSTabletProximity: - case NSOtherMouseDown: - case NSOtherMouseUp: - case NSOtherMouseDragged: + case NSEventTypeLeftMouseDown: + case NSEventTypeLeftMouseUp: + case NSEventTypeRightMouseDown: + case NSEventTypeRightMouseUp: + case NSEventTypeMouseMoved: // ?? + case NSEventTypeLeftMouseDragged: + case NSEventTypeRightMouseDragged: + case NSEventTypeMouseEntered: + case NSEventTypeMouseExited: + case NSEventTypeKeyDown: + case NSEventTypeKeyUp: + case NSEventTypeFlagsChanged: // key modifiers changed? + case NSEventTypeCursorUpdate: // ?? + case NSEventTypeScrollWheel: + case NSEventTypeTabletPoint: + case NSEventTypeTabletProximity: + case NSEventTypeOtherMouseDown: + case NSEventTypeOtherMouseUp: + case NSEventTypeOtherMouseDragged: #ifndef QT_NO_GESTURES case NSEventTypeGesture: // touch events case NSEventTypeMagnify: @@ -335,7 +341,7 @@ static inline void qt_mac_waitForMoreEvents(NSString *runLoopMode = NSDefaultRun // at least one event occur. Setting 'dequeuing' to 'no' in the following call // causes it to hang under certain circumstances (QTBUG-28283), so we tell it // to dequeue instead, just to repost the event again: - NSEvent* event = [NSApp nextEventMatchingMask:NSAnyEventMask + NSEvent* event = [NSApp nextEventMatchingMask:NSEventMaskAny untilDate:[NSDate distantFuture] inMode:runLoopMode dequeue:YES]; @@ -368,13 +374,13 @@ bool QCocoaEventDispatcher::processEvents(QEventLoop::ProcessEventsFlags flags) break; QMacAutoReleasePool pool; - NSEvent* event = 0; + NSEvent* event = nil; // First, send all previously excluded input events, if any: if (!excludeUserEvents) { while (!d->queuedUserInputEvents.isEmpty()) { event = static_cast<NSEvent *>(d->queuedUserInputEvents.takeFirst()); - if (!filterNativeEvent("NSEvent", event, 0)) { + if (!filterNativeEvent("NSEvent", event, nullptr)) { [NSApp sendEvent:event]; retVal = true; } @@ -432,7 +438,7 @@ bool QCocoaEventDispatcher::processEvents(QEventLoop::ProcessEventsFlags flags) retVal = true; } else { int lastSerialCopy = d->lastSerial; - bool hadModalSession = d->currentModalSessionCached != 0; + const bool hadModalSession = d->currentModalSessionCached; // We cannot block the thread (and run in a tight loop). // Instead we will process all current pending events and return. d->ensureNSAppInitialized(); @@ -460,7 +466,7 @@ bool QCocoaEventDispatcher::processEvents(QEventLoop::ProcessEventsFlags flags) // Dispatch all non-user events (but que non-user events up for later). In // this case, we need more control over which events gets dispatched, and // cannot use [NSApp runModalSession:session]: - event = [NSApp nextEventMatchingMask:NSAnyEventMask + event = [NSApp nextEventMatchingMask:NSEventMaskAny untilDate:nil inMode:NSModalPanelRunLoopMode dequeue: YES]; @@ -471,15 +477,15 @@ bool QCocoaEventDispatcher::processEvents(QEventLoop::ProcessEventsFlags flags) d->queuedUserInputEvents.append(event); continue; } - if (!filterNativeEvent("NSEvent", event, 0)) { + if (!filterNativeEvent("NSEvent", event, nullptr)) { [NSApp sendEvent:event]; retVal = true; } } - } while (!d->interrupt && event != nil); + } while (!d->interrupt && event); } else do { // INVARIANT: No modal window is executing. - event = [NSApp nextEventMatchingMask:NSAnyEventMask + event = [NSApp nextEventMatchingMask:NSEventMaskAny untilDate:nil inMode:NSDefaultRunLoopMode dequeue: YES]; @@ -492,18 +498,17 @@ bool QCocoaEventDispatcher::processEvents(QEventLoop::ProcessEventsFlags flags) continue; } } - if (!filterNativeEvent("NSEvent", event, 0)) { + if (!filterNativeEvent("NSEvent", event, nullptr)) { [NSApp sendEvent:event]; retVal = true; } } - } while (!d->interrupt && event != nil); + } while (!d->interrupt && event); if ((d->processEventsFlags & QEventLoop::EventLoopExec) == 0) { - // when called "manually", always send posted events and timers + // When called "manually", always process posted events and timers d->processPostedEvents(); - retVal = d->timerInfoList.activateTimers() > 0 || retVal; - d->maybeStartCFRunLoopTimer(); + retVal = d->processTimers() || retVal; } // be sure to return true if the posted event source fired @@ -514,7 +519,7 @@ bool QCocoaEventDispatcher::processEvents(QEventLoop::ProcessEventsFlags flags) // event recursion to ensure that we spin the correct modal session. // We do the interruptLater at the end of the function to ensure that we don't // disturb the 'wait for more events' below (as deleteLater will post an event): - if (hadModalSession && d->currentModalSessionCached == 0) + if (hadModalSession && !d->currentModalSessionCached) interruptLater = true; } bool canWait = (d->threadData->canWait @@ -611,11 +616,11 @@ void QCocoaEventDispatcherPrivate::temporarilyStopAllModalSessions() QCocoaModalSessionInfo &info = cocoaModalSessionStack[i]; if (info.session) { [NSApp endModalSession:info.session]; - info.session = 0; + info.session = nullptr; [(NSWindow*) info.nswindow release]; } } - currentModalSessionCached = 0; + currentModalSessionCached = nullptr; } NSModalSession QCocoaEventDispatcherPrivate::currentModalSession() @@ -626,7 +631,7 @@ NSModalSession QCocoaEventDispatcherPrivate::currentModalSession() return currentModalSessionCached; if (cocoaModalSessionStack.isEmpty()) - return 0; + return nullptr; int sessionCount = cocoaModalSessionStack.size(); for (int i=0; i<sessionCount; ++i) { @@ -637,6 +642,8 @@ NSModalSession QCocoaEventDispatcherPrivate::currentModalSession() if (!info.session) { QMacAutoReleasePool pool; QCocoaWindow *cocoaWindow = static_cast<QCocoaWindow *>(info.window->handle()); + if (!cocoaWindow) + continue; NSWindow *nswindow = cocoaWindow->nativeWindow(); if (!nswindow) continue; @@ -647,6 +654,15 @@ NSModalSession QCocoaEventDispatcherPrivate::currentModalSession() [(NSWindow*) info.nswindow retain]; QRect rect = cocoaWindow->geometry(); info.session = [NSApp beginModalSessionForWindow:nswindow]; + + // The call to beginModalSessionForWindow above processes events and may + // have deleted or destroyed the window. Check if it's still valid. + if (!info.window) + continue; + cocoaWindow = static_cast<QCocoaWindow *>(info.window->handle()); + if (!cocoaWindow) + continue; + if (rect != cocoaWindow->geometry()) cocoaWindow->setGeometry(rect); } @@ -717,9 +733,9 @@ void QCocoaEventDispatcherPrivate::cleanupModalSessions() currentModalSessionCached = info.session; break; } - currentModalSessionCached = 0; + currentModalSessionCached = nullptr; if (info.session) { - Q_ASSERT(info.nswindow != 0); + Q_ASSERT(info.nswindow); [NSApp endModalSession:info.session]; [(NSWindow *)info.nswindow release]; } @@ -746,10 +762,10 @@ void QCocoaEventDispatcherPrivate::beginModalSession(QWindow *window) // currentModalSession). A QCocoaModalSessionInfo is considered pending to be stopped if // the window pointer is zero, and the session pointer is non-zero (it will be fully // stopped in cleanupModalSessions()). - QCocoaModalSessionInfo info = {window, 0, 0}; + QCocoaModalSessionInfo info = {window, nullptr, nullptr}; cocoaModalSessionStack.push(info); updateChildrenWorksWhenModal(); - currentModalSessionCached = 0; + currentModalSessionCached = nullptr; } void QCocoaEventDispatcherPrivate::endModalSession(QWindow *window) @@ -768,12 +784,12 @@ void QCocoaEventDispatcherPrivate::endModalSession(QWindow *window) if (!info.window) endedSessions++; if (info.window == window) { - info.window = 0; + info.window = nullptr; if (i + endedSessions == stackSize-1) { // The top sessions ended. Interrupt the event dispatcher to // start spinning the correct session immediately. q->interrupt(); - currentModalSessionCached = 0; + currentModalSessionCached = nullptr; cleanupModalSessionsNeeded = true; } } @@ -782,13 +798,13 @@ void QCocoaEventDispatcherPrivate::endModalSession(QWindow *window) QCocoaEventDispatcherPrivate::QCocoaEventDispatcherPrivate() : processEventsFlags(0), - runLoopTimerRef(0), + runLoopTimerRef(nullptr), blockSendPostedEvents(false), currentExecIsNSAppRun(false), nsAppRunCalledByQt(false), cleanupModalSessionsNeeded(false), processEventsCalled(0), - currentModalSessionCached(0), + currentModalSessionCached(nullptr), lastSerial(-1), interrupt(false) { @@ -925,8 +941,8 @@ void QCocoaEventDispatcherPrivate::cancelWaitForMoreEvents() // In case the event dispatcher is waiting for more // events somewhere, we post a dummy event to wake it up: QMacAutoReleasePool pool; - [NSApp postEvent:[NSEvent otherEventWithType:NSApplicationDefined location:NSZeroPoint - modifierFlags:0 timestamp:0. windowNumber:0 context:0 + [NSApp postEvent:[NSEvent otherEventWithType:NSEventTypeApplicationDefined location:NSZeroPoint + modifierFlags:0 timestamp:0. windowNumber:0 context:nil subtype:QtCocoaEventSubTypeWakeup data1:0 data2:0] atStart:NO]; } @@ -1008,7 +1024,7 @@ QCocoaEventDispatcher::~QCocoaEventDispatcher() CFRelease(d->firstTimeObserver); } -QtCocoaInterruptDispatcher* QtCocoaInterruptDispatcher::instance = 0; +QtCocoaInterruptDispatcher* QtCocoaInterruptDispatcher::instance = nullptr; QtCocoaInterruptDispatcher::QtCocoaInterruptDispatcher() : cancelled(false) { @@ -1025,7 +1041,7 @@ QtCocoaInterruptDispatcher::~QtCocoaInterruptDispatcher() { if (cancelled) return; - instance = 0; + instance = nullptr; QCocoaEventDispatcher::instance()->interrupt(); } @@ -1035,7 +1051,7 @@ void QtCocoaInterruptDispatcher::cancelInterruptLater() return; instance->cancelled = true; delete instance; - instance = 0; + instance = nullptr; } void QtCocoaInterruptDispatcher::interruptLater() diff --git a/src/plugins/platforms/cocoa/qcocoafiledialoghelper.mm b/src/plugins/platforms/cocoa/qcocoafiledialoghelper.mm index 94f2125bad..e7243ec250 100644 --- a/src/plugins/platforms/cocoa/qcocoafiledialoghelper.mm +++ b/src/plugins/platforms/cocoa/qcocoafiledialoghelper.mm @@ -50,7 +50,6 @@ #include <private/qguiapplication_p.h> #include "qt_mac_p.h" #include "qcocoahelpers.h" -#include "qcocoamenubar.h" #include "qcocoaeventdispatcher.h" #include <qregexp.h> #include <qbuffer.h> @@ -81,23 +80,6 @@ typedef QSharedPointer<QFileDialogOptions> SharedPointerFileDialogOptions; @interface QT_MANGLE_NAMESPACE(QNSOpenSavePanelDelegate) : NSObject<NSOpenSavePanelDelegate> -{ - @public - NSOpenPanel *mOpenPanel; - NSSavePanel *mSavePanel; - NSView *mAccessoryView; - NSPopUpButton *mPopUpButton; - NSTextField *mTextField; - QCocoaFileDialogHelper *mHelper; - NSString *mCurrentDir; - - int mReturnCode; - - SharedPointerFileDialogOptions mOptions; - QString *mCurrentSelection; - QStringList *mNameFilterDropDownList; - QStringList *mSelectedNameFilter; -} - (NSString *)strip:(const QString &)label; - (BOOL)panel:(id)sender shouldEnableURL:(NSURL *)url; @@ -117,12 +99,27 @@ typedef QSharedPointer<QFileDialogOptions> SharedPointerFileDialogOptions; QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSOpenSavePanelDelegate); -@implementation QNSOpenSavePanelDelegate +@implementation QNSOpenSavePanelDelegate { + @public + NSOpenPanel *mOpenPanel; + NSSavePanel *mSavePanel; + NSView *mAccessoryView; + NSPopUpButton *mPopUpButton; + NSTextField *mTextField; + QCocoaFileDialogHelper *mHelper; + NSString *mCurrentDir; + + int mReturnCode; + + SharedPointerFileDialogOptions mOptions; + QString *mCurrentSelection; + QStringList *mNameFilterDropDownList; + QStringList *mSelectedNameFilter; +} -- (id)initWithAcceptMode: - (const QString &)selectFile - options:(SharedPointerFileDialogOptions)options - helper:(QCocoaFileDialogHelper *)helper +- (instancetype)initWithAcceptMode:(const QString &)selectFile + options:(SharedPointerFileDialogOptions)options + helper:(QCocoaFileDialogHelper *)helper { self = [super init]; mOptions = options; @@ -132,7 +129,7 @@ QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSOpenSavePanelDelegate); } else { mSavePanel = [NSSavePanel savePanel]; [mSavePanel setCanSelectHiddenExtension:YES]; - mOpenPanel = 0; + mOpenPanel = nil; } if ([mSavePanel respondsToSelector:@selector(setLevel:)]) @@ -222,7 +219,6 @@ static QString strippedText(QString s) || [self panel:nil shouldEnableURL:url]; [self updateProperties]; - QCocoaMenuBar::redirectKnownMenuItemsToFirstResponder(); [mSavePanel setNameFieldStringValue:selectable ? info.fileName().toNSString() : @""]; [mOpenPanel beginWithCompletionHandler:^(NSInteger result){ @@ -252,9 +248,7 @@ static QString strippedText(QString s) // Make sure we don't interrupt the runModal call below. QCocoaEventDispatcher::clearCurrentThreadCocoaEventDispatcherInterruptFlag(); - QCocoaMenuBar::redirectKnownMenuItemsToFirstResponder(); mReturnCode = [mSavePanel runModal]; - QCocoaMenuBar::resetKnownMenuItemsToQt(); QAbstractEventDispatcher::instance()->interrupt(); return (mReturnCode == NSModalResponseOK); @@ -274,7 +268,6 @@ static QString strippedText(QString s) || [self panel:nil shouldEnableURL:url]; [self updateProperties]; - QCocoaMenuBar::redirectKnownMenuItemsToFirstResponder(); [mSavePanel setDirectoryURL: [NSURL fileURLWithPath:mCurrentDir]]; [mSavePanel setNameFieldStringValue:selectable ? info.fileName().toNSString() : @""]; @@ -292,7 +285,7 @@ static QString strippedText(QString s) BOOL hidden = NO; if (url) { CFBooleanRef isHiddenProperty; - if (CFURLCopyResourcePropertyForKey((__bridge CFURLRef)url, kCFURLIsHiddenKey, &isHiddenProperty, NULL)) { + if (CFURLCopyResourcePropertyForKey((__bridge CFURLRef)url, kCFURLIsHiddenKey, &isHiddenProperty, nullptr)) { hidden = CFBooleanGetValue(isHiddenProperty); CFRelease(isHiddenProperty); } @@ -406,9 +399,9 @@ static QString strippedText(QString s) { if (mOpenPanel) { QList<QUrl> result; - NSArray* array = [mOpenPanel URLs]; - for (NSUInteger i=0; i<[array count]; ++i) { - QString path = QString::fromNSString([[array objectAtIndex:i] path]).normalized(QString::NormalizationForm_C); + NSArray<NSURL *> *array = [mOpenPanel URLs]; + for (NSURL *url in array) { + QString path = QString::fromNSString(url.path).normalized(QString::NormalizationForm_C); result << QUrl::fromLocalFile(path); } return result; @@ -522,8 +515,8 @@ static QString strippedText(QString s) NSRect textRect = { { 0.0, 3.0 }, { 100.0, 25.0 } }; mTextField = [[NSTextField alloc] initWithFrame:textRect]; [[mTextField cell] setFont:[NSFont systemFontOfSize: - [NSFont systemFontSizeForControlSize:NSRegularControlSize]]]; - [mTextField setAlignment:NSRightTextAlignment]; + [NSFont systemFontSizeForControlSize:NSControlSizeRegular]]]; + [mTextField setAlignment:NSTextAlignmentRight]; [mTextField setEditable:false]; [mTextField setSelectable:false]; [mTextField setBordered:false]; @@ -576,7 +569,7 @@ static QString strippedText(QString s) QT_BEGIN_NAMESPACE QCocoaFileDialogHelper::QCocoaFileDialogHelper() - :mDelegate(0) + : mDelegate(nil) { } @@ -586,7 +579,7 @@ QCocoaFileDialogHelper::~QCocoaFileDialogHelper() return; QMacAutoReleasePool pool; [mDelegate release]; - mDelegate = 0; + mDelegate = nil; } void QCocoaFileDialogHelper::QNSOpenSavePanelDelegate_selectionChanged(const QString &newPath) @@ -596,7 +589,6 @@ void QCocoaFileDialogHelper::QNSOpenSavePanelDelegate_selectionChanged(const QSt void QCocoaFileDialogHelper::QNSOpenSavePanelDelegate_panelClosed(bool accepted) { - QCocoaMenuBar::resetKnownMenuItemsToQt(); if (accepted) { emit accept(); } else { diff --git a/src/plugins/platforms/cocoa/qcocoafontdialoghelper.mm b/src/plugins/platforms/cocoa/qcocoafontdialoghelper.mm index 815882ab06..33b102f3eb 100644 --- a/src/plugins/platforms/cocoa/qcocoafontdialoghelper.mm +++ b/src/plugins/platforms/cocoa/qcocoafontdialoghelper.mm @@ -74,7 +74,15 @@ static QFont qfontForCocoaFont(NSFont *cocoaFont, const QFont &resolveFont) } @interface QT_MANGLE_NAMESPACE(QNSFontPanelDelegate) : NSObject<NSWindowDelegate, QNSPanelDelegate> -{ +- (void)restoreOriginalContentView; +- (void)updateQtFont; +- (void)changeFont:(id)sender; +- (void)finishOffWithCode:(NSInteger)code; +@end + +QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSFontPanelDelegate); + +@implementation QNSFontPanelDelegate { @public NSFontPanel *mFontPanel; QCocoaFontDialogHelper *mHelper; @@ -84,34 +92,26 @@ static QFont qfontForCocoaFont(NSFont *cocoaFont, const QFont &resolveFont) NSInteger mResultCode; BOOL mDialogIsExecuting; BOOL mResultSet; -}; -- (void)restoreOriginalContentView; -- (void)updateQtFont; -- (void)changeFont:(id)sender; -- (void)finishOffWithCode:(NSInteger)code; -@end - -QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSFontPanelDelegate); - -@implementation QNSFontPanelDelegate +} -- (id)init +- (instancetype)init { - self = [super init]; - mFontPanel = [NSFontPanel sharedFontPanel]; - mHelper = 0; - mStolenContentView = 0; - mPanelButtons = 0; - mResultCode = NSModalResponseCancel; - mDialogIsExecuting = false; - mResultSet = false; + if ((self = [super init])) { + mFontPanel = [NSFontPanel sharedFontPanel]; + mHelper = nullptr; + mStolenContentView = nil; + mPanelButtons = nil; + mResultCode = NSModalResponseCancel; + mDialogIsExecuting = false; + mResultSet = false; - [mFontPanel setRestorable:NO]; - [mFontPanel setDelegate:self]; + [mFontPanel setRestorable:NO]; + [mFontPanel setDelegate:self]; - [NSFontManager sharedFontManager].target = self; // Action is changeFont: + [NSFontManager sharedFontManager].target = self; // Action is changeFont: - [mFontPanel retain]; + [mFontPanel retain]; + } return self; } @@ -135,9 +135,9 @@ QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSFontPanelDelegate); [self restoreOriginalContentView]; } else if (!mStolenContentView) { // steal the font panel's contents view - mStolenContentView = [mFontPanel contentView]; + mStolenContentView = mFontPanel.contentView; [mStolenContentView retain]; - [mFontPanel setContentView:0]; + mFontPanel.contentView = nil; // create a new content view and add the stolen one as a subview mPanelButtons = [[QNSPanelContentsWrapper alloc] initWithPanelDelegate:self]; @@ -159,7 +159,7 @@ QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSFontPanelDelegate); // return stolen stuff to its rightful owner [mStolenContentView removeFromSuperview]; [mFontPanel setContentView:mStolenContentView]; - mStolenContentView = 0; + mStolenContentView = nil; [mPanelButtons release]; mPanelButtons = nil; } @@ -191,7 +191,7 @@ QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSFontPanelDelegate); // Get selected font NSFontManager *fontManager = [NSFontManager sharedFontManager]; NSFont *selectedFont = [fontManager selectedFont]; - if (selectedFont == nil) { + if (!selectedFont) { selectedFont = [NSFont systemFontOfSize:[NSFont systemFontSize]]; } NSFont *panelFont = [fontManager convertFont:selectedFont]; @@ -295,7 +295,7 @@ public: void cleanup(QCocoaFontDialogHelper *helper) { if (mDelegate->mHelper == helper) - mDelegate->mHelper = 0; + mDelegate->mHelper = nullptr; } bool exec() @@ -329,7 +329,7 @@ public: void setCurrentFont(const QFont &font) { NSFontManager *mgr = [NSFontManager sharedFontManager]; - const NSFont *nsFont = 0; + NSFont *nsFont = nil; int weight = 5; NSFontTraitMask mask = 0; @@ -347,7 +347,7 @@ public: weight:weight size:fontInfo.pointSize()]; - [mgr setSelectedFont:const_cast<NSFont *>(nsFont) isMultiple:NO]; + [mgr setSelectedFont:nsFont isMultiple:NO]; mDelegate->mQtFont = font; } diff --git a/src/plugins/platforms/cocoa/qcocoaglcontext.h b/src/plugins/platforms/cocoa/qcocoaglcontext.h index 0530aa8201..cef5892989 100644 --- a/src/plugins/platforms/cocoa/qcocoaglcontext.h +++ b/src/plugins/platforms/cocoa/qcocoaglcontext.h @@ -52,39 +52,33 @@ QT_BEGIN_NAMESPACE class QCocoaGLContext : public QPlatformOpenGLContext { public: - QCocoaGLContext(const QSurfaceFormat &format, QPlatformOpenGLContext *share, const QVariant &nativeHandle); + QCocoaGLContext(QOpenGLContext *context); ~QCocoaGLContext(); - QSurfaceFormat format() const override; - - void swapBuffers(QPlatformSurface *surface) override; - bool makeCurrent(QPlatformSurface *surface) override; + void swapBuffers(QPlatformSurface *surface) override; void doneCurrent() override; - QFunctionPointer getProcAddress(const char *procName) override; - void update(); - static NSOpenGLPixelFormat *createNSOpenGLPixelFormat(const QSurfaceFormat &format); - NSOpenGLContext *nsOpenGLContext() const; - + QSurfaceFormat format() const override; bool isSharing() const override; bool isValid() const override; - void windowWasHidden(); + NSOpenGLContext *nativeContext() const; - QVariant nativeHandle() const; + QFunctionPointer getProcAddress(const char *procName) override; private: - void setActiveWindow(QWindow *window); + static NSOpenGLPixelFormat *pixelFormatForSurfaceFormat(const QSurfaceFormat &format); + + bool setDrawable(QPlatformSurface *surface); void updateSurfaceFormat(); - NSOpenGLContext *m_context; - NSOpenGLContext *m_shareContext; + NSOpenGLContext *m_context = nil; + NSOpenGLContext *m_shareContext = nil; QSurfaceFormat m_format; - QPointer<QWindow> m_currentWindow; - bool m_didCheckForSoftwareContext; + bool m_didCheckForSoftwareContext = false; }; QT_END_NAMESPACE diff --git a/src/plugins/platforms/cocoa/qcocoaglcontext.mm b/src/plugins/platforms/cocoa/qcocoaglcontext.mm index 7ffe0003d3..069429796e 100644 --- a/src/plugins/platforms/cocoa/qcocoaglcontext.mm +++ b/src/plugins/platforms/cocoa/qcocoaglcontext.mm @@ -47,8 +47,6 @@ #import <AppKit/AppKit.h> -QT_BEGIN_NAMESPACE - static inline QByteArray getGlString(GLenum param) { if (const GLubyte *s = glGetString(param)) @@ -56,109 +54,66 @@ static inline QByteArray getGlString(GLenum param) return QByteArray(); } -#if !defined(GL_CONTEXT_FLAGS) -#define GL_CONTEXT_FLAGS 0x821E -#endif - -#if !defined(GL_CONTEXT_FLAG_FORWARD_COMPATIBLE_BIT) -#define GL_CONTEXT_FLAG_FORWARD_COMPATIBLE_BIT 0x0001 -#endif - -#if !defined(GL_CONTEXT_PROFILE_MASK) -#define GL_CONTEXT_PROFILE_MASK 0x9126 -#endif - -#if !defined(GL_CONTEXT_CORE_PROFILE_BIT) -#define GL_CONTEXT_CORE_PROFILE_BIT 0x00000001 -#endif - -#if !defined(GL_CONTEXT_COMPATIBILITY_PROFILE_BIT) -#define GL_CONTEXT_COMPATIBILITY_PROFILE_BIT 0x00000002 -#endif - -static void updateFormatFromContext(QSurfaceFormat *format) +@implementation NSOpenGLPixelFormat (QtHelpers) +- (GLint)qt_getAttribute:(NSOpenGLPixelFormatAttribute)attribute { - Q_ASSERT(format); - - // Update the version, profile, and context bit of the format - int major = 0, minor = 0; - QByteArray versionString(getGlString(GL_VERSION)); - if (QPlatformOpenGLContext::parseOpenGLVersion(versionString, major, minor)) { - format->setMajorVersion(major); - format->setMinorVersion(minor); - } - - format->setProfile(QSurfaceFormat::NoProfile); - - Q_ASSERT(format->renderableType() == QSurfaceFormat::OpenGL); - if (format->version() < qMakePair(3, 0)) { - format->setOption(QSurfaceFormat::DeprecatedFunctions); - return; - } - - // Version 3.0 onwards - check if it includes deprecated functionality - GLint value = 0; - glGetIntegerv(GL_CONTEXT_FLAGS, &value); - if (!(value & GL_CONTEXT_FLAG_FORWARD_COMPATIBLE_BIT)) - format->setOption(QSurfaceFormat::DeprecatedFunctions); - - // Debug context option not supported on OS X - - if (format->version() < qMakePair(3, 2)) - return; - - // Version 3.2 and newer have a profile - value = 0; - glGetIntegerv(GL_CONTEXT_PROFILE_MASK, &value); + int value = 0; + [self getValues:&value forAttribute:attribute forVirtualScreen:0]; + return value; +} +@end - if (value & GL_CONTEXT_CORE_PROFILE_BIT) - format->setProfile(QSurfaceFormat::CoreProfile); - else if (value & GL_CONTEXT_COMPATIBILITY_PROFILE_BIT) - format->setProfile(QSurfaceFormat::CompatibilityProfile); +@implementation NSOpenGLContext (QtHelpers) +- (GLint)qt_getParameter:(NSOpenGLContextParameter)parameter +{ + int value = 0; + [self getValues:&value forParameter:parameter]; + return value; } +@end + +QT_BEGIN_NAMESPACE - // NSOpenGLContext is not re-entrant (https://openradar.appspot.com/37064579) -static QMutex s_contextMutex; +Q_LOGGING_CATEGORY(lcQpaOpenGLContext, "qt.qpa.openglcontext", QtWarningMsg); -QCocoaGLContext::QCocoaGLContext(const QSurfaceFormat &format, QPlatformOpenGLContext *share, - const QVariant &nativeHandle) - : m_context(nil), - m_shareContext(nil), - m_format(format), - m_didCheckForSoftwareContext(false) +QCocoaGLContext::QCocoaGLContext(QOpenGLContext *context) + : QPlatformOpenGLContext(), m_format(context->format()) { + QVariant nativeHandle = context->nativeHandle(); if (!nativeHandle.isNull()) { if (!nativeHandle.canConvert<QCocoaNativeContext>()) { - qWarning("QCocoaGLContext: Requires a QCocoaNativeContext"); + qCWarning(lcQpaOpenGLContext, "QOpenGLContext native handle must be a QCocoaNativeContext"); return; } - QCocoaNativeContext handle = nativeHandle.value<QCocoaNativeContext>(); - NSOpenGLContext *context = handle.context(); - if (!context) { - qWarning("QCocoaGLContext: No NSOpenGLContext given"); + m_context = nativeHandle.value<QCocoaNativeContext>().context(); + if (!m_context) { + qCWarning(lcQpaOpenGLContext, "QCocoaNativeContext's NSOpenGLContext can not be null"); return; } - m_context = context; + [m_context retain]; - m_shareContext = share ? static_cast<QCocoaGLContext *>(share)->nsOpenGLContext() : nil; - // OpenGL surfaces can be ordered either above(default) or below the NSWindow. - const GLint order = qt_mac_resolveOption(1, "QT_MAC_OPENGL_SURFACE_ORDER"); - [m_context setValues:&order forParameter:NSOpenGLCPSurfaceOrder]; + + // Note: We have no way of knowing whether the NSOpenGLContext was created with the + // share context as reported by the QOpenGLContext, but we just have to trust that + // it was. It's okey, as the only thing we're using it for is to report isShared(). + if (QPlatformOpenGLContext *shareContext = context->shareHandle()) + m_shareContext = static_cast<QCocoaGLContext *>(shareContext)->nativeContext(); + updateSurfaceFormat(); return; } - // we only support OpenGL contexts under Cocoa + // ----------- Default case, we own the NSOpenGLContext ----------- + + // We only support OpenGL contexts under Cocoa if (m_format.renderableType() == QSurfaceFormat::DefaultRenderableType) m_format.setRenderableType(QSurfaceFormat::OpenGL); if (m_format.renderableType() != QSurfaceFormat::OpenGL) return; - QMacAutoReleasePool pool; // For the SG Canvas render thread + if (QPlatformOpenGLContext *shareContext = context->shareHandle()) { + m_shareContext = static_cast<QCocoaGLContext *>(shareContext)->nativeContext(); - m_shareContext = share ? static_cast<QCocoaGLContext *>(share)->nsOpenGLContext() : nil; - - if (m_shareContext) { // Allow sharing between 3.2 Core and 4.1 Core profile versions in // cases where NSOpenGLContext creates a 4.1 context where a 3.2 // context was requested. Due to the semantics of QSurfaceFormat @@ -167,312 +122,327 @@ QCocoaGLContext::QCocoaGLContext(const QSurfaceFormat &format, QPlatformOpenGLCo GLint shareContextRequestedProfile; [m_shareContext.pixelFormat getValues:&shareContextRequestedProfile forAttribute:NSOpenGLPFAOpenGLProfile forVirtualScreen:0]; - auto shareContextActualProfile = share->format().version(); - - if (shareContextRequestedProfile == NSOpenGLProfileVersion3_2Core && - shareContextActualProfile >= qMakePair(4, 1)) { + auto shareContextActualProfile = shareContext->format().version(); - // There is a mismatch, downgrade requested format to make the - // NSOpenGLPFAOpenGLProfile attributes match. (NSOpenGLContext will - // fail to create a new context if there is a mismatch). + if (shareContextRequestedProfile == NSOpenGLProfileVersion3_2Core + && shareContextActualProfile >= qMakePair(4, 1)) { + // There is a mismatch. Downgrade requested format to make the + // NSOpenGLPFAOpenGLProfile attributes match. (NSOpenGLContext + // will fail to create a new context if there is a mismatch). if (m_format.version() >= qMakePair(4, 1)) m_format.setVersion(3, 2); } } - // create native context for the requested pixel format and share - NSOpenGLPixelFormat *pixelFormat = createNSOpenGLPixelFormat(m_format); + // ------------------------- Create NSOpenGLContext ------------------------- + + NSOpenGLPixelFormat *pixelFormat = [pixelFormatForSurfaceFormat(m_format) autorelease]; m_context = [[NSOpenGLContext alloc] initWithFormat:pixelFormat shareContext:m_shareContext]; - // retry without sharing on context creation failure. if (!m_context && m_shareContext) { - m_shareContext = nil; + qCWarning(lcQpaOpenGLContext, "Could not create NSOpenGLContext with shared context, " + "falling back to unshared context."); m_context = [[NSOpenGLContext alloc] initWithFormat:pixelFormat shareContext:nil]; - if (m_context) - qWarning("QCocoaGLContext: Falling back to unshared context."); + m_shareContext = nil; } - // give up if we still did not get a native context - [pixelFormat release]; if (!m_context) { - qWarning("QCocoaGLContext: Failed to create context."); + qCWarning(lcQpaOpenGLContext, "Failed to create NSOpenGLContext"); return; } - const GLint interval = format.swapInterval() >= 0 ? format.swapInterval() : 1; + // --------------------- Set NSOpenGLContext properties --------------------- + + const GLint interval = m_format.swapInterval() >= 0 ? m_format.swapInterval() : 1; [m_context setValues:&interval forParameter:NSOpenGLCPSwapInterval]; - if (format.alphaBufferSize() > 0) { + if (m_format.alphaBufferSize() > 0) { int zeroOpacity = 0; [m_context setValues:&zeroOpacity forParameter:NSOpenGLCPSurfaceOpacity]; } - - // OpenGL surfaces can be ordered either above(default) or below the NSWindow. + // OpenGL surfaces can be ordered either above(default) or below the NSWindow + // FIXME: Promote to QSurfaceFormat option or property const GLint order = qt_mac_resolveOption(1, "QT_MAC_OPENGL_SURFACE_ORDER"); [m_context setValues:&order forParameter:NSOpenGLCPSurfaceOrder]; updateSurfaceFormat(); } -QCocoaGLContext::~QCocoaGLContext() +NSOpenGLPixelFormat *QCocoaGLContext::pixelFormatForSurfaceFormat(const QSurfaceFormat &format) { - if (m_currentWindow && m_currentWindow.data()->handle()) - static_cast<QCocoaWindow *>(m_currentWindow.data()->handle())->setCurrentContext(0); + QVector<NSOpenGLPixelFormatAttribute> attrs; - [m_context release]; -} + attrs << NSOpenGLPFAOpenGLProfile; + if (format.profile() == QSurfaceFormat::CoreProfile) { + if (format.version() >= qMakePair(4, 1)) + attrs << NSOpenGLProfileVersion4_1Core; + else if (format.version() >= qMakePair(3, 2)) + attrs << NSOpenGLProfileVersion3_2Core; + else + attrs << NSOpenGLProfileVersionLegacy; + } else { + attrs << NSOpenGLProfileVersionLegacy; + } -QVariant QCocoaGLContext::nativeHandle() const -{ - return QVariant::fromValue<QCocoaNativeContext>(QCocoaNativeContext(m_context)); -} + switch (format.swapBehavior()) { + case QSurfaceFormat::SingleBuffer: + break; // The NSOpenGLPixelFormat default, no attribute to set + case QSurfaceFormat::DefaultSwapBehavior: + // Technically this should be single-buffered, but we force double-buffered + // FIXME: Why do we force double-buffered? + Q_FALLTHROUGH(); + case QSurfaceFormat::DoubleBuffer: + attrs.append(NSOpenGLPFADoubleBuffer); + break; + case QSurfaceFormat::TripleBuffer: + attrs.append(NSOpenGLPFATripleBuffer); + break; + } -QSurfaceFormat QCocoaGLContext::format() const -{ - return m_format; -} + if (format.depthBufferSize() > 0) + attrs << NSOpenGLPFADepthSize << format.depthBufferSize(); + if (format.stencilBufferSize() > 0) + attrs << NSOpenGLPFAStencilSize << format.stencilBufferSize(); + if (format.alphaBufferSize() > 0) + attrs << NSOpenGLPFAAlphaSize << format.alphaBufferSize(); + if (format.redBufferSize() > 0 && format.greenBufferSize() > 0 && format.blueBufferSize() > 0) { + const int colorSize = format.redBufferSize() + format.greenBufferSize() + format.blueBufferSize(); + attrs << NSOpenGLPFAColorSize << colorSize << NSOpenGLPFAMinimumPolicy; + } -void QCocoaGLContext::windowWasHidden() -{ - // If the window is hidden, we need to unset the m_currentWindow - // variable so that succeeding makeCurrent's will not abort prematurely - // because of the optimization in setActiveWindow. - // Doing a full doneCurrent here is not preferable, because the GL context - // might be rendering in a different thread at this time. - m_currentWindow.clear(); -} + if (format.samples() > 0) { + attrs << NSOpenGLPFAMultisample + << NSOpenGLPFASampleBuffers << NSOpenGLPixelFormatAttribute(1) + << NSOpenGLPFASamples << NSOpenGLPixelFormatAttribute(format.samples()); + } -void QCocoaGLContext::swapBuffers(QPlatformSurface *surface) -{ - if (surface->surface()->surfaceClass() == QSurface::Offscreen) - return; // Nothing to do + // Allow rendering on GPUs without a connected display + attrs << NSOpenGLPFAAllowOfflineRenderers; - QWindow *window = static_cast<QCocoaWindow *>(surface)->window(); - setActiveWindow(window); + // FIXME: Pull this information out of the NSView + QByteArray useLayer = qgetenv("QT_MAC_WANTS_LAYER"); + if (!useLayer.isEmpty() && useLayer.toInt() > 0) { + // Disable the software rendering fallback. This makes compositing + // OpenGL and raster NSViews using Core Animation layers possible. + attrs << NSOpenGLPFANoRecovery; + } - QMutexLocker locker(&s_contextMutex); - [m_context flushBuffer]; + attrs << 0; // 0-terminate array + return [[NSOpenGLPixelFormat alloc] initWithAttributes:attrs.constData()]; } -bool QCocoaGLContext::makeCurrent(QPlatformSurface *surface) +/*! + Updates the surface format of this context based on properties of + the native context and GL state, so that the result of creating + the context is reflected back in QOpenGLContext. +*/ +void QCocoaGLContext::updateSurfaceFormat() { - Q_ASSERT(surface->surface()->supportsOpenGL()); - - QMacAutoReleasePool pool; + NSOpenGLContext *oldContext = [NSOpenGLContext currentContext]; [m_context makeCurrentContext]; - if (surface->surface()->surfaceClass() == QSurface::Offscreen) - return true; + // --------------------- Query GL state --------------------- - QWindow *window = static_cast<QCocoaWindow *>(surface)->window(); - setActiveWindow(window); - - // Disable high-resolution surfaces when using the software renderer, which has the - // problem that the system silently falls back to a to using a low-resolution buffer - // when a high-resolution buffer is requested. This is not detectable using the NSWindow - // convertSizeToBacking and backingScaleFactor APIs. A typical result of this is that Qt - // will display a quarter of the window content when running in a virtual machine. - if (!m_didCheckForSoftwareContext) { - m_didCheckForSoftwareContext = true; - - const GLubyte* renderer = glGetString(GL_RENDERER); - if (qstrcmp((const char *)renderer, "Apple Software Renderer") == 0) { - NSView *view = static_cast<QCocoaWindow *>(surface)->m_view; - [view setWantsBestResolutionOpenGLSurface:NO]; - } + int major = 0, minor = 0; + QByteArray versionString(getGlString(GL_VERSION)); + if (QPlatformOpenGLContext::parseOpenGLVersion(versionString, major, minor)) { + m_format.setMajorVersion(major); + m_format.setMinorVersion(minor); } - update(); - return true; -} + m_format.setProfile(QSurfaceFormat::NoProfile); + if (m_format.version() >= qMakePair(3, 2)) { + GLint value = 0; + glGetIntegerv(GL_CONTEXT_PROFILE_MASK, &value); + if (value & GL_CONTEXT_CORE_PROFILE_BIT) + m_format.setProfile(QSurfaceFormat::CoreProfile); + else if (value & GL_CONTEXT_COMPATIBILITY_PROFILE_BIT) + m_format.setProfile(QSurfaceFormat::CompatibilityProfile); + } -void QCocoaGLContext::setActiveWindow(QWindow *window) -{ - if (window == m_currentWindow.data()) - return; + m_format.setOption(QSurfaceFormat::DeprecatedFunctions, [&]() { + if (m_format.version() < qMakePair(3, 0)) { + return true; + } else { + GLint value = 0; + glGetIntegerv(GL_CONTEXT_FLAGS, &value); + return !(value & GL_CONTEXT_FLAG_FORWARD_COMPATIBLE_BIT); + } + }()); - if (m_currentWindow && m_currentWindow.data()->handle()) - static_cast<QCocoaWindow *>(m_currentWindow.data()->handle())->setCurrentContext(0); + // Debug contexts not supported on macOS + m_format.setOption(QSurfaceFormat::DebugContext, false); - Q_ASSERT(window->handle()); + // Nor are stereo buffers (deprecated in macOS 10.12) + m_format.setOption(QSurfaceFormat::StereoBuffers, false); - m_currentWindow = window; + // ------------------ Query the pixel format ------------------ - QCocoaWindow *cocoaWindow = static_cast<QCocoaWindow *>(window->handle()); - cocoaWindow->setCurrentContext(this); + NSOpenGLPixelFormat *pixelFormat = m_context.pixelFormat; - Q_ASSERT(!cocoaWindow->isForeignWindow()); - [qnsview_cast(cocoaWindow->view()) setQCocoaGLContext:this]; -} + int colorSize = [pixelFormat qt_getAttribute:NSOpenGLPFAColorSize]; + colorSize /= 4; // The attribute includes the alpha component + m_format.setRedBufferSize(colorSize); + m_format.setGreenBufferSize(colorSize); + m_format.setBlueBufferSize(colorSize); -void QCocoaGLContext::updateSurfaceFormat() -{ - // At present it is impossible to turn an option off on a QSurfaceFormat (see - // https://codereview.qt-project.org/#change,70599). So we have to populate - // the actual surface format from scratch - QSurfaceFormat requestedFormat = m_format; - m_format = QSurfaceFormat(); - m_format.setRenderableType(QSurfaceFormat::OpenGL); - - // CoreGL doesn't require a drawable to make the context current - CGLContextObj oldContext = CGLGetCurrentContext(); - CGLContextObj ctx = static_cast<CGLContextObj>([m_context CGLContextObj]); - CGLSetCurrentContext(ctx); - - // Get the data that OpenGL provides - updateFormatFromContext(&m_format); - - // Get the data contained within the pixel format - CGLPixelFormatObj cglPixelFormat = static_cast<CGLPixelFormatObj>(CGLGetPixelFormat(ctx)); - NSOpenGLPixelFormat *pixelFormat = [[NSOpenGLPixelFormat alloc] initWithCGLPixelFormatObj:cglPixelFormat]; - - int colorSize = -1; - [pixelFormat getValues:&colorSize forAttribute:NSOpenGLPFAColorSize forVirtualScreen:0]; - if (colorSize > 0) { - // This seems to return the total color buffer depth, including alpha - m_format.setRedBufferSize(colorSize / 4); - m_format.setGreenBufferSize(colorSize / 4); - m_format.setBlueBufferSize(colorSize / 4); - } + // Surfaces on macOS always have an alpha channel, but unless the user requested + // one via setAlphaBufferSize(), which triggered setting NSOpenGLCPSurfaceOpacity + // to make the surface non-opaque, we don't want to report back the actual alpha + // size, as that will make the user believe the alpha channel can be used for + // something useful, when in reality it can't, due to the surface being opaque. + if (m_format.alphaBufferSize() > 0) + m_format.setAlphaBufferSize([pixelFormat qt_getAttribute:NSOpenGLPFAAlphaSize]); - // The pixel format always seems to return 8 for alpha. However, the framebuffer only - // seems to have alpha enabled if we requested it explicitly. I can't find any other - // attribute to check explicitly for this so we use our best guess for alpha. - int alphaSize = -1; - [pixelFormat getValues:&alphaSize forAttribute:NSOpenGLPFAAlphaSize forVirtualScreen:0]; - if (alphaSize > 0 && requestedFormat.alphaBufferSize() > 0) - m_format.setAlphaBufferSize(alphaSize); - - int depthSize = -1; - [pixelFormat getValues:&depthSize forAttribute:NSOpenGLPFADepthSize forVirtualScreen:0]; - if (depthSize > 0) - m_format.setDepthBufferSize(depthSize); - - int stencilSize = -1; - [pixelFormat getValues:&stencilSize forAttribute:NSOpenGLPFAStencilSize forVirtualScreen:0]; - if (stencilSize > 0) - m_format.setStencilBufferSize(stencilSize); - - int samples = -1; - [pixelFormat getValues:&samples forAttribute:NSOpenGLPFASamples forVirtualScreen:0]; - if (samples > 0) - m_format.setSamples(samples); - - int doubleBuffered = -1; - int tripleBuffered = -1; - [pixelFormat getValues:&doubleBuffered forAttribute:NSOpenGLPFADoubleBuffer forVirtualScreen:0]; - [pixelFormat getValues:&tripleBuffered forAttribute:NSOpenGLPFATripleBuffer forVirtualScreen:0]; - - if (tripleBuffered == 1) + m_format.setDepthBufferSize([pixelFormat qt_getAttribute:NSOpenGLPFADepthSize]); + m_format.setStencilBufferSize([pixelFormat qt_getAttribute:NSOpenGLPFAStencilSize]); + m_format.setSamples([pixelFormat qt_getAttribute:NSOpenGLPFASamples]); + + if ([pixelFormat qt_getAttribute:NSOpenGLPFATripleBuffer]) m_format.setSwapBehavior(QSurfaceFormat::TripleBuffer); - else if (doubleBuffered == 1) + else if ([pixelFormat qt_getAttribute:NSOpenGLPFADoubleBuffer]) m_format.setSwapBehavior(QSurfaceFormat::DoubleBuffer); else m_format.setSwapBehavior(QSurfaceFormat::SingleBuffer); - int steroBuffers = -1; - [pixelFormat getValues:&steroBuffers forAttribute:NSOpenGLPFAStereo forVirtualScreen:0]; - if (steroBuffers == 1) - m_format.setOption(QSurfaceFormat::StereoBuffers); + // ------------------- Query the context ------------------- - [pixelFormat release]; + m_format.setSwapInterval([m_context qt_getParameter:NSOpenGLCPSwapInterval]); - GLint swapInterval = -1; - [m_context getValues:&swapInterval forParameter:NSOpenGLCPSwapInterval]; - if (swapInterval >= 0) - m_format.setSwapInterval(swapInterval); + if (oldContext) + [oldContext makeCurrentContext]; + else + [NSOpenGLContext clearCurrentContext]; +} - // Restore the original context - CGLSetCurrentContext(oldContext); +QCocoaGLContext::~QCocoaGLContext() +{ + [m_context release]; } -void QCocoaGLContext::doneCurrent() +bool QCocoaGLContext::makeCurrent(QPlatformSurface *surface) { - if (m_currentWindow && m_currentWindow.data()->handle()) - static_cast<QCocoaWindow *>(m_currentWindow.data()->handle())->setCurrentContext(0); + qCDebug(lcQpaOpenGLContext) << "Making" << m_context << "current" + << "in" << QThread::currentThread() << "for" << surface; + + Q_ASSERT(surface->surface()->supportsOpenGL()); - m_currentWindow.clear(); + if (!setDrawable(surface)) + return false; - [NSOpenGLContext clearCurrentContext]; + [m_context makeCurrentContext]; + + if (surface->surface()->surfaceClass() == QSurface::Window) { + // Disable high-resolution surfaces when using the software renderer, which has the + // problem that the system silently falls back to a to using a low-resolution buffer + // when a high-resolution buffer is requested. This is not detectable using the NSWindow + // convertSizeToBacking and backingScaleFactor APIs. A typical result of this is that Qt + // will display a quarter of the window content when running in a virtual machine. + if (!m_didCheckForSoftwareContext) { + // FIXME: This ensures we check only once per context, + // but the context may be used for multiple surfaces. + m_didCheckForSoftwareContext = true; + + const GLubyte* renderer = glGetString(GL_RENDERER); + if (qstrcmp((const char *)renderer, "Apple Software Renderer") == 0) { + NSView *view = static_cast<QCocoaWindow *>(surface)->m_view; + [view setWantsBestResolutionOpenGLSurface:NO]; + } + } + + update(); + } + + return true; } -QFunctionPointer QCocoaGLContext::getProcAddress(const char *procName) +/*! + Sets the drawable object of the NSOpenGLContext, which is the + frame buffer that is the target of OpenGL drawing operations. +*/ +bool QCocoaGLContext::setDrawable(QPlatformSurface *surface) { - return (QFunctionPointer)dlsym(RTLD_DEFAULT, procName); + // Make sure any surfaces released during this process are deallocated + // straight away, otherwise we may run out of surfaces when spinning a + // render-loop that doesn't return to one of the outer pools. + QMacAutoReleasePool pool; + + if (!surface || surface->surface()->surfaceClass() == QSurface::Offscreen) { + // Clear the current drawable and reset the active window, so that GL + // commands that don't target a specific FBO will not end up stomping + // on the previously set drawable. + qCDebug(lcQpaOpenGLContext) << "Clearing current drawable" << m_context.view << "for" << m_context; + [m_context clearDrawable]; + return true; + } + + Q_ASSERT(surface->surface()->surfaceClass() == QSurface::Window); + QNSView *view = qnsview_cast(static_cast<QCocoaWindow *>(surface)->view()); + + if (view == m_context.view) + return true; + + if ((m_context.view = view) != view) { + qCInfo(lcQpaOpenGLContext) << "Failed to set" << view << "as drawable for" << m_context; + return false; + } + + qCInfo(lcQpaOpenGLContext) << "Set drawable for" << m_context << "to" << m_context.view; + + return true; } +// NSOpenGLContext is not re-entrant. Even when using separate contexts per thread, +// view, and window, calls into the API will still deadlock. For more information +// see https://openradar.appspot.com/37064579 +static QMutex s_reentrancyMutex; + void QCocoaGLContext::update() { - QMutexLocker locker(&s_contextMutex); + // Make sure any surfaces released during this process are deallocated + // straight away, otherwise we may run out of surfaces when spinning a + // render-loop that doesn't return to one of the outer pools. + QMacAutoReleasePool pool; + + QMutexLocker locker(&s_reentrancyMutex); + qCInfo(lcQpaOpenGLContext) << "Updating" << m_context << "for" << m_context.view; [m_context update]; } -NSOpenGLPixelFormat *QCocoaGLContext::createNSOpenGLPixelFormat(const QSurfaceFormat &format) +void QCocoaGLContext::swapBuffers(QPlatformSurface *surface) { - QVector<NSOpenGLPixelFormatAttribute> attrs; - - if (format.swapBehavior() == QSurfaceFormat::DoubleBuffer - || format.swapBehavior() == QSurfaceFormat::DefaultSwapBehavior) - attrs.append(NSOpenGLPFADoubleBuffer); - else if (format.swapBehavior() == QSurfaceFormat::TripleBuffer) - attrs.append(NSOpenGLPFATripleBuffer); - - - // Select OpenGL profile - attrs << NSOpenGLPFAOpenGLProfile; - if (format.profile() == QSurfaceFormat::CoreProfile) { - if (format.version() >= qMakePair(4, 1)) - attrs << NSOpenGLProfileVersion4_1Core; - else if (format.version() >= qMakePair(3, 2)) - attrs << NSOpenGLProfileVersion3_2Core; - else - attrs << NSOpenGLProfileVersionLegacy; - } else { - attrs << NSOpenGLProfileVersionLegacy; - } + qCDebug(lcQpaOpenGLContext) << "Swapping" << m_context + << "in" << QThread::currentThread() << "to" << surface; - if (format.depthBufferSize() > 0) - attrs << NSOpenGLPFADepthSize << format.depthBufferSize(); - if (format.stencilBufferSize() > 0) - attrs << NSOpenGLPFAStencilSize << format.stencilBufferSize(); - if (format.alphaBufferSize() > 0) - attrs << NSOpenGLPFAAlphaSize << format.alphaBufferSize(); - if ((format.redBufferSize() > 0) && - (format.greenBufferSize() > 0) && - (format.blueBufferSize() > 0)) { - const int colorSize = format.redBufferSize() + - format.greenBufferSize() + - format.blueBufferSize(); - attrs << NSOpenGLPFAColorSize << colorSize << NSOpenGLPFAMinimumPolicy; - } + if (surface->surface()->surfaceClass() == QSurface::Offscreen) + return; // Nothing to do - if (format.samples() > 0) { - attrs << NSOpenGLPFAMultisample - << NSOpenGLPFASampleBuffers << (NSOpenGLPixelFormatAttribute) 1 - << NSOpenGLPFASamples << (NSOpenGLPixelFormatAttribute) format.samples(); + if (!setDrawable(surface)) { + qCWarning(lcQpaOpenGLContext) << "Can't flush" << m_context + << "without" << surface << "as drawable"; + return; } - if (format.stereo()) - attrs << NSOpenGLPFAStereo; - - attrs << NSOpenGLPFAAllowOfflineRenderers; + QMutexLocker locker(&s_reentrancyMutex); + [m_context flushBuffer]; +} - QByteArray useLayer = qgetenv("QT_MAC_WANTS_LAYER"); - if (!useLayer.isEmpty() && useLayer.toInt() > 0) { - // Disable the software rendering fallback. This makes compositing - // OpenGL and raster NSViews using Core Animation layers possible. - attrs << NSOpenGLPFANoRecovery; - } +void QCocoaGLContext::doneCurrent() +{ + qCDebug(lcQpaOpenGLContext) << "Clearing current context" + << [NSOpenGLContext currentContext] << "in" << QThread::currentThread(); - attrs << 0; + // Note: We do not need to clear the current drawable here. + // As long as there is no current context, GL calls will + // do nothing. - return [[NSOpenGLPixelFormat alloc] initWithAttributes:attrs.constData()]; + [NSOpenGLContext clearCurrentContext]; } -NSOpenGLContext *QCocoaGLContext::nsOpenGLContext() const +QSurfaceFormat QCocoaGLContext::format() const { - return m_context; + return m_format; } bool QCocoaGLContext::isValid() const @@ -485,5 +455,14 @@ bool QCocoaGLContext::isSharing() const return m_shareContext != nil; } -QT_END_NAMESPACE +NSOpenGLContext *QCocoaGLContext::nativeContext() const +{ + return m_context; +} +QFunctionPointer QCocoaGLContext::getProcAddress(const char *procName) +{ + return (QFunctionPointer)dlsym(RTLD_DEFAULT, procName); +} + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/cocoa/qcocoahelpers.h b/src/plugins/platforms/cocoa/qcocoahelpers.h index 84632c1487..953bf331bb 100644 --- a/src/plugins/platforms/cocoa/qcocoahelpers.h +++ b/src/plugins/platforms/cocoa/qcocoahelpers.h @@ -62,25 +62,33 @@ Q_FORWARD_DECLARE_OBJC_CLASS(QT_MANGLE_NAMESPACE(QNSView)); QT_BEGIN_NAMESPACE -Q_DECLARE_LOGGING_CATEGORY(lcQpaCocoaWindow) -Q_DECLARE_LOGGING_CATEGORY(lcQpaCocoaDrawing) -Q_DECLARE_LOGGING_CATEGORY(lcQpaCocoaMouse) +Q_DECLARE_LOGGING_CATEGORY(lcQpaWindow) +Q_DECLARE_LOGGING_CATEGORY(lcQpaDrawing) +Q_DECLARE_LOGGING_CATEGORY(lcQpaMouse) +Q_DECLARE_LOGGING_CATEGORY(lcQpaScreen) class QPixmap; class QString; // Conversion functions -QStringList qt_mac_NSArrayToQStringList(void *nsarray); -void *qt_mac_QStringListToNSMutableArrayVoid(const QStringList &list); - -inline NSMutableArray *qt_mac_QStringListToNSMutableArray(const QStringList &qstrlist) -{ return reinterpret_cast<NSMutableArray *>(qt_mac_QStringListToNSMutableArrayVoid(qstrlist)); } +QStringList qt_mac_NSArrayToQStringList(NSArray<NSString *> *nsarray); +NSMutableArray<NSString *> *qt_mac_QStringListToNSMutableArray(const QStringList &list); NSDragOperation qt_mac_mapDropAction(Qt::DropAction action); NSDragOperation qt_mac_mapDropActions(Qt::DropActions actions); Qt::DropAction qt_mac_mapNSDragOperation(NSDragOperation nsActions); Qt::DropActions qt_mac_mapNSDragOperations(NSDragOperation nsActions); +template <typename T> +typename std::enable_if<std::is_pointer<T>::value, T>::type +qt_objc_cast(id object) +{ + if ([object isKindOfClass:[typename std::remove_pointer<T>::type class]]) + return static_cast<T>(object); + + return nil; +} + QT_MANGLE_NAMESPACE(QNSView) *qnsview_cast(NSView *view); // Misc @@ -91,6 +99,12 @@ QPointF qt_mac_flip(const QPointF &pos, const QRectF &reference); QRectF qt_mac_flip(const QRectF &rect, const QRectF &reference); Qt::MouseButton cocoaButton2QtButton(NSInteger buttonNum); +Qt::MouseButton cocoaButton2QtButton(NSEvent *event); + +QEvent::Type cocoaEvent2QtMouseEvent(NSEvent *event); + +Qt::MouseButtons cocoaMouseButtons2QtMouseButtons(NSInteger pressedMouseButtons); +Qt::MouseButtons currentlyPressedMouseButtons(); // strip out '&' characters, and convert "&&" to a single '&', in menu // text - since menu text is sometimes decorated with these for Windows @@ -170,12 +184,7 @@ QT_END_NAMESPACE - (void)onCancelClicked; @end -@interface QT_MANGLE_NAMESPACE(QNSPanelContentsWrapper) : NSView { - NSButton *_okButton; - NSButton *_cancelButton; - NSView *_panelContents; - NSEdgeInsets _panelContentsMargins; -} +@interface QT_MANGLE_NAMESPACE(QNSPanelContentsWrapper) : NSView @property (nonatomic, readonly) NSButton *okButton; @property (nonatomic, readonly) NSButton *cancelButton; @@ -187,6 +196,7 @@ QT_END_NAMESPACE - (NSButton *)createButtonWithTitle:(const char *)title; - (void)layout; + @end QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSPanelContentsWrapper); @@ -285,26 +295,17 @@ public: } private: - template <std::size_t... Ts> - struct index {}; - - template <std::size_t N, std::size_t... Ts> - struct gen_seq : gen_seq<N - 1, N - 1, Ts...> {}; - - template <std::size_t... Ts> - struct gen_seq<0, Ts...> : index<Ts...> {}; - template <typename ReturnType, bool V> using if_requires_stret = typename std::enable_if<objc_msgsend_requires_stret<ReturnType>::value == V, ReturnType>::type; - template <typename ReturnType, std::size_t... Is> - if_requires_stret<ReturnType, false> msgSendSuper(std::tuple<Args...>& args, index<Is...>) + template <typename ReturnType, int... Is> + if_requires_stret<ReturnType, false> msgSendSuper(std::tuple<Args...>& args, QtPrivate::IndexesList<Is...>) { return qt_msgSendSuper<ReturnType>(m_receiver, m_selector, std::get<Is>(args)...); } - template <typename ReturnType, std::size_t... Is> - if_requires_stret<ReturnType, true> msgSendSuper(std::tuple<Args...>& args, index<Is...>) + template <typename ReturnType, int... Is> + if_requires_stret<ReturnType, true> msgSendSuper(std::tuple<Args...>& args, QtPrivate::IndexesList<Is...>) { return qt_msgSendSuper_stret<ReturnType>(m_receiver, m_selector, std::get<Is>(args)...); } @@ -312,7 +313,7 @@ private: template <typename ReturnType> ReturnType msgSendSuper(std::tuple<Args...>& args) { - return msgSendSuper<ReturnType>(args, gen_seq<sizeof...(Args)>{}); + return msgSendSuper<ReturnType>(args, QtPrivate::makeIndexSequence<sizeof...(Args)>{}); } id m_receiver; diff --git a/src/plugins/platforms/cocoa/qcocoahelpers.mm b/src/plugins/platforms/cocoa/qcocoahelpers.mm index e5954f277c..0f5ddfa49a 100644 --- a/src/plugins/platforms/cocoa/qcocoahelpers.mm +++ b/src/plugins/platforms/cocoa/qcocoahelpers.mm @@ -57,29 +57,28 @@ QT_BEGIN_NAMESPACE -Q_LOGGING_CATEGORY(lcQpaCocoaWindow, "qt.qpa.cocoa.window"); -Q_LOGGING_CATEGORY(lcQpaCocoaDrawing, "qt.qpa.cocoa.drawing"); -Q_LOGGING_CATEGORY(lcQpaCocoaMouse, "qt.qpa.cocoa.mouse"); +Q_LOGGING_CATEGORY(lcQpaWindow, "qt.qpa.window"); +Q_LOGGING_CATEGORY(lcQpaDrawing, "qt.qpa.drawing"); +Q_LOGGING_CATEGORY(lcQpaMouse, "qt.qpa.input.mouse"); +Q_LOGGING_CATEGORY(lcQpaScreen, "qt.qpa.screen"); // // Conversion Functions // -QStringList qt_mac_NSArrayToQStringList(void *nsarray) +QStringList qt_mac_NSArrayToQStringList(NSArray<NSString *> *array) { QStringList result; - NSArray *array = static_cast<NSArray *>(nsarray); - for (NSUInteger i=0; i<[array count]; ++i) - result << QString::fromNSString([array objectAtIndex:i]); + for (NSString *string in array) + result << QString::fromNSString(string); return result; } -void *qt_mac_QStringListToNSMutableArrayVoid(const QStringList &list) +NSMutableArray<NSString *> *qt_mac_QStringListToNSMutableArray(const QStringList &list) { - NSMutableArray *result = [NSMutableArray arrayWithCapacity:list.size()]; - for (int i=0; i<list.size(); ++i){ - [result addObject:list[i].toNSString()]; - } + NSMutableArray<NSString *> *result = [NSMutableArray<NSString *> arrayWithCapacity:list.size()]; + for (const QString &string : list) + [result addObject:string.toNSString()]; return result; } @@ -93,6 +92,7 @@ struct dndenum_mapper static dndenum_mapper dnd_enums[] = { { NSDragOperationLink, Qt::LinkAction, true }, { NSDragOperationMove, Qt::MoveAction, true }, + { NSDragOperationDelete, Qt::MoveAction, true }, { NSDragOperationCopy, Qt::CopyAction, true }, { NSDragOperationGeneric, Qt::CopyAction, false }, { NSDragOperationEvery, Qt::ActionMask, false }, @@ -161,10 +161,7 @@ Qt::DropActions qt_mac_mapNSDragOperations(NSDragOperation nsActions) */ QNSView *qnsview_cast(NSView *view) { - if (![view isKindOfClass:[QNSView class]]) - return nil; - - return static_cast<QNSView *>(view); + return qt_objc_cast<QNSView *>(view); } // @@ -282,6 +279,90 @@ Qt::MouseButton cocoaButton2QtButton(NSInteger buttonNum) return Qt::NoButton; } +/*! + \fn Qt::MouseButton cocoaButton2QtButton(NSEvent *event) + + Returns the Qt::Button that corresponds to an NSEvent.buttonNumber. + + \note AppKit will use buttonNumber 0 to indicate both "left button" + and "no button". Only NSEvents that describes mouse press/release/dragging + events (e.g NSEventTypeOtherMouseDown) will contain a valid + button number. + + \note Wacom tablet might not return the correct button number for NSEvent buttonNumber + on right clicks. Decide here that the button is the "right" button. +*/ +Qt::MouseButton cocoaButton2QtButton(NSEvent *event) +{ + switch (event.type) { + case NSEventTypeMouseMoved: + return Qt::NoButton; + + case NSEventTypeRightMouseUp: + case NSEventTypeRightMouseDown: + case NSEventTypeRightMouseDragged: + return Qt::RightButton; + + default: + break; + } + + return cocoaButton2QtButton(event.buttonNumber); +} + +/*! + \fn QEvent::Type cocoaEvent2QtMouseEvent(NSEvent *event) + + Returns the QEvent::Type that corresponds to an NSEvent.type. +*/ +QEvent::Type cocoaEvent2QtMouseEvent(NSEvent *event) +{ + switch (event.type) { + case NSEventTypeLeftMouseDown: + case NSEventTypeRightMouseDown: + case NSEventTypeOtherMouseDown: + return QEvent::MouseButtonPress; + + case NSEventTypeLeftMouseUp: + case NSEventTypeRightMouseUp: + case NSEventTypeOtherMouseUp: + return QEvent::MouseButtonRelease; + + case NSEventTypeLeftMouseDragged: + case NSEventTypeRightMouseDragged: + case NSEventTypeOtherMouseDragged: + return QEvent::MouseMove; + + case NSEventTypeMouseMoved: + return QEvent::MouseMove; + + default: + break; + } + + return QEvent::None; +} + +/*! + \fn Qt::MouseButtons cocoaMouseButtons2QtMouseButtons(NSInteger pressedMouseButtons) + + Returns the Qt::MouseButtons that corresponds to an NSEvent.pressedMouseButtons. +*/ +Qt::MouseButtons cocoaMouseButtons2QtMouseButtons(NSInteger pressedMouseButtons) +{ + return static_cast<Qt::MouseButton>(pressedMouseButtons & Qt::MouseButtonMask); +} + +/*! + \fn Qt::MouseButtons currentlyPressedMouseButtons() + + Returns the Qt::MouseButtons that corresponds to an NSEvent.pressedMouseButtons. +*/ +Qt::MouseButtons currentlyPressedMouseButtons() +{ + return cocoaMouseButtons2QtMouseButtons(NSEvent.pressedMouseButtons); +} + QString qt_mac_removeAmpersandEscapes(QString s) { return QPlatformTheme::removeMnemonics(s).trimmed(); @@ -298,7 +379,12 @@ QT_END_NAMESPACE the target-action for the OK/Cancel buttons and making sure the layout is consistent. */ -@implementation QNSPanelContentsWrapper +@implementation QNSPanelContentsWrapper { + NSButton *_okButton; + NSButton *_cancelButton; + NSView *_panelContents; + NSEdgeInsets _panelContentsMargins; +} @synthesize okButton = _okButton; @synthesize cancelButton = _cancelButton; @@ -346,7 +432,7 @@ QT_END_NAMESPACE // FIXME: Not obvious, from Cocoa's documentation, that QString::toNSString() makes a deep copy button.title = (NSString *)cleanTitle.toCFString(); ((NSButtonCell *)button.cell).font = - [NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:NSRegularControlSize]]; + [NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:NSControlSizeRegular]]; [self addSubview:button]; return button; } diff --git a/src/plugins/platforms/cocoa/qcocoaintegration.h b/src/plugins/platforms/cocoa/qcocoaintegration.h index 301771fd53..7de7e073de 100644 --- a/src/plugins/platforms/cocoa/qcocoaintegration.h +++ b/src/plugins/platforms/cocoa/qcocoaintegration.h @@ -51,6 +51,9 @@ #include "qcocoadrag.h" #include "qcocoaservices.h" #include "qcocoakeymapper.h" +#if QT_CONFIG(vulkan) +#include "qcocoavulkaninstance.h" +#endif #include <QtCore/QScopedPointer> #include <qpa/qplatformintegration.h> @@ -86,6 +89,11 @@ public: QAbstractEventDispatcher *createEventDispatcher() const override; +#if QT_CONFIG(vulkan) + QPlatformVulkanInstance *createPlatformVulkanInstance(QVulkanInstance *instance) const override; + QCocoaVulkanInstance *getCocoaVulkanInstance() const; +#endif + QCoreTextFontDatabase *fontDatabase() const override; QCocoaNativeInterface *nativeInterface() const override; QPlatformInputContext *inputContext() const override; @@ -144,6 +152,9 @@ private: QScopedPointer<QCocoaServices> mServices; QScopedPointer<QCocoaKeyMapper> mKeyboardMapper; +#if QT_CONFIG(vulkan) + mutable QCocoaVulkanInstance *mCocoaVulkanInstance = nullptr; +#endif QHash<QWindow *, NSToolbar *> mToolbars; QList<QCocoaWindow *> m_popupWindowStack; }; diff --git a/src/plugins/platforms/cocoa/qcocoaintegration.mm b/src/plugins/platforms/cocoa/qcocoaintegration.mm index 55b3805df3..612290c9bd 100644 --- a/src/plugins/platforms/cocoa/qcocoaintegration.mm +++ b/src/plugins/platforms/cocoa/qcocoaintegration.mm @@ -59,6 +59,8 @@ #include <qpa/qplatformoffscreensurface.h> #include <QtCore/qcoreapplication.h> +#include <QtPlatformHeaders/qcocoanativecontext.h> + #include <QtGui/private/qcoregraphics_p.h> #ifdef QT_WIDGETS_LIB @@ -94,11 +96,11 @@ static QCocoaIntegration::Options parseOptions(const QStringList ¶mList) return options; } -QCocoaIntegration *QCocoaIntegration::mInstance = 0; +QCocoaIntegration *QCocoaIntegration::mInstance = nullptr; QCocoaIntegration::QCocoaIntegration(const QStringList ¶mList) : mOptions(parseOptions(paramList)) - , mFontDb(0) + , mFontDb(nullptr) #ifndef QT_NO_ACCESSIBILITY , mAccessibility(new QCocoaAccessibility) #endif @@ -110,7 +112,7 @@ QCocoaIntegration::QCocoaIntegration(const QStringList ¶mList) , mServices(new QCocoaServices) , mKeyboardMapper(new QCocoaKeyMapper) { - if (mInstance != 0) + if (mInstance) qWarning("Creating multiple Cocoa platform integrations is not supported"); mInstance = this; @@ -186,7 +188,7 @@ QCocoaIntegration::QCocoaIntegration(const QStringList ¶mList) QCocoaIntegration::~QCocoaIntegration() { - mInstance = 0; + mInstance = nullptr; qt_resetNSApplicationSendEvent(); @@ -196,7 +198,7 @@ QCocoaIntegration::~QCocoaIntegration() QCocoaApplicationDelegate *delegate = [QCocoaApplicationDelegate sharedDelegate]; [delegate removeAppleEventHandlers]; // reset the application delegate - [[NSApplication sharedApplication] setDelegate: 0]; + [[NSApplication sharedApplication] setDelegate:nil]; } #ifndef QT_NO_CLIPBOARD @@ -225,15 +227,13 @@ QCocoaIntegration::Options QCocoaIntegration::options() const return mOptions; } -Q_LOGGING_CATEGORY(lcCocoaScreen, "qt.qpa.cocoa.screens"); - /*! \brief Synchronizes the screen list, adds new screens, removes deleted ones */ void QCocoaIntegration::updateScreens() { - NSArray *scrs = [NSScreen screens]; - NSMutableArray *screens = [NSMutableArray arrayWithArray:scrs]; + NSArray<NSScreen *> *scrs = [NSScreen screens]; + NSMutableArray<NSScreen *> *screens = [NSMutableArray<NSScreen *> arrayWithArray:scrs]; if ([screens count] == 0) if ([NSScreen mainScreen]) [screens addObject:[NSScreen mainScreen]]; @@ -244,7 +244,7 @@ void QCocoaIntegration::updateScreens() uint screenCount = [screens count]; for (uint i = 0; i < screenCount; i++) { NSScreen* scr = [screens objectAtIndex:i]; - CGDirectDisplayID dpy = [[[scr deviceDescription] objectForKey:@"NSScreenNumber"] unsignedIntValue]; + CGDirectDisplayID dpy = scr.qt_displayId; // If this screen is a mirror and is not the primary one of the mirror set, ignore it. // Exception: The NSScreen API has been observed to a return a screen list with one // mirrored, non-primary screen when Qt is running as a startup item. Always use the @@ -254,8 +254,8 @@ void QCocoaIntegration::updateScreens() if (primary != kCGNullDirectDisplay && primary != dpy) continue; } - QCocoaScreen* screen = NULL; - foreach (QCocoaScreen* existingScr, mScreens) + QCocoaScreen* screen = nullptr; + foreach (QCocoaScreen* existingScr, mScreens) { // NSScreen documentation says do not cache the array returned from [NSScreen screens]. // However in practice, we can identify a screen by its pointer: if resolution changes, // the NSScreen object will be the same instance, just with different values. @@ -263,14 +263,14 @@ void QCocoaIntegration::updateScreens() screen = existingScr; break; } + } if (screen) { remainingScreens.remove(screen); - screen->updateGeometry(); - qCDebug(lcCocoaScreen) << "Updated properties of" << screen; + screen->updateProperties(); } else { screen = new QCocoaScreen(i); mScreens.append(screen); - qCDebug(lcCocoaScreen) << "Adding" << screen; + qCDebug(lcQpaScreen) << "Adding" << screen; screenAdded(screen); } siblings << screen; @@ -287,7 +287,7 @@ void QCocoaIntegration::updateScreens() mScreens.removeOne(screen); // Prevent stale references to NSScreen during destroy screen->m_screenIndex = -1; - qCDebug(lcCocoaScreen) << "Removing" << screen; + qCDebug(lcQpaScreen) << "Removing" << screen; destroyScreen(screen); } } @@ -296,7 +296,7 @@ QCocoaScreen *QCocoaIntegration::screenForNSScreen(NSScreen *nsScreen) { NSUInteger index = [[NSScreen screens] indexOfObject:nsScreen]; if (index == NSNotFound) - return 0; + return nullptr; if (index >= unsigned(mScreens.count())) updateScreens(); @@ -306,7 +306,7 @@ QCocoaScreen *QCocoaIntegration::screenForNSScreen(NSScreen *nsScreen) return screen; } - return 0; + return nullptr; } bool QCocoaIntegration::hasCapability(QPlatformIntegration::Capability cap) const @@ -361,10 +361,8 @@ QPlatformOffscreenSurface *QCocoaIntegration::createPlatformOffscreenSurface(QOf #ifndef QT_NO_OPENGL QPlatformOpenGLContext *QCocoaIntegration::createPlatformOpenGLContext(QOpenGLContext *context) const { - QCocoaGLContext *glContext = new QCocoaGLContext(context->format(), - context->shareHandle(), - context->nativeHandle()); - context->setNativeHandle(glContext->nativeHandle()); + QCocoaGLContext *glContext = new QCocoaGLContext(context); + context->setNativeHandle(QVariant::fromValue<QCocoaNativeContext>(glContext->nativeContext())); return glContext; } #endif @@ -379,6 +377,19 @@ QAbstractEventDispatcher *QCocoaIntegration::createEventDispatcher() const return new QCocoaEventDispatcher; } +#if QT_CONFIG(vulkan) +QPlatformVulkanInstance *QCocoaIntegration::createPlatformVulkanInstance(QVulkanInstance *instance) const +{ + mCocoaVulkanInstance = new QCocoaVulkanInstance(instance); + return mCocoaVulkanInstance; +} + +QCocoaVulkanInstance *QCocoaIntegration::getCocoaVulkanInstance() const +{ + return mCocoaVulkanInstance; +} +#endif + QCoreTextFontDatabase *QCocoaIntegration::fontDatabase() const { return mFontDb.data(); @@ -480,14 +491,14 @@ void QCocoaIntegration::pushPopupWindow(QCocoaWindow *window) QCocoaWindow *QCocoaIntegration::popPopupWindow() { if (m_popupWindowStack.isEmpty()) - return 0; + return nullptr; return m_popupWindowStack.takeLast(); } QCocoaWindow *QCocoaIntegration::activePopupWindow() const { if (m_popupWindowStack.isEmpty()) - return 0; + return nullptr; return m_popupWindowStack.front(); } diff --git a/src/plugins/platforms/cocoa/qcocoaintrospection.h b/src/plugins/platforms/cocoa/qcocoaintrospection.h index 1d984b9857..20001ac71d 100644 --- a/src/plugins/platforms/cocoa/qcocoaintrospection.h +++ b/src/plugins/platforms/cocoa/qcocoaintrospection.h @@ -76,7 +76,7 @@ QT_BEGIN_NAMESPACE -void qt_cocoa_change_implementation(Class baseClass, SEL originalSel, Class proxyClass, SEL replacementSel = 0, SEL backupSel = 0); +void qt_cocoa_change_implementation(Class baseClass, SEL originalSel, Class proxyClass, SEL replacementSel = nil, SEL backupSel = nil); void qt_cocoa_change_back_implementation(Class baseClass, SEL originalSel, SEL backupSel); QT_END_NAMESPACE diff --git a/src/plugins/platforms/cocoa/qcocoakeymapper.mm b/src/plugins/platforms/cocoa/qcocoakeymapper.mm index 80140505d1..5e279a400b 100644 --- a/src/plugins/platforms/cocoa/qcocoakeymapper.mm +++ b/src/plugins/platforms/cocoa/qcocoakeymapper.mm @@ -352,22 +352,22 @@ QCocoaKeyMapper::~QCocoaKeyMapper() Qt::KeyboardModifiers QCocoaKeyMapper::queryKeyboardModifiers() { - return qt_mac_get_modifiers(GetCurrentEventKeyModifiers()); + return qt_mac_get_modifiers(GetCurrentKeyModifiers()); } bool QCocoaKeyMapper::updateKeyboard() { - const UCKeyboardLayout *uchrData = 0; + const UCKeyboardLayout *uchrData = nullptr; QCFType<TISInputSourceRef> source = TISCopyInputMethodKeyboardLayoutOverride(); if (!source) source = TISCopyCurrentKeyboardInputSource(); if (keyboard_mode != NullMode && source == currentInputSource) { return false; } - Q_ASSERT(source != 0); + Q_ASSERT(source); CFDataRef data = static_cast<CFDataRef>(TISGetInputSourceProperty(source, kTISPropertyUnicodeKeyLayoutData)); - uchrData = data ? reinterpret_cast<const UCKeyboardLayout *>(CFDataGetBytePtr(data)) : 0; + uchrData = data ? reinterpret_cast<const UCKeyboardLayout *>(CFDataGetBytePtr(data)) : nullptr; keyboard_kind = LMGetKbdType(); if (uchrData) { @@ -386,7 +386,7 @@ void QCocoaKeyMapper::deleteLayouts() for (int i = 0; i < 255; ++i) { if (keyLayout[i]) { delete keyLayout[i]; - keyLayout[i] = 0; + keyLayout[i] = nullptr; } } } diff --git a/src/plugins/platforms/cocoa/qcocoamenu.h b/src/plugins/platforms/cocoa/qcocoamenu.h index 6db4e04c61..34d8428188 100644 --- a/src/plugins/platforms/cocoa/qcocoamenu.h +++ b/src/plugins/platforms/cocoa/qcocoamenu.h @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2018 The Qt Company Ltd. ** Copyright (C) 2012 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author James Turner <james.turner@kdab.com> ** Contact: https://www.qt.io/licensing/ ** diff --git a/src/plugins/platforms/cocoa/qcocoamenu.mm b/src/plugins/platforms/cocoa/qcocoamenu.mm index a788ac7d45..7b96fca3f9 100644 --- a/src/plugins/platforms/cocoa/qcocoamenu.mm +++ b/src/plugins/platforms/cocoa/qcocoamenu.mm @@ -1,5 +1,6 @@ /**************************************************************************** ** +** Copyright (C) 2018 The Qt Company Ltd. ** Copyright (C) 2012 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author James Turner <james.turner@kdab.com> ** Contact: https://www.qt.io/licensing/ ** @@ -53,7 +54,7 @@ QT_BEGIN_NAMESPACE QCocoaMenu::QCocoaMenu() : - m_attachedItem(0), + m_attachedItem(nil), m_updateTimer(0), m_enabled(true), m_parentEnabled(true), @@ -62,14 +63,14 @@ QCocoaMenu::QCocoaMenu() : { QMacAutoReleasePool pool; - m_nativeMenu = [[QCocoaNSMenu alloc] initWithQPAMenu:this]; + m_nativeMenu = [[QCocoaNSMenu alloc] initWithPlatformMenu:this]; } QCocoaMenu::~QCocoaMenu() { - foreach (QCocoaMenuItem *item, m_menuItems) { + for (auto *item : qAsConst(m_menuItems)) { if (item->menuParent() == this) - item->setMenuParent(0); + item->setMenuParent(nullptr); } [m_nativeMenu release]; @@ -79,7 +80,7 @@ void QCocoaMenu::setText(const QString &text) { QMacAutoReleasePool pool; QString stripped = qt_mac_removeAmpersandEscapes(text); - [m_nativeMenu setTitle:stripped.toNSString()]; + m_nativeMenu.title = stripped.toNSString(); } void QCocoaMenu::setMinimumWidth(int width) @@ -132,7 +133,7 @@ void QCocoaMenu::insertMenuItem(QPlatformMenuItem *menuItem, QPlatformMenuItem * void QCocoaMenu::insertNative(QCocoaMenuItem *item, QCocoaMenuItem *beforeItem) { - setItemTargetAction(item); + item->resolveTargetAction(); NSMenuItem *nativeItem = item->nsItem(); // Someone's adding new items after aboutToShow() was emitted if (isOpen() && nativeItem && item->menu()) @@ -187,25 +188,25 @@ void QCocoaMenu::removeMenuItem(QPlatformMenuItem *menuItem) } if (cocoaItem->menuParent() == this) - cocoaItem->setMenuParent(0); + cocoaItem->setMenuParent(nullptr); // Ignore any parent enabled state cocoaItem->setParentEnabled(true); m_menuItems.removeOne(cocoaItem); if (!cocoaItem->isMerged()) { - if (m_nativeMenu != [cocoaItem->nsItem() menu]) { + if (m_nativeMenu != cocoaItem->nsItem().menu) { qWarning("Item to remove does not belong to this menu"); return; } - [m_nativeMenu removeItem: cocoaItem->nsItem()]; + [m_nativeMenu removeItem:cocoaItem->nsItem()]; } } QCocoaMenuItem *QCocoaMenu::itemOrNull(int index) const { if ((index < 0) || (index >= m_menuItems.size())) - return 0; + return nullptr; return m_menuItems.at(index); } @@ -247,8 +248,8 @@ void QCocoaMenu::syncMenuItem_helper(QPlatformMenuItem *menuItem, bool menubarUp // native item was changed for some reason if (oldItem) { if (wasMerged) { - [oldItem setEnabled:NO]; - [oldItem setHidden:YES]; + oldItem.enabled = NO; + oldItem.hidden = YES; } else { [m_nativeMenu removeItem:oldItem]; } @@ -278,31 +279,27 @@ void QCocoaMenu::syncSeparatorsCollapsible(bool enable) bool previousIsSeparator = true; // setting to true kills all the separators placed at the top. NSMenuItem *previousItem = nil; - NSArray *itemArray = [m_nativeMenu itemArray]; - for (unsigned int i = 0; i < [itemArray count]; ++i) { - NSMenuItem *item = reinterpret_cast<NSMenuItem *>([itemArray objectAtIndex:i]); - if ([item isSeparatorItem]) { - QCocoaMenuItem *cocoaItem = reinterpret_cast<QCocoaMenuItem *>([item tag]); - if (cocoaItem) + for (NSMenuItem *item in m_nativeMenu.itemArray) { + if (item.separatorItem) { + if (auto *cocoaItem = qt_objc_cast<QCocoaNSMenuItem *>(item).platformMenuItem) cocoaItem->setVisible(!previousIsSeparator); - [item setHidden:previousIsSeparator]; + item.hidden = previousIsSeparator; } - if (![item isHidden]) { + if (!item.hidden) { previousItem = item; - previousIsSeparator = ([previousItem isSeparatorItem]); + previousIsSeparator = previousItem.separatorItem; } } // We now need to check the final item since we don't want any separators at the end of the list. if (previousItem && previousIsSeparator) { - QCocoaMenuItem *cocoaItem = reinterpret_cast<QCocoaMenuItem *>([previousItem tag]); - if (cocoaItem) + if (auto *cocoaItem = qt_objc_cast<QCocoaNSMenuItem *>(previousItem).platformMenuItem) cocoaItem->setVisible(false); - [previousItem setHidden:YES]; + previousItem.hidden = YES; } } else { - foreach (QCocoaMenuItem *item, m_menuItems) { + for (auto *item : qAsConst(m_menuItems)) { if (!item->isSeparator()) continue; @@ -324,7 +321,7 @@ void QCocoaMenu::setEnabled(bool enabled) bool QCocoaMenu::isEnabled() const { - return m_attachedItem ? [m_attachedItem isEnabled] : m_enabled && m_parentEnabled; + return m_attachedItem ? m_attachedItem.enabled : m_enabled && m_parentEnabled; } void QCocoaMenu::setVisible(bool visible) @@ -337,11 +334,11 @@ void QCocoaMenu::showPopup(const QWindow *parentWindow, const QRect &targetRect, QMacAutoReleasePool pool; QPoint pos = QPoint(targetRect.left(), targetRect.top() + targetRect.height()); - QCocoaWindow *cocoaWindow = parentWindow ? static_cast<QCocoaWindow *>(parentWindow->handle()) : 0; + QCocoaWindow *cocoaWindow = parentWindow ? static_cast<QCocoaWindow *>(parentWindow->handle()) : nullptr; NSView *view = cocoaWindow ? cocoaWindow->view() : nil; NSMenuItem *nsItem = item ? ((QCocoaMenuItem *)item)->nsItem() : nil; - QScreen *screen = 0; + QScreen *screen = nullptr; if (parentWindow) screen = parentWindow->screen(); if (!screen && !QGuiApplication::screens().isEmpty()) @@ -360,9 +357,9 @@ void QCocoaMenu::showPopup(const QWindow *parentWindow, const QRect &targetRect, // respect the menu's minimum width. NSPopUpButtonCell *popupCell = [[[NSPopUpButtonCell alloc] initTextCell:@"" pullsDown:NO] autorelease]; - [popupCell setAltersStateOfSelectedItem:NO]; - [popupCell setTransparent:YES]; - [popupCell setMenu:m_nativeMenu]; + popupCell.altersStateOfSelectedItem = NO; + popupCell.transparent = YES; + popupCell.menu = m_nativeMenu; [popupCell selectItem:nsItem]; QCocoaScreen *cocoaScreen = static_cast<QCocoaScreen *>(screen->handle()); @@ -394,7 +391,7 @@ void QCocoaMenu::showPopup(const QWindow *parentWindow, const QRect &targetRect, if (view) { // Finally, we need to synthesize an event. - NSEvent *menuEvent = [NSEvent mouseEventWithType:NSRightMouseDown + NSEvent *menuEvent = [NSEvent mouseEventWithType:NSEventTypeRightMouseDown location:nsPos modifierFlags:0 timestamp:0 @@ -405,7 +402,7 @@ void QCocoaMenu::showPopup(const QWindow *parentWindow, const QRect &targetRect, pressure:1.0]; [NSMenu popUpContextMenu:m_nativeMenu withEvent:menuEvent forView:view]; } else { - [m_nativeMenu popUpMenuPositioningItem:nsItem atLocation:nsPos inView:0]; + [m_nativeMenu popUpMenuPositioningItem:nsItem atLocation:nsPos inView:nil]; } } @@ -425,17 +422,17 @@ QPlatformMenuItem *QCocoaMenu::menuItemAt(int position) const if (0 <= position && position < m_menuItems.count()) return m_menuItems.at(position); - return 0; + return nullptr; } QPlatformMenuItem *QCocoaMenu::menuItemForTag(quintptr tag) const { - foreach (QCocoaMenuItem *item, m_menuItems) { + for (auto *item : qAsConst(m_menuItems)) { if (item->tag() == tag) return item; } - return 0; + return nullptr; } QList<QCocoaMenuItem *> QCocoaMenu::items() const @@ -446,7 +443,7 @@ QList<QCocoaMenuItem *> QCocoaMenu::items() const QList<QCocoaMenuItem *> QCocoaMenu::merged() const { QList<QCocoaMenuItem *> result; - foreach (QCocoaMenuItem *item, m_menuItems) { + for (auto *item : qAsConst(m_menuItems)) { if (item->menu()) { // recurse into submenus result.append(item->menu()->merged()); continue; @@ -467,7 +464,7 @@ void QCocoaMenu::propagateEnabledState(bool enabled) if (!m_enabled && enabled) // Some ancestor was enabled, but this menu is not return; - foreach (QCocoaMenuItem *item, m_menuItems) { + for (auto *item : qAsConst(m_menuItems)) { if (QCocoaMenu *menu = item->menu()) menu->propagateEnabledState(enabled); else @@ -495,11 +492,4 @@ NSMenuItem *QCocoaMenu::attachedItem() const return m_attachedItem; } -void QCocoaMenu::setItemTargetAction(QCocoaMenuItem *item) const -{ - auto *nsItem = item->nsItem(); - nsItem.target = m_nativeMenu; - nsItem.action = @selector(qt_itemFired:); -} - QT_END_NAMESPACE diff --git a/src/plugins/platforms/cocoa/qcocoamenubar.h b/src/plugins/platforms/cocoa/qcocoamenubar.h index 6f3aca3a51..50b6e69720 100644 --- a/src/plugins/platforms/cocoa/qcocoamenubar.h +++ b/src/plugins/platforms/cocoa/qcocoamenubar.h @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2018 The Qt Company Ltd. ** Copyright (C) 2012 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author James Turner <james.turner@kdab.com> ** Contact: https://www.qt.io/licensing/ ** @@ -60,17 +60,16 @@ public: void removeMenu(QPlatformMenu *menu) override; void syncMenu(QPlatformMenu *menuItem) override; void handleReparent(QWindow *newParentWindow) override; + QWindow *parentWindow() const override; QPlatformMenu *menuForTag(quintptr tag) const override; inline NSMenu *nsMenu() const { return m_nativeMenu; } - static void redirectKnownMenuItemsToFirstResponder(); - static void resetKnownMenuItemsToQt(); static void updateMenuBarImmediately(); QList<QCocoaMenuItem*> merged() const; - NSMenuItem *itemForRole(QPlatformMenuItem::MenuRole r); + NSMenuItem *itemForRole(QPlatformMenuItem::MenuRole role); QCocoaWindow *cocoaWindow() const; void syncMenu_helper(QPlatformMenu *menu, bool menubarUpdate); diff --git a/src/plugins/platforms/cocoa/qcocoamenubar.mm b/src/plugins/platforms/cocoa/qcocoamenubar.mm index 61ac5eb7f0..30bff78a36 100644 --- a/src/plugins/platforms/cocoa/qcocoamenubar.mm +++ b/src/plugins/platforms/cocoa/qcocoamenubar.mm @@ -1,5 +1,6 @@ /**************************************************************************** ** +** Copyright (C) 2018 The Qt Company Ltd. ** Copyright (C) 2012 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author James Turner <james.turner@kdab.com> ** Contact: https://www.qt.io/licensing/ ** @@ -67,7 +68,7 @@ QCocoaMenuBar::~QCocoaMenuBar() #ifdef QT_COCOA_ENABLE_MENU_DEBUG qDebug() << "~QCocoaMenuBar" << this; #endif - foreach (QCocoaMenu *menu, m_menus) { + for (auto menu : qAsConst(m_menus)) { if (!menu) continue; NSMenuItem *item = nativeItemForMenu(menu); @@ -79,14 +80,13 @@ QCocoaMenuBar::~QCocoaMenuBar() static_menubars.removeOne(this); if (!m_window.isNull() && m_window->menubar() == this) { - m_window->setMenubar(0); + m_window->setMenubar(nullptr); // Delete the children first so they do not cause // the native menu items to be hidden after // the menu bar was updated qDeleteAll(children()); updateMenuBarImmediately(); - resetKnownMenuItemsToQt(); } } @@ -199,8 +199,8 @@ void QCocoaMenuBar::syncMenu_helper(QPlatformMenu *menu, bool menubarUpdate) // If the NSMenu has no visble items, or only separators, we should hide it // on the menubar. This can happen after syncing the menu items since they // can be moved to other menus. - for (NSMenuItem *item in [cocoaMenu->nsMenu() itemArray]) - if (![item isSeparatorItem] && ![item isHidden]) { + for (NSMenuItem *item in cocoaMenu->nsMenu().itemArray) + if (!item.separatorItem && !item.hidden) { shouldHide = NO; break; } @@ -230,7 +230,7 @@ void QCocoaMenuBar::handleReparent(QWindow *newParentWindow) if (!m_window.isNull()) m_window->setMenubar(nullptr); - if (newParentWindow == nullptr) { + if (!newParentWindow) { m_window.clear(); } else { newParentWindow->create(); @@ -241,82 +241,28 @@ void QCocoaMenuBar::handleReparent(QWindow *newParentWindow) updateMenuBarImmediately(); } +QWindow *QCocoaMenuBar::parentWindow() const +{ + return m_window ? m_window->window() : nullptr; +} + + QCocoaWindow *QCocoaMenuBar::findWindowForMenubar() { if (qApp->focusWindow()) return static_cast<QCocoaWindow*>(qApp->focusWindow()->handle()); - return NULL; + return nullptr; } QCocoaMenuBar *QCocoaMenuBar::findGlobalMenubar() { - foreach (QCocoaMenuBar *mb, static_menubars) { - if (mb->m_window.isNull()) - return mb; + for (auto *menubar : qAsConst(static_menubars)) { + if (menubar->m_window.isNull()) + return menubar; } - return NULL; -} - -void QCocoaMenuBar::redirectKnownMenuItemsToFirstResponder() -{ - // QTBUG-17291: http://forums.macrumors.com/showthread.php?t=1249452 - // When a dialog is opened, shortcuts for actions inside the dialog (cut, paste, ...) - // continue to go through the same menu items which claimed those shortcuts. - // They are not keystrokes which we can intercept in any other way; the OS intercepts them. - // The menu items had to be created by the application. That's why we need roles - // to identify those "special" menu items which can be useful even when non-Qt - // native widgets are in focus. When the native widget is focused it will be the - // first responder, so the menu item needs to have its target be the first responder; - // this is done by setting it to nil. - - // This function will find all menu items on all menus which have - // "special" roles, set the target and also set the standard actions which - // apply to those roles. But afterwards it is necessary to call - // resetKnownMenuItemsToQt() to put back the target and action so that - // those menu items will go back to invoking their associated QActions. - foreach (QCocoaMenuBar *mb, static_menubars) - foreach (QCocoaMenu *m, mb->m_menus) - foreach (QCocoaMenuItem *i, m->items()) { - bool known = true; - switch (i->effectiveRole()) { - case QPlatformMenuItem::CutRole: - [i->nsItem() setAction:@selector(cut:)]; - break; - case QPlatformMenuItem::CopyRole: - [i->nsItem() setAction:@selector(copy:)]; - break; - case QPlatformMenuItem::PasteRole: - [i->nsItem() setAction:@selector(paste:)]; - break; - case QPlatformMenuItem::SelectAllRole: - [i->nsItem() setAction:@selector(selectAll:)]; - break; - // We may discover later that there are other roles/actions which - // are meaningful to standard native widgets; they can be added. - default: - known = false; - break; - } - if (known) - [i->nsItem() setTarget:nil]; - } -} - -void QCocoaMenuBar::resetKnownMenuItemsToQt() -{ - // Undo the effect of redirectKnownMenuItemsToFirstResponder(): - // reset the menu items' target/action. - foreach (QCocoaMenuBar *mb, static_menubars) { - foreach (QCocoaMenu *m, mb->m_menus) { - foreach (QCocoaMenuItem *i, m->items()) { - if (i->effectiveRole() >= QPlatformMenuItem::ApplicationSpecificRole) { - m->setItemTargetAction(i); - } - } - } - } + return nullptr; } void QCocoaMenuBar::updateMenuBarImmediately() @@ -325,7 +271,7 @@ void QCocoaMenuBar::updateMenuBarImmediately() QCocoaMenuBar *mb = findGlobalMenubar(); QCocoaWindow *cw = findWindowForMenubar(); - QWindow *win = cw ? cw->window() : 0; + QWindow *win = cw ? cw->window() : nullptr; if (win && (win->flags() & Qt::Popup) == Qt::Popup) { // context menus, comboboxes, etc. don't need to update the menubar, // but if an application has only Qt::Tool window(s) on start, @@ -353,7 +299,7 @@ void QCocoaMenuBar::updateMenuBarImmediately() #endif bool disableForModal = mb->shouldDisable(cw); - foreach (QCocoaMenu *menu, mb->m_menus) { + for (auto menu : qAsConst(mb->m_menus)) { if (!menu) continue; NSMenuItem *item = mb->nativeItemForMenu(menu); @@ -368,16 +314,16 @@ void QCocoaMenuBar::updateMenuBarImmediately() [loader ensureAppMenuInMenu:mb->nsMenu()]; NSMutableSet *mergedItems = [[NSMutableSet setWithCapacity:mb->merged().count()] retain]; - foreach (QCocoaMenuItem *m, mb->merged()) { - [mergedItems addObject:m->nsItem()]; - m->syncMerged(); + for (auto mergedItem : mb->merged()) { + [mergedItems addObject:mergedItem->nsItem()]; + mergedItem->syncMerged(); } // hide+disable all mergeable items we're not currently using for (NSMenuItem *mergeable in [loader mergeable]) { if (![mergedItems containsObject:mergeable]) { - [mergeable setHidden:YES]; - [mergeable setEnabled:NO]; + mergeable.hidden = YES; + mergeable.enabled = NO; } } @@ -389,7 +335,7 @@ void QCocoaMenuBar::updateMenuBarImmediately() QList<QCocoaMenuItem*> QCocoaMenuBar::merged() const { QList<QCocoaMenuItem*> r; - foreach (QCocoaMenu* menu, m_menus) + for (auto menu : qAsConst(m_menus)) r.append(menu->merged()); return r; @@ -409,11 +355,11 @@ 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. - foreach (QWindow *w, topWindows) { - if (w->isVisible() && w->modality() == Qt::ApplicationModal) { + for (auto *window : qAsConst(topWindows)) { + if (window->isVisible() && window->modality() == Qt::ApplicationModal) { // check for other visible windows - foreach (QWindow *other, topWindows) { - if ((w != other) && (other->isVisible())) { + for (auto *other : qAsConst(topWindows)) { + if ((window != other) && (other->isVisible())) { // INVARIANT: we found another visible window // on screen other than our modalWidget. We therefore // disable the menu bar to follow normal modality logic: @@ -433,21 +379,21 @@ bool QCocoaMenuBar::shouldDisable(QCocoaWindow *active) const QPlatformMenu *QCocoaMenuBar::menuForTag(quintptr tag) const { - foreach (QCocoaMenu *menu, m_menus) { + for (auto menu : qAsConst(m_menus)) if (menu->tag() == tag) return menu; - } - return 0; + return nullptr; } -NSMenuItem *QCocoaMenuBar::itemForRole(QPlatformMenuItem::MenuRole r) +NSMenuItem *QCocoaMenuBar::itemForRole(QPlatformMenuItem::MenuRole role) { - foreach (QCocoaMenu *m, m_menus) - foreach (QCocoaMenuItem *i, m->items()) - if (i->effectiveRole() == r) - return i->nsItem(); - return nullptr; + for (auto menu : qAsConst(m_menus)) + for (auto *item : menu->items()) + if (item->effectiveRole() == role) + return item->nsItem(); + + return nil; } QCocoaWindow *QCocoaMenuBar::cocoaWindow() const diff --git a/src/plugins/platforms/cocoa/qcocoamenuitem.h b/src/plugins/platforms/cocoa/qcocoamenuitem.h index 2b598ee3a0..20fc741fb8 100644 --- a/src/plugins/platforms/cocoa/qcocoamenuitem.h +++ b/src/plugins/platforms/cocoa/qcocoamenuitem.h @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2018 The Qt Company Ltd. ** Copyright (C) 2012 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author James Turner <james.turner@kdab.com> ** Contact: https://www.qt.io/licensing/ ** @@ -108,6 +108,7 @@ public: QCocoaMenu *menu() const { return m_menu; } MenuRole effectiveRole() const; + void resolveTargetAction(); private: QString mergeText(); diff --git a/src/plugins/platforms/cocoa/qcocoamenuitem.mm b/src/plugins/platforms/cocoa/qcocoamenuitem.mm index f8f9648822..21faa6d985 100644 --- a/src/plugins/platforms/cocoa/qcocoamenuitem.mm +++ b/src/plugins/platforms/cocoa/qcocoamenuitem.mm @@ -1,5 +1,6 @@ /**************************************************************************** ** +** Copyright (C) 2018 The Qt Company Ltd. ** Copyright (C) 2012 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author James Turner <james.turner@kdab.com> ** Contact: https://www.qt.io/licensing/ ** @@ -41,6 +42,7 @@ #include "qcocoamenuitem.h" +#include "qcocoansmenu.h" #include "qcocoamenu.h" #include "qcocoamenubar.h" #include "messages.h" @@ -49,7 +51,6 @@ #include "qcocoaapplication.h" // for custom application category #include "qcocoamenuloader.h" #include <QtGui/private/qcoregraphics_p.h> -#include <QtCore/qregularexpression.h> #include <QtCore/QDebug> @@ -60,13 +61,13 @@ static quint32 constructModifierMask(quint32 accel_key) quint32 ret = 0; const bool dontSwap = qApp->testAttribute(Qt::AA_MacDontSwapCtrlAndMeta); if ((accel_key & Qt::CTRL) == Qt::CTRL) - ret |= (dontSwap ? NSControlKeyMask : NSCommandKeyMask); + ret |= (dontSwap ? NSEventModifierFlagControl : NSEventModifierFlagCommand); if ((accel_key & Qt::META) == Qt::META) - ret |= (dontSwap ? NSCommandKeyMask : NSControlKeyMask); + ret |= (dontSwap ? NSEventModifierFlagCommand : NSEventModifierFlagControl); if ((accel_key & Qt::ALT) == Qt::ALT) - ret |= NSAlternateKeyMask; + ret |= NSEventModifierFlagOption; if ((accel_key & Qt::SHIFT) == Qt::SHIFT) - ret |= NSShiftKeyMask; + ret |= NSEventModifierFlagShift; return ret; } @@ -92,9 +93,9 @@ NSUInteger keySequenceModifierMask(const QKeySequence &accel) #endif QCocoaMenuItem::QCocoaMenuItem() : - m_native(NULL), + m_native(nil), m_itemView(nil), - m_menu(NULL), + m_menu(nullptr), m_role(NoRole), m_iconSize(16), m_textSynced(false), @@ -112,9 +113,9 @@ QCocoaMenuItem::~QCocoaMenuItem() QMacAutoReleasePool pool; if (m_menu && m_menu->menuParent() == this) - m_menu->setMenuParent(0); + m_menu->setMenuParent(nullptr); if (m_merged) { - [m_native setHidden:YES]; + m_native.hidden = YES; } else { if (m_menu && m_menu->attachedItem() == m_native) m_menu->setAttachedItem(nil); @@ -140,7 +141,7 @@ void QCocoaMenuItem::setMenu(QPlatformMenu *menu) return; if (m_menu && m_menu->menuParent() == this) { - m_menu->setMenuParent(0); + m_menu->setMenuParent(nullptr); // Free the menu from its parent's influence m_menu->propagateEnabledState(true); if (m_native && m_menu->attachedItem() == m_native) @@ -210,82 +211,75 @@ void QCocoaMenuItem::setNativeContents(WId item) return; [m_itemView release]; m_itemView = [itemView retain]; - [m_itemView setAutoresizesSubviews:YES]; - [m_itemView setAutoresizingMask:NSViewWidthSizable]; - [m_itemView setHidden:NO]; - [m_itemView setNeedsDisplay:YES]; + m_itemView.autoresizesSubviews = YES; + m_itemView.autoresizingMask = NSViewWidthSizable; + m_itemView.hidden = NO; + m_itemView.needsDisplay = YES; } NSMenuItem *QCocoaMenuItem::sync() { - if (m_isSeparator != [m_native isSeparatorItem]) { + if (m_isSeparator != m_native.separatorItem) { [m_native release]; - if (m_isSeparator) { - m_native = [[NSMenuItem separatorItem] retain]; - [m_native setTag:reinterpret_cast<NSInteger>(this)]; - } else + if (m_isSeparator) + m_native = [[QCocoaNSMenuItem separatorItemWithPlatformMenuItem:this] retain]; + else m_native = nil; } if ((m_role != NoRole && !m_textSynced) || m_merged) { - NSMenuItem *mergeItem = nil; + QCocoaMenuBar *menubar = nullptr; + if (m_role == TextHeuristicRole) { + // Recognized menu roles are only found in the first menus below the menubar + QObject *p = menuParent(); + int depth = 1; + while (depth < 3 && p && !(menubar = qobject_cast<QCocoaMenuBar *>(p))) { + ++depth; + QCocoaMenuObject *menuObject = dynamic_cast<QCocoaMenuObject *>(p); + Q_ASSERT(menuObject); + p = menuObject->menuParent(); + } + + if (menubar && depth < 3) + m_detectedRole = detectMenuRole(m_text); + else + m_detectedRole = NoRole; + } + QCocoaMenuLoader *loader = [QCocoaMenuLoader sharedMenuLoader]; - switch (m_role) { - case ApplicationSpecificRole: - mergeItem = [loader appSpecificMenuItem:reinterpret_cast<NSInteger>(this)]; - break; + NSMenuItem *mergeItem = nil; + const auto role = effectiveRole(); + switch (role) { case AboutRole: mergeItem = [loader aboutMenuItem]; break; case AboutQtRole: mergeItem = [loader aboutQtMenuItem]; break; + case PreferencesRole: + mergeItem = [loader preferencesMenuItem]; + break; + case ApplicationSpecificRole: + mergeItem = [loader appSpecificMenuItem:this]; + break; case QuitRole: mergeItem = [loader quitMenuItem]; break; - case PreferencesRole: - mergeItem = [loader preferencesMenuItem]; + case CutRole: + case CopyRole: + case PasteRole: + case SelectAllRole: + if (menubar) + mergeItem = menubar->itemForRole(role); break; - case TextHeuristicRole: { - QObject *p = menuParent(); - int depth = 1; - QCocoaMenuBar *menubar = 0; - while (depth < 3 && p && !(menubar = qobject_cast<QCocoaMenuBar *>(p))) { - ++depth; - QCocoaMenuObject *menuObject = dynamic_cast<QCocoaMenuObject *>(p); - Q_ASSERT(menuObject); - p = menuObject->menuParent(); - } - if (depth == 3 || !menubar) - break; // Menu item too deep in the hierarchy, or not connected to any menubar - - m_detectedRole = detectMenuRole(m_text); - switch (m_detectedRole) { - case QPlatformMenuItem::AboutRole: - if (m_text.indexOf(QRegularExpression(QString::fromLatin1("qt$"), - QRegularExpression::CaseInsensitiveOption)) == -1) - mergeItem = [loader aboutMenuItem]; - else - mergeItem = [loader aboutQtMenuItem]; - break; - case QPlatformMenuItem::PreferencesRole: - mergeItem = [loader preferencesMenuItem]; - break; - case QPlatformMenuItem::QuitRole: - mergeItem = [loader quitMenuItem]; - break; - default: - if (m_detectedRole >= CutRole && m_detectedRole < RoleCount && menubar) - mergeItem = menubar->itemForRole(m_detectedRole); - if (!m_text.isEmpty()) - m_textSynced = true; - break; - } + case NoRole: + // The heuristic couldn't resolve the menu role + m_textSynced = false; break; - } - default: - qWarning() << "Menu item" << m_text << "has unsupported role" << m_role; + if (!m_text.isEmpty()) + m_textSynced = true; + break; } if (mergeItem) { @@ -294,7 +288,8 @@ NSMenuItem *QCocoaMenuItem::sync() [mergeItem retain]; [m_native release]; m_native = mergeItem; - [m_native setTag:reinterpret_cast<NSInteger>(this)]; + if (auto *nativeItem = qt_objc_cast<QCocoaNSMenuItem *>(m_native)) + nativeItem.platformMenuItem = this; } else if (m_merged) { // was previously merged, but no longer [m_native release]; @@ -306,14 +301,14 @@ NSMenuItem *QCocoaMenuItem::sync() } if (!m_native) { - m_native = [[NSMenuItem alloc] initWithTitle:m_text.toNSString() - action:nil - keyEquivalent:@""]; - [m_native setTag:reinterpret_cast<NSInteger>(this)]; + m_native = [[QCocoaNSMenuItem alloc] initWithPlatformMenuItem:this]; + m_native.title = m_text.toNSString(); } - [m_native setHidden: !m_isVisible]; - [m_native setView:m_itemView]; + resolveTargetAction(); + + m_native.hidden = !m_isVisible; + m_native.view = m_itemView; QString text = mergeText(); #ifndef QT_NO_SHORTCUT @@ -331,39 +326,35 @@ NSMenuItem *QCocoaMenuItem::sync() NSFont *customMenuFont = [NSFont fontWithName:m_font.family().toNSString() size:m_font.pointSize()]; if (customMenuFont) { - NSArray *keys = [NSArray arrayWithObjects:NSFontAttributeName, nil]; - NSArray *objects = [NSArray arrayWithObjects:customMenuFont, nil]; - NSDictionary *attributes = [NSDictionary dictionaryWithObjects:objects forKeys:keys]; NSAttributedString *str = [[[NSAttributedString alloc] initWithString:finalString.toNSString() - attributes:attributes] autorelease]; - [m_native setAttributedTitle: str]; + attributes:@{NSFontAttributeName: customMenuFont}] autorelease]; + m_native.attributedTitle = str; useAttributedTitle = true; } } - if (!useAttributedTitle) { - [m_native setTitle:finalString.toNSString()]; - } + if (!useAttributedTitle) + m_native.title = finalString.toNSString(); #ifndef QT_NO_SHORTCUT if (accel.count() == 1) { - [m_native setKeyEquivalent:keySequenceToKeyEqivalent(accel)]; - [m_native setKeyEquivalentModifierMask:keySequenceModifierMask(accel)]; + m_native.keyEquivalent = keySequenceToKeyEqivalent(accel); + m_native.keyEquivalentModifierMask = keySequenceModifierMask(accel); } else #endif { - [m_native setKeyEquivalent:@""]; - [m_native setKeyEquivalentModifierMask:NSCommandKeyMask]; + m_native.keyEquivalent = @""; + m_native.keyEquivalentModifierMask = NSEventModifierFlagCommand; } NSImage *img = nil; if (!m_icon.isNull()) { img = qt_mac_create_nsimage(m_icon, m_iconSize); - [img setSize:NSMakeSize(m_iconSize, m_iconSize)]; + img.size = CGSizeMake(m_iconSize, m_iconSize); } - [m_native setImage:img]; + m_native.image = img; [img release]; - [m_native setState:m_checked ? NSOnState : NSOffState]; + m_native.state = m_checked ? NSOnState : NSOffState; return m_native; } @@ -408,8 +399,8 @@ void QCocoaMenuItem::syncMerged() qWarning("Trying to sync a non-merged item"); return; } - [m_native setTag:reinterpret_cast<NSInteger>(this)]; - [m_native setHidden: !m_isVisible]; + + m_native.hidden = !m_isVisible; } void QCocoaMenuItem::setParentEnabled(bool enabled) @@ -434,4 +425,39 @@ void QCocoaMenuItem::setIconSize(int size) m_iconSize = size; } +void QCocoaMenuItem::resolveTargetAction() +{ + if (m_native.separatorItem) + return; + + // Some items created by QCocoaMenuLoader are not + // instances of QCocoaNSMenuItem and have their + // target/action set as Interface Builder would. + if (![m_native isMemberOfClass:[QCocoaNSMenuItem class]]) + return; + + // Use the responder chain and ensure native modal dialogs + // continue receiving cut/copy/paste/etc. key equivalents. + SEL roleAction; + switch (effectiveRole()) { + case CutRole: + roleAction = @selector(cut:); + break; + case CopyRole: + roleAction = @selector(copy:); + break; + case PasteRole: + roleAction = @selector(paste:); + break; + case SelectAllRole: + roleAction = @selector(selectAll:); + break; + default: + roleAction = @selector(qt_itemFired:); + } + + m_native.action = roleAction; + m_native.target = nil; +} + QT_END_NAMESPACE diff --git a/src/plugins/platforms/cocoa/qcocoamenuloader.h b/src/plugins/platforms/cocoa/qcocoamenuloader.h index 95f347646c..5e83327854 100644 --- a/src/plugins/platforms/cocoa/qcocoamenuloader.h +++ b/src/plugins/platforms/cocoa/qcocoamenuloader.h @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2018 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the plugins of the Qt Toolkit. @@ -54,42 +54,20 @@ #import <AppKit/AppKit.h> #include <QtCore/private/qcore_mac_p.h> -@interface QT_MANGLE_NAMESPACE(QCocoaMenuLoader) : NSResponder -{ - IBOutlet NSMenu *theMenu; - IBOutlet NSMenu *appMenu; - IBOutlet NSMenuItem *quitItem; - IBOutlet NSMenuItem *preferencesItem; - IBOutlet NSMenuItem *aboutItem; - IBOutlet NSMenuItem *aboutQtItem; - IBOutlet NSMenuItem *hideItem; - NSMenuItem *lastAppSpecificItem; - NSMenuItem *servicesItem; - NSMenuItem *hideAllOthersItem; - NSMenuItem *showAllItem; -} +QT_FORWARD_DECLARE_CLASS(QCocoaMenuItem); + +@interface QT_MANGLE_NAMESPACE(QCocoaMenuLoader) : NSObject + (instancetype)sharedMenuLoader; -- (instancetype)init; -- (void)ensureAppMenuInMenu:(NSMenu *)menu; -- (void)removeActionsFromAppMenu; -- (NSMenu *)applicationMenu; - (NSMenu *)menu; +- (void)ensureAppMenuInMenu:(NSMenu *)menu; - (NSMenuItem *)quitMenuItem; - (NSMenuItem *)preferencesMenuItem; - (NSMenuItem *)aboutMenuItem; - (NSMenuItem *)aboutQtMenuItem; - (NSMenuItem *)hideMenuItem; -- (NSMenuItem *)appSpecificMenuItem:(NSInteger)tag; -- (IBAction)terminate:(id)sender; -- (IBAction)orderFrontStandardAboutPanel:(id)sender; -- (IBAction)hideOtherApplications:(id)sender; -- (IBAction)unhideAllApplications:(id)sender; -- (IBAction)hide:(id)sender; -- (IBAction)qtDispatcherToQPAMenuItem:(id)sender; -- (void)orderFrontCharacterPalette:(id)sender; -- (BOOL)validateMenuItem:(NSMenuItem*)menuItem; +- (NSMenuItem *)appSpecificMenuItem:(QCocoaMenuItem *)platformItem; - (void)qtTranslateApplicationMenu; -- (NSArray *)mergeable; +- (NSArray<NSMenuItem *> *)mergeable; @end QT_NAMESPACE_ALIAS_OBJC_CLASS(QCocoaMenuLoader); diff --git a/src/plugins/platforms/cocoa/qcocoamenuloader.mm b/src/plugins/platforms/cocoa/qcocoamenuloader.mm index 4432d3e27a..da0fc5c6a1 100644 --- a/src/plugins/platforms/cocoa/qcocoamenuloader.mm +++ b/src/plugins/platforms/cocoa/qcocoamenuloader.mm @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2018 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the plugins of the Qt Toolkit. @@ -41,6 +41,7 @@ #include "messages.h" #include "qcocoahelpers.h" +#include "qcocoansmenu.h" #include "qcocoamenubar.h" #include "qcocoamenuitem.h" #include "qcocoaintegration.h" @@ -50,7 +51,19 @@ #include <QtCore/qcoreapplication.h> #include <QtGui/private/qguiapplication_p.h> -@implementation QCocoaMenuLoader +@implementation QCocoaMenuLoader { + NSMenu *theMenu; + NSMenu *appMenu; + NSMenuItem *quitItem; + NSMenuItem *preferencesItem; + NSMenuItem *aboutItem; + NSMenuItem *aboutQtItem; + NSMenuItem *hideItem; + NSMenuItem *lastAppSpecificItem; + NSMenuItem *servicesItem; + NSMenuItem *hideAllOthersItem; + NSMenuItem *showAllItem; +} + (instancetype)sharedMenuLoader { @@ -83,17 +96,19 @@ appItem.submenu = appMenu; // About Application - aboutItem = [[NSMenuItem alloc] initWithTitle:[@"About " stringByAppendingString:appName] - action:@selector(orderFrontStandardAboutPanel:) - keyEquivalent:@""]; + aboutItem = [[QCocoaNSMenuItem alloc] init]; + aboutItem.title = [@"About " stringByAppendingString:appName]; + // FIXME This seems useless since barely adding a QAction + // with AboutRole role will reset the target/action aboutItem.target = self; + aboutItem.action = @selector(orderFrontStandardAboutPanel:); // Disable until a QAction is associated aboutItem.enabled = NO; aboutItem.hidden = YES; [appMenu addItem:aboutItem]; // About Qt (shameless self-promotion) - aboutQtItem = [[NSMenuItem alloc] init]; + aboutQtItem = [[QCocoaNSMenuItem alloc] init]; aboutQtItem.title = @"About Qt"; // Disable until a QAction is associated aboutQtItem.enabled = NO; @@ -103,15 +118,19 @@ [appMenu addItem:[NSMenuItem separatorItem]]; // Preferences - preferencesItem = [[NSMenuItem alloc] initWithTitle:@"Preferences…" - action:@selector(qtDispatcherToQPAMenuItem:) - keyEquivalent:@","]; - preferencesItem.target = self; + preferencesItem = [[QCocoaNSMenuItem alloc] init]; + preferencesItem.title = @"Preferences…"; + preferencesItem.keyEquivalent = @","; // Disable until a QAction is associated preferencesItem.enabled = NO; preferencesItem.hidden = YES; [appMenu addItem:preferencesItem]; + // We'll be adding app specific items after this. The macOS HIG state that, + // "In general, a Preferences menu item should be the first app-specific menu item." + // https://developer.apple.com/macos/human-interface-guidelines/menus/menu-bar-menus/ + lastAppSpecificItem = preferencesItem; + [appMenu addItem:[NSMenuItem separatorItem]]; // Services item and menu @@ -136,7 +155,7 @@ action:@selector(hideOtherApplications:) keyEquivalent:@"h"]; hideAllOthersItem.target = self; - hideAllOthersItem.keyEquivalentModifierMask = NSCommandKeyMask | NSAlternateKeyMask; + hideAllOthersItem.keyEquivalentModifierMask = NSEventModifierFlagCommand | NSEventModifierFlagOption; [appMenu addItem:hideAllOthersItem]; // Show All @@ -149,10 +168,13 @@ [appMenu addItem:[NSMenuItem separatorItem]]; // Quit Application - quitItem = [[NSMenuItem alloc] initWithTitle:[@"Quit " stringByAppendingString:appName] - action:@selector(terminate:) - keyEquivalent:@"q"]; - quitItem.target = self; + quitItem = [[QCocoaNSMenuItem alloc] init]; + quitItem.title = [@"Quit " stringByAppendingString:appName]; + quitItem.keyEquivalent = @"q"; + // This will remain true until synced with a QCocoaMenuItem. + // This way, we will always have a functional Quit menu item + // even if no QAction is added. + quitItem.action = @selector(terminate:); [appMenu addItem:quitItem]; } @@ -184,7 +206,7 @@ // windows with different menu bars), we never recreate this menu, but // instead pull it out the current menu bar and place into the new one: NSMenu *mainMenu = [NSApp mainMenu]; - if ([NSApp mainMenu] == menu) + if (mainMenu == menu) return; // nothing to do (menu is the current menu bar)! #ifndef QT_NAMESPACE @@ -205,17 +227,11 @@ unparentAppMenu(appMenu.supermenu); NSMenuItem *appMenuItem = [[NSMenuItem alloc] initWithTitle:@"Apple" - action:nil keyEquivalent:@""]; - [appMenuItem setSubmenu:appMenu]; + action:nil keyEquivalent:@""]; + appMenuItem.submenu = appMenu; [menu insertItem:appMenuItem atIndex:0]; } -- (void)removeActionsFromAppMenu -{ - for (NSMenuItem *item in [appMenu itemArray]) - [item setTag:0]; -} - - (NSMenu *)menu { return [[theMenu retain] autorelease]; @@ -251,41 +267,32 @@ return [[hideItem retain] autorelease]; } -- (NSMenuItem *)appSpecificMenuItem:(NSInteger)tag +- (NSMenuItem *)appSpecificMenuItem:(QCocoaMenuItem *)platformItem { - NSMenuItem *item = [appMenu itemWithTag:tag]; - - // No reason to create the item if it already exists. See QTBUG-27202. - if (item) - return [[item retain] autorelease]; + // No reason to create the item if it already exists. + for (NSMenuItem *item in appMenu.itemArray) + if (qt_objc_cast<QCocoaNSMenuItem *>(item).platformMenuItem == platformItem) + return [[item retain] autorelease]; // Create an App-Specific menu item, insert it into the menu and return // it as an autorelease item. - item = [[NSMenuItem alloc] init]; + QCocoaNSMenuItem *item; + if (platformItem->isSeparator()) + item = [[QCocoaNSMenuItem separatorItemWithPlatformMenuItem:platformItem] retain]; + else + item = [[QCocoaNSMenuItem alloc] initWithPlatformMenuItem:platformItem]; + + const auto location = [appMenu indexOfItem:lastAppSpecificItem]; - NSInteger location; - if (lastAppSpecificItem == nil) { - location = [appMenu indexOfItem:aboutQtItem]; - } else { - location = [appMenu indexOfItem:lastAppSpecificItem]; + if (!lastAppSpecificItem.separatorItem) [lastAppSpecificItem release]; - } lastAppSpecificItem = item; // Keep track of this for later (i.e., don't release it) + [appMenu insertItem:item atIndex:location + 1]; return [[item retain] autorelease]; } -- (BOOL) acceptsFirstResponder -{ - return YES; -} - -- (void)terminate:(id)sender -{ - [NSApp terminate:sender]; -} - - (void)orderFrontStandardAboutPanel:(id)sender { [NSApp orderFrontStandardAboutPanel:sender]; @@ -308,61 +315,36 @@ - (void)qtTranslateApplicationMenu { - #ifndef QT_NO_TRANSLATION - [servicesItem setTitle:qt_mac_applicationmenu_string(ServicesAppMenuItem).toNSString()]; - [hideItem setTitle:qt_mac_applicationmenu_string(HideAppMenuItem).arg(qt_mac_applicationName()).toNSString()]; - [hideAllOthersItem setTitle:qt_mac_applicationmenu_string(HideOthersAppMenuItem).toNSString()]; - [showAllItem setTitle:qt_mac_applicationmenu_string(ShowAllAppMenuItem).toNSString()]; - [preferencesItem setTitle:qt_mac_applicationmenu_string(PreferencesAppMenuItem).toNSString()]; - [quitItem setTitle:qt_mac_applicationmenu_string(QuitAppMenuItem).arg(qt_mac_applicationName()).toNSString()]; - [aboutItem setTitle:qt_mac_applicationmenu_string(AboutAppMenuItem).arg(qt_mac_applicationName()).toNSString()]; + aboutItem.title = qt_mac_applicationmenu_string(AboutAppMenuItem).arg(qt_mac_applicationName()).toNSString(); + preferencesItem.title = qt_mac_applicationmenu_string(PreferencesAppMenuItem).toNSString(); + servicesItem.title = qt_mac_applicationmenu_string(ServicesAppMenuItem).toNSString(); + hideItem.title = qt_mac_applicationmenu_string(HideAppMenuItem).arg(qt_mac_applicationName()).toNSString(); + hideAllOthersItem.title = qt_mac_applicationmenu_string(HideOthersAppMenuItem).toNSString(); + showAllItem.title = qt_mac_applicationmenu_string(ShowAllAppMenuItem).toNSString(); + quitItem.title = qt_mac_applicationmenu_string(QuitAppMenuItem).arg(qt_mac_applicationName()).toNSString(); #endif } -- (IBAction)qtDispatcherToQPAMenuItem:(id)sender -{ - NSMenuItem *item = static_cast<NSMenuItem *>(sender); - if (item == quitItem) { - // We got here because someone was once the quitItem, but it has been - // abandoned (e.g., the menubar was deleted). In the meantime, just do - // normal QApplication::quit(). - qApp->quit(); - return; - } - - if ([item tag]) { - QCocoaMenuItem *cocoaItem = reinterpret_cast<QCocoaMenuItem *>([item tag]); - QScopedScopeLevelCounter scopeLevelCounter(QGuiApplicationPrivate::instance()->threadData); - cocoaItem->activated(); - } -} - -- (void)orderFrontCharacterPalette:(id)sender -{ - [NSApp orderFrontCharacterPalette:sender]; -} - - (BOOL)validateMenuItem:(NSMenuItem*)menuItem { - if ([menuItem action] == @selector(hideOtherApplications:) - || [menuItem action] == @selector(unhideAllApplications:)) { + if (menuItem.action == @selector(hideOtherApplications:) + || menuItem.action == @selector(unhideAllApplications:)) return [NSApp validateMenuItem:menuItem]; - } else if ([menuItem action] == @selector(hide:)) { + + if (menuItem.action == @selector(hide:)) { if (QCocoaIntegration::instance()->activePopupWindow()) return NO; return [NSApp validateMenuItem:menuItem]; - } else if ([menuItem tag]) { - QCocoaMenuItem *cocoaItem = reinterpret_cast<QCocoaMenuItem *>([menuItem tag]); - return cocoaItem->isEnabled(); - } else { - return [menuItem isEnabled]; } + + return menuItem.enabled; } -- (NSArray*) mergeable +- (NSArray<NSMenuItem *> *)mergeable { - // don't include the quitItem here, since we want it always visible and enabled regardless + // Don't include the quitItem here, since we want it always visible and enabled regardless + // Note that lastAppSpecificItem may be nil, so we can't use @[] here. return [NSArray arrayWithObjects:preferencesItem, aboutItem, aboutQtItem, lastAppSpecificItem, nil]; } diff --git a/src/plugins/platforms/cocoa/qcocoanativeinterface.mm b/src/plugins/platforms/cocoa/qcocoanativeinterface.mm index 955b147bfd..7979e430ac 100644 --- a/src/plugins/platforms/cocoa/qcocoanativeinterface.mm +++ b/src/plugins/platforms/cocoa/qcocoanativeinterface.mm @@ -71,6 +71,10 @@ #include <AppKit/AppKit.h> +#if QT_CONFIG(vulkan) +#include <MoltenVK/mvk_vulkan.h> +#endif + QT_BEGIN_NAMESPACE QCocoaNativeInterface::QCocoaNativeInterface() @@ -81,31 +85,32 @@ QCocoaNativeInterface::QCocoaNativeInterface() void *QCocoaNativeInterface::nativeResourceForContext(const QByteArray &resourceString, QOpenGLContext *context) { if (!context) - return 0; + return nullptr; if (resourceString.toLower() == "nsopenglcontext") return nsOpenGLContextForContext(context); if (resourceString.toLower() == "cglcontextobj") return cglContextForContext(context); - return 0; + return nullptr; } #endif void *QCocoaNativeInterface::nativeResourceForWindow(const QByteArray &resourceString, QWindow *window) { if (!window->handle()) - return 0; + return nullptr; if (resourceString == "nsview") { return static_cast<QCocoaWindow *>(window->handle())->m_view; -#ifndef QT_NO_OPENGL - } else if (resourceString == "nsopenglcontext") { - return static_cast<QCocoaWindow *>(window->handle())->currentContext()->nsOpenGLContext(); -#endif } else if (resourceString == "nswindow") { return static_cast<QCocoaWindow *>(window->handle())->nativeWindow(); +#if QT_CONFIG(vulkan) + } else if (resourceString == "vkSurface") { + if (QVulkanInstance *instance = window->vulkanInstance()) + return static_cast<QCocoaVulkanInstance *>(instance->handle())->createSurface(window); +#endif } - return 0; + return nullptr; } QPlatformNativeInterface::NativeResourceForIntegrationFunction QCocoaNativeInterface::nativeResourceFunctionForIntegration(const QByteArray &resource) @@ -143,7 +148,7 @@ QPlatformNativeInterface::NativeResourceForIntegrationFunction QCocoaNativeInter if (resource.toLower() == "testcontentborderposition") return NativeResourceForIntegrationFunction(QCocoaNativeInterface::testContentBorderPosition); - return 0; + return nullptr; } QPlatformPrinterSupport *QCocoaNativeInterface::createPlatformPrinterSupport() @@ -152,7 +157,7 @@ QPlatformPrinterSupport *QCocoaNativeInterface::createPlatformPrinterSupport() return new QCocoaPrinterSupport(); #else qFatal("Printing is not supported when Qt is configured with -no-widgets"); - return 0; + return nullptr; #endif } @@ -166,7 +171,7 @@ void *QCocoaNativeInterface::NSPrintInfoForPrintEngine(QPrintEngine *printEngine #else Q_UNUSED(printEngine); qFatal("Printing is not supported when Qt is configured with -no-widgets"); - return 0; + return nullptr; #endif } @@ -180,10 +185,10 @@ QPixmap QCocoaNativeInterface::defaultBackgroundPixmapForQWizard() CFURLRef url = (CFURLRef)CFArrayGetValueAtIndex(urls, 0); QCFType<CFBundleRef> bundle = CFBundleCreate(kCFAllocatorDefault, url); if (bundle) { - url = CFBundleCopyResourceURL(bundle, CFSTR("Background"), CFSTR("png"), 0); + url = CFBundleCopyResourceURL(bundle, CFSTR("Background"), CFSTR("png"), nullptr); if (url) { - QCFType<CGImageSourceRef> imageSource = CGImageSourceCreateWithURL(url, 0); - QCFType<CGImageRef> image = CGImageSourceCreateImageAtIndex(imageSource, 0, 0); + QCFType<CGImageSourceRef> imageSource = CGImageSourceCreateWithURL(url, nullptr); + QCFType<CGImageRef> image = CGImageSourceCreateImageAtIndex(imageSource, 0, nullptr); if (image) { int width = CGImageGetWidth(image); int height = CGImageGetHeight(image); @@ -213,18 +218,16 @@ void *QCocoaNativeInterface::cglContextForContext(QOpenGLContext* context) NSOpenGLContext *nsOpenGLContext = static_cast<NSOpenGLContext*>(nsOpenGLContextForContext(context)); if (nsOpenGLContext) return [nsOpenGLContext CGLContextObj]; - return 0; + return nullptr; } void *QCocoaNativeInterface::nsOpenGLContextForContext(QOpenGLContext* context) { if (context) { - QCocoaGLContext *cocoaGLContext = static_cast<QCocoaGLContext *>(context->handle()); - if (cocoaGLContext) { - return cocoaGLContext->nsOpenGLContext(); - } + if (QCocoaGLContext *cocoaGLContext = static_cast<QCocoaGLContext *>(context->handle())) + return cocoaGLContext->nativeContext(); } - return 0; + return nullptr; } #endif @@ -256,7 +259,7 @@ void QCocoaNativeInterface::setDockMenu(QPlatformMenu *platformMenu) QMacAutoReleasePool pool; QCocoaMenu *cocoaPlatformMenu = static_cast<QCocoaMenu *>(platformMenu); NSMenu *menu = cocoaPlatformMenu->nsMenu(); - [[QCocoaApplicationDelegate sharedDelegate] setDockMenu:menu]; + [QCocoaApplicationDelegate sharedDelegate].dockMenu = menu; } void *QCocoaNativeInterface::qMenuToNSMenu(QPlatformMenu *platformMenu) @@ -285,8 +288,9 @@ QImage QCocoaNativeInterface::cgImageToQImage(CGImageRef image) void QCocoaNativeInterface::setEmbeddedInForeignView(QPlatformWindow *window, bool embedded) { + Q_UNUSED(embedded); // "embedded" state is now automatically detected QCocoaWindow *cocoaPlatformWindow = static_cast<QCocoaWindow *>(window); - cocoaPlatformWindow->setEmbeddedInForeignView(embedded); + cocoaPlatformWindow->setEmbeddedInForeignView(); } void QCocoaNativeInterface::registerTouchWindow(QWindow *window, bool enable) diff --git a/src/plugins/platforms/cocoa/qcocoansmenu.h b/src/plugins/platforms/cocoa/qcocoansmenu.h index a9c3e4fff9..6cbb6e4a01 100644 --- a/src/plugins/platforms/cocoa/qcocoansmenu.h +++ b/src/plugins/platforms/cocoa/qcocoansmenu.h @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2017 The Qt Company Ltd. +** Copyright (C) 2018 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the plugins of the Qt Toolkit. @@ -53,12 +53,10 @@ #import <AppKit/AppKit.h> -#include <QtCore/qpointer.h> #include <qcocoahelpers.h> QT_FORWARD_DECLARE_CLASS(QCocoaMenu); -typedef QPointer<QCocoaMenu> QCocoaMenuPointer; - +QT_FORWARD_DECLARE_CLASS(QCocoaMenuItem); @interface QT_MANGLE_NAMESPACE(QCocoaNSMenuDelegate) : NSObject <NSMenuDelegate> @@ -72,18 +70,24 @@ typedef QPointer<QCocoaMenu> QCocoaMenuPointer; @interface QT_MANGLE_NAMESPACE(QCocoaNSMenu) : NSMenu -@property (readonly, nonatomic) QCocoaMenuPointer qpaMenu; +@property (readonly, nonatomic) QCocoaMenu *platformMenu; + +- (instancetype)initWithPlatformMenu:(QCocoaMenu *)menu; + +@end -- (instancetype)initWithQPAMenu:(QCocoaMenu *)menu; +@interface QT_MANGLE_NAMESPACE(QCocoaNSMenuItem) : NSMenuItem -- (void)qt_itemFired:(NSMenuItem *)item; +@property (nonatomic) QCocoaMenuItem *platformMenuItem; -- (BOOL)worksWhenModal; -- (BOOL)validateMenuItem:(NSMenuItem*)item; // NSMenuValidation ++ (instancetype)separatorItemWithPlatformMenuItem:(QCocoaMenuItem *)menuItem; +- (instancetype)initWithPlatformMenuItem:(QCocoaMenuItem *)menuItem; +- (instancetype)init; @end QT_NAMESPACE_ALIAS_OBJC_CLASS(QCocoaNSMenu); +QT_NAMESPACE_ALIAS_OBJC_CLASS(QCocoaNSMenuItem); QT_NAMESPACE_ALIAS_OBJC_CLASS(QCocoaNSMenuDelegate); #endif // QCOCOANSMENU_H diff --git a/src/plugins/platforms/cocoa/qcocoansmenu.mm b/src/plugins/platforms/cocoa/qcocoansmenu.mm index 19a0f950e4..65b0832d9f 100644 --- a/src/plugins/platforms/cocoa/qcocoansmenu.mm +++ b/src/plugins/platforms/cocoa/qcocoansmenu.mm @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2017 The Qt Company Ltd. +** Copyright (C) 2018 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the plugins of the Qt Toolkit. @@ -44,17 +44,15 @@ #include "qcocoawindow.h" #import "qnsview.h" -#include <QtCore/qmetaobject.h> -#include <QtCore/private/qthread_p.h> -#include <QtGui/private/qguiapplication_p.h> +#include <QtCore/qcoreapplication.h> +#include <QtCore/qcoreevent.h> -static NSString *qt_mac_removePrivateUnicode(NSString* string) +static NSString *qt_mac_removePrivateUnicode(NSString *string) { - int len = [string length]; - if (len) { - QVarLengthArray <unichar, 10> characters(len); + if (const int len = string.length) { + QVarLengthArray<unichar, 10> characters(len); bool changed = false; - for (int i = 0; i<len; i++) { + for (int i = 0; i < len; i++) { characters[i] = [string characterAtIndex:i]; // check if they belong to key codes in private unicode range // currently we need to handle only the NSDeleteFunctionKey @@ -70,11 +68,14 @@ static NSString *qt_mac_removePrivateUnicode(NSString* string) } @implementation QCocoaNSMenu +{ + QPointer<QCocoaMenu> _platformMenu; +} -- (instancetype)initWithQPAMenu:(QCocoaMenu *)menu +- (instancetype)initWithPlatformMenu:(QCocoaMenu *)menu { if ((self = [super initWithTitle:@"Untitled"])) { - _qpaMenu = menu; + _platformMenu = menu; self.autoenablesItems = YES; self.delegate = [QCocoaNSMenuDelegate sharedMenuDelegate]; } @@ -82,46 +83,58 @@ static NSString *qt_mac_removePrivateUnicode(NSString* string) return self; } -// Cocoa will query the menu item's target for the worksWhenModal selector. -// So we need to implement this to allow the items to be handled correctly -// when a modal dialog is visible. See documentation for NSMenuItem.target. -- (BOOL)worksWhenModal +- (QCocoaMenu *)platformMenu { - if (!QGuiApplication::modalWindow()) - return YES; - if (const auto *mb = qobject_cast<QCocoaMenuBar *>(self.qpaMenu->menuParent())) - return QGuiApplication::modalWindow()->handle() == mb->cocoaWindow() ? YES : NO; - return YES; + return _platformMenu.data(); } -- (void)qt_itemFired:(NSMenuItem *)item +@end + +@implementation QCocoaNSMenuItem { - auto *qpaItem = reinterpret_cast<QCocoaMenuItem *>(item.tag); - // Menu-holding items also get a target to play nicely - // with NSMenuValidation but should not trigger. - if (!qpaItem || qpaItem->menu()) - return; + QPointer<QCocoaMenuItem> _platformMenuItem; +} - QScopedScopeLevelCounter scopeLevelCounter(QGuiApplicationPrivate::instance()->threadData); - QGuiApplicationPrivate::modifier_buttons = [QNSView convertKeyModifiers:[NSEvent modifierFlags]]; ++ (instancetype)separatorItemWithPlatformMenuItem:(QCocoaMenuItem *)menuItem +{ + // Safe because +[NSMenuItem separatorItem] invokes [[self alloc] init] + auto *item = qt_objc_cast<QCocoaNSMenuItem *>([self separatorItem]); + Q_ASSERT_X(item, qPrintable(__FUNCTION__), + "Did +[NSMenuItem separatorItem] not invoke [[self alloc] init]?"); + if (item) + item.platformMenuItem = menuItem; + + return item; +} - static QMetaMethod activatedSignal = QMetaMethod::fromSignal(&QCocoaMenuItem::activated); - activatedSignal.invoke(qpaItem, Qt::QueuedConnection); +- (instancetype)initWithPlatformMenuItem:(QCocoaMenuItem *)menuItem +{ + if ((self = [super initWithTitle:@"" action:nil keyEquivalent:@""])) { + _platformMenuItem = menuItem; + } + + return self; } -- (BOOL)validateMenuItem:(NSMenuItem*)item +- (instancetype)init { - auto *qpaItem = reinterpret_cast<QCocoaMenuItem *>(item.tag); - // Menu-holding items are always enabled, as it's conventional in Cocoa - if (!qpaItem || qpaItem->menu()) - return YES; + return [self initWithPlatformMenuItem:nullptr]; +} - return qpaItem->isEnabled(); +- (QCocoaMenuItem *)platformMenuItem +{ + return _platformMenuItem.data(); +} + +- (void)setPlatformMenuItem:(QCocoaMenuItem *)menuItem +{ + _platformMenuItem = menuItem; } @end -#define CHECK_MENU_CLASS(menu) Q_ASSERT([menu isMemberOfClass:[QCocoaNSMenu class]]) +#define CHECK_MENU_CLASS(menu) Q_ASSERT_X([menu isMemberOfClass:[QCocoaNSMenu class]], \ + __FUNCTION__, "Menu is not a QCocoaNSMenu") @implementation QCocoaNSMenuDelegate @@ -153,14 +166,15 @@ static NSString *qt_mac_removePrivateUnicode(NSString* string) if (shouldCancel) return NO; - const auto &qpaMenu = static_cast<QCocoaNSMenu *>(menu).qpaMenu; - if (qpaMenu.isNull()) + const auto &platformMenu = static_cast<QCocoaNSMenu *>(menu).platformMenu; + if (!platformMenu) return YES; - auto *menuItem = reinterpret_cast<QCocoaMenuItem *>(item.tag); - if (qpaMenu->items().contains(menuItem)) { - if (QCocoaMenu *itemSubmenu = menuItem->menu()) - itemSubmenu->setAttachedItem(item); + if (auto *platformItem = qt_objc_cast<QCocoaNSMenuItem *>(item).platformMenuItem) { + if (platformMenu->items().contains(platformItem)) { + if (auto *itemSubmenu = platformItem->menu()) + itemSubmenu->setAttachedItem(item); + } } return YES; @@ -169,32 +183,31 @@ static NSString *qt_mac_removePrivateUnicode(NSString* string) - (void)menu:(NSMenu *)menu willHighlightItem:(NSMenuItem *)item { CHECK_MENU_CLASS(menu); - auto *qpaItem = reinterpret_cast<QCocoaMenuItem *>(item.tag); - if (qpaItem) - qpaItem->hovered(); + if (auto *platformItem = qt_objc_cast<QCocoaNSMenuItem *>(item).platformMenuItem) + emit platformItem->hovered(); } - (void)menuWillOpen:(NSMenu *)menu { CHECK_MENU_CLASS(menu); - const auto &qpaMenu = static_cast<QCocoaNSMenu *>(menu).qpaMenu; - if (qpaMenu.isNull()) + auto *platformMenu = static_cast<QCocoaNSMenu *>(menu).platformMenu; + if (!platformMenu) return; - qpaMenu->setIsOpen(true); - emit qpaMenu->aboutToShow(); + platformMenu->setIsOpen(true); + emit platformMenu->aboutToShow(); } - (void)menuDidClose:(NSMenu *)menu { CHECK_MENU_CLASS(menu); - const auto &qpaMenu = static_cast<QCocoaNSMenu *>(menu).qpaMenu; - if (qpaMenu.isNull()) + auto *platformMenu = static_cast<QCocoaNSMenu *>(menu).platformMenu; + if (!platformMenu) return; - qpaMenu->setIsOpen(false); + platformMenu->setIsOpen(false); // wrong, but it's the best we can do - emit qpaMenu->aboutToHide(); + emit platformMenu->aboutToHide(); } - (BOOL)menuHasKeyEquivalent:(NSMenu *)menu forEvent:(NSEvent *)event target:(id *)target action:(SEL *)action @@ -212,7 +225,8 @@ static NSString *qt_mac_removePrivateUnicode(NSString* string) CHECK_MENU_CLASS(menu); // Interested only in Shift, Cmd, Ctrl & Alt Keys, so ignoring masks like, Caps lock, Num Lock ... - static const NSUInteger mask = NSShiftKeyMask | NSControlKeyMask | NSCommandKeyMask | NSAlternateKeyMask; + static const NSUInteger mask = NSEventModifierFlagShift | NSEventModifierFlagControl + | NSEventModifierFlagCommand | NSEventModifierFlagOption; // Change the private unicode keys to the ones used in setting the "Key Equivalents" NSString *characters = qt_mac_removePrivateUnicode(event.charactersIgnoringModifiers); @@ -229,30 +243,17 @@ static NSString *qt_mac_removePrivateUnicode(NSString* string) } if (keyEquivalentItem) { - if (!keyEquivalentItem.target) { - // This item was modified by QCocoaMenuBar::redirectKnownMenuItemsToFirstResponder - // and it looks like we're running a modal session for NSOpenPanel/NSSavePanel. - // QCocoaFileDialogHelper is actually the only place we use this and we run NSOpenPanel modal - // (modal sheet, window modal, application modal). - // Whatever the current first responder is, let's give it a chance - // and do not touch the Qt's focusObject (which is different from some native view - // having a focus inside NSSave/OpenPanel. - *target = nil; - *action = keyEquivalentItem.action; - return YES; - } - QObject *object = qApp->focusObject(); if (object) { QChar ch; int keyCode; - ulong nativeModifiers = [event modifierFlags]; - Qt::KeyboardModifiers modifiers = [QNSView convertKeyModifiers: nativeModifiers]; - NSString *charactersIgnoringModifiers = [event charactersIgnoringModifiers]; - NSString *characters = [event characters]; + ulong nativeModifiers = event.modifierFlags; + Qt::KeyboardModifiers modifiers = [QNSView convertKeyModifiers:nativeModifiers]; + NSString *charactersIgnoringModifiers = event.charactersIgnoringModifiers; + NSString *characters = event.characters; - if ([charactersIgnoringModifiers length] > 0) { // convert the first character into a key code - if ((modifiers & Qt::ControlModifier) && ([characters length] != 0)) { + if (charactersIgnoringModifiers.length > 0) { // convert the first character into a key code + if ((modifiers & Qt::ControlModifier) && characters.length > 0) { ch = QChar([characters characterAtIndex:0]); } else { ch = QChar([charactersIgnoringModifiers characterAtIndex:0]); @@ -269,7 +270,7 @@ static NSString *qt_mac_removePrivateUnicode(NSString* string) accel_ev.ignore(); QCoreApplication::sendEvent(object, &accel_ev); if (accel_ev.isAccepted()) { - [[NSApp keyWindow] sendEvent: event]; + [[NSApp keyWindow] sendEvent:event]; *target = nil; *action = nil; return YES; diff --git a/src/plugins/platforms/cocoa/qcocoaprintdevice.mm b/src/plugins/platforms/cocoa/qcocoaprintdevice.mm index bfe6cd09b6..24ec7ca9a4 100644 --- a/src/plugins/platforms/cocoa/qcocoaprintdevice.mm +++ b/src/plugins/platforms/cocoa/qcocoaprintdevice.mm @@ -67,17 +67,17 @@ static QPrint::DuplexMode macToDuplexMode(const PMDuplexMode &mode) QCocoaPrintDevice::QCocoaPrintDevice() : QPlatformPrintDevice(), - m_printer(0), - m_session(0), - m_ppd(0) + m_printer(nullptr), + m_session(nullptr), + m_ppd(nullptr) { } QCocoaPrintDevice::QCocoaPrintDevice(const QString &id) : QPlatformPrintDevice(id), - m_printer(0), - m_session(0), - m_ppd(0) + m_printer(nullptr), + m_session(nullptr), + m_ppd(nullptr) { if (!id.isEmpty()) { m_printer = PMPrinterCreateFromPrinterID(id.toCFString()); @@ -411,7 +411,7 @@ QPrint::ColorMode QCocoaPrintDevice::defaultColorMode() const ppd_option_t *colorModel = ppdFindOption(m_ppd, "DefaultColorModel"); if (!colorModel) colorModel = ppdFindOption(m_ppd, "ColorModel"); - if (!colorModel || (colorModel && !qstrcmp(colorModel->defchoice, "Gray"))) + if (!colorModel || qstrcmp(colorModel->defchoice, "Gray") != 0) return QPrint::Color; } return QPrint::GrayScale; @@ -443,11 +443,11 @@ bool QCocoaPrintDevice::openPpdFile() { if (m_ppd) ppdClose(m_ppd); - m_ppd = 0; - CFURLRef ppdURL = NULL; + m_ppd = nullptr; + CFURLRef ppdURL = nullptr; char ppdPath[MAXPATHLEN]; if (PMPrinterCopyDescriptionURL(m_printer, kPMPPDDescriptionType, &ppdURL) == noErr - && ppdURL != NULL) { + && ppdURL) { if (CFURLGetFileSystemRepresentation(ppdURL, true, (UInt8*)ppdPath, sizeof(ppdPath))) m_ppd = ppdOpenFile(ppdPath); CFRelease(ppdURL); @@ -470,7 +470,7 @@ PMPaper QCocoaPrintDevice::macPaper(const QPageSize &pageSize) const if (m_macPapers.contains(pageSize.key())) return m_macPapers.value(pageSize.key()); // For any other page size, whether custom or just unsupported, needs to be a custom PMPaper - PMPaper paper = 0; + PMPaper paper = nullptr; PMPaperMargins paperMargins; paperMargins.left = m_customMargins.left(); paperMargins.right = m_customMargins.right(); diff --git a/src/plugins/platforms/cocoa/qcocoascreen.h b/src/plugins/platforms/cocoa/qcocoascreen.h index 3d59c3de79..9ded98df32 100644 --- a/src/plugins/platforms/cocoa/qcocoascreen.h +++ b/src/plugins/platforms/cocoa/qcocoascreen.h @@ -75,7 +75,11 @@ public: // Additional methods void setVirtualSiblings(const QList<QPlatformScreen *> &siblings) { m_siblings = siblings; } NSScreen *nativeScreen() const; - void updateGeometry(); + void updateProperties(); + + void requestUpdate(); + void deliverUpdateRequests(); + bool isRunningDisplayLink() const; static QCocoaScreen *primaryScreen(); @@ -96,6 +100,10 @@ public: QSizeF m_physicalSize; QCocoaCursor *m_cursor; QList<QPlatformScreen *> m_siblings; + + CVDisplayLinkRef m_displayLink = nullptr; + dispatch_source_t m_displayLinkSource = nullptr; + QAtomicInt m_pendingUpdates; }; #ifndef QT_NO_DEBUG_STREAM @@ -104,5 +112,9 @@ QDebug operator<<(QDebug debug, const QCocoaScreen *screen); QT_END_NAMESPACE +@interface NSScreen (QtExtras) +@property(readonly) CGDirectDisplayID qt_displayId; +@end + #endif diff --git a/src/plugins/platforms/cocoa/qcocoascreen.mm b/src/plugins/platforms/cocoa/qcocoascreen.mm index 3e8261dfc2..f82ef202b1 100644 --- a/src/plugins/platforms/cocoa/qcocoascreen.mm +++ b/src/plugins/platforms/cocoa/qcocoascreen.mm @@ -47,6 +47,10 @@ #include <IOKit/graphics/IOGraphicsLib.h> +#include <QtGui/private/qwindow_p.h> + +#include <QtCore/private/qeventdispatcher_cf_p.h> + QT_BEGIN_NAMESPACE class QCoreTextFontEngine; @@ -55,18 +59,22 @@ class QFontEngineFT; QCocoaScreen::QCocoaScreen(int screenIndex) : QPlatformScreen(), m_screenIndex(screenIndex), m_refreshRate(60.0) { - updateGeometry(); + updateProperties(); m_cursor = new QCocoaCursor; } QCocoaScreen::~QCocoaScreen() { delete m_cursor; + + CVDisplayLinkRelease(m_displayLink); + if (m_displayLinkSource) + dispatch_release(m_displayLinkSource); } NSScreen *QCocoaScreen::nativeScreen() const { - NSArray *screens = [NSScreen screens]; + NSArray<NSScreen *> *screens = [NSScreen screens]; // Stale reference, screen configuration has changed if (m_screenIndex < 0 || (NSUInteger)m_screenIndex >= [screens count]) @@ -107,12 +115,17 @@ static QString displayName(CGDirectDisplayID displayID) return QString(); } -void QCocoaScreen::updateGeometry() +void QCocoaScreen::updateProperties() { NSScreen *nsScreen = nativeScreen(); if (!nsScreen) return; + const QRect previousGeometry = m_geometry; + const QRect previousAvailableGeometry = m_availableGeometry; + const QDpi previousLogicalDpi = m_logicalDpi; + const qreal previousRefreshRate = m_refreshRate; + // The reference screen for the geometry is always the primary screen QRectF primaryScreenGeometry = QRectF::fromCGRect([[NSScreen screens] firstObject].frame); m_geometry = qt_mac_flip(QRectF::fromCGRect(nsScreen.frame), primaryScreenGeometry).toRect(); @@ -121,8 +134,7 @@ void QCocoaScreen::updateGeometry() m_format = QImage::Format_RGB32; m_depth = NSBitsPerPixelFromDepth([nsScreen depth]); - NSDictionary *devDesc = [nsScreen deviceDescription]; - CGDirectDisplayID dpy = [[devDesc objectForKey:@"NSScreenNumber"] unsignedIntValue]; + CGDirectDisplayID dpy = nsScreen.qt_displayId; CGSize size = CGDisplayScreenSize(dpy); m_physicalSize = QSizeF(size.width, size.height); m_logicalDpi.first = 72; @@ -135,25 +147,230 @@ void QCocoaScreen::updateGeometry() m_name = displayName(dpy); - QWindowSystemInterface::handleScreenGeometryChange(screen(), geometry(), availableGeometry()); - QWindowSystemInterface::handleScreenLogicalDotsPerInchChange(screen(), m_logicalDpi.first, m_logicalDpi.second); - QWindowSystemInterface::handleScreenRefreshRateChange(screen(), m_refreshRate); - - // When a screen changes its geometry, AppKit will send us a NSWindowDidMoveNotification - // for each window, resulting in calls to handleGeometryChange(), but this happens before - // the NSApplicationDidChangeScreenParametersNotification, so when we map the new geometry - // (which is correct at that point) to the screen using QCocoaScreen::mapFromNative(), we - // end up using the stale screen geometry, and the new window geometry we report is wrong. - // To make sure we finally report the correct window geometry, we need to do another pass - // of geometry reporting, now that the screen properties have been updates. FIXME: Ideally - // this would be solved by not caching the screen properties in QCocoaScreen, but that - // requires more research. - for (QWindow *window : windows()) { - if (QCocoaWindow *cocoaWindow = static_cast<QCocoaWindow*>(window->handle())) - cocoaWindow->handleGeometryChange(); + const bool didChangeGeometry = m_geometry != previousGeometry || m_availableGeometry != previousAvailableGeometry; + + if (didChangeGeometry) + QWindowSystemInterface::handleScreenGeometryChange(screen(), geometry(), availableGeometry()); + if (m_logicalDpi != previousLogicalDpi) + QWindowSystemInterface::handleScreenLogicalDotsPerInchChange(screen(), m_logicalDpi.first, m_logicalDpi.second); + if (m_refreshRate != previousRefreshRate) + QWindowSystemInterface::handleScreenRefreshRateChange(screen(), m_refreshRate); + + qCDebug(lcQpaScreen) << "Updated properties for" << this; + + if (didChangeGeometry) { + // When a screen changes its geometry, AppKit will send us a NSWindowDidMoveNotification + // for each window, resulting in calls to handleGeometryChange(), but this happens before + // the NSApplicationDidChangeScreenParametersNotification, so when we map the new geometry + // (which is correct at that point) to the screen using QCocoaScreen::mapFromNative(), we + // end up using the stale screen geometry, and the new window geometry we report is wrong. + // To make sure we finally report the correct window geometry, we need to do another pass + // of geometry reporting, now that the screen properties have been updates. FIXME: Ideally + // this would be solved by not caching the screen properties in QCocoaScreen, but that + // requires more research. + for (QWindow *window : windows()) { + if (QCocoaWindow *cocoaWindow = static_cast<QCocoaWindow*>(window->handle())) + cocoaWindow->handleGeometryChange(); + } + } +} + +// ----------------------- Display link ----------------------- + +Q_LOGGING_CATEGORY(lcQpaScreenUpdates, "qt.qpa.screen.updates", QtCriticalMsg); + +void QCocoaScreen::requestUpdate() +{ + if (!m_displayLink) { + CVDisplayLinkCreateWithCGDisplay(nativeScreen().qt_displayId, &m_displayLink); + CVDisplayLinkSetOutputCallback(m_displayLink, [](CVDisplayLinkRef, const CVTimeStamp*, + const CVTimeStamp*, CVOptionFlags, CVOptionFlags*, void* displayLinkContext) -> int { + // FIXME: It would be nice if update requests would include timing info + static_cast<QCocoaScreen*>(displayLinkContext)->deliverUpdateRequests(); + return kCVReturnSuccess; + }, this); + qCDebug(lcQpaScreenUpdates) << "Display link created for" << this; + + // During live window resizing -[NSWindow _resizeWithEvent:] will spin a local event loop + // in event-tracking mode, dequeuing only the mouse drag events needed to update the window's + // frame. It will repeatedly spin this loop until no longer receiving any mouse drag events, + // and will then update the frame (effectively coalescing/compressing the events). Unfortunately + // the events are pulled out using -[NSApplication nextEventMatchingEventMask:untilDate:inMode:dequeue:] + // which internally uses CFRunLoopRunSpecific, so the event loop will also process GCD queues and other + // runloop sources that have been added to the tracking mode. This includes the GCD display-link + // source that we use to marshal the display-link callback over to the main thread. If the + // subsequent delivery of the update-request on the main thread stalls due to inefficient + // user code, the NSEventThread will have had time to deliver additional mouse drag events, + // and the logic in -[NSWindow _resizeWithEvent:] will keep on compressing events and never + // get to the point of actually updating the window frame, making it seem like the window + // is stuck in its original size. Only when the user stops moving their mouse, and the event + // queue is completely drained of drag events, will the window frame be updated. + + // By keeping an event tap listening for drag events, registered as a version 1 runloop source, + // we prevent the GCD source from being prioritized, giving the resize logic enough time + // to finish coalescing the events. This is incidental, but conveniently gives us the behavior + // we are looking for, interleaving display-link updates and resize events. + static CFMachPortRef eventTap = []() { + CFMachPortRef eventTap = CGEventTapCreateForPid(getpid(), kCGTailAppendEventTap, + kCGEventTapOptionListenOnly, NSEventMaskLeftMouseDragged, + [](CGEventTapProxy, CGEventType type, CGEventRef event, void *) -> CGEventRef { + if (type == kCGEventTapDisabledByTimeout) + qCWarning(lcQpaScreenUpdates) << "Event tap disabled due to timeout!"; + return event; // Listen only tap, so what we return doesn't really matter + }, nullptr); + CGEventTapEnable(eventTap, false); // Event taps are normally enabled when created + static CFRunLoopSourceRef runLoopSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, eventTap, 0); + CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource, kCFRunLoopCommonModes); + + NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; + [center addObserverForName:NSWindowWillStartLiveResizeNotification object:nil queue:nil + usingBlock:^(NSNotification *notification) { + qCDebug(lcQpaScreenUpdates) << "Live resize of" << notification.object + << "started. Enabling event tap"; + CGEventTapEnable(eventTap, true); + }]; + [center addObserverForName:NSWindowDidEndLiveResizeNotification object:nil queue:nil + usingBlock:^(NSNotification *notification) { + qCDebug(lcQpaScreenUpdates) << "Live resize of" << notification.object + << "ended. Disabling event tap"; + CGEventTapEnable(eventTap, false); + }]; + return eventTap; + }(); + Q_UNUSED(eventTap); + } + + if (!CVDisplayLinkIsRunning(m_displayLink)) { + qCDebug(lcQpaScreenUpdates) << "Starting display link for" << this; + CVDisplayLinkStart(m_displayLink); + } +} + +// Helper to allow building up debug output in multiple steps +struct DeferredDebugHelper +{ + DeferredDebugHelper(const QLoggingCategory &cat) { + if (cat.isDebugEnabled()) + debug = new QDebug(QMessageLogger().debug(cat).nospace()); + } + ~DeferredDebugHelper() { + flushOutput(); + } + void flushOutput() { + if (debug) { + delete debug; + debug = nullptr; + } + } + QDebug *debug = nullptr; +}; + +#define qDeferredDebug(helper) if (Q_UNLIKELY(helper.debug)) *helper.debug + +void QCocoaScreen::deliverUpdateRequests() +{ + if (!QGuiApplication::instance()) + return; + + // The CVDisplayLink callback is a notification that it's a good time to produce a new frame. + // Since the callback is delivered on a separate thread we have to marshal it over to the + // main thread, as Qt requires update requests to be delivered there. This needs to happen + // asynchronously, as otherwise we may end up deadlocking if the main thread calls back + // into any of the CVDisplayLink APIs. + if (QThread::currentThread() != QGuiApplication::instance()->thread()) { + // We're explicitly not using the data of the GCD source to track the pending updates, + // as the data isn't reset to 0 until after the event handler, and also doesn't update + // during the event handler, both of which we need to track late frames. + const int pendingUpdates = ++m_pendingUpdates; + + DeferredDebugHelper screenUpdates(lcQpaScreenUpdates()); + qDeferredDebug(screenUpdates) << "display link callback for screen " << m_screenIndex; + + if (const int framesAheadOfDelivery = pendingUpdates - 1) { + // If we have more than one update pending it means that a previous display link callback + // has not been fully processed on the main thread, either because GCD hasn't delivered + // it on the main thread yet, because the processing of the update request is taking + // too long, or because the update request was deferred due to window live resizing. + qDeferredDebug(screenUpdates) << ", " << framesAheadOfDelivery << " frame(s) ahead"; + + // We skip the frame completely if we're live-resizing, to not put any extra + // strain on the main thread runloop. Otherwise we assume we should push frames + // as fast as possible, and hopefully the callback will be delivered on the + // main thread just when the previous finished. + if (qt_apple_sharedApplication().keyWindow.inLiveResize) { + qDeferredDebug(screenUpdates) << "; waiting for main thread to catch up"; + return; + } + } + + qDeferredDebug(screenUpdates) << "; signaling dispatch source"; + + if (!m_displayLinkSource) { + m_displayLinkSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, dispatch_get_main_queue()); + dispatch_source_set_event_handler(m_displayLinkSource, ^{ + deliverUpdateRequests(); + }); + dispatch_resume(m_displayLinkSource); + } + + dispatch_source_merge_data(m_displayLinkSource, 1); + + } else { + DeferredDebugHelper screenUpdates(lcQpaScreenUpdates()); + qDeferredDebug(screenUpdates) << "gcd event handler on main thread"; + + const int pendingUpdates = m_pendingUpdates; + if (pendingUpdates > 1) + qDeferredDebug(screenUpdates) << ", " << (pendingUpdates - 1) << " frame(s) behind display link"; + + screenUpdates.flushOutput(); + + bool pauseUpdates = true; + + auto windows = QGuiApplication::allWindows(); + for (int i = 0; i < windows.size(); ++i) { + QWindow *window = windows.at(i); + QPlatformWindow *platformWindow = window->handle(); + if (!platformWindow) + continue; + + if (!platformWindow->hasPendingUpdateRequest()) + continue; + + if (window->screen() != screen()) + continue; + + // Skip windows that are not doing update requests via display link + if (!(window->format().swapInterval() > 0)) + continue; + + platformWindow->deliverUpdateRequest(); + + // Another update request was triggered, keep the display link running + if (platformWindow->hasPendingUpdateRequest()) + pauseUpdates = false; + } + + if (pauseUpdates) { + // Pause the display link if there are no pending update requests + qCDebug(lcQpaScreenUpdates) << "Stopping display link for" << this; + CVDisplayLinkStop(m_displayLink); + } + + if (const int missedUpdates = m_pendingUpdates.fetchAndStoreRelaxed(0) - pendingUpdates) { + qCWarning(lcQpaScreenUpdates) << "main thread missed" << missedUpdates + << "update(s) from display link during update request delivery"; + } } } +bool QCocoaScreen::isRunningDisplayLink() const +{ + return m_displayLink && CVDisplayLinkIsRunning(m_displayLink); +} + +// ----------------------------------------------------------- + qreal QCocoaScreen::devicePixelRatio() const { QMacAutoReleasePool pool; @@ -179,7 +396,7 @@ QWindow *QCocoaScreen::topLevelAt(const QPoint &point) const // belowWindowWithWindowNumber] may return windows that are not interesting // to Qt. The search iterates until a suitable window or no window is found. NSInteger topWindowNumber = 0; - QWindow *window = 0; + QWindow *window = nullptr; do { // Get the top-most window, below any previously rejected window. topWindowNumber = [NSWindow windowNumberAtPoint:screenPoint @@ -187,7 +404,7 @@ QWindow *QCocoaScreen::topLevelAt(const QPoint &point) const // Continue the search if the window does not belong to this process. NSWindow *nsWindow = [NSApp windowWithWindowNumber:topWindowNumber]; - if (nsWindow == 0) + if (!nsWindow) continue; // Continue the search if the window does not belong to Qt. @@ -324,3 +541,12 @@ QDebug operator<<(QDebug debug, const QCocoaScreen *screen) #endif // !QT_NO_DEBUG_STREAM QT_END_NAMESPACE + +@implementation NSScreen (QtExtras) + +- (CGDirectDisplayID)qt_displayId +{ + return [self.deviceDescription[@"NSScreenNumber"] unsignedIntValue]; +} + +@end diff --git a/src/plugins/platforms/cocoa/qcocoasystemsettings.mm b/src/plugins/platforms/cocoa/qcocoasystemsettings.mm index bad91a2d5d..60eb8cdf68 100644 --- a/src/plugins/platforms/cocoa/qcocoasystemsettings.mm +++ b/src/plugins/platforms/cocoa/qcocoasystemsettings.mm @@ -45,6 +45,16 @@ #include <QtGui/qfont.h> #include <QtGui/private/qcoregraphics_p.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); +@end +#endif + QT_BEGIN_NAMESPACE QPalette * qt_mac_createSystemPalette() @@ -65,18 +75,25 @@ QPalette * qt_mac_createSystemPalette() palette->setBrush(QPalette::Disabled, QPalette::Text, dark); palette->setBrush(QPalette::Disabled, QPalette::ButtonText, dark); palette->setBrush(QPalette::Disabled, QPalette::Base, backgroundBrush); + palette->setBrush(QPalette::Active, QPalette::Base, backgroundBrush); + palette->setBrush(QPalette::Inactive, QPalette::Base, backgroundBrush); palette->setColor(QPalette::Disabled, QPalette::Dark, QColor(191, 191, 191)); palette->setColor(QPalette::Active, QPalette::Dark, QColor(191, 191, 191)); palette->setColor(QPalette::Inactive, QPalette::Dark, QColor(191, 191, 191)); // System palette initialization: - palette->setBrush(QPalette::Active, QPalette::Highlight, - qt_mac_toQBrush([NSColor selectedControlColor])); - QBrush br = qt_mac_toQBrush([NSColor secondarySelectedControlColor]); - palette->setBrush(QPalette::Inactive, QPalette::Highlight, br); - palette->setBrush(QPalette::Disabled, QPalette::Highlight, br); + 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); + } - palette->setBrush(QPalette::Shadow, background.darker(170)); + palette->setBrush(QPalette::Shadow, qt_mac_toQColor([NSColor shadowColor])); qc = qt_mac_toQColor([NSColor controlTextColor]); palette->setColor(QPalette::Active, QPalette::Text, qc); @@ -98,10 +115,11 @@ QPalette * qt_mac_createSystemPalette() struct QMacPaletteMap { inline QMacPaletteMap(QPlatformTheme::Palette p, NSColor *a, NSColor *i) : - paletteRole(p), active(a), inactive(i) { } + active(a), inactive(i), paletteRole(p) { } + NSColor *active; + NSColor *inactive; QPlatformTheme::Palette paletteRole; - NSColor *active, *inactive; }; #define MAC_PALETTE_ENTRY(pal, active, inactive) \ @@ -131,7 +149,7 @@ QHash<QPlatformTheme::Palette, QPalette*> qt_mac_createRolePalettes() QColor qc; for (int i = 0; i < mac_widget_colors_count; i++) { QPalette &pal = *qt_mac_createSystemPalette(); - if (mac_widget_colors[i].active != 0) { + if (mac_widget_colors[i].active) { qc = qt_mac_toQColor(mac_widget_colors[i].active); pal.setColor(QPalette::Active, QPalette::Text, qc); pal.setColor(QPalette::Inactive, QPalette::Text, qc); @@ -146,7 +164,18 @@ QHash<QPlatformTheme::Palette, QPalette*> qt_mac_createRolePalettes() } if (mac_widget_colors[i].paletteRole == QPlatformTheme::MenuPalette || mac_widget_colors[i].paletteRole == QPlatformTheme::MenuBarPalette) { - pal.setBrush(QPalette::Highlight, qt_mac_toQColor([NSColor selectedMenuItemColor])); + NSColor *selectedMenuItemColor = nil; + if (__builtin_available(macOS 10.14, *)) { + // Cheap approximation for NSVisualEffectView (see deprecation note for selectedMenuItemTextColor) + selectedMenuItemColor = [[NSColor selectedContentBackgroundColor] highlightWithLevel:0.4]; + } 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 deliberatly choose to use + // keyboardFocusIndicatorColor instead, which appears to have the same color value. + selectedMenuItemColor = [NSColor keyboardFocusIndicatorColor]; + } + pal.setBrush(QPalette::Highlight, qt_mac_toQColor(selectedMenuItemColor)); qc = qt_mac_toQColor([NSColor labelColor]); pal.setBrush(QPalette::ButtonText, qc); pal.setBrush(QPalette::Text, qc); @@ -164,20 +193,36 @@ QHash<QPlatformTheme::Palette, QPalette*> qt_mac_createRolePalettes() pal.setColor(QPalette::Active, QPalette::ButtonText, pal.color(QPalette::Active, QPalette::Text)); } 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 selectedControlColor]; + pal.setBrush(QPalette::Inactive, QPalette::HighlightedText, + pal.brush(QPalette::Active, QPalette::Text)); + } + pal.setBrush(QPalette::Base, qt_mac_toQBrush(baseColors[0])); + pal.setBrush(QPalette::AlternateBase, qt_mac_toQBrush(baseColors[1])); pal.setBrush(QPalette::Active, QPalette::Highlight, - qt_mac_toQBrush([NSColor alternateSelectedControlColor])); + qt_mac_toQBrush(activeHighlightColor)); pal.setBrush(QPalette::Active, QPalette::HighlightedText, qt_mac_toQBrush([NSColor alternateSelectedControlTextColor])); pal.setBrush(QPalette::Inactive, QPalette::Text, - pal.brush(QPalette::Active, QPalette::Text)); - pal.setBrush(QPalette::Inactive, QPalette::HighlightedText, - pal.brush(QPalette::Active, QPalette::Text)); + pal.brush(QPalette::Active, QPalette::Text)); } else if (mac_widget_colors[i].paletteRole == QPlatformTheme::TextEditPalette) { + pal.setBrush(QPalette::Active, QPalette::Base, qt_mac_toQColor([NSColor textBackgroundColor])); pal.setBrush(QPalette::Inactive, QPalette::Text, pal.brush(QPalette::Active, QPalette::Text)); pal.setBrush(QPalette::Inactive, QPalette::HighlightedText, pal.brush(QPalette::Active, QPalette::Text)); - } else if (mac_widget_colors[i].paletteRole == QPlatformTheme::TextLineEditPalette) { + } else if (mac_widget_colors[i].paletteRole == QPlatformTheme::TextLineEditPalette + || mac_widget_colors[i].paletteRole == QPlatformTheme::ComboBoxPalette) { + pal.setBrush(QPalette::Active, QPalette::Base, qt_mac_toQColor([NSColor textBackgroundColor])); pal.setBrush(QPalette::Disabled, QPalette::Base, pal.brush(QPalette::Active, QPalette::Base)); } diff --git a/src/plugins/platforms/cocoa/qcocoasystemtrayicon.h b/src/plugins/platforms/cocoa/qcocoasystemtrayicon.h index 2f1a1e42a9..d831612c22 100644 --- a/src/plugins/platforms/cocoa/qcocoasystemtrayicon.h +++ b/src/plugins/platforms/cocoa/qcocoasystemtrayicon.h @@ -55,7 +55,7 @@ class QSystemTrayIconSys; class Q_GUI_EXPORT QCocoaSystemTrayIcon : public QPlatformSystemTrayIcon { public: - QCocoaSystemTrayIcon() : m_sys(0) {} + QCocoaSystemTrayIcon() : m_sys(nullptr) {} void init() override; void cleanup() override; diff --git a/src/plugins/platforms/cocoa/qcocoasystemtrayicon.mm b/src/plugins/platforms/cocoa/qcocoasystemtrayicon.mm index 5e6913a89a..0158895441 100644 --- a/src/plugins/platforms/cocoa/qcocoasystemtrayicon.mm +++ b/src/plugins/platforms/cocoa/qcocoasystemtrayicon.mm @@ -95,31 +95,16 @@ QT_USE_NAMESPACE @class QT_MANGLE_NAMESPACE(QNSImageView); @interface QT_MANGLE_NAMESPACE(QNSStatusItem) : NSObject <NSUserNotificationCenterDelegate> -{ -@public - QCocoaSystemTrayIcon *systray; - NSStatusItem *item; - QCocoaMenu *menu; - QIcon icon; - QT_MANGLE_NAMESPACE(QNSImageView) *imageCell; -} --(id)initWithSysTray:(QCocoaSystemTrayIcon *)systray; --(void)dealloc; --(NSStatusItem*)item; --(QRectF)geometry; +@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; -- (BOOL)userNotificationCenter:(NSUserNotificationCenter *)center shouldPresentNotification:(NSUserNotification *)notification; -- (void)userNotificationCenter:(NSUserNotificationCenter *)center didActivateNotification:(NSUserNotification *)notification; @end -@interface QT_MANGLE_NAMESPACE(QNSImageView) : NSImageView { - BOOL down; - QT_MANGLE_NAMESPACE(QNSStatusItem) *parent; -} --(id)initWithParent:(QT_MANGLE_NAMESPACE(QNSStatusItem)*)myParent; --(void)menuTrackingDone:(NSNotification*)notification; --(void)mousePressed:(NSEvent *)mouseEvent button:(Qt::MouseButton)mouseButton; +@interface QT_MANGLE_NAMESPACE(QNSImageView) : NSImageView @end QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSStatusItem); @@ -162,7 +147,7 @@ QRect QCocoaSystemTrayIcon::geometry() const void QCocoaSystemTrayIcon::cleanup() { delete m_sys; - m_sys = 0; + m_sys = nullptr; } static bool heightCompareFunction (QSize a, QSize b) { return (a.height() < b.height()); } @@ -178,7 +163,7 @@ void QCocoaSystemTrayIcon::updateIcon(const QIcon &icon) if (!m_sys) return; - m_sys->item->icon = icon; + m_sys->item.icon = icon; // The reccomended maximum title bar icon height is 18 points // (device independent pixels). The menu height on past and @@ -249,8 +234,8 @@ void QCocoaSystemTrayIcon::updateMenu(QPlatformMenu *menu) if (!m_sys) return; - m_sys->item->menu = static_cast<QCocoaMenu *>(menu); - if (menu && [m_sys->item->menu->nsMenu() numberOfItems] > 0) { + 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]; @@ -289,23 +274,29 @@ void QCocoaSystemTrayIcon::showMessage(const QString &title, const QString &mess } QT_END_NAMESPACE -@implementation QNSImageView --(id)initWithParent:(QNSStatusItem*)myParent { +@implementation NSStatusItem (Qt) +@end + +@implementation QNSImageView { + BOOL down; + QT_MANGLE_NAMESPACE(QNSStatusItem) *parent; +} + +- (instancetype)initWithParent:(QNSStatusItem *)myParent { self = [super init]; parent = myParent; down = NO; return self; } --(void)menuTrackingDone:(NSNotification*)notification +- (void)menuTrackingDone:(NSNotification *)__unused notification { - Q_UNUSED(notification); down = NO; [self setNeedsDisplay:YES]; } --(void)mousePressed:(NSEvent *)mouseEvent button:(Qt::MouseButton)mouseButton +- (void)mousePressed:(NSEvent *)mouseEvent { down = YES; int clickCount = [mouseEvent clickCount]; @@ -315,16 +306,16 @@ QT_END_NAMESPACE [self menuTrackingDone:nil]; [parent doubleClickSelector:self]; } else { - [parent triggerSelector:self button:mouseButton]; + [parent triggerSelector:self button:cocoaButton2QtButton(mouseEvent)]; } } --(void)mouseDown:(NSEvent *)mouseEvent +- (void)mouseDown:(NSEvent *)mouseEvent { - [self mousePressed:mouseEvent button:Qt::LeftButton]; + [self mousePressed:mouseEvent]; } --(void)mouseUp:(NSEvent *)mouseEvent +- (void)mouseUp:(NSEvent *)mouseEvent { Q_UNUSED(mouseEvent); [self menuTrackingDone:nil]; @@ -332,10 +323,10 @@ QT_END_NAMESPACE - (void)rightMouseDown:(NSEvent *)mouseEvent { - [self mousePressed:mouseEvent button:Qt::RightButton]; + [self mousePressed:mouseEvent]; } --(void)rightMouseUp:(NSEvent *)mouseEvent +- (void)rightMouseUp:(NSEvent *)mouseEvent { Q_UNUSED(mouseEvent); [self menuTrackingDone:nil]; @@ -343,29 +334,36 @@ QT_END_NAMESPACE - (void)otherMouseDown:(NSEvent *)mouseEvent { - [self mousePressed:mouseEvent button:cocoaButton2QtButton([mouseEvent buttonNumber])]; + [self mousePressed:mouseEvent]; } --(void)otherMouseUp:(NSEvent *)mouseEvent +- (void)otherMouseUp:(NSEvent *)mouseEvent { Q_UNUSED(mouseEvent); [self menuTrackingDone:nil]; } --(void)drawRect:(NSRect)rect { +- (void)drawRect:(NSRect)rect { [[parent item] drawStatusBarBackgroundInRect:rect withHighlight:down]; [super drawRect:rect]; } @end -@implementation QNSStatusItem +@implementation QNSStatusItem { + QCocoaSystemTrayIcon *systray; + NSStatusItem *item; + QT_MANGLE_NAMESPACE(QNSImageView) *imageCell; +} + +@synthesize menu = menu; +@synthesize icon = icon; --(id)initWithSysTray:(QCocoaSystemTrayIcon *)sys +- (instancetype)initWithSysTray:(QCocoaSystemTrayIcon *)sys { self = [super init]; if (self) { item = [[[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength] retain]; - menu = 0; + menu = nullptr; systray = sys; imageCell = [[QNSImageView alloc] initWithParent:self]; [item setView: imageCell]; @@ -373,19 +371,19 @@ QT_END_NAMESPACE return self; } --(void)dealloc { +- (void)dealloc { [[NSStatusBar systemStatusBar] removeStatusItem:item]; [[NSNotificationCenter defaultCenter] removeObserver:imageCell]; [imageCell release]; [item release]; [super dealloc]; - } --(NSStatusItem*)item { +- (NSStatusItem *)item { return item; } --(QRectF)geometry { + +- (QRectF)geometry { if (NSWindow *window = [[item view] window]) { if (QCocoaScreen *screen = QCocoaIntegration::instance()->screenForNSScreen([window screen])) return screen->mapFromNative([window frame]); diff --git a/src/plugins/platforms/cocoa/qcocoatheme.h b/src/plugins/platforms/cocoa/qcocoatheme.h index 69eaf8db56..c42fa7d2e8 100644 --- a/src/plugins/platforms/cocoa/qcocoatheme.h +++ b/src/plugins/platforms/cocoa/qcocoatheme.h @@ -43,7 +43,7 @@ #include <QtCore/QHash> #include <qpa/qplatformtheme.h> -Q_FORWARD_DECLARE_OBJC_CLASS(QT_MANGLE_NAMESPACE(QCocoaThemeNotificationReceiver)); +Q_FORWARD_DECLARE_OBJC_CLASS(QT_MANGLE_NAMESPACE(QCocoaThemeAppAppearanceObserver)); QT_BEGIN_NAMESPACE @@ -78,11 +78,13 @@ public: static const char *name; + void handleSystemThemeChange(); + private: mutable QPalette *m_systemPalette; mutable QHash<QPlatformTheme::Palette, QPalette*> m_palettes; mutable QHash<QPlatformTheme::Font, QFont*> m_fonts; - mutable QT_MANGLE_NAMESPACE(QCocoaThemeNotificationReceiver) *m_notificationReceiver; + QT_MANGLE_NAMESPACE(QCocoaThemeAppAppearanceObserver) *m_appearanceObserver; }; QT_END_NAMESPACE diff --git a/src/plugins/platforms/cocoa/qcocoatheme.mm b/src/plugins/platforms/cocoa/qcocoatheme.mm index 93f0400916..a2229159b5 100644 --- a/src/plugins/platforms/cocoa/qcocoatheme.mm +++ b/src/plugins/platforms/cocoa/qcocoatheme.mm @@ -42,6 +42,7 @@ #include "qcocoatheme.h" #include "messages.h" +#include <QtCore/QOperatingSystemVersion> #include <QtCore/QVariant> #include "qcocoasystemsettings.h" @@ -75,30 +76,50 @@ #endif #endif -#include <Carbon/Carbon.h> +#include <CoreServices/CoreServices.h> -@interface QT_MANGLE_NAMESPACE(QCocoaThemeNotificationReceiver) : NSObject { -QCocoaTheme *mPrivate; -} -- (id)initWithPrivate:(QCocoaTheme *)priv; -- (void)systemColorsDidChange:(NSNotification *)notification; +#if !QT_MACOS_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_14) +@interface NSApplication (MojaveForwardDeclarations) +@property (readonly, strong) NSAppearance *effectiveAppearance NS_AVAILABLE_MAC(10_14); @end +#endif -QT_NAMESPACE_ALIAS_OBJC_CLASS(QCocoaThemeNotificationReceiver); +@interface QT_MANGLE_NAMESPACE(QCocoaThemeAppAppearanceObserver) : NSObject +@property (readonly, nonatomic) QCocoaTheme *theme; +- (instancetype)initWithTheme:(QCocoaTheme *)theme; +@end -@implementation QCocoaThemeNotificationReceiver -- (id)initWithPrivate:(QCocoaTheme *)priv +QT_NAMESPACE_ALIAS_OBJC_CLASS(QCocoaThemeAppAppearanceObserver); + +@implementation QCocoaThemeAppAppearanceObserver +- (instancetype)initWithTheme:(QCocoaTheme *)theme { - self = [super init]; - mPrivate = priv; + if ((self = [super init])) { + _theme = theme; + [NSApp addObserver:self forKeyPath:@"effectiveAppearance" options:NSKeyValueObservingOptionNew context:nullptr]; + } return self; } -- (void)systemColorsDidChange:(NSNotification *)notification +- (void)dealloc { - Q_UNUSED(notification); - mPrivate->reset(); - QWindowSystemInterface::handleThemeChange(nullptr); + [NSApp removeObserver:self forKeyPath:@"effectiveAppearance"]; + [super dealloc]; +} + +- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object + change:(NSDictionary<NSKeyValueChangeKey, id> *)change context:(void *)context +{ + Q_UNUSED(change); + Q_UNUSED(context); + + Q_ASSERT(object == NSApp); + Q_ASSERT([keyPath isEqualToString:@"effectiveAppearance"]); + + if (__builtin_available(macOS 10.14, *)) + NSAppearance.currentAppearance = NSApp.effectiveAppearance; + + self.theme->handleSystemThemeChange(); } @end @@ -107,19 +128,22 @@ QT_BEGIN_NAMESPACE const char *QCocoaTheme::name = "cocoa"; QCocoaTheme::QCocoaTheme() - :m_systemPalette(0) + : m_systemPalette(nullptr), m_appearanceObserver(nil) { - m_notificationReceiver = [[QT_MANGLE_NAMESPACE(QCocoaThemeNotificationReceiver) alloc] initWithPrivate:this]; - [[NSNotificationCenter defaultCenter] addObserver:m_notificationReceiver - selector:@selector(systemColorsDidChange:) - name:NSSystemColorsDidChangeNotification - object:nil]; + if (QOperatingSystemVersion::current() >= QOperatingSystemVersion::MacOSMojave) + m_appearanceObserver = [[QCocoaThemeAppAppearanceObserver alloc] initWithTheme:this]; + + [[NSNotificationCenter defaultCenter] addObserverForName:NSSystemColorsDidChangeNotification + object:nil queue:nil usingBlock:^(NSNotification *) { + handleSystemThemeChange(); + }]; } QCocoaTheme::~QCocoaTheme() { - [[NSNotificationCenter defaultCenter] removeObserver:m_notificationReceiver]; - [m_notificationReceiver release]; + if (m_appearanceObserver) + [m_appearanceObserver release]; + reset(); qDeleteAll(m_fonts); } @@ -132,6 +156,15 @@ void QCocoaTheme::reset() m_palettes.clear(); } +void QCocoaTheme::handleSystemThemeChange() +{ + reset(); + m_systemPalette = qt_mac_createSystemPalette(); + m_palettes = qt_mac_createRolePalettes(); + + QWindowSystemInterface::handleThemeChange(nullptr); +} + bool QCocoaTheme::usePlatformNativeDialog(DialogType dialogType) const { if (dialogType == QPlatformTheme::FileDialog) @@ -147,7 +180,7 @@ bool QCocoaTheme::usePlatformNativeDialog(DialogType dialogType) const return false; } -QPlatformDialogHelper * QCocoaTheme::createPlatformDialogHelper(DialogType dialogType) const +QPlatformDialogHelper *QCocoaTheme::createPlatformDialogHelper(DialogType dialogType) const { switch (dialogType) { #if defined(QT_WIDGETS_LIB) && QT_CONFIG(filedialog) @@ -163,7 +196,7 @@ QPlatformDialogHelper * QCocoaTheme::createPlatformDialogHelper(DialogType dialo return new QCocoaFontDialogHelper(); #endif default: - return 0; + return nullptr; } } @@ -183,9 +216,9 @@ const QPalette *QCocoaTheme::palette(Palette type) const } else { if (m_palettes.isEmpty()) m_palettes = qt_mac_createRolePalettes(); - return m_palettes.value(type, 0); + return m_palettes.value(type, nullptr); } - return 0; + return nullptr; } QHash<QPlatformTheme::Font, QFont *> qt_mac_createRoleFonts() @@ -199,7 +232,7 @@ const QFont *QCocoaTheme::font(Font type) const if (m_fonts.isEmpty()) { m_fonts = qt_mac_createRoleFonts(); } - return m_fonts.value(type, 0); + return m_fonts.value(type, nullptr); } //! \internal diff --git a/src/plugins/platforms/cocoa/qcocoavulkaninstance.h b/src/plugins/platforms/cocoa/qcocoavulkaninstance.h new file mode 100644 index 0000000000..5fe6a612af --- /dev/null +++ b/src/plugins/platforms/cocoa/qcocoavulkaninstance.h @@ -0,0 +1,75 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of 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$ +** +****************************************************************************/ + +#ifndef QCOCOAVULKANINSTANCE_H +#define QCOCOAVULKANINSTANCE_H + +// Include mvk_vulkan.h first. The order is important since +// mvk_vulkan.h just defines VK_USE_PLATFORM_MACOS_MVK (or the IOS +// variant) and includes vulkan.h. If something else included vulkan.h +// before this then we wouldn't get the MVK specifics... +#include <MoltenVK/mvk_vulkan.h> + +#include <QtCore/QHash> +#include <QtVulkanSupport/private/qbasicvulkanplatforminstance_p.h> + +#include <AppKit/AppKit.h> + +QT_BEGIN_NAMESPACE + +class QCocoaVulkanInstance : public QBasicPlatformVulkanInstance +{ +public: + QCocoaVulkanInstance(QVulkanInstance *instance); + ~QCocoaVulkanInstance(); + + void createOrAdoptInstance() override; + + VkSurfaceKHR *createSurface(QWindow *window); + VkSurfaceKHR createSurface(NSView *view); +private: + QVulkanInstance *m_instance = nullptr; + QLibrary m_lib; + VkSurfaceKHR m_nullSurface = nullptr; + PFN_vkCreateMacOSSurfaceMVK m_createSurface = nullptr; +}; + +QT_END_NAMESPACE + +#endif // QXCBVULKANINSTANCE_H diff --git a/src/plugins/platforms/cocoa/qcocoavulkaninstance.mm b/src/plugins/platforms/cocoa/qcocoavulkaninstance.mm new file mode 100644 index 0000000000..b00fde6c6f --- /dev/null +++ b/src/plugins/platforms/cocoa/qcocoavulkaninstance.mm @@ -0,0 +1,95 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of 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$ +** +****************************************************************************/ + +#include "qcocoavulkaninstance.h" +#include "qcocoawindow.h" + +QT_BEGIN_NAMESPACE + +QCocoaVulkanInstance::QCocoaVulkanInstance(QVulkanInstance *instance) + : m_instance(instance) +{ + loadVulkanLibrary(QStringLiteral("vulkan")); +} + +QCocoaVulkanInstance::~QCocoaVulkanInstance() +{ +} + +void QCocoaVulkanInstance::createOrAdoptInstance() +{ + initInstance(m_instance, QByteArrayList()); +} + +VkSurfaceKHR *QCocoaVulkanInstance::createSurface(QWindow *window) +{ + QCocoaWindow *cocoaWindow = static_cast<QCocoaWindow *>(window->handle()); + if (cocoaWindow->m_vulkanSurface) + destroySurface(cocoaWindow->m_vulkanSurface); + cocoaWindow->m_vulkanSurface = createSurface(cocoaWindow->m_view); + return &cocoaWindow->m_vulkanSurface; +} + +VkSurfaceKHR QCocoaVulkanInstance::createSurface(NSView *view) +{ + if (!m_createSurface) { + m_createSurface = reinterpret_cast<PFN_vkCreateMacOSSurfaceMVK>( + m_vkGetInstanceProcAddr(m_vkInst, "vkCreateMacOSSurfaceMVK")); + } + if (!m_createSurface) { + qWarning("Failed to find vkCreateMacOSSurfaceMVK"); + return m_nullSurface; + } + + VkMacOSSurfaceCreateInfoMVK surfaceInfo; + surfaceInfo.sType = VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK; + surfaceInfo.pNext = nullptr; + surfaceInfo.flags = 0; + surfaceInfo.pView = view; + + VkSurfaceKHR surface = nullptr; + VkResult err = m_createSurface(m_vkInst, &surfaceInfo, nullptr, &surface); + if (err != VK_SUCCESS) + qWarning("Failed to create Vulkan surface: %d", err); + + return surface; +} + + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/cocoa/qcocoawindow.h b/src/plugins/platforms/cocoa/qcocoawindow.h index fb91c53a7a..8f1bdb8af0 100644 --- a/src/plugins/platforms/cocoa/qcocoawindow.h +++ b/src/plugins/platforms/cocoa/qcocoawindow.h @@ -53,6 +53,10 @@ #include "qnswindow.h" #include "qt_mac_p.h" +#if QT_CONFIG(vulkan) +#include <MoltenVK/mvk_vulkan.h> +#endif + QT_BEGIN_NAMESPACE #ifndef QT_NO_DEBUG_STREAM @@ -112,6 +116,7 @@ public: void raise() override; void lower() override; bool isExposed() const override; + bool isEmbedded() const override; bool isOpaque() const; void propagateSizeHints() override; void setOpacity(qreal level) override; @@ -124,6 +129,8 @@ public: bool isForeignWindow() const override; void requestUpdate() override; + void deliverUpdateRequest() override; + void requestActivateWindow() override; WId winId() const override; @@ -132,7 +139,7 @@ public: NSView *view() const; NSWindow *nativeWindow() const; - void setEmbeddedInForeignView(bool subwindow); + void setEmbeddedInForeignView(); Q_NOTIFICATION_HANDLER(NSViewFrameDidChangeNotification) void viewDidChangeFrame(); Q_NOTIFICATION_HANDLER(NSViewGlobalFrameDidChangeNotification) void viewDidChangeGlobalFrame(); @@ -162,11 +169,6 @@ public: NSUInteger windowStyleMask(Qt::WindowFlags flags); void setWindowZoomButton(Qt::WindowFlags flags); -#ifndef QT_NO_OPENGL - void setCurrentContext(QCocoaGLContext *context); - QCocoaGLContext *currentContext() const; -#endif - bool setWindowModified(bool modified) override; void setFrameStrutEventsEnabled(bool enabled) override; @@ -237,11 +239,6 @@ public: // for QNSView NSView *m_view; QCocoaNSWindow *m_nsWindow; - // TODO merge to one variable if possible - bool m_viewIsEmbedded; // true if the m_view is actually embedded in a "foreign" NSView hiearchy - bool m_viewIsToBeEmbedded; // true if the m_view is intended to be embedded in a "foreign" NSView hiearchy - - Qt::WindowFlags m_windowFlags; Qt::WindowStates m_lastReportedWindowState; Qt::WindowModality m_windowModality; QPointer<QWindow> m_enterLeaveTargetWindow; @@ -251,9 +248,6 @@ public: // for QNSView bool m_inSetVisible; bool m_inSetGeometry; bool m_inSetStyleMask; -#ifndef QT_NO_OPENGL - QCocoaGLContext *m_glContext; -#endif QCocoaMenuBar *m_menubar; bool m_needsInvalidateShadow; @@ -283,6 +277,10 @@ public: // for QNSView }; QHash<quintptr, BorderRange> m_contentBorderAreas; // identifer -> uppper/lower QHash<quintptr, bool> m_enabledContentBorderAreas; // identifer -> enabled state (true/false) + +#if QT_CONFIG(vulkan) + VkSurfaceKHR m_vulkanSurface = nullptr; +#endif }; #ifndef QT_NO_DEBUG_STREAM diff --git a/src/plugins/platforms/cocoa/qcocoawindow.mm b/src/plugins/platforms/cocoa/qcocoawindow.mm index 9b10c8b053..1de8577ebe 100644 --- a/src/plugins/platforms/cocoa/qcocoawindow.mm +++ b/src/plugins/platforms/cocoa/qcocoawindow.mm @@ -117,24 +117,19 @@ static void qRegisterNotificationCallbacks() return; } - if (lcCocoaNotifications().isDebugEnabled()) { - if (cocoaWindows.isEmpty()) { - qCDebug(lcCocoaNotifications) << "Could not find forwarding target for" << - qPrintable(notificationName) << "from" << notification.object; - } else { - QVector<QCocoaWindow *> debugWindows; - for (QCocoaWindow *cocoaWindow : cocoaWindows) - debugWindows += cocoaWindow; - qCDebug(lcCocoaNotifications) << "Forwarding" << qPrintable(notificationName) << - "to" << debugWindows; - } + if (lcCocoaNotifications().isDebugEnabled() && !cocoaWindows.isEmpty()) { + QVector<QCocoaWindow *> debugWindows; + for (QCocoaWindow *cocoaWindow : cocoaWindows) + debugWindows += cocoaWindow; + qCDebug(lcCocoaNotifications) << "Forwarding" << qPrintable(notificationName) << + "to" << debugWindows; } // FIXME: Could be a foreign window, look up by iterating top level QWindows for (QCocoaWindow *cocoaWindow : cocoaWindows) { if (!method.invoke(cocoaWindow, Qt::DirectConnection)) { - qCWarning(lcQpaCocoaWindow) << "Failed to invoke NSNotification callback for" + qCWarning(lcQpaWindow) << "Failed to invoke NSNotification callback for" << notification.name << "on" << cocoaWindow; } } @@ -148,9 +143,7 @@ const int QCocoaWindow::NoAlertRequest = -1; QCocoaWindow::QCocoaWindow(QWindow *win, WId nativeHandle) : QPlatformWindow(win) , m_view(nil) - , m_nsWindow(0) - , m_viewIsEmbedded(false) - , m_viewIsToBeEmbedded(false) + , m_nsWindow(nil) , m_lastReportedWindowState(Qt::WindowNoState) , m_windowModality(Qt::NonModal) , m_windowUnderMouse(false) @@ -158,10 +151,7 @@ QCocoaWindow::QCocoaWindow(QWindow *win, WId nativeHandle) , m_inSetVisible(false) , m_inSetGeometry(false) , m_inSetStyleMask(false) -#ifndef QT_NO_OPENGL - , m_glContext(0) -#endif - , m_menubar(0) + , m_menubar(nullptr) , m_needsInvalidateShadow(false) , m_hasModalSession(false) , m_frameStrutEventsEnabled(false) @@ -173,7 +163,7 @@ QCocoaWindow::QCocoaWindow(QWindow *win, WId nativeHandle) , m_topContentBorderThickness(0) , m_bottomContentBorderThickness(0) { - qCDebug(lcQpaCocoaWindow) << "QCocoaWindow::QCocoaWindow" << window(); + qCDebug(lcQpaWindow) << "QCocoaWindow::QCocoaWindow" << window(); if (nativeHandle) { m_view = reinterpret_cast<NSView *>(nativeHandle); @@ -183,41 +173,24 @@ QCocoaWindow::QCocoaWindow(QWindow *win, WId nativeHandle) void QCocoaWindow::initialize() { - qCDebug(lcQpaCocoaWindow) << "QCocoaWindow::initialize" << window(); + qCDebug(lcQpaWindow) << "QCocoaWindow::initialize" << window(); QMacAutoReleasePool pool; - if (!m_view) { + if (!m_view) m_view = [[QNSView alloc] initWithCocoaWindow:this]; - // Enable high-dpi OpenGL for retina displays. Enabling has the side - // effect that Cocoa will start calling glViewport(0, 0, width, height), - // overriding any glViewport calls in application code. This is usually not a - // problem, except if the appilcation wants to have a "custom" viewport. - // (like the hellogl example) - if (window()->supportsOpenGL()) { - BOOL enable = qt_mac_resolveOption(YES, window(), "_q_mac_wantsBestResolutionOpenGLSurface", - "QT_MAC_WANTS_BEST_RESOLUTION_OPENGL_SURFACE"); - [m_view setWantsBestResolutionOpenGLSurface:enable]; - // See also QCocoaGLContext::makeCurrent for software renderer workarounds. - } - BOOL enable = qt_mac_resolveOption(NO, window(), "_q_mac_wantsLayer", - "QT_MAC_WANTS_LAYER"); - [m_view setWantsLayer:enable]; - } setGeometry(initialGeometry(window(), windowGeometry(), defaultWindowWidth, defaultWindowHeight)); recreateWindowIfNeeded(); window()->setGeometry(geometry()); - if (window()->isTopLevel()) - setWindowIcon(window()->icon()); m_initialized = true; } QCocoaWindow::~QCocoaWindow() { - qCDebug(lcQpaCocoaWindow) << "QCocoaWindow::~QCocoaWindow" << window(); + qCDebug(lcQpaWindow) << "QCocoaWindow::~QCocoaWindow" << window(); QMacAutoReleasePool pool; [m_nsWindow makeFirstResponder:nil]; @@ -232,10 +205,16 @@ QCocoaWindow::~QCocoaWindow() if (!isForeignWindow()) [[NSNotificationCenter defaultCenter] removeObserver:m_view]; - // While it is unlikely that this window will be in the popup stack - // during deletetion we clear any pointers here to make sure. - if (QCocoaIntegration::instance()) { - QCocoaIntegration::instance()->popupWindowStack()->removeAll(this); + if (QCocoaIntegration *cocoaIntegration = QCocoaIntegration::instance()) { + // While it is unlikely that this window will be in the popup stack + // during deletetion we clear any pointers here to make sure. + cocoaIntegration->popupWindowStack()->removeAll(this); + +#if QT_CONFIG(vulkan) + auto vulcanInstance = cocoaIntegration->getCocoaVulkanInstance(); + if (vulcanInstance) + vulcanInstance->destroySurface(m_vulkanSurface); +#endif } [m_view release]; @@ -255,7 +234,7 @@ QSurfaceFormat QCocoaWindow::format() const void QCocoaWindow::setGeometry(const QRect &rectIn) { - qCDebug(lcQpaCocoaWindow) << "QCocoaWindow::setGeometry" << window() << rectIn; + qCDebug(lcQpaWindow) << "QCocoaWindow::setGeometry" << window() << rectIn; QBoolBlocker inSetGeometry(m_inSetGeometry, true); @@ -283,7 +262,7 @@ QRect QCocoaWindow::geometry() const // QWindows that are embedded in a NSView hiearchy may be considered // top-level from Qt's point of view but are not from Cocoa's point // of view. Embedded QWindows get global (screen) geometry. - if (m_viewIsEmbedded) { + if (isEmbedded()) { NSPoint windowPoint = [m_view convertPoint:NSMakePoint(0, 0) toView:nil]; NSRect screenRect = [[m_view window] convertRectToScreen:NSMakeRect(windowPoint.x, windowPoint.y, 1, 1)]; NSPoint screenPoint = screenRect.origin; @@ -297,12 +276,12 @@ QRect QCocoaWindow::geometry() const void QCocoaWindow::setCocoaGeometry(const QRect &rect) { - qCDebug(lcQpaCocoaWindow) << "QCocoaWindow::setCocoaGeometry" << window() << rect; + qCDebug(lcQpaWindow) << "QCocoaWindow::setCocoaGeometry" << window() << rect; QMacAutoReleasePool pool; QPlatformWindow::setGeometry(rect); - if (m_viewIsEmbedded) { + if (isEmbedded()) { if (!isForeignWindow()) { [m_view setFrame:NSMakeRect(0, 0, rect.width(), rect.height())]; } @@ -321,12 +300,12 @@ void QCocoaWindow::setCocoaGeometry(const QRect &rect) void QCocoaWindow::setVisible(bool visible) { - qCDebug(lcQpaCocoaWindow) << "QCocoaWindow::setVisible" << window() << visible; + qCDebug(lcQpaWindow) << "QCocoaWindow::setVisible" << window() << visible; m_inSetVisible = true; QMacAutoReleasePool pool; - QCocoaWindow *parentCocoaWindow = 0; + QCocoaWindow *parentCocoaWindow = nullptr; if (window()->transientParent()) parentCocoaWindow = static_cast<QCocoaWindow *>(window()->transientParent()->handle()); @@ -355,9 +334,9 @@ void QCocoaWindow::setVisible(bool visible) // Since this isn't a native popup, the window manager doesn't close the popup when you click outside NSWindow *nativeParentWindow = parentCocoaWindow->nativeWindow(); NSUInteger parentStyleMask = nativeParentWindow.styleMask; - if ((m_resizableTransientParent = (parentStyleMask & NSResizableWindowMask)) - && !(nativeParentWindow.styleMask & NSFullScreenWindowMask)) - nativeParentWindow.styleMask &= ~NSResizableWindowMask; + if ((m_resizableTransientParent = (parentStyleMask & NSWindowStyleMaskResizable)) + && !(nativeParentWindow.styleMask & NSWindowStyleMaskFullScreen)) + nativeParentWindow.styleMask &= ~NSWindowStyleMaskResizable; } } @@ -378,13 +357,13 @@ void QCocoaWindow::setVisible(bool visible) } else if (window()->modality() != Qt::NonModal) { // show the window as application modal QCocoaEventDispatcher *cocoaEventDispatcher = qobject_cast<QCocoaEventDispatcher *>(QGuiApplication::instance()->eventDispatcher()); - Q_ASSERT(cocoaEventDispatcher != 0); + Q_ASSERT(cocoaEventDispatcher); QCocoaEventDispatcherPrivate *cocoaEventDispatcherPrivate = static_cast<QCocoaEventDispatcherPrivate *>(QObjectPrivate::get(cocoaEventDispatcher)); cocoaEventDispatcherPrivate->beginModalSession(window()); m_hasModalSession = true; } else if ([m_view.window canBecomeKeyWindow]) { QCocoaEventDispatcher *cocoaEventDispatcher = qobject_cast<QCocoaEventDispatcher *>(QGuiApplication::instance()->eventDispatcher()); - QCocoaEventDispatcherPrivate *cocoaEventDispatcherPrivate = 0; + QCocoaEventDispatcherPrivate *cocoaEventDispatcherPrivate = nullptr; if (cocoaEventDispatcher) cocoaEventDispatcherPrivate = static_cast<QCocoaEventDispatcherPrivate *>(QObjectPrivate::get(cocoaEventDispatcher)); @@ -402,12 +381,15 @@ void QCocoaWindow::setVisible(bool visible) ((NSPanel *)m_view.window).worksWhenModal = YES; if (!(parentCocoaWindow && window()->transientParent()->isActive()) && window()->type() == Qt::Popup) { removeMonitor(); - monitor = [NSEvent addGlobalMonitorForEventsMatchingMask:NSLeftMouseDownMask|NSRightMouseDownMask|NSOtherMouseDownMask|NSMouseMovedMask handler:^(NSEvent *e) { - QPointF localPoint = QCocoaScreen::mapFromNative([NSEvent mouseLocation]); - const auto button = e.type == NSEventTypeMouseMoved ? Qt::NoButton : cocoaButton2QtButton([e buttonNumber]); - const auto eventType = e.type == NSEventTypeMouseMoved ? QEvent::MouseMove : QEvent::MouseButtonPress; - QWindowSystemInterface::handleMouseEvent(window(), window()->mapFromGlobal(localPoint.toPoint()), localPoint, - Qt::MouseButtons(uint(NSEvent.pressedMouseButtons & 0xFFFF)), button, eventType); + NSEventMask eventMask = NSEventMaskLeftMouseDown | NSEventMaskRightMouseDown + | NSEventMaskOtherMouseDown | NSEventMaskMouseMoved; + monitor = [NSEvent addGlobalMonitorForEventsMatchingMask:eventMask handler:^(NSEvent *e) { + const auto button = cocoaButton2QtButton(e); + const auto buttons = currentlyPressedMouseButtons(); + const auto eventType = cocoaEvent2QtMouseEvent(e); + const auto globalPoint = QCocoaScreen::mapFromNative(NSEvent.mouseLocation); + const auto localPoint = window()->mapFromGlobal(globalPoint.toPoint()); + QWindowSystemInterface::handleMouseEvent(window(), localPoint, globalPoint, buttons, button, eventType); }]; } } @@ -420,12 +402,8 @@ void QCocoaWindow::setVisible(bool visible) [m_view setHidden:NO]; } else { // qDebug() << "close" << this; -#ifndef QT_NO_OPENGL - if (m_glContext) - m_glContext->windowWasHidden(); -#endif QCocoaEventDispatcher *cocoaEventDispatcher = qobject_cast<QCocoaEventDispatcher *>(QGuiApplication::instance()->eventDispatcher()); - QCocoaEventDispatcherPrivate *cocoaEventDispatcherPrivate = 0; + QCocoaEventDispatcherPrivate *cocoaEventDispatcherPrivate = nullptr; if (cocoaEventDispatcher) cocoaEventDispatcherPrivate = static_cast<QCocoaEventDispatcherPrivate *>(QObjectPrivate::get(cocoaEventDispatcher)); if (isContentView()) { @@ -463,9 +441,9 @@ void QCocoaWindow::setVisible(bool visible) if (parentCocoaWindow && window()->type() == Qt::Popup) { NSWindow *nativeParentWindow = parentCocoaWindow->nativeWindow(); if (m_resizableTransientParent - && !(nativeParentWindow.styleMask & NSFullScreenWindowMask)) + && !(nativeParentWindow.styleMask & NSWindowStyleMaskFullScreen)) // A window should not be resizable while a transient popup is open - nativeParentWindow.styleMask |= NSResizableWindowMask; + nativeParentWindow.styleMask |= NSWindowStyleMaskResizable; } } @@ -493,7 +471,8 @@ NSInteger QCocoaWindow::windowLevel(Qt::WindowFlags flags) // Any "special" window should be in at least the same level as its parent. if (type != Qt::Window) { const QWindow * const transientParent = window()->transientParent(); - const QCocoaWindow * const transientParentWindow = transientParent ? static_cast<QCocoaWindow *>(transientParent->handle()) : 0; + const QCocoaWindow * const transientParentWindow = transientParent ? + static_cast<QCocoaWindow *>(transientParent->handle()) : nullptr; if (transientParentWindow) windowLevel = qMax([transientParentWindow->nativeWindow() level], windowLevel); } @@ -507,39 +486,39 @@ NSUInteger QCocoaWindow::windowStyleMask(Qt::WindowFlags flags) const bool frameless = (flags & Qt::FramelessWindowHint) || windowIsPopupType(type); // Remove zoom button by disabling resize for CustomizeWindowHint windows, except for - // Qt::Tool windows (e.g. dock windows) which should always be resizeable. - const bool resizeable = !(flags & Qt::CustomizeWindowHint) || (type == Qt::Tool); + // Qt::Tool windows (e.g. dock windows) which should always be resizable. + const bool resizable = !(flags & Qt::CustomizeWindowHint) || (type == Qt::Tool); // Select base window type. Note that the value of NSBorderlessWindowMask is 0. - NSUInteger styleMask = (frameless || !resizeable) ? NSBorderlessWindowMask : NSResizableWindowMask; + NSUInteger styleMask = (frameless || !resizable) ? NSWindowStyleMaskBorderless : NSWindowStyleMaskResizable; if (frameless) { // No further customizations for frameless since there are no window decorations. } else if (flags & Qt::CustomizeWindowHint) { if (flags & Qt::WindowTitleHint) - styleMask |= NSTitledWindowMask; + styleMask |= NSWindowStyleMaskTitled; if (flags & Qt::WindowCloseButtonHint) - styleMask |= NSClosableWindowMask; + styleMask |= NSWindowStyleMaskClosable; if (flags & Qt::WindowMinimizeButtonHint) - styleMask |= NSMiniaturizableWindowMask; + styleMask |= NSWindowStyleMaskMiniaturizable; if (flags & Qt::WindowMaximizeButtonHint) - styleMask |= NSResizableWindowMask; + styleMask |= NSWindowStyleMaskResizable; } else { - styleMask |= NSClosableWindowMask | NSTitledWindowMask; + styleMask |= NSWindowStyleMaskClosable | NSWindowStyleMaskTitled; if (type != Qt::Dialog) - styleMask |= NSMiniaturizableWindowMask; + styleMask |= NSWindowStyleMaskMiniaturizable; } if (type == Qt::Tool) - styleMask |= NSUtilityWindowMask; + styleMask |= NSWindowStyleMaskUtilityWindow; if (m_drawContentBorderGradient) - styleMask |= NSTexturedBackgroundWindowMask; + styleMask |= NSWindowStyleMaskTexturedBackground; // Don't wipe fullscreen state - if (m_view.window.styleMask & NSFullScreenWindowMask) - styleMask |= NSFullScreenWindowMask; + if (m_view.window.styleMask & NSWindowStyleMaskFullScreen) + styleMask |= NSWindowStyleMaskFullScreen; return styleMask; } @@ -562,54 +541,283 @@ void QCocoaWindow::setWindowZoomButton(Qt::WindowFlags flags) void QCocoaWindow::setWindowFlags(Qt::WindowFlags flags) { - if (isContentView()) { - // While setting style mask we can have handleGeometryChange calls on a content - // view with null geometry, reporting an invalid coordinates as a result. - m_inSetStyleMask = true; - m_view.window.styleMask = windowStyleMask(flags); - m_inSetStyleMask = false; - m_view.window.level = this->windowLevel(flags); - - m_view.window.hasShadow = !(flags & Qt::NoDropShadowWindowHint); - - if (!(flags & Qt::FramelessWindowHint)) - setWindowTitle(window()->title()); - - Qt::WindowType type = window()->type(); - if ((type & Qt::Popup) != Qt::Popup && (type & Qt::Dialog) != Qt::Dialog) { - NSWindowCollectionBehavior behavior = m_view.window.collectionBehavior; - if ((flags & Qt::WindowFullscreenButtonHint) || m_view.window.qt_fullScreen) { - behavior |= NSWindowCollectionBehaviorFullScreenPrimary; - behavior &= ~NSWindowCollectionBehaviorFullScreenAuxiliary; - } else { - behavior |= NSWindowCollectionBehaviorFullScreenAuxiliary; - behavior &= ~NSWindowCollectionBehaviorFullScreenPrimary; - } - m_view.window.collectionBehavior = behavior; + if (!isContentView()) + return; + + // While setting style mask we can have handleGeometryChange calls on a content + // view with null geometry, reporting an invalid coordinates as a result. + m_inSetStyleMask = true; + m_view.window.styleMask = windowStyleMask(flags); + m_inSetStyleMask = false; + + Qt::WindowType type = static_cast<Qt::WindowType>(int(flags & Qt::WindowType_Mask)); + if ((type & Qt::Popup) != Qt::Popup && (type & Qt::Dialog) != Qt::Dialog) { + NSWindowCollectionBehavior behavior = m_view.window.collectionBehavior; + if ((flags & Qt::WindowFullscreenButtonHint) || m_view.window.qt_fullScreen) { + behavior |= NSWindowCollectionBehaviorFullScreenPrimary; + behavior &= ~NSWindowCollectionBehaviorFullScreenAuxiliary; + } else { + behavior |= NSWindowCollectionBehaviorFullScreenAuxiliary; + behavior &= ~NSWindowCollectionBehaviorFullScreenPrimary; } - setWindowZoomButton(flags); - - // Make window ignore mouse events if WindowTransparentForInput is set. - // Note that ignoresMouseEvents has a special initial state where events - // are ignored (passed through) based on window transparency, and that - // setting the property to false does not return us to that state. Instead, - // this makes the window capture all mouse events. Take care to only - // set the property if needed. FIXME: recreate window if needed or find - // some other way to implement WindowTransparentForInput. - bool ignoreMouse = flags & Qt::WindowTransparentForInput; - if (m_view.window.ignoresMouseEvents != ignoreMouse) - m_view.window.ignoresMouseEvents = ignoreMouse; + m_view.window.collectionBehavior = behavior; } - m_windowFlags = flags; + // Set styleMask and collectionBehavior before applying window level, as + // the window level change will trigger verification of the two properties. + m_view.window.level = this->windowLevel(flags); + + m_view.window.hasShadow = !(flags & Qt::NoDropShadowWindowHint); + + if (!(flags & Qt::FramelessWindowHint)) + setWindowTitle(window()->title()); + + setWindowZoomButton(flags); + + // Make window ignore mouse events if WindowTransparentForInput is set. + // Note that ignoresMouseEvents has a special initial state where events + // are ignored (passed through) based on window transparency, and that + // setting the property to false does not return us to that state. Instead, + // this makes the window capture all mouse events. Take care to only + // set the property if needed. FIXME: recreate window if needed or find + // some other way to implement WindowTransparentForInput. + bool ignoreMouse = flags & Qt::WindowTransparentForInput; + if (m_view.window.ignoresMouseEvents != ignoreMouse) + m_view.window.ignoresMouseEvents = ignoreMouse; } +// ----------------------- Window state ----------------------- + +/*! + Changes the state of the NSWindow, going in/out of minimize/zoomed/fullscreen + + When this is called from QWindow::setWindowState(), the QWindow state has not been + updated yet, so window()->windowState() will reflect the previous state that was + reported to QtGui. +*/ void QCocoaWindow::setWindowState(Qt::WindowStates state) { if (window()->isVisible()) applyWindowState(state); // Window state set for hidden windows take effect when show() is called } +void QCocoaWindow::applyWindowState(Qt::WindowStates requestedState) +{ + if (!isContentView()) + return; + + const Qt::WindowState currentState = windowState(); + const Qt::WindowState newState = QWindowPrivate::effectiveState(requestedState); + + if (newState == currentState) + return; + + qCDebug(lcQpaWindow) << "Applying" << newState << "to" << window() << "in" << currentState; + + const NSSize contentSize = m_view.frame.size; + if (contentSize.width <= 0 || contentSize.height <= 0) { + // If content view width or height is 0 then the window animations will crash so + // do nothing. We report the current state back to reflect the failed operation. + qWarning("invalid window content view size, check your window geometry"); + handleWindowStateChanged(HandleUnconditionally); + return; + } + + const NSWindow *nsWindow = m_view.window; + + if (nsWindow.styleMask & NSWindowStyleMaskUtilityWindow + && newState & (Qt::WindowMinimized | Qt::WindowFullScreen)) { + qWarning() << window()->type() << "windows can not be made" << newState; + handleWindowStateChanged(HandleUnconditionally); + return; + } + + const id sender = nsWindow; + + // First we need to exit states that can't transition directly to other states + switch (currentState) { + case Qt::WindowMinimized: + [nsWindow deminiaturize:sender]; + Q_ASSERT_X(windowState() != Qt::WindowMinimized, "QCocoaWindow", + "[NSWindow deminiaturize:] is synchronous"); + break; + case Qt::WindowFullScreen: { + toggleFullScreen(); + // Exiting fullscreen is not synchronous, so we need to wait for the + // NSWindowDidExitFullScreenNotification before continuing to apply + // the new state. + return; + } + default:; + } + + // Then we apply the new state if needed + if (newState == windowState()) + return; + + switch (newState) { + case Qt::WindowFullScreen: + toggleFullScreen(); + break; + case Qt::WindowMaximized: + toggleMaximized(); + break; + case Qt::WindowMinimized: + [nsWindow miniaturize:sender]; + break; + case Qt::WindowNoState: + if (windowState() == Qt::WindowMaximized) + toggleMaximized(); + break; + default: + Q_UNREACHABLE(); + } +} + +Qt::WindowState QCocoaWindow::windowState() const +{ + // FIXME: Support compound states (Qt::WindowStates) + + 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; + + // 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; +} + +void QCocoaWindow::toggleMaximized() +{ + const NSWindow *window = m_view.window; + + // The NSWindow needs to be resizable, otherwise the window will + // not be possible to zoom back to non-zoomed state. + const bool wasResizable = window.styleMask & NSWindowStyleMaskResizable; + window.styleMask |= NSWindowStyleMaskResizable; + + const id sender = window; + [window zoom:sender]; + + if (!wasResizable) + window.styleMask &= ~NSWindowStyleMaskResizable; +} + +void QCocoaWindow::toggleFullScreen() +{ + const NSWindow *window = m_view.window; + + // The window needs to have the correct collection behavior for the + // toggleFullScreen call to have an effect. The collection behavior + // will be reset in windowDidEnterFullScreen/windowDidLeaveFullScreen. + window.collectionBehavior |= NSWindowCollectionBehaviorFullScreenPrimary; + + const id sender = window; + [window toggleFullScreen:sender]; +} + +void QCocoaWindow::windowWillEnterFullScreen() +{ + if (!isContentView()) + return; + + // The NSWindow needs to be resizable, otherwise we'll end up with + // the normal window geometry, centered in the middle of the screen + // on a black background. The styleMask will be reset below. + m_view.window.styleMask |= NSWindowStyleMaskResizable; +} + +bool QCocoaWindow::isTransitioningToFullScreen() const +{ + NSWindow *window = m_view.window; + return window.styleMask & NSWindowStyleMaskFullScreen && !window.qt_fullScreen; +} + +void QCocoaWindow::windowDidEnterFullScreen() +{ + if (!isContentView()) + return; + + Q_ASSERT_X(m_view.window.qt_fullScreen, "QCocoaWindow", + "FullScreen category processes window notifications first"); + + // Reset to original styleMask + setWindowFlags(window()->flags()); + + handleWindowStateChanged(); +} + +void QCocoaWindow::windowWillExitFullScreen() +{ + if (!isContentView()) + return; + + // The NSWindow needs to be resizable, otherwise we'll end up with + // a weird zoom animation. The styleMask will be reset below. + m_view.window.styleMask |= NSWindowStyleMaskResizable; +} + +void QCocoaWindow::windowDidExitFullScreen() +{ + if (!isContentView()) + return; + + Q_ASSERT_X(!m_view.window.qt_fullScreen, "QCocoaWindow", + "FullScreen category processes window notifications first"); + + // Reset to original styleMask + setWindowFlags(window()->flags()); + + Qt::WindowState requestedState = window()->windowState(); + + // Deliver update of QWindow state + handleWindowStateChanged(); + + if (requestedState != windowState() && requestedState != Qt::WindowFullScreen) { + // We were only going out of full screen as an intermediate step before + // progressing into the final step, so re-sync the desired state. + applyWindowState(requestedState); + } +} + +void QCocoaWindow::windowDidMiniaturize() +{ + if (!isContentView()) + return; + + handleWindowStateChanged(); +} + +void QCocoaWindow::windowDidDeminiaturize() +{ + if (!isContentView()) + return; + + handleWindowStateChanged(); +} + +void QCocoaWindow::handleWindowStateChanged(HandleFlags flags) +{ + Qt::WindowState currentState = windowState(); + if (!(flags & HandleUnconditionally) && currentState == m_lastReportedWindowState) + return; + + qCDebug(lcQpaWindow) << "QCocoaWindow::handleWindowStateChanged" << + m_lastReportedWindowState << "-->" << currentState; + + QWindowSystemInterface::handleWindowStateChanged<QWindowSystemInterface::SynchronousDelivery>( + window(), currentState, m_lastReportedWindowState); + m_lastReportedWindowState = currentState; +} + +// ------------------------------------------------------------ + void QCocoaWindow::setWindowTitle(const QString &title) { if (!isContentView()) @@ -680,7 +888,7 @@ bool QCocoaWindow::isAlertState() const void QCocoaWindow::raise() { - qCDebug(lcQpaCocoaWindow) << "QCocoaWindow::raise" << window(); + qCDebug(lcQpaWindow) << "QCocoaWindow::raise" << window(); // ### handle spaces (see Qt 4 raise_sys in qwidget_mac.mm) if (!isContentView()) @@ -705,7 +913,7 @@ void QCocoaWindow::raise() void QCocoaWindow::lower() { - qCDebug(lcQpaCocoaWindow) << "QCocoaWindow::lower" << window(); + qCDebug(lcQpaWindow) << "QCocoaWindow::lower" << window(); if (!isContentView()) return; @@ -718,6 +926,22 @@ bool QCocoaWindow::isExposed() const return !m_exposedRect.isEmpty(); } +bool QCocoaWindow::isEmbedded() const +{ + // Child QWindows are not embedded + if (window()->parent()) + return false; + + // Top-level QWindows with non-Qt NSWindows are embedded + if (m_view.window) + return !([m_view.window isKindOfClass:[QNSWindow class]] || + [m_view.window isKindOfClass:[QNSPanel class]]); + + // The window has no QWindow parent but also no NSWindow, + // conservatively reuturn false. + return false; +} + bool QCocoaWindow::isOpaque() const { // OpenGL surfaces can be ordered either above(default) or below the NSWindow. @@ -737,7 +961,7 @@ void QCocoaWindow::propagateSizeHints() if (!isContentView()) return; - qCDebug(lcQpaCocoaWindow) << "QCocoaWindow::propagateSizeHints" << window() + qCDebug(lcQpaWindow) << "QCocoaWindow::propagateSizeHints" << window() << "min:" << windowMinimumSize() << "max:" << windowMaximumSize() << "increment:" << windowSizeIncrement() << "base:" << windowBaseSize(); @@ -754,7 +978,7 @@ void QCocoaWindow::propagateSizeHints() window.contentMaxSize = NSSizeFromCGSize(windowMaximumSize().toCGSize()); // The window may end up with a fixed size; in this case the zoom button should be disabled. - setWindowZoomButton(m_windowFlags); + setWindowZoomButton(this->window()->flags()); // sizeIncrement is observed to take values of (-1, -1) and (0, 0) for windows that should be // resizable and that have no specific size increment set. Cocoa expects (1.0, 1.0) in this case. @@ -771,7 +995,7 @@ void QCocoaWindow::propagateSizeHints() void QCocoaWindow::setOpacity(qreal level) { - qCDebug(lcQpaCocoaWindow) << "QCocoaWindow::setOpacity" << level; + qCDebug(lcQpaWindow) << "QCocoaWindow::setOpacity" << level; if (!isContentView()) return; @@ -780,7 +1004,7 @@ void QCocoaWindow::setOpacity(qreal level) void QCocoaWindow::setMask(const QRegion ®ion) { - qCDebug(lcQpaCocoaWindow) << "QCocoaWindow::setMask" << window() << region; + qCDebug(lcQpaWindow) << "QCocoaWindow::setMask" << window() << region; if (m_view.layer) { if (!region.isEmpty()) { @@ -803,18 +1027,12 @@ void QCocoaWindow::setMask(const QRegion ®ion) // time, and so would not result in an updated backingstore. m_needsInvalidateShadow = true; [m_view setNeedsDisplay:YES]; - - // FIXME: [NSWindow invalidateShadow] has no effect when in layer-backed mode, - // so if the mask is changed after the initial mask is applied, it will not - // result in any visual change to the shadow. This is an Apple bug, and there - // may be ways to work around it, such as calling setFrame on the window to - // trigger some internal invalidation, but that needs more research. } } bool QCocoaWindow::setKeyboardGrabEnabled(bool grab) { - qCDebug(lcQpaCocoaWindow) << "QCocoaWindow::setKeyboardGrabEnabled" << window() << grab; + qCDebug(lcQpaWindow) << "QCocoaWindow::setKeyboardGrabEnabled" << window() << grab; if (!isContentView()) return false; @@ -826,7 +1044,7 @@ bool QCocoaWindow::setKeyboardGrabEnabled(bool grab) bool QCocoaWindow::setMouseGrabEnabled(bool grab) { - qCDebug(lcQpaCocoaWindow) << "QCocoaWindow::setMouseGrabEnabled" << window() << grab; + qCDebug(lcQpaWindow) << "QCocoaWindow::setMouseGrabEnabled" << window() << grab; if (!isContentView()) return false; @@ -843,10 +1061,10 @@ WId QCocoaWindow::winId() const void QCocoaWindow::setParent(const QPlatformWindow *parentWindow) { - qCDebug(lcQpaCocoaWindow) << "QCocoaWindow::setParent" << window() << (parentWindow ? parentWindow->window() : 0); + qCDebug(lcQpaWindow) << "QCocoaWindow::setParent" << window() << (parentWindow ? parentWindow->window() : 0); // recreate the window for compatibility - bool unhideAfterRecreate = parentWindow && !m_viewIsToBeEmbedded && ![m_view isHidden]; + bool unhideAfterRecreate = parentWindow && !isEmbedded() && ![m_view isHidden]; recreateWindowIfNeeded(); if (unhideAfterRecreate) [m_view setHidden:NO]; @@ -863,9 +1081,8 @@ NSWindow *QCocoaWindow::nativeWindow() const return m_view.window; } -void QCocoaWindow::setEmbeddedInForeignView(bool embedded) +void QCocoaWindow::setEmbeddedInForeignView() { - m_viewIsToBeEmbedded = embedded; // Release any previosly created NSWindow. [m_nsWindow closeAndRelease]; m_nsWindow = 0; @@ -948,8 +1165,8 @@ void QCocoaWindow::windowDidBecomeKey() QWindowSystemInterface::handleEnterEvent(m_enterLeaveTargetWindow, windowPoint, screenPoint); } - if (!windowIsPopupType() && !qnsview_cast(m_view).isMenuView) - QWindowSystemInterface::handleWindowActivated(window()); + if (!windowIsPopupType()) + QWindowSystemInterface::handleWindowActivated<QWindowSystemInterface::SynchronousDelivery>(window()); } void QCocoaWindow::windowDidResignKey() @@ -966,82 +1183,8 @@ void QCocoaWindow::windowDidResignKey() NSWindow *keyWindow = [NSApp keyWindow]; if (!keyWindow || keyWindow == m_view.window) { // No new key window, go ahead and set the active window to zero - if (!windowIsPopupType() && !qnsview_cast(m_view).isMenuView) - QWindowSystemInterface::handleWindowActivated(0); - } -} - -void QCocoaWindow::windowDidMiniaturize() -{ - if (!isContentView()) - return; - - handleWindowStateChanged(); -} - -void QCocoaWindow::windowDidDeminiaturize() -{ - if (!isContentView()) - return; - - handleWindowStateChanged(); -} - -void QCocoaWindow::windowWillEnterFullScreen() -{ - if (!isContentView()) - return; - - // The NSWindow needs to be resizable, otherwise we'll end up with - // the normal window geometry, centered in the middle of the screen - // on a black background. The styleMask will be reset below. - m_view.window.styleMask |= NSResizableWindowMask; -} - -void QCocoaWindow::windowDidEnterFullScreen() -{ - if (!isContentView()) - return; - - Q_ASSERT_X(m_view.window.qt_fullScreen, "QCocoaWindow", - "FullScreen category processes window notifications first"); - - // Reset to original styleMask - setWindowFlags(m_windowFlags); - - handleWindowStateChanged(); -} - -void QCocoaWindow::windowWillExitFullScreen() -{ - if (!isContentView()) - return; - - // The NSWindow needs to be resizable, otherwise we'll end up with - // a weird zoom animation. The styleMask will be reset below. - m_view.window.styleMask |= NSResizableWindowMask; -} - -void QCocoaWindow::windowDidExitFullScreen() -{ - if (!isContentView()) - return; - - Q_ASSERT_X(!m_view.window.qt_fullScreen, "QCocoaWindow", - "FullScreen category processes window notifications first"); - - // Reset to original styleMask - setWindowFlags(m_windowFlags); - - Qt::WindowState requestedState = window()->windowState(); - - // Deliver update of QWindow state - handleWindowStateChanged(); - - if (requestedState != windowState() && requestedState != Qt::WindowFullScreen) { - // We were only going out of full screen as an intermediate step before - // progressing into the final step, so re-sync the desired state. - applyWindowState(requestedState); + if (!windowIsPopupType()) + QWindowSystemInterface::handleWindowActivated<QWindowSystemInterface::SynchronousDelivery>(0); } } @@ -1068,8 +1211,24 @@ void QCocoaWindow::windowDidChangeScreen() if (!window()) return; - if (QCocoaScreen *cocoaScreen = QCocoaIntegration::instance()->screenForNSScreen(m_view.window.screen)) - QWindowSystemInterface::handleWindowScreenChanged(window(), cocoaScreen->screen()); + const bool wasRunningDisplayLink = static_cast<QCocoaScreen *>(screen())->isRunningDisplayLink(); + + if (QCocoaScreen *newScreen = QCocoaIntegration::instance()->screenForNSScreen(m_view.window.screen)) { + if (newScreen == screen()) { + // Screen properties have changed. Will be handled by + // NSApplicationDidChangeScreenParametersNotification + // in QCocoaIntegration::updateScreens(). + return; + } + + qCDebug(lcQpaWindow) << window() << "moved to" << newScreen; + QWindowSystemInterface::handleWindowScreenChanged<QWindowSystemInterface::SynchronousDelivery>(window(), newScreen->screen()); + + if (hasPendingUpdateRequest() && wasRunningDisplayLink) + requestUpdate(); // Restart display-link on new screen + } else { + qCWarning(lcQpaWindow) << "Failed to get QCocoaScreen for" << m_view.window.screen; + } } void QCocoaWindow::windowWillClose() @@ -1083,7 +1242,7 @@ void QCocoaWindow::windowWillClose() bool QCocoaWindow::windowShouldClose() { - qCDebug(lcQpaCocoaWindow) << "QCocoaWindow::windowShouldClose" << window(); + qCDebug(lcQpaWindow) << "QCocoaWindow::windowShouldClose" << window(); // This callback should technically only determine if the window // should (be allowed to) close, but since our QPA API to determine // that also involves actually closing the window we do both at the @@ -1104,22 +1263,14 @@ void QCocoaWindow::handleGeometryChange() if (!m_initialized) return; - // Don't send the geometry change if the QWindow is designated to be - // embedded in a foreign view hierarchy but has not actually been - // embedded yet - it's too early. - if (m_viewIsToBeEmbedded && !m_viewIsEmbedded) - return; - // It can happen that the current NSWindow is nil (if we are changing styleMask // from/to borderless, and the content view is being re-parented), which results // in invalid coordinates. if (m_inSetStyleMask && !m_view.window) return; - const bool isEmbedded = m_viewIsToBeEmbedded || m_viewIsEmbedded; - QRect newGeometry; - if (isContentView() && !isEmbedded) { + if (isContentView() && !isEmbedded()) { // Content views are positioned at (0, 0) in the window, so we resolve via the window CGRect contentRect = [m_view.window contentRectForFrameRect:m_view.window.frame]; @@ -1130,7 +1281,7 @@ void QCocoaWindow::handleGeometryChange() newGeometry = QRectF::fromCGRect(m_view.frame).toRect(); } - qCDebug(lcQpaCocoaWindow) << "QCocoaWindow::handleGeometryChange" << window() + qCDebug(lcQpaWindow) << "QCocoaWindow::handleGeometryChange" << window() << "current" << geometry() << "new" << newGeometry; QWindowSystemInterface::handleGeometryChange(window(), newGeometry); @@ -1162,24 +1313,10 @@ void QCocoaWindow::handleExposeEvent(const QRegion ®ion) m_exposedRect = QRect(); } - qCDebug(lcQpaCocoaDrawing) << "QCocoaWindow::handleExposeEvent" << window() << region << "isExposed" << isExposed(); + qCDebug(lcQpaDrawing) << "QCocoaWindow::handleExposeEvent" << window() << region << "isExposed" << isExposed(); QWindowSystemInterface::handleExposeEvent<QWindowSystemInterface::SynchronousDelivery>(window(), region); } -void QCocoaWindow::handleWindowStateChanged(HandleFlags flags) -{ - Qt::WindowState currentState = windowState(); - if (!(flags & HandleUnconditionally) && currentState == m_lastReportedWindowState) - return; - - qCDebug(lcQpaCocoaWindow) << "QCocoaWindow::handleWindowStateChanged" << - m_lastReportedWindowState << "-->" << currentState; - - QWindowSystemInterface::handleWindowStateChanged<QWindowSystemInterface::SynchronousDelivery>( - window(), currentState, m_lastReportedWindowState); - m_lastReportedWindowState = currentState; -} - // -------------------------------------------------------------------------- bool QCocoaWindow::windowIsPopupType(Qt::WindowType type) const @@ -1192,18 +1329,6 @@ bool QCocoaWindow::windowIsPopupType(Qt::WindowType type) const return ((type & Qt::Popup) == Qt::Popup); } -#ifndef QT_NO_OPENGL -void QCocoaWindow::setCurrentContext(QCocoaGLContext *context) -{ - m_glContext = context; -} - -QCocoaGLContext *QCocoaWindow::currentContext() const -{ - return m_glContext; -} -#endif - /*! Checks if the window is the content view of its immediate NSWindow. @@ -1230,6 +1355,7 @@ void QCocoaWindow::recreateWindowIfNeeded() QPlatformWindow *parentWindow = QPlatformWindow::parent(); + const bool isEmbeddedView = isEmbedded(); RecreationReasons recreateReason = RecreationNotNeeded; QCocoaWindow *oldParentCocoaWindow = nullptr; @@ -1246,7 +1372,7 @@ void QCocoaWindow::recreateWindowIfNeeded() if (m_windowModality != window()->modality()) recreateReason |= WindowModalityChanged; - const bool shouldBeContentView = !parentWindow && !(m_viewIsToBeEmbedded || m_viewIsEmbedded); + const bool shouldBeContentView = !parentWindow && !isEmbeddedView; if (isContentView() != shouldBeContentView) recreateReason |= ContentViewChanged; @@ -1258,7 +1384,7 @@ void QCocoaWindow::recreateWindowIfNeeded() if (isPanel != shouldBePanel) recreateReason |= PanelChanged; - qCDebug(lcQpaCocoaWindow) << "QCocoaWindow::recreateWindowIfNeeded" << window() << recreateReason; + qCDebug(lcQpaWindow) << "QCocoaWindow::recreateWindowIfNeeded" << window() << recreateReason; if (recreateReason == RecreationNotNeeded) return; @@ -1268,33 +1394,28 @@ void QCocoaWindow::recreateWindowIfNeeded() // Remove current window (if any) if ((isContentView() && !shouldBeContentView) || (recreateReason & PanelChanged)) { if (m_nsWindow) { - qCDebug(lcQpaCocoaWindow) << "Getting rid of existing window" << m_nsWindow; + qCDebug(lcQpaWindow) << "Getting rid of existing window" << m_nsWindow; if (m_nsWindow.observationInfo) { - qCCritical(lcQpaCocoaWindow) << m_nsWindow << "has active key-value observers (KVO)!" + qCCritical(lcQpaWindow) << m_nsWindow << "has active key-value observers (KVO)!" << "These will stop working now that the window is recreated, and will result in exceptions" << "when the observers are removed. Break in QCocoaWindow::recreateWindowIfNeeded to debug."; } [m_nsWindow closeAndRelease]; - if (isContentView()) { + if (isContentView() && !isEmbeddedView) { // We explicitly disassociate m_view from the window's contentView, // as AppKit does not automatically do this in response to removing // the view from the NSThemeFrame subview list, so we might end up // with a NSWindow contentView pointing to a deallocated NSView. m_view.window.contentView = nil; } - m_nsWindow = 0; + m_nsWindow = nil; } } - if (shouldBeContentView) { - bool noPreviousWindow = m_nsWindow == 0; - QCocoaNSWindow *newWindow = nullptr; - if (noPreviousWindow) - newWindow = createNSWindow(shouldBePanel); - + if (shouldBeContentView && !m_nsWindow) { // Move view to new NSWindow if needed - if (newWindow) { - qCDebug(lcQpaCocoaWindow) << "Ensuring that" << m_view << "is content view for" << newWindow; + if (auto *newWindow = createNSWindow(shouldBePanel)) { + qCDebug(lcQpaWindow) << "Ensuring that" << m_view << "is content view for" << newWindow; [m_view setPostsFrameChangedNotifications:NO]; [newWindow setContentView:m_view]; [m_view setPostsFrameChangedNotifications:YES]; @@ -1304,14 +1425,14 @@ void QCocoaWindow::recreateWindowIfNeeded() } } - if (m_viewIsToBeEmbedded) { + if (isEmbeddedView) { // An embedded window doesn't have its own NSWindow. } else if (!parentWindow) { // QPlatformWindow subclasses must sync up with QWindow on creation: propagateSizeHints(); setWindowFlags(window()->flags()); setWindowTitle(window()->title()); - setWindowFilePath(window()->filePath()); + setWindowFilePath(window()->filePath()); // Also sets window icon setWindowState(window()->windowState()); } else { // Child windows have no NSWindow, link the NSViews instead. @@ -1334,14 +1455,28 @@ void QCocoaWindow::recreateWindowIfNeeded() // top-level QWindows may have an attached NSToolBar, call // update function which will attach to the NSWindow. - if (!parentWindow) + if (!parentWindow && !isEmbeddedView) updateNSToolbar(); } void QCocoaWindow::requestUpdate() { - qCDebug(lcQpaCocoaDrawing) << "QCocoaWindow::requestUpdate" << window(); - QPlatformWindow::requestUpdate(); + const int swapInterval = format().swapInterval(); + qCDebug(lcQpaDrawing) << "QCocoaWindow::requestUpdate" << window() << "swapInterval" << swapInterval; + + if (swapInterval > 0) { + // Vsync is enabled, deliver via CVDisplayLink + static_cast<QCocoaScreen *>(screen())->requestUpdate(); + } else { + // Fall back to the un-throttled timer-based callback + QPlatformWindow::requestUpdate(); + } +} + +void QCocoaWindow::deliverUpdateRequest() +{ + qCDebug(lcQpaDrawing) << "Delivering update request to" << window(); + QPlatformWindow::deliverUpdateRequest(); } void QCocoaWindow::requestActivateWindow() @@ -1375,7 +1510,7 @@ QCocoaNSWindow *QCocoaWindow::createNSWindow(bool shouldBePanel) } if (!targetScreen) { - qCWarning(lcQpaCocoaWindow) << "Window position" << rect << "outside any known screen, using primary screen"; + qCWarning(lcQpaWindow) << "Window position" << rect << "outside any known screen, using primary screen"; targetScreen = QGuiApplication::primaryScreen(); // AppKit will only reposition a window that's outside the target screen area if // the window has a title bar. If left out, the window ends up with no screen. @@ -1399,8 +1534,10 @@ QCocoaNSWindow *QCocoaWindow::createNSWindow(bool shouldBePanel) Q_ASSERT_X(nsWindow.screen == cocoaScreen->nativeScreen(), "QCocoaWindow", "Resulting NSScreen should match the requested NSScreen"); - if (targetScreen != window()->screen()) - QWindowSystemInterface::handleWindowScreenChanged(window(), targetScreen); + if (targetScreen != window()->screen()) { + QWindowSystemInterface::handleWindowScreenChanged< + QWindowSystemInterface::SynchronousDelivery>(window(), targetScreen); + } nsWindow.delegate = [[QNSWindowDelegate alloc] initWithQCocoaWindow:this]; @@ -1473,138 +1610,6 @@ void QCocoaWindow::removeMonitor() monitor = nil; } -/*! - Applies the given state to the NSWindow, going in/out of minimize/zoomed/fullscreen - - When this is called from QWindow::setWindowState(), the QWindow state has not been - updated yet, so window()->windowState() will reflect the previous state that was - reported to QtGui. -*/ -void QCocoaWindow::applyWindowState(Qt::WindowStates requestedState) -{ - if (!isContentView()) - return; - - const Qt::WindowState currentState = windowState(); - const Qt::WindowState newState = QWindowPrivate::effectiveState(requestedState); - - if (newState == currentState) - return; - - const NSSize contentSize = m_view.frame.size; - if (contentSize.width <= 0 || contentSize.height <= 0) { - // If content view width or height is 0 then the window animations will crash so - // do nothing. We report the current state back to reflect the failed operation. - qWarning("invalid window content view size, check your window geometry"); - handleWindowStateChanged(HandleUnconditionally); - return; - } - - const NSWindow *nsWindow = m_view.window; - - if (nsWindow.styleMask & NSUtilityWindowMask - && newState & (Qt::WindowMinimized | Qt::WindowFullScreen)) { - qWarning() << window()->type() << "windows can not be made" << newState; - handleWindowStateChanged(HandleUnconditionally); - return; - } - - const id sender = nsWindow; - - // First we need to exit states that can't transition directly to other states - switch (currentState) { - case Qt::WindowMinimized: - [nsWindow deminiaturize:sender]; - Q_ASSERT_X(windowState() != Qt::WindowMinimized, "QCocoaWindow", - "[NSWindow deminiaturize:] is synchronous"); - break; - case Qt::WindowFullScreen: { - toggleFullScreen(); - // Exiting fullscreen is not synchronous, so we need to wait for the - // NSWindowDidExitFullScreenNotification before continuing to apply - // the new state. - return; - } - default:; - } - - // Then we apply the new state if needed - if (newState == windowState()) - return; - - switch (newState) { - case Qt::WindowFullScreen: - toggleFullScreen(); - break; - case Qt::WindowMaximized: - toggleMaximized(); - break; - case Qt::WindowMinimized: - [nsWindow miniaturize:sender]; - break; - case Qt::WindowNoState: - if (windowState() == Qt::WindowMaximized) - toggleMaximized(); - break; - default: - Q_UNREACHABLE(); - } -} - -void QCocoaWindow::toggleMaximized() -{ - const NSWindow *window = m_view.window; - - // The NSWindow needs to be resizable, otherwise the window will - // not be possible to zoom back to non-zoomed state. - const bool wasResizable = window.styleMask & NSResizableWindowMask; - window.styleMask |= NSResizableWindowMask; - - const id sender = window; - [window zoom:sender]; - - if (!wasResizable) - window.styleMask &= ~NSResizableWindowMask; -} - -void QCocoaWindow::toggleFullScreen() -{ - const NSWindow *window = m_view.window; - - // The window needs to have the correct collection behavior for the - // toggleFullScreen call to have an effect. The collection behavior - // will be reset in windowDidEnterFullScreen/windowDidLeaveFullScreen. - window.collectionBehavior |= NSWindowCollectionBehaviorFullScreenPrimary; - - const id sender = window; - [window toggleFullScreen:sender]; -} - -bool QCocoaWindow::isTransitioningToFullScreen() const -{ - NSWindow *window = m_view.window; - return window.styleMask & NSFullScreenWindowMask && !window.qt_fullScreen; -} - -Qt::WindowState QCocoaWindow::windowState() const -{ - // FIXME: Support compound states (Qt::WindowStates) - - 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; - - // 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; -} - bool QCocoaWindow::setWindowModified(bool modified) { if (!isContentView()) @@ -1685,7 +1690,7 @@ void QCocoaWindow::applyContentBorderThickness(NSWindow *window) return; if (!m_drawContentBorderGradient) { - window.styleMask = window.styleMask & ~NSTexturedBackgroundWindowMask; + window.styleMask = window.styleMask & ~NSWindowStyleMaskTexturedBackground; [window.contentView.superview setNeedsDisplay:YES]; window.titlebarAppearsTransparent = NO; return; @@ -1711,7 +1716,7 @@ void QCocoaWindow::applyContentBorderThickness(NSWindow *window) int effectiveBottomContentBorderThickness = m_bottomContentBorderThickness; - [window setStyleMask:[window styleMask] | NSTexturedBackgroundWindowMask]; + [window setStyleMask:[window styleMask] | NSWindowStyleMaskTexturedBackground]; window.titlebarAppearsTransparent = YES; [window setContentBorderThickness:effectiveTopContentBorderThickness forEdge:NSMaxYEdge]; diff --git a/src/plugins/platforms/cocoa/qmacclipboard.h b/src/plugins/platforms/cocoa/qmacclipboard.h index 1d229a55d2..f2f460c048 100644 --- a/src/plugins/platforms/cocoa/qmacclipboard.h +++ b/src/plugins/platforms/cocoa/qmacclipboard.h @@ -54,7 +54,7 @@ public: enum DataRequestType { EagerRequest, LazyRequest }; private: struct Promise { - Promise() : itemId(0), convertor(0) { } + Promise() : itemId(0), convertor(nullptr) { } static Promise eagerPromise(int itemId, QMacInternalPasteboardMime *c, QString m, QMacMimeData *d, int o = 0); static Promise lazyPromise(int itemId, QMacInternalPasteboardMime *c, QString m, QMacMimeData *d, int o = 0); @@ -79,7 +79,7 @@ private: public: QMacPasteboard(PasteboardRef p, uchar mime_type=0); QMacPasteboard(uchar mime_type); - QMacPasteboard(CFStringRef name=0, uchar mime_type=0); + QMacPasteboard(CFStringRef name=nullptr, uchar mime_type=0); ~QMacPasteboard(); bool hasFlavor(QString flavor) const; diff --git a/src/plugins/platforms/cocoa/qmacclipboard.mm b/src/plugins/platforms/cocoa/qmacclipboard.mm index f3467fdc73..5939003c64 100644 --- a/src/plugins/platforms/cocoa/qmacclipboard.mm +++ b/src/plugins/platforms/cocoa/qmacclipboard.mm @@ -75,7 +75,7 @@ QMacPasteboard::Promise::Promise(int itemId, QMacInternalPasteboardMime *c, QStr // Request the data from the application immediately for eager requests. if (dataRequestType == QMacPasteboard::EagerRequest) { variantData = md->variantData(m); - mimeData = 0; + mimeData = nullptr; } else { mimeData = md; } @@ -94,8 +94,8 @@ QMacPasteboard::QMacPasteboard(uchar mt) { mac_mime_source = false; mime_type = mt ? mt : uchar(QMacInternalPasteboardMime::MIME_ALL); - paste = 0; - OSStatus err = PasteboardCreate(0, &paste); + paste = nullptr; + OSStatus err = PasteboardCreate(nullptr, &paste); if (err == noErr) { PasteboardSetPromiseKeeper(paste, promiseKeeper, this); } else { @@ -108,7 +108,7 @@ QMacPasteboard::QMacPasteboard(CFStringRef name, uchar mt) { mac_mime_source = false; mime_type = mt ? mt : uchar(QMacInternalPasteboardMime::MIME_ALL); - paste = 0; + paste = nullptr; OSStatus err = PasteboardCreate(name, &paste); if (err == noErr) { PasteboardSetPromiseKeeper(paste, promiseKeeper, this); @@ -165,7 +165,7 @@ OSStatus QMacPasteboard::promiseKeeper(PasteboardRef paste, PasteboardItemID id, // we have promised this data, but won't be able to convert, so return null data. // This helps in making the application/x-qt-mime-type-name hidden from normal use. QByteArray ba; - QCFType<CFDataRef> data = CFDataCreate(0, (UInt8*)ba.constData(), ba.size()); + QCFType<CFDataRef> data = CFDataCreate(nullptr, (UInt8*)ba.constData(), ba.size()); PasteboardPutItemFlavor(paste, id, flavor, data, kPasteboardFlavorNoFlags); return noErr; } @@ -196,7 +196,7 @@ OSStatus QMacPasteboard::promiseKeeper(PasteboardRef paste, PasteboardItemID id, if (md.size() <= promise.offset) return cantGetFlavorErr; const QByteArray &ba = md[promise.offset]; - QCFType<CFDataRef> data = CFDataCreate(0, (UInt8*)ba.constData(), ba.size()); + QCFType<CFDataRef> data = CFDataCreate(nullptr, (UInt8*)ba.constData(), ba.size()); PasteboardPutItemFlavor(paste, id, flavor, data, kPasteboardFlavorNoFlags); return noErr; } @@ -553,7 +553,7 @@ QMacPasteboard::sync() const const bool fromGlobal = PasteboardSynchronize(paste) & kPasteboardModified; if (fromGlobal) - const_cast<QMacPasteboard *>(this)->setMimeData(0); + const_cast<QMacPasteboard *>(this)->setMimeData(nullptr); #ifdef DEBUG_PASTEBOARD if (fromGlobal) diff --git a/src/plugins/platforms/cocoa/qmultitouch_mac.mm b/src/plugins/platforms/cocoa/qmultitouch_mac.mm index 9eca4d2d43..10652dc6a1 100644 --- a/src/plugins/platforms/cocoa/qmultitouch_mac.mm +++ b/src/plugins/platforms/cocoa/qmultitouch_mac.mm @@ -107,7 +107,7 @@ QCocoaTouch *QCocoaTouch::findQCocoaTouch(NSTouch *nstouch) qint64 identity = qint64([nstouch identity]); if (_currentTouches.contains(identity)) return _currentTouches.value(identity); - return 0; + return nullptr; } Qt::TouchPointState QCocoaTouch::toTouchPointState(NSTouchPhase nsState) diff --git a/src/plugins/platforms/cocoa/qnsview.h b/src/plugins/platforms/cocoa/qnsview.h index e2ea862cd5..b40dfe0d14 100644 --- a/src/plugins/platforms/cocoa/qnsview.h +++ b/src/plugins/platforms/cocoa/qnsview.h @@ -41,111 +41,45 @@ #define QNSVIEW_H #include <AppKit/AppKit.h> - -#include <QtCore/QPointer> -#include <QtCore/QSet> -#include <QtGui/QImage> -#include <QtGui/QAccessible> +#include <MetalKit/MetalKit.h> #include "private/qcore_mac_p.h" QT_BEGIN_NAMESPACE class QCocoaWindow; class QCocoaGLContext; +class QPointF; QT_END_NAMESPACE Q_FORWARD_DECLARE_OBJC_CLASS(QT_MANGLE_NAMESPACE(QNSViewMouseMoveHelper)); +Q_FORWARD_DECLARE_OBJC_CLASS(QT_MANGLE_NAMESPACE(QCocoaNSMenuItem)); -@interface QT_MANGLE_NAMESPACE(QNSView) : NSView <NSTextInputClient> { - QPointer<QCocoaWindow> m_platformWindow; - NSTrackingArea *m_trackingArea; - Qt::MouseButtons m_buttons; - Qt::MouseButtons m_acceptedMouseDowns; - Qt::MouseButtons m_frameStrutButtons; - QString m_composingText; - QPointer<QObject> m_composingFocusObject; - bool m_sendKeyEvent; - QStringList *currentCustomDragTypes; - bool m_dontOverrideCtrlLMB; - bool m_sendUpAsRightButton; - Qt::KeyboardModifiers currentWheelModifiers; -#ifndef QT_NO_OPENGL - QCocoaGLContext *m_glContext; - bool m_shouldSetGLContextinDrawRect; -#endif - NSString *m_inputSource; - QT_MANGLE_NAMESPACE(QNSViewMouseMoveHelper) *m_mouseMoveHelper; - bool m_resendKeyEvent; - bool m_scrolling; - bool m_updatingDrag; - NSEvent *m_currentlyInterpretedKeyEvent; - bool m_isMenuView; - QSet<quint32> m_acceptedKeyDowns; - bool m_updateRequested; -} +@interface QT_MANGLE_NAMESPACE(QNSView) : NSView @property (nonatomic, retain) NSCursor *cursor; -- (id)init; -- (id)initWithCocoaWindow:(QCocoaWindow *)platformWindow; -#ifndef QT_NO_OPENGL -- (void)setQCocoaGLContext:(QCocoaGLContext *)context; -#endif -- (void)drawRect:(NSRect)dirtyRect; -- (void)textInputContextKeyboardSelectionDidChangeNotification : (NSNotification *) textInputContextKeyboardSelectionDidChangeNotification; -- (void)viewDidHide; -- (void)removeFromSuperview; -- (void)cancelComposingText; - -- (BOOL)isFlipped; -- (BOOL)acceptsFirstResponder; -- (BOOL)becomeFirstResponder; -- (BOOL)isOpaque; +- (instancetype)initWithCocoaWindow:(QCocoaWindow *)platformWindow; - (void)convertFromScreen:(NSPoint)mouseLocation toWindowPoint:(QPointF *)qtWindowPoint andScreenPoint:(QPointF *)qtScreenPoint; -- (void)resetMouseButtons; +@end -- (void)requestUpdate; - -- (void)handleMouseEvent:(NSEvent *)theEvent; -- (bool)handleMouseDownEvent:(NSEvent *)theEvent withButton:(int)buttonNumber; -- (bool)handleMouseDraggedEvent:(NSEvent *)theEvent withButton:(int)buttonNumber; -- (bool)handleMouseUpEvent:(NSEvent *)theEvent withButton:(int)buttonNumber; -- (void)mouseDown:(NSEvent *)theEvent; -- (void)mouseDragged:(NSEvent *)theEvent; -- (void)mouseUp:(NSEvent *)theEvent; -- (void)mouseMovedImpl:(NSEvent *)theEvent; -- (void)mouseEnteredImpl:(NSEvent *)theEvent; -- (void)mouseExitedImpl:(NSEvent *)theEvent; -- (void)rightMouseDown:(NSEvent *)theEvent; -- (void)rightMouseDragged:(NSEvent *)theEvent; -- (void)rightMouseUp:(NSEvent *)theEvent; -- (void)otherMouseDown:(NSEvent *)theEvent; -- (void)otherMouseDragged:(NSEvent *)theEvent; -- (void)otherMouseUp:(NSEvent *)theEvent; +@interface QT_MANGLE_NAMESPACE(QNSView) (MouseAPI) - (void)handleFrameStrutMouseEvent:(NSEvent *)theEvent; +- (void)resetMouseButtons; +@end -#ifndef QT_NO_TABLETEVENT -- (bool)handleTabletEvent: (NSEvent *)theEvent; -- (void)tabletPoint: (NSEvent *)theEvent; -- (void)tabletProximity: (NSEvent *)theEvent; -#endif - -- (int) convertKeyCode : (QChar)keyCode; -+ (Qt::KeyboardModifiers) convertKeyModifiers : (ulong)modifierFlags; -- (bool)handleKeyEvent:(NSEvent *)theEvent eventType:(int)eventType; -- (void)keyDown:(NSEvent *)theEvent; -- (void)keyUp:(NSEvent *)theEvent; - -- (void)registerDragTypes; -- (NSDragOperation)handleDrag:(id <NSDraggingInfo>)sender; +@interface QT_MANGLE_NAMESPACE(QNSView) (KeysAPI) ++ (Qt::KeyboardModifiers)convertKeyModifiers:(ulong)modifierFlags; +@end +@interface QT_MANGLE_NAMESPACE(QNSView) (ComplexTextAPI) +- (void)unmarkText; +- (void)cancelComposingText; @end @interface QT_MANGLE_NAMESPACE(QNSView) (QtExtras) @property (nonatomic, readonly) QCocoaWindow *platformWindow; -@property (nonatomic, readonly) BOOL isMenuView; @end QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSView); diff --git a/src/plugins/platforms/cocoa/qnsview.mm b/src/plugins/platforms/cocoa/qnsview.mm index 1c400a1ec9..9bd53ed334 100644 --- a/src/plugins/platforms/cocoa/qnsview.mm +++ b/src/plugins/platforms/cocoa/qnsview.mm @@ -51,7 +51,11 @@ #include <qpa/qwindowsysteminterface.h> #include <QtGui/QTextFormat> #include <QtCore/QDebug> +#include <QtCore/QPointer> +#include <QtCore/QSet> #include <QtCore/qsysinfo.h> +#include <QtGui/QAccessible> +#include <QtGui/QImage> #include <private/qguiapplication_p.h> #include <private/qcoregraphics_p.h> #include <private/qwindow_p.h> @@ -61,94 +65,112 @@ #endif #include "qcocoaintegration.h" -#ifdef QT_COCOA_ENABLE_ACCESSIBILITY_INSPECTOR -#include <accessibilityinspector.h> -#endif +// Private interface +@interface QT_MANGLE_NAMESPACE(QNSView) () +- (BOOL)isTransparentForUserInput; +@end -Q_LOGGING_CATEGORY(lcQpaTouch, "qt.qpa.input.touch") -#ifndef QT_NO_GESTURES -Q_LOGGING_CATEGORY(lcQpaGestures, "qt.qpa.input.gestures") -#endif -Q_LOGGING_CATEGORY(lcQpaTablet, "qt.qpa.input.tablet") +@interface QT_MANGLE_NAMESPACE(QNSView) (Drawing) <CALayerDelegate> +- (BOOL)wantsLayerHelper; +@end @interface QT_MANGLE_NAMESPACE(QNSViewMouseMoveHelper) : NSObject -{ - QNSView *view; -} - -- (id)initWithView:(QNSView *)theView; - +- (instancetype)initWithView:(QNSView *)theView; - (void)mouseMoved:(NSEvent *)theEvent; - (void)mouseEntered:(NSEvent *)theEvent; - (void)mouseExited:(NSEvent *)theEvent; - (void)cursorUpdate:(NSEvent *)theEvent; - @end -@implementation QT_MANGLE_NAMESPACE(QNSViewMouseMoveHelper) - -- (id)initWithView:(QNSView *)theView -{ - self = [super init]; - if (self) { - view = theView; - } - return self; -} - -- (void)mouseMoved:(NSEvent *)theEvent -{ - [view mouseMovedImpl:theEvent]; -} +@interface QT_MANGLE_NAMESPACE(QNSView) (Mouse) +- (NSPoint)screenMousePoint:(NSEvent *)theEvent; +- (void)mouseMovedImpl:(NSEvent *)theEvent; +- (void)mouseEnteredImpl:(NSEvent *)theEvent; +- (void)mouseExitedImpl:(NSEvent *)theEvent; +@end -- (void)mouseEntered:(NSEvent *)theEvent -{ - [view mouseEnteredImpl:theEvent]; -} +@interface QT_MANGLE_NAMESPACE(QNSView) (Touch) +@end -- (void)mouseExited:(NSEvent *)theEvent -{ - [view mouseExitedImpl:theEvent]; -} +@interface QT_MANGLE_NAMESPACE(QNSView) (Tablet) +- (bool)handleTabletEvent:(NSEvent *)theEvent; +@end -- (void)cursorUpdate:(NSEvent *)theEvent -{ - [view cursorUpdate:theEvent]; -} +@interface QT_MANGLE_NAMESPACE(QNSView) (Gestures) +@end +@interface QT_MANGLE_NAMESPACE(QNSView) (Dragging) +-(void)registerDragTypes; @end -// Private interface -@interface QT_MANGLE_NAMESPACE(QNSView) () -- (BOOL)isTransparentForUserInput; +@interface QT_MANGLE_NAMESPACE(QNSView) (Keys) @end -@implementation QT_MANGLE_NAMESPACE(QNSView) +@interface QT_MANGLE_NAMESPACE(QNSView) (ComplexText) <NSTextInputClient> +- (void)textInputContextKeyboardSelectionDidChangeNotification:(NSNotification *)textInputContextKeyboardSelectionDidChangeNotification; +@end -- (id) init -{ - if (self = [super initWithFrame:NSZeroRect]) { +@implementation QT_MANGLE_NAMESPACE(QNSView) { + QPointer<QCocoaWindow> m_platformWindow; + NSTrackingArea *m_trackingArea; + Qt::MouseButtons m_buttons; + Qt::MouseButtons m_acceptedMouseDowns; + Qt::MouseButtons m_frameStrutButtons; + QString m_composingText; + QPointer<QObject> m_composingFocusObject; + bool m_sendKeyEvent; + bool m_dontOverrideCtrlLMB; + bool m_sendUpAsRightButton; + Qt::KeyboardModifiers m_currentWheelModifiers; + NSString *m_inputSource; + QT_MANGLE_NAMESPACE(QNSViewMouseMoveHelper) *m_mouseMoveHelper; + bool m_resendKeyEvent; + bool m_scrolling; + bool m_updatingDrag; + NSEvent *m_currentlyInterpretedKeyEvent; + QSet<quint32> m_acceptedKeyDowns; +} + +- (instancetype)initWithCocoaWindow:(QCocoaWindow *)platformWindow +{ + if ((self = [super initWithFrame:NSZeroRect])) { + m_platformWindow = platformWindow; m_buttons = Qt::NoButton; m_acceptedMouseDowns = Qt::NoButton; m_frameStrutButtons = Qt::NoButton; m_sendKeyEvent = false; -#ifndef QT_NO_OPENGL - m_glContext = 0; - m_shouldSetGLContextinDrawRect = false; -#endif - currentCustomDragTypes = 0; - m_dontOverrideCtrlLMB = false; m_sendUpAsRightButton = false; - m_inputSource = 0; + m_inputSource = nil; m_mouseMoveHelper = [[QT_MANGLE_NAMESPACE(QNSViewMouseMoveHelper) alloc] initWithView:self]; m_resendKeyEvent = false; m_scrolling = false; m_updatingDrag = false; - m_currentlyInterpretedKeyEvent = 0; - m_isMenuView = false; + m_currentlyInterpretedKeyEvent = nil; + m_dontOverrideCtrlLMB = qt_mac_resolveOption(false, platformWindow->window(), + "_q_platform_MacDontOverrideCtrlLMB", "QT_MAC_DONT_OVERRIDE_CTRL_LMB"); + m_trackingArea = nil; + self.focusRingType = NSFocusRingTypeNone; self.cursor = nil; - m_updateRequested = false; + self.wantsLayer = [self wantsLayerHelper]; + + // Enable high-DPI OpenGL for retina displays. Enabling has the side + // effect that Cocoa will start calling glViewport(0, 0, width, height), + // overriding any glViewport calls in application code. This is usually not a + // problem, except if the application wants to have a "custom" viewport. + // (like the hellogl example) + if (m_platformWindow->window()->supportsOpenGL()) { + self.wantsBestResolutionOpenGLSurface = qt_mac_resolveOption(YES, m_platformWindow->window(), + "_q_mac_wantsBestResolutionOpenGLSurface", "QT_MAC_WANTS_BEST_RESOLUTION_OPENGL_SURFACE"); + // See also QCocoaGLContext::makeCurrent for software renderer workarounds. + } + + [self registerDragTypes]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(textInputContextKeyboardSelectionDidChangeNotification:) + name:NSTextInputContextKeyboardSelectionDidChangeNotification + object:nil]; } return self; } @@ -163,47 +185,9 @@ Q_LOGGING_CATEGORY(lcQpaTablet, "qt.qpa.input.tablet") [[NSNotificationCenter defaultCenter] removeObserver:self]; [m_mouseMoveHelper release]; - delete currentCustomDragTypes; - [super dealloc]; } -- (id)initWithCocoaWindow:(QCocoaWindow *)platformWindow -{ - self = [self init]; - if (!self) - return 0; - - m_platformWindow = platformWindow; - m_sendKeyEvent = false; - m_dontOverrideCtrlLMB = qt_mac_resolveOption(false, platformWindow->window(), "_q_platform_MacDontOverrideCtrlLMB", "QT_MAC_DONT_OVERRIDE_CTRL_LMB"); - m_trackingArea = nil; - -#ifdef QT_COCOA_ENABLE_ACCESSIBILITY_INSPECTOR - // prevent rift in space-time continuum, disable - // accessibility for the accessibility inspector's windows. - static bool skipAccessibilityForInspectorWindows = false; - if (!skipAccessibilityForInspectorWindows) { - - // m_accessibleRoot = window->accessibleRoot(); - - AccessibilityInspector *inspector = new AccessibilityInspector(window); - skipAccessibilityForInspectorWindows = true; - inspector->inspectWindow(window); - skipAccessibilityForInspectorWindows = false; - } -#endif - - [self registerDragTypes]; - - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(textInputContextKeyboardSelectionDidChangeNotification:) - name:NSTextInputContextKeyboardSelectionDidChangeNotification - object:nil]; - - return self; -} - - (NSString *)description { NSMutableString *description = [NSMutableString stringWithString:[super description]]; @@ -220,33 +204,18 @@ Q_LOGGING_CATEGORY(lcQpaTablet, "qt.qpa.input.tablet") return description; } -#ifndef QT_NO_OPENGL -- (void) setQCocoaGLContext:(QCocoaGLContext *)context -{ - m_glContext = context; - [m_glContext->nsOpenGLContext() setView:self]; - if (![m_glContext->nsOpenGLContext() view]) { - //was unable to set view - m_shouldSetGLContextinDrawRect = true; - } -} -#endif - - (void)viewDidMoveToSuperview { if (!m_platformWindow) return; - if (!(m_platformWindow->m_viewIsToBeEmbedded)) + if (!m_platformWindow->isEmbedded()) return; if ([self superview]) { - m_platformWindow->m_viewIsEmbedded = true; QWindowSystemInterface::handleGeometryChange(m_platformWindow->window(), m_platformWindow->geometry()); [self setNeedsDisplay:YES]; QWindowSystemInterface::flushWindowSystemEvents(); - } else { - m_platformWindow->m_viewIsEmbedded = false; } } @@ -268,15 +237,6 @@ Q_LOGGING_CATEGORY(lcQpaTablet, "qt.qpa.input.tablet") return focusWindow; } -- (void)textInputContextKeyboardSelectionDidChangeNotification : (NSNotification *) textInputContextKeyboardSelectionDidChangeNotification -{ - Q_UNUSED(textInputContextKeyboardSelectionDidChangeNotification) - if (([NSApp keyWindow] == [self window]) && [[self window] firstResponder] == self) { - QCocoaInputContext *ic = qobject_cast<QCocoaInputContext *>(QCocoaIntegration::instance()->inputContext()); - ic->updateLocale(); - } -} - - (void)viewDidHide { if (!m_platformWindow->isExposed()) @@ -294,107 +254,6 @@ Q_LOGGING_CATEGORY(lcQpaTablet, "qt.qpa.input.tablet") [super removeFromSuperview]; } -- (BOOL) isOpaque -{ - if (!m_platformWindow) - return true; - return m_platformWindow->isOpaque(); -} - -- (void)requestUpdate -{ - if (self.needsDisplay) { - // If the view already has needsDisplay set it means that there may be code waiting for - // a real expose event, so we can't issue setNeedsDisplay now as a way to trigger an - // update request. We will re-trigger requestUpdate from drawRect. - return; - } - - [self setNeedsDisplay:YES]; - m_updateRequested = true; -} - -- (void)setNeedsDisplayInRect:(NSRect)rect -{ - [super setNeedsDisplayInRect:rect]; - m_updateRequested = false; -} - -- (void)drawRect:(NSRect)dirtyRect -{ - Q_UNUSED(dirtyRect); - - if (!m_platformWindow) - return; - - QRegion exposedRegion; - const NSRect *dirtyRects; - NSInteger numDirtyRects; - [self getRectsBeingDrawn:&dirtyRects count:&numDirtyRects]; - for (int i = 0; i < numDirtyRects; ++i) - exposedRegion += QRectF::fromCGRect(dirtyRects[i]).toRect(); - - qCDebug(lcQpaCocoaDrawing) << "[QNSView drawRect:]" << m_platformWindow->window() << exposedRegion; - [self updateRegion:exposedRegion]; -} - -- (void)updateRegion:(QRegion)dirtyRegion -{ -#ifndef QT_NO_OPENGL - if (m_glContext && m_shouldSetGLContextinDrawRect) { - [m_glContext->nsOpenGLContext() setView:self]; - m_shouldSetGLContextinDrawRect = false; - } -#endif - - QWindowPrivate *windowPrivate = qt_window_private(m_platformWindow->window()); - - if (m_updateRequested) { - Q_ASSERT(windowPrivate->updateRequestPending); - qCDebug(lcQpaCocoaWindow) << "Delivering update request to" << m_platformWindow->window(); - windowPrivate->deliverUpdateRequest(); - m_updateRequested = false; - } else { - m_platformWindow->handleExposeEvent(dirtyRegion); - } - - if (m_updateRequested && windowPrivate->updateRequestPending) { - // A call to QWindow::requestUpdate was issued during event delivery above, - // but AppKit will reset the needsDisplay state of the view after completing - // the current display cycle, so we need to defer the request to redisplay. - // FIXME: Perhaps this should be a trigger to enable CADisplayLink? - qCDebug(lcQpaCocoaDrawing) << "[QNSView drawRect:] issuing deferred setNeedsDisplay due to pending update request"; - dispatch_async(dispatch_get_main_queue (), ^{ [self requestUpdate]; }); - } -} - -- (BOOL)wantsUpdateLayer -{ - return YES; -} - -- (void)updateLayer -{ - if (!m_platformWindow) - return; - - qCDebug(lcQpaCocoaDrawing) << "[QNSView updateLayer]" << m_platformWindow->window(); - - // FIXME: Find out if there's a way to resolve the dirty rect like in drawRect: - [self updateRegion:QRectF::fromCGRect(self.bounds).toRect()]; -} - -- (void)viewDidChangeBackingProperties -{ - if (self.layer) - self.layer.contentsScale = self.window.backingScaleFactor; -} - -- (BOOL)isFlipped -{ - return YES; -} - - (BOOL)isTransparentForUserInput { return m_platformWindow->window() && @@ -407,7 +266,7 @@ Q_LOGGING_CATEGORY(lcQpaTablet, "qt.qpa.input.tablet") return NO; if ([self isTransparentForUserInput]) return NO; - if (!m_platformWindow->windowIsPopupType() && !m_isMenuView) + if (!m_platformWindow->windowIsPopupType()) QWindowSystemInterface::handleWindowActivated([self topLevelWindow]); return YES; } @@ -416,8 +275,6 @@ Q_LOGGING_CATEGORY(lcQpaTablet, "qt.qpa.input.tablet") { if (!m_platformWindow) return NO; - if (m_isMenuView) - return NO; if (m_platformWindow->shouldRefuseKeyWindowAndFirstResponder()) return NO; if ([self isTransparentForUserInput]) @@ -427,16 +284,6 @@ Q_LOGGING_CATEGORY(lcQpaTablet, "qt.qpa.input.tablet") return YES; } -- (BOOL)acceptsFirstMouse:(NSEvent *)theEvent -{ - Q_UNUSED(theEvent) - if (!m_platformWindow) - return NO; - if ([self isTransparentForUserInput]) - return NO; - return YES; -} - - (NSView *)hitTest:(NSPoint)aPoint { NSView *candidate = [super hitTest:aPoint]; @@ -476,1611 +323,22 @@ Q_LOGGING_CATEGORY(lcQpaTablet, "qt.qpa.input.tablet") *qtScreenPoint = QCocoaScreen::mapFromNative(mouseLocation); } -- (void)resetMouseButtons -{ - m_buttons = Qt::NoButton; - m_frameStrutButtons = Qt::NoButton; -} - -- (NSPoint) screenMousePoint:(NSEvent *)theEvent -{ - NSPoint screenPoint; - if (theEvent) { - NSPoint windowPoint = [theEvent locationInWindow]; - if (qIsNaN(windowPoint.x) || qIsNaN(windowPoint.y)) { - screenPoint = [NSEvent mouseLocation]; - } else { - NSRect screenRect = [[theEvent window] convertRectToScreen:NSMakeRect(windowPoint.x, windowPoint.y, 1, 1)]; - screenPoint = screenRect.origin; - } - } else { - screenPoint = [NSEvent mouseLocation]; - } - return screenPoint; -} - -- (void)handleMouseEvent:(NSEvent *)theEvent -{ - if (!m_platformWindow) - return; - -#ifndef QT_NO_TABLETEVENT - // Tablet events may come in via the mouse event handlers, - // check if this is a valid tablet event first. - if ([self handleTabletEvent: theEvent]) - return; -#endif - - QPointF qtWindowPoint; - QPointF qtScreenPoint; - QNSView *targetView = self; - if (!targetView.platformWindow) - return; - - // Popups implicitly grap mouse events; forward to the active popup if there is one - if (QCocoaWindow *popup = QCocoaIntegration::instance()->activePopupWindow()) { - // Tooltips must be transparent for mouse events - // The bug reference is QTBUG-46379 - if (!popup->m_windowFlags.testFlag(Qt::ToolTip)) { - if (QNSView *popupView = qnsview_cast(popup->view())) - targetView = popupView; - } - } - - [targetView convertFromScreen:[self screenMousePoint:theEvent] toWindowPoint:&qtWindowPoint andScreenPoint:&qtScreenPoint]; - ulong timestamp = [theEvent timestamp] * 1000; - - QCocoaDrag* nativeDrag = QCocoaIntegration::instance()->drag(); - nativeDrag->setLastMouseEvent(theEvent, self); - - Qt::KeyboardModifiers keyboardModifiers = [QNSView convertKeyModifiers:[theEvent modifierFlags]]; - QWindowSystemInterface::handleMouseEvent(targetView->m_platformWindow->window(), timestamp, qtWindowPoint, qtScreenPoint, - m_buttons, keyboardModifiers, Qt::MouseEventNotSynthesized); -} - -- (void)handleFrameStrutMouseEvent:(NSEvent *)theEvent -{ - if (!m_platformWindow) - return; - - // get m_buttons in sync - // Don't send frme strut events if we are in the middle of a mouse drag. - if (m_buttons != Qt::NoButton) - return; - - NSEventType ty = [theEvent type]; - switch (ty) { - case NSLeftMouseDown: - m_frameStrutButtons |= Qt::LeftButton; - break; - case NSLeftMouseUp: - m_frameStrutButtons &= ~Qt::LeftButton; - break; - case NSRightMouseDown: - m_frameStrutButtons |= Qt::RightButton; - break; - case NSLeftMouseDragged: - m_frameStrutButtons |= Qt::LeftButton; - break; - case NSRightMouseDragged: - m_frameStrutButtons |= Qt::RightButton; - break; - case NSRightMouseUp: - m_frameStrutButtons &= ~Qt::RightButton; - break; - case NSOtherMouseDown: - m_frameStrutButtons |= cocoaButton2QtButton([theEvent buttonNumber]); - break; - case NSOtherMouseUp: - m_frameStrutButtons &= ~cocoaButton2QtButton([theEvent buttonNumber]); - default: - break; - } - - NSWindow *window = [self window]; - NSPoint windowPoint = [theEvent locationInWindow]; - - int windowScreenY = [window frame].origin.y + [window frame].size.height; - NSPoint windowCoord = [self convertPoint:[self frame].origin toView:nil]; - int viewScreenY = [window convertRectToScreen:NSMakeRect(windowCoord.x, windowCoord.y, 0, 0)].origin.y; - int titleBarHeight = windowScreenY - viewScreenY; - - NSPoint nsViewPoint = [self convertPoint: windowPoint fromView: nil]; - QPoint qtWindowPoint = QPoint(nsViewPoint.x, titleBarHeight + nsViewPoint.y); - NSPoint screenPoint = [window convertRectToScreen:NSMakeRect(windowPoint.x, windowPoint.y, 0, 0)].origin; - QPoint qtScreenPoint = QCocoaScreen::mapFromNative(screenPoint).toPoint(); - - ulong timestamp = [theEvent timestamp] * 1000; - QWindowSystemInterface::handleFrameStrutMouseEvent(m_platformWindow->window(), timestamp, qtWindowPoint, qtScreenPoint, m_frameStrutButtons); -} - -- (bool)handleMouseDownEvent:(NSEvent *)theEvent withButton:(int)buttonNumber -{ - if ([self isTransparentForUserInput]) - return false; - - Qt::MouseButton button = cocoaButton2QtButton(buttonNumber); - - QPointF qtWindowPoint; - QPointF qtScreenPoint; - [self convertFromScreen:[self screenMousePoint:theEvent] toWindowPoint:&qtWindowPoint andScreenPoint:&qtScreenPoint]; - Q_UNUSED(qtScreenPoint); - - // Maintain masked state for the button for use by MouseDragged and MouseUp. - QRegion mask = m_platformWindow->window()->mask(); - const bool masked = !mask.isEmpty() && !mask.contains(qtWindowPoint.toPoint()); - if (masked) - m_acceptedMouseDowns &= ~button; - else - m_acceptedMouseDowns |= button; - - // Forward masked out events to the next responder - if (masked) - return false; - - m_buttons |= button; - - [self handleMouseEvent:theEvent]; - return true; -} - -- (bool)handleMouseDraggedEvent:(NSEvent *)theEvent withButton:(int)buttonNumber -{ - if ([self isTransparentForUserInput]) - return false; - - Qt::MouseButton button = cocoaButton2QtButton(buttonNumber); - - // Forward the event to the next responder if Qt did not accept the - // corresponding mouse down for this button - if (!(m_acceptedMouseDowns & button) == button) - return false; - - [self handleMouseEvent:theEvent]; - return true; -} - -- (bool)handleMouseUpEvent:(NSEvent *)theEvent withButton:(int)buttonNumber -{ - if ([self isTransparentForUserInput]) - return false; - - Qt::MouseButton button = cocoaButton2QtButton(buttonNumber); - - // Forward the event to the next responder if Qt did not accept the - // corresponding mouse down for this button - if (!(m_acceptedMouseDowns & button) == button) - return false; - - if (m_sendUpAsRightButton && button == Qt::LeftButton) - button = Qt::RightButton; - if (button == Qt::RightButton) - m_sendUpAsRightButton = false; - - m_buttons &= ~button; - - [self handleMouseEvent:theEvent]; - return true; -} - -- (void)mouseDown:(NSEvent *)theEvent -{ - if ([self isTransparentForUserInput]) - return [super mouseDown:theEvent]; - m_sendUpAsRightButton = false; - - // Handle any active poup windows; clicking outisde them should close them - // all. Don't do anything or clicks inside one of the menus, let Cocoa - // handle that case. Note that in practice many windows of the Qt::Popup type - // will actually close themselves in this case using logic implemented in - // that particular poup type (for example context menus). However, Qt expects - // that plain popup QWindows will also be closed, so we implement the logic - // here as well. - QList<QCocoaWindow *> *popups = QCocoaIntegration::instance()->popupWindowStack(); - if (!popups->isEmpty()) { - // Check if the click is outside all popups. - bool inside = false; - QPointF qtScreenPoint = QCocoaScreen::mapFromNative([self screenMousePoint:theEvent]); - for (QList<QCocoaWindow *>::const_iterator it = popups->begin(); it != popups->end(); ++it) { - if ((*it)->geometry().contains(qtScreenPoint.toPoint())) { - inside = true; - break; - } - } - // Close the popups if the click was outside. - if (!inside) { - Qt::WindowType type = QCocoaIntegration::instance()->activePopupWindow()->window()->type(); - while (QCocoaWindow *popup = QCocoaIntegration::instance()->popPopupWindow()) { - QWindowSystemInterface::handleCloseEvent(popup->window()); - QWindowSystemInterface::flushWindowSystemEvents(); - } - // Consume the mouse event when closing the popup, except for tool tips - // were it's expected that the event is processed normally. - if (type != Qt::ToolTip) - return; - } - } - - QPointF qtWindowPoint; - QPointF qtScreenPoint; - [self convertFromScreen:[self screenMousePoint:theEvent] toWindowPoint:&qtWindowPoint andScreenPoint:&qtScreenPoint]; - Q_UNUSED(qtScreenPoint); - - QRegion mask = m_platformWindow->window()->mask(); - const bool masked = !mask.isEmpty() && !mask.contains(qtWindowPoint.toPoint()); - // Maintain masked state for the button for use by MouseDragged and Up. - if (masked) - m_acceptedMouseDowns &= ~Qt::LeftButton; - else - m_acceptedMouseDowns |= Qt::LeftButton; - - // Forward masked out events to the next responder - if (masked) { - [super mouseDown:theEvent]; - return; - } - - if ([self hasMarkedText]) { - [[NSTextInputContext currentInputContext] handleEvent:theEvent]; - } else { - auto ctrlOrMetaModifier = qApp->testAttribute(Qt::AA_MacDontSwapCtrlAndMeta) ? Qt::ControlModifier : Qt::MetaModifier; - if (!m_dontOverrideCtrlLMB && [QNSView convertKeyModifiers:[theEvent modifierFlags]] & ctrlOrMetaModifier) { - m_buttons |= Qt::RightButton; - m_sendUpAsRightButton = true; - } else { - m_buttons |= Qt::LeftButton; - } - [self handleMouseEvent:theEvent]; - } -} - -- (void)mouseDragged:(NSEvent *)theEvent -{ - const bool accepted = [self handleMouseDraggedEvent:theEvent withButton:[theEvent buttonNumber]]; - if (!accepted) - [super mouseDragged:theEvent]; -} - -- (void)mouseUp:(NSEvent *)theEvent -{ - const bool accepted = [self handleMouseUpEvent:theEvent withButton:[theEvent buttonNumber]]; - if (!accepted) - [super mouseUp:theEvent]; -} - -- (void)rightMouseDown:(NSEvent *)theEvent -{ - // Wacom tablet might not return the correct button number for NSEvent buttonNumber - // on right clicks. Decide here that the button is the "right" button and forward - // the button number to the mouse (and tablet) handler. - const bool accepted = [self handleMouseDownEvent:theEvent withButton:1]; - if (!accepted) - [super rightMouseDown:theEvent]; -} - -- (void)rightMouseDragged:(NSEvent *)theEvent -{ - const bool accepted = [self handleMouseDraggedEvent:theEvent withButton:1]; - if (!accepted) - [super rightMouseDragged:theEvent]; -} - -- (void)rightMouseUp:(NSEvent *)theEvent -{ - const bool accepted = [self handleMouseUpEvent:theEvent withButton:1]; - if (!accepted) - [super rightMouseUp:theEvent]; -} - -- (void)otherMouseDown:(NSEvent *)theEvent -{ - const bool accepted = [self handleMouseDownEvent:theEvent withButton:[theEvent buttonNumber]]; - if (!accepted) - [super otherMouseDown:theEvent]; -} - -- (void)otherMouseDragged:(NSEvent *)theEvent -{ - const bool accepted = [self handleMouseDraggedEvent:theEvent withButton:[theEvent buttonNumber]]; - if (!accepted) - [super otherMouseDragged:theEvent]; -} - -- (void)otherMouseUp:(NSEvent *)theEvent -{ - const bool accepted = [self handleMouseUpEvent:theEvent withButton:[theEvent buttonNumber]]; - if (!accepted) - [super otherMouseUp:theEvent]; -} - -- (void)updateTrackingAreas -{ - [super updateTrackingAreas]; - - QMacAutoReleasePool pool; - - // NSTrackingInVisibleRect keeps care of updating once the tracking is set up, so bail out early - if (m_trackingArea && [[self trackingAreas] containsObject:m_trackingArea]) - return; - - // Ideally, we shouldn't have NSTrackingMouseMoved events included below, it should - // only be turned on if mouseTracking, hover is on or a tool tip is set. - // Unfortunately, Qt will send "tooltip" events on mouse moves, so we need to - // turn it on in ALL case. That means EVERY QWindow gets to pay the cost of - // mouse moves delivered to it (Apple recommends keeping it OFF because there - // is a performance hit). So it goes. - NSUInteger trackingOptions = NSTrackingMouseEnteredAndExited | NSTrackingActiveInActiveApp - | NSTrackingInVisibleRect | NSTrackingMouseMoved | NSTrackingCursorUpdate; - [m_trackingArea release]; - m_trackingArea = [[NSTrackingArea alloc] initWithRect:[self frame] - options:trackingOptions - owner:m_mouseMoveHelper - userInfo:nil]; - [self addTrackingArea:m_trackingArea]; -} - -- (void)cursorUpdate:(NSEvent *)theEvent -{ - qCDebug(lcQpaCocoaMouse) << "[QNSView cursorUpdate:]" << self.cursor; - - // Note: We do not get this callback when moving from a subview that - // uses the legacy cursorRect API, so the cursor is reset to the arrow - // cursor. See rdar://34183708 - - if (self.cursor) - [self.cursor set]; - else - [super cursorUpdate:theEvent]; -} - -- (void)mouseMovedImpl:(NSEvent *)theEvent -{ - if (!m_platformWindow) - return; - - if ([self isTransparentForUserInput]) - return; - - QPointF windowPoint; - QPointF screenPoint; - [self convertFromScreen:[self screenMousePoint:theEvent] toWindowPoint:&windowPoint andScreenPoint:&screenPoint]; - QWindow *childWindow = m_platformWindow->childWindowAt(windowPoint.toPoint()); - - // Top-level windows generate enter-leave events for sub-windows. - // Qt wants to know which window (if any) will be entered at the - // the time of the leave. This is dificult to accomplish by - // handling mouseEnter and mouseLeave envents, since they are sent - // individually to different views. - if (m_platformWindow->isContentView() && childWindow) { - if (childWindow != m_platformWindow->m_enterLeaveTargetWindow) { - QWindowSystemInterface::handleEnterLeaveEvent(childWindow, m_platformWindow->m_enterLeaveTargetWindow, windowPoint, screenPoint); - m_platformWindow->m_enterLeaveTargetWindow = childWindow; - } - } - - // Cocoa keeps firing mouse move events for obscured parent views. Qt should not - // send those events so filter them out here. - if (childWindow != m_platformWindow->window()) - return; - - [self handleMouseEvent: theEvent]; -} - -- (void)mouseEnteredImpl:(NSEvent *)theEvent -{ - Q_UNUSED(theEvent) - if (!m_platformWindow) - return; - - m_platformWindow->m_windowUnderMouse = true; - - if ([self isTransparentForUserInput]) - return; - - // Top-level windows generate enter events for sub-windows. - if (!m_platformWindow->isContentView()) - return; - - QPointF windowPoint; - QPointF screenPoint; - [self convertFromScreen:[self screenMousePoint:theEvent] toWindowPoint:&windowPoint andScreenPoint:&screenPoint]; - m_platformWindow->m_enterLeaveTargetWindow = m_platformWindow->childWindowAt(windowPoint.toPoint()); - QWindowSystemInterface::handleEnterEvent(m_platformWindow->m_enterLeaveTargetWindow, windowPoint, screenPoint); -} - -- (void)mouseExitedImpl:(NSEvent *)theEvent -{ - Q_UNUSED(theEvent); - if (!m_platformWindow) - return; - - m_platformWindow->m_windowUnderMouse = false; - - if ([self isTransparentForUserInput]) - return; - - // Top-level windows generate leave events for sub-windows. - if (!m_platformWindow->isContentView()) - return; - - QWindowSystemInterface::handleLeaveEvent(m_platformWindow->m_enterLeaveTargetWindow); - m_platformWindow->m_enterLeaveTargetWindow = 0; -} - -#ifndef QT_NO_TABLETEVENT -struct QCocoaTabletDeviceData -{ - QTabletEvent::TabletDevice device; - QTabletEvent::PointerType pointerType; - uint capabilityMask; - qint64 uid; -}; - -typedef QHash<uint, QCocoaTabletDeviceData> QCocoaTabletDeviceDataHash; -Q_GLOBAL_STATIC(QCocoaTabletDeviceDataHash, tabletDeviceDataHash) - -- (bool)handleTabletEvent: (NSEvent *)theEvent -{ - static bool ignoreButtonMapping = qEnvironmentVariableIsSet("QT_MAC_TABLET_IGNORE_BUTTON_MAPPING"); - - if (!m_platformWindow) - return false; - - NSEventType eventType = [theEvent type]; - if (eventType != NSTabletPoint && [theEvent subtype] != NSTabletPointEventSubtype) - return false; // Not a tablet event. - - ulong timestamp = [theEvent timestamp] * 1000; - - QPointF windowPoint; - QPointF screenPoint; - [self convertFromScreen:[self screenMousePoint:theEvent] toWindowPoint: &windowPoint andScreenPoint: &screenPoint]; - - uint deviceId = [theEvent deviceID]; - if (!tabletDeviceDataHash->contains(deviceId)) { - // Error: Unknown tablet device. Qt also gets into this state - // when running on a VM. This appears to be harmless; don't - // print a warning. - return false; - } - const QCocoaTabletDeviceData &deviceData = tabletDeviceDataHash->value(deviceId); - - bool down = (eventType != NSMouseMoved); - - qreal pressure; - if (down) { - pressure = [theEvent pressure]; - } else { - pressure = 0.0; - } - - NSPoint tilt = [theEvent tilt]; - int xTilt = qRound(tilt.x * 60.0); - int yTilt = qRound(tilt.y * -60.0); - qreal tangentialPressure = 0; - qreal rotation = 0; - int z = 0; - if (deviceData.capabilityMask & 0x0200) - z = [theEvent absoluteZ]; - - if (deviceData.capabilityMask & 0x0800) - tangentialPressure = ([theEvent tangentialPressure] * 2.0) - 1.0; - - rotation = 360.0 - [theEvent rotation]; - if (rotation > 180.0) - rotation -= 360.0; - - Qt::KeyboardModifiers keyboardModifiers = [QNSView convertKeyModifiers:[theEvent modifierFlags]]; - Qt::MouseButtons buttons = ignoreButtonMapping ? static_cast<Qt::MouseButtons>(static_cast<uint>([theEvent buttonMask])) : m_buttons; - - qCDebug(lcQpaTablet, "event on tablet %d with tool %d type %d unique ID %lld pos %6.1f, %6.1f root pos %6.1f, %6.1f buttons 0x%x pressure %4.2lf tilt %d, %d rotation %6.2lf", - deviceId, deviceData.device, deviceData.pointerType, deviceData.uid, - windowPoint.x(), windowPoint.y(), screenPoint.x(), screenPoint.y(), - static_cast<uint>(buttons), pressure, xTilt, yTilt, rotation); - - QWindowSystemInterface::handleTabletEvent(m_platformWindow->window(), timestamp, windowPoint, screenPoint, - deviceData.device, deviceData.pointerType, buttons, pressure, xTilt, yTilt, - tangentialPressure, rotation, z, deviceData.uid, - keyboardModifiers); - return true; -} - -- (void)tabletPoint:(NSEvent *)theEvent -{ - if ([self isTransparentForUserInput]) - return [super tabletPoint:theEvent]; - - [self handleTabletEvent: theEvent]; -} - -static QTabletEvent::TabletDevice wacomTabletDevice(NSEvent *theEvent) -{ - qint64 uid = [theEvent uniqueID]; - uint bits = [theEvent vendorPointingDeviceType]; - if (bits == 0 && uid != 0) { - // Fallback. It seems that the driver doesn't always include all the information. - // High-End Wacom devices store their "type" in the uper bits of the Unique ID. - // I'm not sure how to handle it for consumer devices, but I'll test that in a bit. - bits = uid >> 32; - } - - QTabletEvent::TabletDevice device; - // Defined in the "EN0056-NxtGenImpGuideX" - // on Wacom's Developer Website (www.wacomeng.com) - if (((bits & 0x0006) == 0x0002) && ((bits & 0x0F06) != 0x0902)) { - device = QTabletEvent::Stylus; - } else { - switch (bits & 0x0F06) { - case 0x0802: - device = QTabletEvent::Stylus; - break; - case 0x0902: - device = QTabletEvent::Airbrush; - break; - case 0x0004: - device = QTabletEvent::FourDMouse; - break; - case 0x0006: - device = QTabletEvent::Puck; - break; - case 0x0804: - device = QTabletEvent::RotationStylus; - break; - default: - device = QTabletEvent::NoDevice; - } - } - return device; -} - -- (void)tabletProximity:(NSEvent *)theEvent -{ - if ([self isTransparentForUserInput]) - return [super tabletProximity:theEvent]; - - ulong timestamp = [theEvent timestamp] * 1000; - - QCocoaTabletDeviceData deviceData; - deviceData.uid = [theEvent uniqueID]; - deviceData.capabilityMask = [theEvent capabilityMask]; - - switch ([theEvent pointingDeviceType]) { - case NSUnknownPointingDevice: - default: - deviceData.pointerType = QTabletEvent::UnknownPointer; - break; - case NSPenPointingDevice: - deviceData.pointerType = QTabletEvent::Pen; - break; - case NSCursorPointingDevice: - deviceData.pointerType = QTabletEvent::Cursor; - break; - case NSEraserPointingDevice: - deviceData.pointerType = QTabletEvent::Eraser; - break; - } - - deviceData.device = wacomTabletDevice(theEvent); - - // The deviceID is "unique" while in the proximity, it's a key that we can use for - // linking up QCocoaTabletDeviceData to an event (especially if there are two devices in action). - bool entering = [theEvent isEnteringProximity]; - uint deviceId = [theEvent deviceID]; - if (entering) { - tabletDeviceDataHash->insert(deviceId, deviceData); - } else { - tabletDeviceDataHash->remove(deviceId); - } - - qCDebug(lcQpaTablet, "proximity change on tablet %d: current tool %d type %d unique ID %lld", - deviceId, deviceData.device, deviceData.pointerType, deviceData.uid); +@end - if (entering) { - QWindowSystemInterface::handleTabletEnterProximityEvent(timestamp, deviceData.device, deviceData.pointerType, deviceData.uid); - } else { - QWindowSystemInterface::handleTabletLeaveProximityEvent(timestamp, deviceData.device, deviceData.pointerType, deviceData.uid); - } -} +#include "qnsview_drawing.mm" +#include "qnsview_mouse.mm" +#include "qnsview_touch.mm" +#include "qnsview_gestures.mm" +#include "qnsview_tablet.mm" +#include "qnsview_dragging.mm" +#include "qnsview_keys.mm" +#include "qnsview_complextext.mm" +#include "qnsview_menus.mm" +#ifndef QT_NO_ACCESSIBILITY +#include "qnsview_accessibility.mm" #endif -- (bool)shouldSendSingleTouch -{ - if (!m_platformWindow) - return true; - - // QtWidgets expects single-point touch events, QtDeclarative does not. - // Until there is an API we solve this by looking at the window class type. - return m_platformWindow->window()->inherits("QWidgetWindow"); -} - -- (void)touchesBeganWithEvent:(NSEvent *)event -{ - if (!m_platformWindow) - return; - - const NSTimeInterval timestamp = [event timestamp]; - const QList<QWindowSystemInterface::TouchPoint> points = QCocoaTouch::getCurrentTouchPointList(event, [self shouldSendSingleTouch]); - qCDebug(lcQpaTouch) << "touchesBeganWithEvent" << points << "from device" << hex << [event deviceID]; - QWindowSystemInterface::handleTouchEvent(m_platformWindow->window(), timestamp * 1000, QCocoaTouch::getTouchDevice(QTouchDevice::TouchPad, [event deviceID]), points); -} - -- (void)touchesMovedWithEvent:(NSEvent *)event -{ - if (!m_platformWindow) - return; - - const NSTimeInterval timestamp = [event timestamp]; - const QList<QWindowSystemInterface::TouchPoint> points = QCocoaTouch::getCurrentTouchPointList(event, [self shouldSendSingleTouch]); - qCDebug(lcQpaTouch) << "touchesMovedWithEvent" << points << "from device" << hex << [event deviceID]; - QWindowSystemInterface::handleTouchEvent(m_platformWindow->window(), timestamp * 1000, QCocoaTouch::getTouchDevice(QTouchDevice::TouchPad, [event deviceID]), points); -} - -- (void)touchesEndedWithEvent:(NSEvent *)event -{ - if (!m_platformWindow) - return; - - const NSTimeInterval timestamp = [event timestamp]; - const QList<QWindowSystemInterface::TouchPoint> points = QCocoaTouch::getCurrentTouchPointList(event, [self shouldSendSingleTouch]); - qCDebug(lcQpaTouch) << "touchesEndedWithEvent" << points << "from device" << hex << [event deviceID]; - QWindowSystemInterface::handleTouchEvent(m_platformWindow->window(), timestamp * 1000, QCocoaTouch::getTouchDevice(QTouchDevice::TouchPad, [event deviceID]), points); -} - -- (void)touchesCancelledWithEvent:(NSEvent *)event -{ - if (!m_platformWindow) - return; - - const NSTimeInterval timestamp = [event timestamp]; - const QList<QWindowSystemInterface::TouchPoint> points = QCocoaTouch::getCurrentTouchPointList(event, [self shouldSendSingleTouch]); - qCDebug(lcQpaTouch) << "touchesCancelledWithEvent" << points << "from device" << hex << [event deviceID]; - QWindowSystemInterface::handleTouchEvent(m_platformWindow->window(), timestamp * 1000, QCocoaTouch::getTouchDevice(QTouchDevice::TouchPad, [event deviceID]), points); -} - -#ifndef QT_NO_GESTURES - -- (bool)handleGestureAsBeginEnd:(NSEvent *)event -{ - if (QOperatingSystemVersion::current() < QOperatingSystemVersion::OSXElCapitan) - return false; - - if ([event phase] == NSEventPhaseBegan) { - [self beginGestureWithEvent:event]; - return true; - } - - if ([event phase] == NSEventPhaseEnded) { - [self endGestureWithEvent:event]; - return true; - } - - return false; -} -- (void)magnifyWithEvent:(NSEvent *)event -{ - if (!m_platformWindow) - return; - - if ([self handleGestureAsBeginEnd:event]) - return; - - qCDebug(lcQpaGestures) << "magnifyWithEvent" << [event magnification] << "from device" << hex << [event deviceID]; - const NSTimeInterval timestamp = [event timestamp]; - QPointF windowPoint; - QPointF screenPoint; - [self convertFromScreen:[self screenMousePoint:event] toWindowPoint:&windowPoint andScreenPoint:&screenPoint]; - QWindowSystemInterface::handleGestureEventWithRealValue(m_platformWindow->window(), QCocoaTouch::getTouchDevice(QTouchDevice::TouchPad, [event deviceID]), timestamp, Qt::ZoomNativeGesture, - [event magnification], windowPoint, screenPoint); -} - -- (void)smartMagnifyWithEvent:(NSEvent *)event -{ - if (!m_platformWindow) - return; - - static bool zoomIn = true; - qCDebug(lcQpaGestures) << "smartMagnifyWithEvent" << zoomIn << "from device" << hex << [event deviceID]; - const NSTimeInterval timestamp = [event timestamp]; - QPointF windowPoint; - QPointF screenPoint; - [self convertFromScreen:[self screenMousePoint:event] toWindowPoint:&windowPoint andScreenPoint:&screenPoint]; - QWindowSystemInterface::handleGestureEventWithRealValue(m_platformWindow->window(), QCocoaTouch::getTouchDevice(QTouchDevice::TouchPad, [event deviceID]), timestamp, Qt::SmartZoomNativeGesture, - zoomIn ? 1.0f : 0.0f, windowPoint, screenPoint); - zoomIn = !zoomIn; -} - -- (void)rotateWithEvent:(NSEvent *)event -{ - if (!m_platformWindow) - return; - - if ([self handleGestureAsBeginEnd:event]) - return; - - const NSTimeInterval timestamp = [event timestamp]; - QPointF windowPoint; - QPointF screenPoint; - [self convertFromScreen:[self screenMousePoint:event] toWindowPoint:&windowPoint andScreenPoint:&screenPoint]; - QWindowSystemInterface::handleGestureEventWithRealValue(m_platformWindow->window(), QCocoaTouch::getTouchDevice(QTouchDevice::TouchPad, [event deviceID]), timestamp, Qt::RotateNativeGesture, - -[event rotation], windowPoint, screenPoint); -} - -- (void)swipeWithEvent:(NSEvent *)event -{ - if (!m_platformWindow) - return; - - qCDebug(lcQpaGestures) << "swipeWithEvent" << [event deltaX] << [event deltaY] << "from device" << hex << [event deviceID]; - const NSTimeInterval timestamp = [event timestamp]; - QPointF windowPoint; - QPointF screenPoint; - [self convertFromScreen:[self screenMousePoint:event] toWindowPoint:&windowPoint andScreenPoint:&screenPoint]; - - qreal angle = 0.0f; - if ([event deltaX] == 1) - angle = 180.0f; - else if ([event deltaX] == -1) - angle = 0.0f; - else if ([event deltaY] == 1) - angle = 90.0f; - else if ([event deltaY] == -1) - angle = 270.0f; - - QWindowSystemInterface::handleGestureEventWithRealValue(m_platformWindow->window(), QCocoaTouch::getTouchDevice(QTouchDevice::TouchPad, [event deviceID]), timestamp, Qt::SwipeNativeGesture, - angle, windowPoint, screenPoint); -} - -- (void)beginGestureWithEvent:(NSEvent *)event -{ - if (!m_platformWindow) - return; - - const NSTimeInterval timestamp = [event timestamp]; - QPointF windowPoint; - QPointF screenPoint; - [self convertFromScreen:[self screenMousePoint:event] toWindowPoint:&windowPoint andScreenPoint:&screenPoint]; - qCDebug(lcQpaGestures) << "beginGestureWithEvent @" << windowPoint << "from device" << hex << [event deviceID]; - QWindowSystemInterface::handleGestureEvent(m_platformWindow->window(), QCocoaTouch::getTouchDevice(QTouchDevice::TouchPad, [event deviceID]), timestamp, Qt::BeginNativeGesture, - windowPoint, screenPoint); -} - -- (void)endGestureWithEvent:(NSEvent *)event -{ - if (!m_platformWindow) - return; - - qCDebug(lcQpaGestures) << "endGestureWithEvent" << "from device" << hex << [event deviceID]; - const NSTimeInterval timestamp = [event timestamp]; - QPointF windowPoint; - QPointF screenPoint; - [self convertFromScreen:[self screenMousePoint:event] toWindowPoint:&windowPoint andScreenPoint:&screenPoint]; - QWindowSystemInterface::handleGestureEvent(m_platformWindow->window(), QCocoaTouch::getTouchDevice(QTouchDevice::TouchPad, [event deviceID]), timestamp, Qt::EndNativeGesture, - windowPoint, screenPoint); -} -#endif // QT_NO_GESTURES - -#if QT_CONFIG(wheelevent) -- (void)scrollWheel:(NSEvent *)theEvent -{ - if (!m_platformWindow) - return; - - if ([self isTransparentForUserInput]) - return [super scrollWheel:theEvent]; - - QPoint angleDelta; - Qt::MouseEventSource source = Qt::MouseEventNotSynthesized; - if ([theEvent hasPreciseScrollingDeltas]) { - // The mouse device contains pixel scroll wheel support (Mighty Mouse, Trackpad). - // Since deviceDelta is delivered as pixels rather than degrees, we need to - // convert from pixels to degrees in a sensible manner. - // It looks like 1/4 degrees per pixel behaves most native. - // (NB: Qt expects the unit for delta to be 8 per degree): - const int pixelsToDegrees = 2; // 8 * 1/4 - angleDelta.setX([theEvent scrollingDeltaX] * pixelsToDegrees); - angleDelta.setY([theEvent scrollingDeltaY] * pixelsToDegrees); - source = Qt::MouseEventSynthesizedBySystem; - } else { - // Remove acceleration, and use either -120 or 120 as delta: - angleDelta.setX(qBound(-120, int([theEvent deltaX] * 10000), 120)); - angleDelta.setY(qBound(-120, int([theEvent deltaY] * 10000), 120)); - } - - QPoint pixelDelta; - if ([theEvent hasPreciseScrollingDeltas]) { - pixelDelta.setX([theEvent scrollingDeltaX]); - pixelDelta.setY([theEvent scrollingDeltaY]); - } else { - // docs: "In the case of !hasPreciseScrollingDeltas, multiply the delta with the line width." - // scrollingDeltaX seems to return a minimum value of 0.1 in this case, map that to two pixels. - const CGFloat lineWithEstimate = 20.0; - pixelDelta.setX([theEvent scrollingDeltaX] * lineWithEstimate); - pixelDelta.setY([theEvent scrollingDeltaY] * lineWithEstimate); - } - - QPointF qt_windowPoint; - QPointF qt_screenPoint; - [self convertFromScreen:[self screenMousePoint:theEvent] toWindowPoint:&qt_windowPoint andScreenPoint:&qt_screenPoint]; - NSTimeInterval timestamp = [theEvent timestamp]; - ulong qt_timestamp = timestamp * 1000; - - // Prevent keyboard modifier state from changing during scroll event streams. - // A two-finger trackpad flick generates a stream of scroll events. We want - // the keyboard modifier state to be the state at the beginning of the - // flick in order to avoid changing the interpretation of the events - // mid-stream. One example of this happening would be when pressing cmd - // after scrolling in Qt Creator: not taking the phase into account causes - // the end of the event stream to be interpreted as font size changes. - NSEventPhase momentumPhase = [theEvent momentumPhase]; - if (momentumPhase == NSEventPhaseNone) { - currentWheelModifiers = [QNSView convertKeyModifiers:[theEvent modifierFlags]]; - } - - NSEventPhase phase = [theEvent phase]; - Qt::ScrollPhase ph = Qt::ScrollUpdate; - - // MayBegin is likely to happen. We treat it the same as an actual begin. - if (phase == NSEventPhaseMayBegin) { - m_scrolling = true; - ph = Qt::ScrollBegin; - } else if (phase == NSEventPhaseBegan) { - // If MayBegin did not happen, Began is the actual beginning. - if (!m_scrolling) - ph = Qt::ScrollBegin; - m_scrolling = true; - } else if (phase == NSEventPhaseEnded || phase == NSEventPhaseCancelled || - momentumPhase == NSEventPhaseEnded || momentumPhase == NSEventPhaseCancelled) { - ph = Qt::ScrollEnd; - m_scrolling = false; - } else if (phase == NSEventPhaseNone && momentumPhase == NSEventPhaseNone) { - ph = Qt::NoScrollPhase; - } - // "isInverted": natural OS X scrolling, inverted from the Qt/other platform/Jens perspective. - bool isInverted = [theEvent isDirectionInvertedFromDevice]; - - qCDebug(lcQpaCocoaMouse) << "scroll wheel @ window pos" << qt_windowPoint << "delta px" << pixelDelta << "angle" << angleDelta << "phase" << ph << (isInverted ? "inverted" : ""); - QWindowSystemInterface::handleWheelEvent(m_platformWindow->window(), qt_timestamp, qt_windowPoint, qt_screenPoint, pixelDelta, angleDelta, currentWheelModifiers, ph, source, isInverted); -} -#endif // QT_CONFIG(wheelevent) - -- (int) convertKeyCode : (QChar)keyChar -{ - return qt_mac_cocoaKey2QtKey(keyChar); -} - -+ (Qt::KeyboardModifiers) convertKeyModifiers : (ulong)modifierFlags -{ - const bool dontSwapCtrlAndMeta = qApp->testAttribute(Qt::AA_MacDontSwapCtrlAndMeta); - Qt::KeyboardModifiers qtMods =Qt::NoModifier; - if (modifierFlags & NSShiftKeyMask) - qtMods |= Qt::ShiftModifier; - if (modifierFlags & NSControlKeyMask) - qtMods |= dontSwapCtrlAndMeta ? Qt::ControlModifier : Qt::MetaModifier; - if (modifierFlags & NSAlternateKeyMask) - qtMods |= Qt::AltModifier; - if (modifierFlags & NSCommandKeyMask) - qtMods |= dontSwapCtrlAndMeta ? Qt::MetaModifier : Qt::ControlModifier; - if (modifierFlags & NSNumericPadKeyMask) - qtMods |= Qt::KeypadModifier; - return qtMods; -} - -- (bool)handleKeyEvent:(NSEvent *)nsevent eventType:(int)eventType -{ - ulong timestamp = [nsevent timestamp] * 1000; - ulong nativeModifiers = [nsevent modifierFlags]; - Qt::KeyboardModifiers modifiers = [QNSView convertKeyModifiers: nativeModifiers]; - NSString *charactersIgnoringModifiers = [nsevent charactersIgnoringModifiers]; - NSString *characters = [nsevent characters]; - if (m_inputSource != characters) { - [m_inputSource release]; - m_inputSource = [characters retain]; - } - - // There is no way to get the scan code from carbon/cocoa. But we cannot - // use the value 0, since it indicates that the event originates from somewhere - // else than the keyboard. - quint32 nativeScanCode = 1; - quint32 nativeVirtualKey = [nsevent keyCode]; - - QChar ch = QChar::ReplacementCharacter; - int keyCode = Qt::Key_unknown; - - // If a dead key occurs as a result of pressing a key combination then - // characters will have 0 length, but charactersIgnoringModifiers will - // have a valid character in it. This enables key combinations such as - // ALT+E to be used as a shortcut with an English keyboard even though - // pressing ALT+E will give a dead key while doing normal text input. - if ([characters length] != 0 || [charactersIgnoringModifiers length] != 0) { - auto ctrlOrMetaModifier = qApp->testAttribute(Qt::AA_MacDontSwapCtrlAndMeta) ? Qt::ControlModifier : Qt::MetaModifier; - if (((modifiers & ctrlOrMetaModifier) || (modifiers & Qt::AltModifier)) && ([charactersIgnoringModifiers length] != 0)) - ch = QChar([charactersIgnoringModifiers characterAtIndex:0]); - else if ([characters length] != 0) - ch = QChar([characters characterAtIndex:0]); - keyCode = [self convertKeyCode:ch]; - } - - // we will send a key event unless the input method sets m_sendKeyEvent to false - m_sendKeyEvent = true; - QString text; - // 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)) && (ch.unicode() < 0xf700 || ch.unicode() > 0xf8ff)) - text = QString::fromNSString(characters); - - QWindow *window = [self topLevelWindow]; - - // Popups implicitly grab key events; forward to the active popup if there is one. - // This allows popups to e.g. intercept shortcuts and close the popup in response. - if (QCocoaWindow *popup = QCocoaIntegration::instance()->activePopupWindow()) { - if (!popup->m_windowFlags.testFlag(Qt::ToolTip)) - window = popup->window(); - } - - if (eventType == QEvent::KeyPress) { - - if (m_composingText.isEmpty()) { - m_sendKeyEvent = !QWindowSystemInterface::handleShortcutEvent(window, timestamp, keyCode, - modifiers, nativeScanCode, nativeVirtualKey, nativeModifiers, text, [nsevent isARepeat], 1); - - // Handling a shortcut may result in closing the window - if (!m_platformWindow) - return true; - } - - QObject *fo = m_platformWindow->window()->focusObject(); - if (m_sendKeyEvent && fo) { - QInputMethodQueryEvent queryEvent(Qt::ImEnabled | Qt::ImHints); - if (QCoreApplication::sendEvent(fo, &queryEvent)) { - bool imEnabled = queryEvent.value(Qt::ImEnabled).toBool(); - Qt::InputMethodHints hints = static_cast<Qt::InputMethodHints>(queryEvent.value(Qt::ImHints).toUInt()); - if (imEnabled && !(hints & Qt::ImhDigitsOnly || hints & Qt::ImhFormattedNumbersOnly || hints & Qt::ImhHiddenText)) { - // pass the key event to the input method. note that m_sendKeyEvent may be set to false during this call - m_currentlyInterpretedKeyEvent = nsevent; - [self interpretKeyEvents:[NSArray arrayWithObject:nsevent]]; - m_currentlyInterpretedKeyEvent = 0; - } - } - } - if (m_resendKeyEvent) - m_sendKeyEvent = true; - } - - bool accepted = true; - if (m_sendKeyEvent && m_composingText.isEmpty()) { - QWindowSystemInterface::handleExtendedKeyEvent(window, timestamp, QEvent::Type(eventType), keyCode, modifiers, - nativeScanCode, nativeVirtualKey, nativeModifiers, text, [nsevent isARepeat], 1, false); - accepted = QWindowSystemInterface::flushWindowSystemEvents(); - } - m_sendKeyEvent = false; - m_resendKeyEvent = false; - return accepted; -} - -- (void)keyDown:(NSEvent *)nsevent -{ - if ([self isTransparentForUserInput]) - return [super keyDown:nsevent]; - - const bool accepted = [self handleKeyEvent:nsevent eventType:int(QEvent::KeyPress)]; - - // When Qt is used to implement a plugin for a native application we - // want to propagate unhandled events to other native views. However, - // Qt does not always set the accepted state correctly (in particular - // for return key events), so do this for plugin applications only - // to prevent incorrect forwarding in the general case. - const bool shouldPropagate = QCoreApplication::testAttribute(Qt::AA_PluginApplication) && !accepted; - - // Track keyDown acceptance/forward state for later acceptance of the keyUp. - if (!shouldPropagate) - m_acceptedKeyDowns.insert([nsevent keyCode]); - - if (shouldPropagate) - [super keyDown:nsevent]; -} - -- (void)keyUp:(NSEvent *)nsevent -{ - if ([self isTransparentForUserInput]) - return [super keyUp:nsevent]; - - const bool keyUpAccepted = [self handleKeyEvent:nsevent eventType:int(QEvent::KeyRelease)]; - - // Propagate the keyUp if neither Qt accepted it nor the corresponding KeyDown was - // accepted. Qt text controls wil often not use and ignore keyUp events, but we - // want to avoid propagating unmatched keyUps. - const bool keyDownAccepted = m_acceptedKeyDowns.remove([nsevent keyCode]); - if (!keyUpAccepted && !keyDownAccepted) - [super keyUp:nsevent]; -} - -- (void)cancelOperation:(id)sender -{ - Q_UNUSED(sender); - - NSEvent *currentEvent = [NSApp currentEvent]; - if (!currentEvent || currentEvent.type != NSKeyDown) - return; - - // Handling the key event may recurse back here through interpretKeyEvents - // (when IM is enabled), so we need to guard against that. - if (currentEvent == m_currentlyInterpretedKeyEvent) - return; - - // Send Command+Key_Period and Escape as normal keypresses so that - // the key sequence is delivered through Qt. That way clients can - // intercept the shortcut and override its effect. - [self handleKeyEvent:currentEvent eventType:int(QEvent::KeyPress)]; -} - -- (void)flagsChanged:(NSEvent *)nsevent -{ - ulong timestamp = [nsevent timestamp] * 1000; - ulong modifiers = [nsevent modifierFlags]; - Qt::KeyboardModifiers qmodifiers = [QNSView convertKeyModifiers:modifiers]; - - // calculate the delta and remember the current modifiers for next time - static ulong m_lastKnownModifiers; - ulong lastKnownModifiers = m_lastKnownModifiers; - ulong delta = lastKnownModifiers ^ modifiers; - m_lastKnownModifiers = modifiers; - - struct qt_mac_enum_mapper - { - ulong mac_mask; - Qt::Key qt_code; - }; - static qt_mac_enum_mapper modifier_key_symbols[] = { - { NSShiftKeyMask, Qt::Key_Shift }, - { NSControlKeyMask, Qt::Key_Meta }, - { NSCommandKeyMask, Qt::Key_Control }, - { NSAlternateKeyMask, Qt::Key_Alt }, - { NSAlphaShiftKeyMask, Qt::Key_CapsLock }, - { 0ul, Qt::Key_unknown } }; - for (int i = 0; modifier_key_symbols[i].mac_mask != 0u; ++i) { - uint mac_mask = modifier_key_symbols[i].mac_mask; - if ((delta & mac_mask) == 0u) - continue; - - Qt::Key qtCode = modifier_key_symbols[i].qt_code; - if (qApp->testAttribute(Qt::AA_MacDontSwapCtrlAndMeta)) { - if (qtCode == Qt::Key_Meta) - qtCode = Qt::Key_Control; - else if (qtCode == Qt::Key_Control) - qtCode = Qt::Key_Meta; - } - QWindowSystemInterface::handleKeyEvent(m_platformWindow->window(), - timestamp, - (lastKnownModifiers & mac_mask) ? QEvent::KeyRelease : QEvent::KeyPress, - qtCode, - qmodifiers ^ [QNSView convertKeyModifiers:mac_mask]); - } -} - -- (void) insertNewline:(id)sender -{ - Q_UNUSED(sender); - m_resendKeyEvent = true; -} - -- (void) doCommandBySelector:(SEL)aSelector -{ - [self tryToPerform:aSelector with:self]; -} - -- (void) insertText:(id)aString replacementRange:(NSRange)replacementRange -{ - Q_UNUSED(replacementRange) - - if (m_sendKeyEvent && m_composingText.isEmpty() && [aString isEqualToString:m_inputSource]) { - // don't send input method events for simple text input (let handleKeyEvent send key events instead) - return; - } - - QString commitString; - if ([aString length]) { - if ([aString isKindOfClass:[NSAttributedString class]]) { - commitString = QString::fromCFString(reinterpret_cast<CFStringRef>([aString string])); - } else { - commitString = QString::fromCFString(reinterpret_cast<CFStringRef>(aString)); - }; - } - if (QObject *fo = m_platformWindow->window()->focusObject()) { - QInputMethodQueryEvent queryEvent(Qt::ImEnabled); - if (QCoreApplication::sendEvent(fo, &queryEvent)) { - if (queryEvent.value(Qt::ImEnabled).toBool()) { - QInputMethodEvent e; - e.setCommitString(commitString); - QCoreApplication::sendEvent(fo, &e); - // prevent handleKeyEvent from sending a key event - m_sendKeyEvent = false; - } - } - } - - m_composingText.clear(); - m_composingFocusObject = nullptr; -} - -- (void) setMarkedText:(id)aString selectedRange:(NSRange)selectedRange replacementRange:(NSRange)replacementRange -{ - Q_UNUSED(replacementRange) - QString preeditString; - - QList<QInputMethodEvent::Attribute> attrs; - attrs<<QInputMethodEvent::Attribute(QInputMethodEvent::Cursor, selectedRange.location + selectedRange.length, 1, QVariant()); - - if ([aString isKindOfClass:[NSAttributedString class]]) { - // Preedit string has attribution - preeditString = QString::fromCFString(reinterpret_cast<CFStringRef>([aString string])); - int composingLength = preeditString.length(); - int index = 0; - // Create attributes for individual sections of preedit text - while (index < composingLength) { - NSRange effectiveRange; - NSRange range = NSMakeRange(index, composingLength-index); - NSDictionary *attributes = [aString attributesAtIndex:index - longestEffectiveRange:&effectiveRange - inRange:range]; - NSNumber *underlineStyle = [attributes objectForKey:NSUnderlineStyleAttributeName]; - if (underlineStyle) { - QColor clr (Qt::black); - NSColor *color = [attributes objectForKey:NSUnderlineColorAttributeName]; - if (color) { - clr = qt_mac_toQColor(color); - } - QTextCharFormat format; - format.setFontUnderline(true); - format.setUnderlineColor(clr); - attrs<<QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, - effectiveRange.location, - effectiveRange.length, - format); - } - index = effectiveRange.location + effectiveRange.length; - } - } else { - // No attributes specified, take only the preedit text. - preeditString = QString::fromCFString(reinterpret_cast<CFStringRef>(aString)); - } - - if (attrs.isEmpty()) { - QTextCharFormat format; - format.setFontUnderline(true); - attrs<<QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, - 0, preeditString.length(), format); - } - - m_composingText = preeditString; - - if (QObject *fo = m_platformWindow->window()->focusObject()) { - m_composingFocusObject = fo; - QInputMethodQueryEvent queryEvent(Qt::ImEnabled); - if (QCoreApplication::sendEvent(fo, &queryEvent)) { - if (queryEvent.value(Qt::ImEnabled).toBool()) { - QInputMethodEvent e(preeditString, attrs); - QCoreApplication::sendEvent(fo, &e); - // prevent handleKeyEvent from sending a key event - m_sendKeyEvent = false; - } - } - } -} - -- (void)cancelComposingText -{ - if (m_composingText.isEmpty()) - return; - - if (m_composingFocusObject) { - QInputMethodQueryEvent queryEvent(Qt::ImEnabled); - if (QCoreApplication::sendEvent(m_composingFocusObject, &queryEvent)) { - if (queryEvent.value(Qt::ImEnabled).toBool()) { - QInputMethodEvent e; - QCoreApplication::sendEvent(m_composingFocusObject, &e); - } - } - } - - m_composingText.clear(); - m_composingFocusObject = nullptr; -} - -- (void) unmarkText -{ - if (!m_composingText.isEmpty()) { - if (QObject *fo = m_platformWindow->window()->focusObject()) { - QInputMethodQueryEvent queryEvent(Qt::ImEnabled); - if (QCoreApplication::sendEvent(fo, &queryEvent)) { - if (queryEvent.value(Qt::ImEnabled).toBool()) { - QInputMethodEvent e; - e.setCommitString(m_composingText); - QCoreApplication::sendEvent(fo, &e); - } - } - } - } - m_composingText.clear(); - m_composingFocusObject = nullptr; -} - -- (BOOL) hasMarkedText -{ - return (m_composingText.isEmpty() ? NO: YES); -} - -- (NSAttributedString *) attributedSubstringForProposedRange:(NSRange)aRange actualRange:(NSRangePointer)actualRange -{ - Q_UNUSED(actualRange) - QObject *fo = m_platformWindow->window()->focusObject(); - if (!fo) - return nil; - QInputMethodQueryEvent queryEvent(Qt::ImEnabled | Qt::ImCurrentSelection); - if (!QCoreApplication::sendEvent(fo, &queryEvent)) - return nil; - if (!queryEvent.value(Qt::ImEnabled).toBool()) - return nil; - - QString selectedText = queryEvent.value(Qt::ImCurrentSelection).toString(); - if (selectedText.isEmpty()) - return nil; - - QCFString string(selectedText.mid(aRange.location, aRange.length)); - const NSString *tmpString = reinterpret_cast<const NSString *>((CFStringRef)string); - return [[[NSAttributedString alloc] initWithString:const_cast<NSString *>(tmpString)] autorelease]; -} - -- (NSRange) markedRange -{ - NSRange range; - if (!m_composingText.isEmpty()) { - range.location = 0; - range.length = m_composingText.length(); - } else { - range.location = NSNotFound; - range.length = 0; - } - return range; -} - -- (NSRange) selectedRange -{ - NSRange selectedRange = {0, 0}; - - QObject *fo = m_platformWindow->window()->focusObject(); - if (!fo) - return selectedRange; - QInputMethodQueryEvent queryEvent(Qt::ImEnabled | Qt::ImCurrentSelection); - if (!QCoreApplication::sendEvent(fo, &queryEvent)) - return selectedRange; - if (!queryEvent.value(Qt::ImEnabled).toBool()) - return selectedRange; - - QString selectedText = queryEvent.value(Qt::ImCurrentSelection).toString(); - - if (!selectedText.isEmpty()) { - selectedRange.location = 0; - selectedRange.length = selectedText.length(); - } - return selectedRange; -} - -- (NSRect) firstRectForCharacterRange:(NSRange)aRange actualRange:(NSRangePointer)actualRange -{ - Q_UNUSED(aRange) - Q_UNUSED(actualRange) - QObject *fo = m_platformWindow->window()->focusObject(); - if (!fo) - return NSZeroRect; - - QInputMethodQueryEvent queryEvent(Qt::ImEnabled); - if (!QCoreApplication::sendEvent(fo, &queryEvent)) - return NSZeroRect; - if (!queryEvent.value(Qt::ImEnabled).toBool()) - return NSZeroRect; - - if (!m_platformWindow->window()) - return NSZeroRect; - - // The returned rect is always based on the internal cursor. - QRect mr = qApp->inputMethod()->cursorRectangle().toRect(); - mr.moveBottomLeft(m_platformWindow->window()->mapToGlobal(mr.bottomLeft())); - return QCocoaScreen::mapToNative(mr); -} - -- (NSUInteger)characterIndexForPoint:(NSPoint)aPoint -{ - // We don't support cursor movements using mouse while composing. - Q_UNUSED(aPoint); - return NSNotFound; -} - -- (NSArray*)validAttributesForMarkedText -{ - if (!m_platformWindow) - return nil; - - if (m_platformWindow->window() != QGuiApplication::focusWindow()) - return nil; - - QObject *fo = m_platformWindow->window()->focusObject(); - if (!fo) - return nil; - - QInputMethodQueryEvent queryEvent(Qt::ImEnabled); - if (!QCoreApplication::sendEvent(fo, &queryEvent)) - return nil; - if (!queryEvent.value(Qt::ImEnabled).toBool()) - return nil; - - // Support only underline color/style. - return [NSArray arrayWithObjects:NSUnderlineColorAttributeName, - NSUnderlineStyleAttributeName, nil]; -} - --(void)registerDragTypes -{ - QMacAutoReleasePool pool; - QStringList customTypes = qt_mac_enabledDraggedTypes(); - if (currentCustomDragTypes == 0 || *currentCustomDragTypes != customTypes) { - if (currentCustomDragTypes == 0) - currentCustomDragTypes = new QStringList(); - *currentCustomDragTypes = customTypes; - const NSString* mimeTypeGeneric = @"com.trolltech.qt.MimeTypeName"; - NSMutableArray *supportedTypes = [NSMutableArray arrayWithObjects:NSColorPboardType, - NSFilenamesPboardType, NSStringPboardType, - NSFilenamesPboardType, NSPostScriptPboardType, NSTIFFPboardType, - NSRTFPboardType, NSTabularTextPboardType, NSFontPboardType, - NSRulerPboardType, NSFileContentsPboardType, NSColorPboardType, - NSRTFDPboardType, NSHTMLPboardType, - NSURLPboardType, NSPDFPboardType, NSVCardPboardType, - NSFilesPromisePboardType, NSInkTextPboardType, - NSMultipleTextSelectionPboardType, mimeTypeGeneric, nil]; - // Add custom types supported by the application. - for (int i = 0; i < customTypes.size(); i++) { - [supportedTypes addObject:customTypes[i].toNSString()]; - } - [self registerForDraggedTypes:supportedTypes]; - } -} - -static QWindow *findEventTargetWindow(QWindow *candidate) -{ - while (candidate) { - if (!(candidate->flags() & Qt::WindowTransparentForInput)) - return candidate; - candidate = candidate->parent(); - } - return candidate; -} - -static QPoint mapWindowCoordinates(QWindow *source, QWindow *target, QPoint point) -{ - return target->mapFromGlobal(source->mapToGlobal(point)); -} - -- (NSDragOperation)draggingSession:(NSDraggingSession *)session - sourceOperationMaskForDraggingContext:(NSDraggingContext)context -{ - Q_UNUSED(session); - Q_UNUSED(context); - QCocoaDrag* nativeDrag = QCocoaIntegration::instance()->drag(); - return qt_mac_mapDropActions(nativeDrag->currentDrag()->supportedActions()); -} - -- (BOOL)ignoreModifierKeysForDraggingSession:(NSDraggingSession *)session -{ - Q_UNUSED(session); - // According to the "Dragging Sources" chapter on Cocoa DnD Programming - // (https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/DragandDrop/Concepts/dragsource.html), - // if the control, option, or command key is pressed, the source’s - // operation mask is filtered to only contain a reduced set of operations. - // - // Since Qt already takes care of tracking the keyboard modifiers, we - // don't need (or want) Cocoa to filter anything. Instead, we'll let - // the application do the actual filtering. - return YES; -} - -- (BOOL)wantsPeriodicDraggingUpdates -{ - // From the documentation: - // - // "If the destination returns NO, these messages are sent only when the mouse moves - // or a modifier flag changes. Otherwise the destination gets the default behavior, - // where it receives periodic dragging-updated messages even if nothing changes." - // - // We do not want these constant drag update events while mouse is stationary, - // since we do all animations (autoscroll) with timers. - return NO; -} - - -- (BOOL)wantsPeriodicDraggingUpdates:(void *)dummy -{ - // This method never gets called. It's a workaround for Apple's - // bug: they first respondsToSelector : @selector(wantsPeriodicDraggingUpdates:) - // (note ':') and then call -wantsPeriodicDraggingUpdate (without colon). - // So, let's make them happy. - Q_UNUSED(dummy); - - return NO; -} - -- (void)updateCursorFromDragResponse:(QPlatformDragQtResponse)response drag:(QCocoaDrag *)drag -{ - const QPixmap pixmapCursor = drag->currentDrag()->dragCursor(response.acceptedAction()); - NSCursor *nativeCursor = nil; - - if (pixmapCursor.isNull()) { - switch (response.acceptedAction()) { - case Qt::CopyAction: - nativeCursor = [NSCursor dragCopyCursor]; - break; - case Qt::LinkAction: - nativeCursor = [NSCursor dragLinkCursor]; - break; - case Qt::IgnoreAction: - // Uncomment the next lines if forbiden cursor wanted on non droppable targets. - /*nativeCursor = [NSCursor operationNotAllowedCursor]; - break;*/ - case Qt::MoveAction: - default: - nativeCursor = [NSCursor arrowCursor]; - break; - } - } - else { - NSImage *nsimage = qt_mac_create_nsimage(pixmapCursor); - nsimage.size = NSSizeFromCGSize((pixmapCursor.size() / pixmapCursor.devicePixelRatioF()).toCGSize()); - nativeCursor = [[NSCursor alloc] initWithImage:nsimage hotSpot:NSZeroPoint]; - [nsimage release]; - } - - // change the cursor - [nativeCursor set]; - - // Make sure the cursor is updated correctly if the mouse does not move and window is under cursor - // by creating a fake move event, unless on 10.14 and later where doing so will trigger a security - // warning dialog. FIXME: Find a way to update the cursor without fake mouse events. - if (QOperatingSystemVersion::current() >= QOperatingSystemVersion::MacOSMojave) - return; - - if (m_updatingDrag) - return; - - const QPoint mousePos(QCursor::pos()); - CGEventRef moveEvent(CGEventCreateMouseEvent( - NULL, kCGEventMouseMoved, - CGPointMake(mousePos.x(), mousePos.y()), - kCGMouseButtonLeft // ignored - )); - CGEventPost(kCGHIDEventTap, moveEvent); - CFRelease(moveEvent); -} - -- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender -{ - return [self handleDrag : sender]; -} - -- (NSDragOperation)draggingUpdated:(id <NSDraggingInfo>)sender -{ - m_updatingDrag = true; - const NSDragOperation ret([self handleDrag : sender]); - m_updatingDrag = false; - - return ret; -} - -// Sends drag update to Qt, return the action -- (NSDragOperation)handleDrag:(id <NSDraggingInfo>)sender -{ - if (!m_platformWindow) - return NSDragOperationNone; - - NSPoint windowPoint = [self convertPoint: [sender draggingLocation] fromView: nil]; - QPoint qt_windowPoint(windowPoint.x, windowPoint.y); - Qt::DropActions qtAllowed = qt_mac_mapNSDragOperations([sender draggingSourceOperationMask]); - - QWindow *target = findEventTargetWindow(m_platformWindow->window()); - if (!target) - return NSDragOperationNone; - - // update these so selecting move/copy/link works - QGuiApplicationPrivate::modifier_buttons = [QNSView convertKeyModifiers: [[NSApp currentEvent] modifierFlags]]; - - QPlatformDragQtResponse response(false, Qt::IgnoreAction, QRect()); - QCocoaDrag* nativeDrag = QCocoaIntegration::instance()->drag(); - if (nativeDrag->currentDrag()) { - // The drag was started from within the application - response = QWindowSystemInterface::handleDrag(target, nativeDrag->dragMimeData(), mapWindowCoordinates(m_platformWindow->window(), target, qt_windowPoint), qtAllowed); - [self updateCursorFromDragResponse:response drag:nativeDrag]; - } else { - QCocoaDropData mimeData([sender draggingPasteboard]); - response = QWindowSystemInterface::handleDrag(target, &mimeData, mapWindowCoordinates(m_platformWindow->window(), target, qt_windowPoint), qtAllowed); - } - - return qt_mac_mapDropAction(response.acceptedAction()); -} - -- (void)draggingExited:(id <NSDraggingInfo>)sender -{ - if (!m_platformWindow) - return; - - QWindow *target = findEventTargetWindow(m_platformWindow->window()); - if (!target) - return; - - NSPoint windowPoint = [self convertPoint: [sender draggingLocation] fromView: nil]; - QPoint qt_windowPoint(windowPoint.x, windowPoint.y); - - // Send 0 mime data to indicate drag exit - QWindowSystemInterface::handleDrag(target, 0, mapWindowCoordinates(m_platformWindow->window(), target, qt_windowPoint), Qt::IgnoreAction); -} - -// called on drop, send the drop to Qt and return if it was accepted. -- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender -{ - if (!m_platformWindow) - return false; - - QWindow *target = findEventTargetWindow(m_platformWindow->window()); - if (!target) - return false; - - NSPoint windowPoint = [self convertPoint: [sender draggingLocation] fromView: nil]; - QPoint qt_windowPoint(windowPoint.x, windowPoint.y); - Qt::DropActions qtAllowed = qt_mac_mapNSDragOperations([sender draggingSourceOperationMask]); - - QPlatformDropQtResponse response(false, Qt::IgnoreAction); - QCocoaDrag* nativeDrag = QCocoaIntegration::instance()->drag(); - if (nativeDrag->currentDrag()) { - // The drag was started from within the application - response = QWindowSystemInterface::handleDrop(target, nativeDrag->dragMimeData(), mapWindowCoordinates(m_platformWindow->window(), target, qt_windowPoint), qtAllowed); - } else { - QCocoaDropData mimeData([sender draggingPasteboard]); - response = QWindowSystemInterface::handleDrop(target, &mimeData, mapWindowCoordinates(m_platformWindow->window(), target, qt_windowPoint), qtAllowed); - } - if (response.isAccepted()) { - QCocoaDrag* nativeDrag = QCocoaIntegration::instance()->drag(); - nativeDrag->setAcceptedAction(response.acceptedAction()); - } - return response.isAccepted(); -} - -- (void)draggingSession:(NSDraggingSession *)session - endedAtPoint:(NSPoint)screenPoint - operation:(NSDragOperation)operation -{ - Q_UNUSED(session); - Q_UNUSED(operation); - - if (!m_platformWindow) - return; - - QWindow *target = findEventTargetWindow(m_platformWindow->window()); - if (!target) - return; - - // keep our state, and QGuiApplication state (buttons member) in-sync, - // or future mouse events will be processed incorrectly - NSUInteger pmb = [NSEvent pressedMouseButtons]; - for (int buttonNumber = 0; buttonNumber < 32; buttonNumber++) { // see cocoaButton2QtButton() for the 32 value - if (!(pmb & (1 << buttonNumber))) - m_buttons &= ~cocoaButton2QtButton(buttonNumber); - } - - NSPoint windowPoint = [self.window convertRectFromScreen:NSMakeRect(screenPoint.x, screenPoint.y, 1, 1)].origin; - NSPoint nsViewPoint = [self convertPoint: windowPoint fromView: nil]; // NSView/QWindow coordinates - QPoint qtWindowPoint(nsViewPoint.x, nsViewPoint.y); - QPoint qtScreenPoint = QCocoaScreen::mapFromNative(screenPoint).toPoint(); - - QWindowSystemInterface::handleMouseEvent(target, mapWindowCoordinates(m_platformWindow->window(), target, qtWindowPoint), qtScreenPoint, m_buttons); -} - -@end +// ----------------------------------------------------- @implementation QT_MANGLE_NAMESPACE(QNSView) (QtExtras) @@ -2089,9 +347,4 @@ static QPoint mapWindowCoordinates(QWindow *source, QWindow *target, QPoint poin return m_platformWindow.data();; } -- (BOOL)isMenuView -{ - return m_isMenuView; -} - @end diff --git a/src/plugins/platforms/cocoa/qnsviewaccessibility.mm b/src/plugins/platforms/cocoa/qnsview_accessibility.mm index 645a93edf7..32ec0b74d4 100644 --- a/src/plugins/platforms/cocoa/qnsviewaccessibility.mm +++ b/src/plugins/platforms/cocoa/qnsview_accessibility.mm @@ -37,57 +37,54 @@ ** ****************************************************************************/ -#include "qnsview.h" -#include "qcocoahelpers.h" +// This file is included from qnsview.mm, and only used to organize the code + #include "qcocoaaccessibility.h" #include "qcocoaaccessibilityelement.h" #include "qcocoaintegration.h" #include <QtGui/qaccessible.h> -#include <QtCore/QDebug> #import <AppKit/NSAccessibility.h> -#ifndef QT_NO_ACCESSIBILITY - -@implementation QNSView (QNSViewAccessibility) +@implementation QT_MANGLE_NAMESPACE(QNSView) (Accessibility) -- (id)childAccessibleElement { - if (m_platformWindow.isNull()) +- (id)childAccessibleElement +{ + QCocoaWindow *platformWindow = self.platformWindow; + if (!platformWindow || !platformWindow->window()->accessibleRoot()) return nil; - if (!m_platformWindow->window()->accessibleRoot()) - return nil; - - QAccessible::Id childId = QAccessible::uniqueId(m_platformWindow->window()->accessibleRoot()); - return [QMacAccessibilityElement elementWithId: childId]; + QAccessible::Id childId = QAccessible::uniqueId(platformWindow->window()->accessibleRoot()); + return [QMacAccessibilityElement elementWithId:childId]; } // The QNSView is a container that the user does not interact directly with: // Remove it from the user-visible accessibility tree. -- (BOOL)accessibilityIsIgnored { +- (BOOL)accessibilityIsIgnored +{ return YES; } -- (id)accessibilityAttributeValue:(NSString *)attribute { +- (id)accessibilityAttributeValue:(NSString *)attribute +{ // activate accessibility updates QCocoaIntegration::instance()->accessibility()->setActive(true); - if ([attribute isEqualToString:NSAccessibilityChildrenAttribute]) { + if ([attribute isEqualToString:NSAccessibilityChildrenAttribute]) return NSAccessibilityUnignoredChildrenForOnlyChild([self childAccessibleElement]); - } else { + else return [super accessibilityAttributeValue:attribute]; - } } -- (id)accessibilityHitTest:(NSPoint)point { - return [[self childAccessibleElement] accessibilityHitTest: point]; +- (id)accessibilityHitTest:(NSPoint)point +{ + return [[self childAccessibleElement] accessibilityHitTest:point]; } -- (id)accessibilityFocusedUIElement { +- (id)accessibilityFocusedUIElement +{ return [[self childAccessibleElement] accessibilityFocusedUIElement]; } @end - -#endif // QT_NO_ACCESSIBILITY diff --git a/src/plugins/platforms/cocoa/qnsview_complextext.mm b/src/plugins/platforms/cocoa/qnsview_complextext.mm new file mode 100644 index 0000000000..d357082d33 --- /dev/null +++ b/src/plugins/platforms/cocoa/qnsview_complextext.mm @@ -0,0 +1,315 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +// This file is included from qnsview.mm, and only used to organize the code + +@implementation QT_MANGLE_NAMESPACE(QNSView) (ComplexTextAPI) + +- (void)cancelComposingText +{ + if (m_composingText.isEmpty()) + return; + + if (m_composingFocusObject) { + QInputMethodQueryEvent queryEvent(Qt::ImEnabled); + if (QCoreApplication::sendEvent(m_composingFocusObject, &queryEvent)) { + if (queryEvent.value(Qt::ImEnabled).toBool()) { + QInputMethodEvent e; + QCoreApplication::sendEvent(m_composingFocusObject, &e); + } + } + } + + m_composingText.clear(); + m_composingFocusObject = nullptr; +} + +- (void)unmarkText +{ + if (!m_composingText.isEmpty()) { + if (QObject *fo = m_platformWindow->window()->focusObject()) { + QInputMethodQueryEvent queryEvent(Qt::ImEnabled); + if (QCoreApplication::sendEvent(fo, &queryEvent)) { + if (queryEvent.value(Qt::ImEnabled).toBool()) { + QInputMethodEvent e; + e.setCommitString(m_composingText); + QCoreApplication::sendEvent(fo, &e); + } + } + } + } + m_composingText.clear(); + m_composingFocusObject = nullptr; +} + +@end + +@implementation QT_MANGLE_NAMESPACE(QNSView) (ComplexText) + +- (void)insertNewline:(id)sender +{ + Q_UNUSED(sender); + m_resendKeyEvent = true; +} + +- (void)doCommandBySelector:(SEL)aSelector +{ + [self tryToPerform:aSelector with:self]; +} + +- (void)insertText:(id)aString replacementRange:(NSRange)replacementRange +{ + Q_UNUSED(replacementRange) + + if (m_sendKeyEvent && m_composingText.isEmpty() && [aString isEqualToString:m_inputSource]) { + // don't send input method events for simple text input (let handleKeyEvent send key events instead) + return; + } + + QString commitString; + if ([aString length]) { + if ([aString isKindOfClass:[NSAttributedString class]]) { + commitString = QString::fromCFString(reinterpret_cast<CFStringRef>([aString string])); + } else { + commitString = QString::fromCFString(reinterpret_cast<CFStringRef>(aString)); + }; + } + if (QObject *fo = m_platformWindow->window()->focusObject()) { + QInputMethodQueryEvent queryEvent(Qt::ImEnabled); + if (QCoreApplication::sendEvent(fo, &queryEvent)) { + if (queryEvent.value(Qt::ImEnabled).toBool()) { + QInputMethodEvent e; + e.setCommitString(commitString); + QCoreApplication::sendEvent(fo, &e); + // prevent handleKeyEvent from sending a key event + m_sendKeyEvent = false; + } + } + } + + m_composingText.clear(); + m_composingFocusObject = nullptr; +} + +- (void)setMarkedText:(id)aString selectedRange:(NSRange)selectedRange replacementRange:(NSRange)replacementRange +{ + Q_UNUSED(replacementRange) + QString preeditString; + + QList<QInputMethodEvent::Attribute> attrs; + attrs<<QInputMethodEvent::Attribute(QInputMethodEvent::Cursor, selectedRange.location + selectedRange.length, 1, QVariant()); + + if ([aString isKindOfClass:[NSAttributedString class]]) { + // Preedit string has attribution + preeditString = QString::fromCFString(reinterpret_cast<CFStringRef>([aString string])); + int composingLength = preeditString.length(); + int index = 0; + // Create attributes for individual sections of preedit text + while (index < composingLength) { + NSRange effectiveRange; + NSRange range = NSMakeRange(index, composingLength-index); + NSDictionary *attributes = [aString attributesAtIndex:index + longestEffectiveRange:&effectiveRange + inRange:range]; + NSNumber *underlineStyle = [attributes objectForKey:NSUnderlineStyleAttributeName]; + if (underlineStyle) { + QColor clr (Qt::black); + NSColor *color = [attributes objectForKey:NSUnderlineColorAttributeName]; + if (color) { + clr = qt_mac_toQColor(color); + } + QTextCharFormat format; + format.setFontUnderline(true); + format.setUnderlineColor(clr); + attrs<<QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, + effectiveRange.location, + effectiveRange.length, + format); + } + index = effectiveRange.location + effectiveRange.length; + } + } else { + // No attributes specified, take only the preedit text. + preeditString = QString::fromCFString(reinterpret_cast<CFStringRef>(aString)); + } + + if (attrs.isEmpty()) { + QTextCharFormat format; + format.setFontUnderline(true); + attrs<<QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, + 0, preeditString.length(), format); + } + + m_composingText = preeditString; + + if (QObject *fo = m_platformWindow->window()->focusObject()) { + m_composingFocusObject = fo; + QInputMethodQueryEvent queryEvent(Qt::ImEnabled); + if (QCoreApplication::sendEvent(fo, &queryEvent)) { + if (queryEvent.value(Qt::ImEnabled).toBool()) { + QInputMethodEvent e(preeditString, attrs); + QCoreApplication::sendEvent(fo, &e); + // prevent handleKeyEvent from sending a key event + m_sendKeyEvent = false; + } + } + } +} + +- (BOOL)hasMarkedText +{ + return (m_composingText.isEmpty() ? NO: YES); +} + +- (NSAttributedString *)attributedSubstringForProposedRange:(NSRange)aRange actualRange:(NSRangePointer)actualRange +{ + Q_UNUSED(actualRange) + QObject *fo = m_platformWindow->window()->focusObject(); + if (!fo) + return nil; + QInputMethodQueryEvent queryEvent(Qt::ImEnabled | Qt::ImCurrentSelection); + if (!QCoreApplication::sendEvent(fo, &queryEvent)) + return nil; + if (!queryEvent.value(Qt::ImEnabled).toBool()) + return nil; + + QString selectedText = queryEvent.value(Qt::ImCurrentSelection).toString(); + if (selectedText.isEmpty()) + return nil; + + QCFString string(selectedText.mid(aRange.location, aRange.length)); + const NSString *tmpString = reinterpret_cast<const NSString *>((CFStringRef)string); + return [[[NSAttributedString alloc] initWithString:const_cast<NSString *>(tmpString)] autorelease]; +} + +- (NSRange)markedRange +{ + NSRange range; + if (!m_composingText.isEmpty()) { + range.location = 0; + range.length = m_composingText.length(); + } else { + range.location = NSNotFound; + range.length = 0; + } + return range; +} + +- (NSRange)selectedRange +{ + NSRange selectedRange = {0, 0}; + + QObject *fo = m_platformWindow->window()->focusObject(); + if (!fo) + return selectedRange; + QInputMethodQueryEvent queryEvent(Qt::ImEnabled | Qt::ImCurrentSelection); + if (!QCoreApplication::sendEvent(fo, &queryEvent)) + return selectedRange; + if (!queryEvent.value(Qt::ImEnabled).toBool()) + return selectedRange; + + QString selectedText = queryEvent.value(Qt::ImCurrentSelection).toString(); + + if (!selectedText.isEmpty()) { + selectedRange.location = 0; + selectedRange.length = selectedText.length(); + } + return selectedRange; +} + +- (NSRect)firstRectForCharacterRange:(NSRange)aRange actualRange:(NSRangePointer)actualRange +{ + Q_UNUSED(aRange) + Q_UNUSED(actualRange) + + QObject *fo = m_platformWindow->window()->focusObject(); + if (!fo) + return NSZeroRect; + + QInputMethodQueryEvent queryEvent(Qt::ImEnabled); + if (!QCoreApplication::sendEvent(fo, &queryEvent)) + return NSZeroRect; + if (!queryEvent.value(Qt::ImEnabled).toBool()) + return NSZeroRect; + + // The returned rect is always based on the internal cursor. + QRect mr = qApp->inputMethod()->cursorRectangle().toRect(); + mr.moveBottomLeft(m_platformWindow->window()->mapToGlobal(mr.bottomLeft())); + return QCocoaScreen::mapToNative(mr); +} + +- (NSUInteger)characterIndexForPoint:(NSPoint)aPoint +{ + // We don't support cursor movements using mouse while composing. + Q_UNUSED(aPoint); + return NSNotFound; +} + +- (NSArray<NSString *> *)validAttributesForMarkedText +{ + if (!m_platformWindow) + return nil; + + if (m_platformWindow->window() != QGuiApplication::focusWindow()) + return nil; + + QObject *fo = m_platformWindow->window()->focusObject(); + if (!fo) + return nil; + + QInputMethodQueryEvent queryEvent(Qt::ImEnabled); + if (!QCoreApplication::sendEvent(fo, &queryEvent)) + return nil; + if (!queryEvent.value(Qt::ImEnabled).toBool()) + return nil; + + // Support only underline color/style. + return @[NSUnderlineColorAttributeName, NSUnderlineStyleAttributeName]; +} + +- (void)textInputContextKeyboardSelectionDidChangeNotification:(NSNotification *)textInputContextKeyboardSelectionDidChangeNotification +{ + Q_UNUSED(textInputContextKeyboardSelectionDidChangeNotification) + if (([NSApp keyWindow] == self.window) && self.window.firstResponder == self) { + QCocoaInputContext *ic = qobject_cast<QCocoaInputContext *>(QCocoaIntegration::instance()->inputContext()); + ic->updateLocale(); + } +} + +@end diff --git a/src/plugins/platforms/cocoa/qnsview_dragging.mm b/src/plugins/platforms/cocoa/qnsview_dragging.mm new file mode 100644 index 0000000000..1c38c5326c --- /dev/null +++ b/src/plugins/platforms/cocoa/qnsview_dragging.mm @@ -0,0 +1,300 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +// This file is included from qnsview.mm, and only used to organize the code + +@implementation QT_MANGLE_NAMESPACE(QNSView) (Dragging) + +-(void)registerDragTypes +{ + QMacAutoReleasePool pool; + + NSString * const mimeTypeGeneric = @"com.trolltech.qt.MimeTypeName"; + NSMutableArray<NSString *> *supportedTypes = [NSMutableArray<NSString *> arrayWithArray:@[ + NSColorPboardType, + NSFilenamesPboardType, NSStringPboardType, + NSFilenamesPboardType, NSPostScriptPboardType, NSTIFFPboardType, + NSRTFPboardType, NSTabularTextPboardType, NSFontPboardType, + NSRulerPboardType, NSFileContentsPboardType, NSColorPboardType, + NSRTFDPboardType, NSHTMLPboardType, + NSURLPboardType, NSPDFPboardType, NSVCardPboardType, + NSFilesPromisePboardType, NSInkTextPboardType, + NSMultipleTextSelectionPboardType, mimeTypeGeneric]]; + + // Add custom types supported by the application. + for (const QString &customType : qt_mac_enabledDraggedTypes()) + [supportedTypes addObject:customType.toNSString()]; + + [self registerForDraggedTypes:supportedTypes]; +} + +static QWindow *findEventTargetWindow(QWindow *candidate) +{ + while (candidate) { + if (!(candidate->flags() & Qt::WindowTransparentForInput)) + return candidate; + candidate = candidate->parent(); + } + return candidate; +} + +static QPoint mapWindowCoordinates(QWindow *source, QWindow *target, QPoint point) +{ + return target->mapFromGlobal(source->mapToGlobal(point)); +} + +- (NSDragOperation)draggingSession:(NSDraggingSession *)session + sourceOperationMaskForDraggingContext:(NSDraggingContext)context +{ + Q_UNUSED(session); + Q_UNUSED(context); + QCocoaDrag* nativeDrag = QCocoaIntegration::instance()->drag(); + return qt_mac_mapDropActions(nativeDrag->currentDrag()->supportedActions()); +} + +- (BOOL)ignoreModifierKeysForDraggingSession:(NSDraggingSession *)session +{ + Q_UNUSED(session); + // According to the "Dragging Sources" chapter on Cocoa DnD Programming + // (https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/DragandDrop/Concepts/dragsource.html), + // if the control, option, or command key is pressed, the source’s + // operation mask is filtered to only contain a reduced set of operations. + // + // Since Qt already takes care of tracking the keyboard modifiers, we + // don't need (or want) Cocoa to filter anything. Instead, we'll let + // the application do the actual filtering. + return YES; +} + +- (BOOL)wantsPeriodicDraggingUpdates +{ + // From the documentation: + // + // "If the destination returns NO, these messages are sent only when the mouse moves + // or a modifier flag changes. Otherwise the destination gets the default behavior, + // where it receives periodic dragging-updated messages even if nothing changes." + // + // We do not want these constant drag update events while mouse is stationary, + // since we do all animations (autoscroll) with timers. + return NO; +} + + +- (BOOL)wantsPeriodicDraggingUpdates:(void *)dummy +{ + // This method never gets called. It's a workaround for Apple's + // bug: they first respondsToSelector : @selector(wantsPeriodicDraggingUpdates:) + // (note ':') and then call -wantsPeriodicDraggingUpdate (without colon). + // So, let's make them happy. + Q_UNUSED(dummy); + + return NO; +} + +- (void)updateCursorFromDragResponse:(QPlatformDragQtResponse)response drag:(QCocoaDrag *)drag +{ + const QPixmap pixmapCursor = drag->currentDrag()->dragCursor(response.acceptedAction()); + NSCursor *nativeCursor = nil; + + if (pixmapCursor.isNull()) { + switch (response.acceptedAction()) { + case Qt::CopyAction: + nativeCursor = [NSCursor dragCopyCursor]; + break; + case Qt::LinkAction: + nativeCursor = [NSCursor dragLinkCursor]; + break; + case Qt::IgnoreAction: + // Uncomment the next lines if forbiden cursor wanted on non droppable targets. + /*nativeCursor = [NSCursor operationNotAllowedCursor]; + break;*/ + case Qt::MoveAction: + default: + nativeCursor = [NSCursor arrowCursor]; + break; + } + } + else { + NSImage *nsimage = qt_mac_create_nsimage(pixmapCursor); + nsimage.size = NSSizeFromCGSize((pixmapCursor.size() / pixmapCursor.devicePixelRatioF()).toCGSize()); + nativeCursor = [[NSCursor alloc] initWithImage:nsimage hotSpot:NSZeroPoint]; + [nsimage release]; + } + + // change the cursor + [nativeCursor set]; + + // Make sure the cursor is updated correctly if the mouse does not move and window is under cursor + // by creating a fake move event, unless on 10.14 and later where doing so will trigger a security + // warning dialog. FIXME: Find a way to update the cursor without fake mouse events. + if (QOperatingSystemVersion::current() >= QOperatingSystemVersion::MacOSMojave) + return; + + if (m_updatingDrag) + return; + + const QPoint mousePos(QCursor::pos()); + CGEventRef moveEvent(CGEventCreateMouseEvent( + NULL, kCGEventMouseMoved, + CGPointMake(mousePos.x(), mousePos.y()), + kCGMouseButtonLeft // ignored + )); + CGEventPost(kCGHIDEventTap, moveEvent); + CFRelease(moveEvent); +} + +- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender +{ + return [self handleDrag : sender]; +} + +- (NSDragOperation)draggingUpdated:(id <NSDraggingInfo>)sender +{ + m_updatingDrag = true; + const NSDragOperation ret([self handleDrag : sender]); + m_updatingDrag = false; + + return ret; +} + +// Sends drag update to Qt, return the action +- (NSDragOperation)handleDrag:(id <NSDraggingInfo>)sender +{ + if (!m_platformWindow) + return NSDragOperationNone; + + NSPoint windowPoint = [self convertPoint: [sender draggingLocation] fromView: nil]; + QPoint qt_windowPoint(windowPoint.x, windowPoint.y); + Qt::DropActions qtAllowed = qt_mac_mapNSDragOperations([sender draggingSourceOperationMask]); + + QWindow *target = findEventTargetWindow(m_platformWindow->window()); + if (!target) + return NSDragOperationNone; + + const auto modifiers = [QNSView convertKeyModifiers:NSApp.currentEvent.modifierFlags]; + const auto buttons = currentlyPressedMouseButtons(); + const auto point = mapWindowCoordinates(m_platformWindow->window(), target, qt_windowPoint); + + QPlatformDragQtResponse response(false, Qt::IgnoreAction, QRect()); + QCocoaDrag* nativeDrag = QCocoaIntegration::instance()->drag(); + if (nativeDrag->currentDrag()) { + // The drag was started from within the application + response = QWindowSystemInterface::handleDrag(target, nativeDrag->dragMimeData(), + point, qtAllowed, buttons, modifiers); + [self updateCursorFromDragResponse:response drag:nativeDrag]; + } else { + QCocoaDropData mimeData([sender draggingPasteboard]); + response = QWindowSystemInterface::handleDrag(target, &mimeData, + point, qtAllowed, buttons, modifiers); + } + + return qt_mac_mapDropAction(response.acceptedAction()); +} + +- (void)draggingExited:(id <NSDraggingInfo>)sender +{ + if (!m_platformWindow) + return; + + QWindow *target = findEventTargetWindow(m_platformWindow->window()); + if (!target) + return; + + NSPoint windowPoint = [self convertPoint: [sender draggingLocation] fromView: nil]; + QPoint qt_windowPoint(windowPoint.x, windowPoint.y); + + // Send 0 mime data to indicate drag exit + QWindowSystemInterface::handleDrag(target, nullptr, + mapWindowCoordinates(m_platformWindow->window(), target, qt_windowPoint), + Qt::IgnoreAction, Qt::NoButton, Qt::NoModifier); +} + +// called on drop, send the drop to Qt and return if it was accepted. +- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender +{ + if (!m_platformWindow) + return false; + + QWindow *target = findEventTargetWindow(m_platformWindow->window()); + if (!target) + return false; + + NSPoint windowPoint = [self convertPoint: [sender draggingLocation] fromView: nil]; + QPoint qt_windowPoint(windowPoint.x, windowPoint.y); + Qt::DropActions qtAllowed = qt_mac_mapNSDragOperations([sender draggingSourceOperationMask]); + + QPlatformDropQtResponse response(false, Qt::IgnoreAction); + QCocoaDrag* nativeDrag = QCocoaIntegration::instance()->drag(); + const auto modifiers = [QNSView convertKeyModifiers:NSApp.currentEvent.modifierFlags]; + const auto buttons = currentlyPressedMouseButtons(); + const auto point = mapWindowCoordinates(m_platformWindow->window(), target, qt_windowPoint); + + if (nativeDrag->currentDrag()) { + // The drag was started from within the application + response = QWindowSystemInterface::handleDrop(target, nativeDrag->dragMimeData(), + point, qtAllowed, buttons, modifiers); + } else { + QCocoaDropData mimeData([sender draggingPasteboard]); + response = QWindowSystemInterface::handleDrop(target, &mimeData, + point, qtAllowed, buttons, modifiers); + } + return response.isAccepted(); +} + +- (void)draggingSession:(NSDraggingSession *)session + endedAtPoint:(NSPoint)screenPoint + operation:(NSDragOperation)operation +{ + Q_UNUSED(session); + Q_UNUSED(screenPoint); + + if (!m_platformWindow) + return; + + QWindow *target = findEventTargetWindow(m_platformWindow->window()); + if (!target) + return; + + QCocoaDrag* nativeDrag = QCocoaIntegration::instance()->drag(); + nativeDrag->setAcceptedAction(qt_mac_mapNSDragOperation(operation)); + + m_buttons = currentlyPressedMouseButtons(); +} + +@end diff --git a/src/plugins/platforms/cocoa/qnsview_drawing.mm b/src/plugins/platforms/cocoa/qnsview_drawing.mm new file mode 100644 index 0000000000..4f9d17504d --- /dev/null +++ b/src/plugins/platforms/cocoa/qnsview_drawing.mm @@ -0,0 +1,167 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +// This file is included from qnsview.mm, and only used to organize the code + +@implementation QT_MANGLE_NAMESPACE(QNSView) (Drawing) + +- (BOOL)isOpaque +{ + if (!m_platformWindow) + return true; + return m_platformWindow->isOpaque(); +} + +- (BOOL)isFlipped +{ + return YES; +} + +- (void)drawRect:(NSRect)dirtyRect +{ + Q_UNUSED(dirtyRect); + + if (!m_platformWindow) + return; + + QRegion exposedRegion; + const NSRect *dirtyRects; + NSInteger numDirtyRects; + [self getRectsBeingDrawn:&dirtyRects count:&numDirtyRects]; + for (int i = 0; i < numDirtyRects; ++i) + exposedRegion += QRectF::fromCGRect(dirtyRects[i]).toRect(); + + qCDebug(lcQpaDrawing) << "[QNSView drawRect:]" << m_platformWindow->window() << exposedRegion; + m_platformWindow->handleExposeEvent(exposedRegion); +} + +- (BOOL)shouldUseMetalLayer +{ + // MetalSurface needs a layer, and so does VulkanSurface (via MoltenVK) + QSurface::SurfaceType surfaceType = m_platformWindow->window()->surfaceType(); + return surfaceType == QWindow::MetalSurface || surfaceType == QWindow::VulkanSurface; +} + +- (BOOL)wantsLayerHelper +{ + Q_ASSERT(m_platformWindow); + + bool wantsLayer = qt_mac_resolveOption(true, m_platformWindow->window(), + "_q_mac_wantsLayer", "QT_MAC_WANTS_LAYER"); + + bool layerForSurfaceType = [self shouldUseMetalLayer]; + + return wantsLayer || layerForSurfaceType; +} + +- (CALayer *)makeBackingLayer +{ + if ([self shouldUseMetalLayer]) { + // Check if Metal is supported. If it isn't then it's most likely + // too late at this point and the QWindow will be non-functional, + // but we can at least print a warning. + if (![MTLCreateSystemDefaultDevice() autorelease]) { + qWarning() << "QWindow initialization error: Metal is not supported"; + return [super makeBackingLayer]; + } + + CAMetalLayer *layer = [CAMetalLayer layer]; + + // Set the contentsScale for the layer. This is normally done in + // viewDidChangeBackingProperties, however on startup that function + // is called before the layer is created here. The layer's drawableSize + // is updated from layoutSublayersOfLayer as usual. + layer.contentsScale = self.window.backingScaleFactor; + + return layer; + } + + return [super makeBackingLayer]; +} + +- (NSViewLayerContentsRedrawPolicy)layerContentsRedrawPolicy +{ + // We need to set this explicitly since the super implementation + // returns LayerContentsRedrawNever for custom layers like CAMetalLayer. + return NSViewLayerContentsRedrawDuringViewResize; +} + +- (void)updateMetalLayerDrawableSize:(CAMetalLayer *)layer +{ + CGSize drawableSize = layer.bounds.size; + drawableSize.width *= layer.contentsScale; + drawableSize.height *= layer.contentsScale; + layer.drawableSize = drawableSize; +} + +- (void)layoutSublayersOfLayer:(CALayer *)layer +{ + if ([layer isKindOfClass:CAMetalLayer.class]) + [self updateMetalLayerDrawableSize:static_cast<CAMetalLayer* >(layer)]; +} + +- (void)displayLayer:(CALayer *)layer +{ + Q_ASSERT(layer == self.layer); + + if (!m_platformWindow) + return; + + qCDebug(lcQpaDrawing) << "[QNSView displayLayer]" << m_platformWindow->window(); + + // FIXME: Find out if there's a way to resolve the dirty rect like in drawRect: + m_platformWindow->handleExposeEvent(QRectF::fromCGRect(self.bounds).toRect()); +} + +- (void)viewDidChangeBackingProperties +{ + CALayer *layer = self.layer; + if (!layer) + return; + + layer.contentsScale = self.window.backingScaleFactor; + + // Metal layers must be manually updated on e.g. screen change + if ([layer isKindOfClass:CAMetalLayer.class]) { + [self updateMetalLayerDrawableSize:static_cast<CAMetalLayer* >(layer)]; + [self setNeedsDisplay:YES]; + } +} + +@end diff --git a/src/plugins/platforms/cocoa/qnsview_gestures.mm b/src/plugins/platforms/cocoa/qnsview_gestures.mm new file mode 100644 index 0000000000..61d551ee0e --- /dev/null +++ b/src/plugins/platforms/cocoa/qnsview_gestures.mm @@ -0,0 +1,169 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +// This file is included from qnsview.mm, and only used to organize the code + +#ifndef QT_NO_GESTURES + +Q_LOGGING_CATEGORY(lcQpaGestures, "qt.qpa.input.gestures") + +@implementation QT_MANGLE_NAMESPACE(QNSView) (Gestures) + +- (bool)handleGestureAsBeginEnd:(NSEvent *)event +{ + if (QOperatingSystemVersion::current() < QOperatingSystemVersion::OSXElCapitan) + return false; + + if ([event phase] == NSEventPhaseBegan) { + [self beginGestureWithEvent:event]; + return true; + } + + if ([event phase] == NSEventPhaseEnded) { + [self endGestureWithEvent:event]; + return true; + } + + return false; +} +- (void)magnifyWithEvent:(NSEvent *)event +{ + if (!m_platformWindow) + return; + + if ([self handleGestureAsBeginEnd:event]) + return; + + qCDebug(lcQpaGestures) << "magnifyWithEvent" << [event magnification] << "from device" << hex << [event deviceID]; + const NSTimeInterval timestamp = [event timestamp]; + QPointF windowPoint; + QPointF screenPoint; + [self convertFromScreen:[self screenMousePoint:event] toWindowPoint:&windowPoint andScreenPoint:&screenPoint]; + QWindowSystemInterface::handleGestureEventWithRealValue(m_platformWindow->window(), QCocoaTouch::getTouchDevice(QTouchDevice::TouchPad, [event deviceID]), timestamp, Qt::ZoomNativeGesture, + [event magnification], windowPoint, screenPoint); +} + +- (void)smartMagnifyWithEvent:(NSEvent *)event +{ + if (!m_platformWindow) + return; + + static bool zoomIn = true; + qCDebug(lcQpaGestures) << "smartMagnifyWithEvent" << zoomIn << "from device" << hex << [event deviceID]; + const NSTimeInterval timestamp = [event timestamp]; + QPointF windowPoint; + QPointF screenPoint; + [self convertFromScreen:[self screenMousePoint:event] toWindowPoint:&windowPoint andScreenPoint:&screenPoint]; + QWindowSystemInterface::handleGestureEventWithRealValue(m_platformWindow->window(), QCocoaTouch::getTouchDevice(QTouchDevice::TouchPad, [event deviceID]), timestamp, Qt::SmartZoomNativeGesture, + zoomIn ? 1.0f : 0.0f, windowPoint, screenPoint); + zoomIn = !zoomIn; +} + +- (void)rotateWithEvent:(NSEvent *)event +{ + if (!m_platformWindow) + return; + + if ([self handleGestureAsBeginEnd:event]) + return; + + const NSTimeInterval timestamp = [event timestamp]; + QPointF windowPoint; + QPointF screenPoint; + [self convertFromScreen:[self screenMousePoint:event] toWindowPoint:&windowPoint andScreenPoint:&screenPoint]; + QWindowSystemInterface::handleGestureEventWithRealValue(m_platformWindow->window(), QCocoaTouch::getTouchDevice(QTouchDevice::TouchPad, [event deviceID]), timestamp, Qt::RotateNativeGesture, + -[event rotation], windowPoint, screenPoint); +} + +- (void)swipeWithEvent:(NSEvent *)event +{ + if (!m_platformWindow) + return; + + qCDebug(lcQpaGestures) << "swipeWithEvent" << [event deltaX] << [event deltaY] << "from device" << hex << [event deviceID]; + const NSTimeInterval timestamp = [event timestamp]; + QPointF windowPoint; + QPointF screenPoint; + [self convertFromScreen:[self screenMousePoint:event] toWindowPoint:&windowPoint andScreenPoint:&screenPoint]; + + qreal angle = 0.0f; + if ([event deltaX] == 1) + angle = 180.0f; + else if ([event deltaX] == -1) + angle = 0.0f; + else if ([event deltaY] == 1) + angle = 90.0f; + else if ([event deltaY] == -1) + angle = 270.0f; + + QWindowSystemInterface::handleGestureEventWithRealValue(m_platformWindow->window(), QCocoaTouch::getTouchDevice(QTouchDevice::TouchPad, [event deviceID]), timestamp, Qt::SwipeNativeGesture, + angle, windowPoint, screenPoint); +} + +- (void)beginGestureWithEvent:(NSEvent *)event +{ + if (!m_platformWindow) + return; + + const NSTimeInterval timestamp = [event timestamp]; + QPointF windowPoint; + QPointF screenPoint; + [self convertFromScreen:[self screenMousePoint:event] toWindowPoint:&windowPoint andScreenPoint:&screenPoint]; + qCDebug(lcQpaGestures) << "beginGestureWithEvent @" << windowPoint << "from device" << hex << [event deviceID]; + QWindowSystemInterface::handleGestureEvent(m_platformWindow->window(), QCocoaTouch::getTouchDevice(QTouchDevice::TouchPad, [event deviceID]), timestamp, Qt::BeginNativeGesture, + windowPoint, screenPoint); +} + +- (void)endGestureWithEvent:(NSEvent *)event +{ + if (!m_platformWindow) + return; + + qCDebug(lcQpaGestures) << "endGestureWithEvent" << "from device" << hex << [event deviceID]; + const NSTimeInterval timestamp = [event timestamp]; + QPointF windowPoint; + QPointF screenPoint; + [self convertFromScreen:[self screenMousePoint:event] toWindowPoint:&windowPoint andScreenPoint:&screenPoint]; + QWindowSystemInterface::handleGestureEvent(m_platformWindow->window(), QCocoaTouch::getTouchDevice(QTouchDevice::TouchPad, [event deviceID]), timestamp, Qt::EndNativeGesture, + windowPoint, screenPoint); +} + +@end + +#endif // QT_NO_GESTURES diff --git a/src/plugins/platforms/cocoa/qnsview_keys.mm b/src/plugins/platforms/cocoa/qnsview_keys.mm new file mode 100644 index 0000000000..85c0607265 --- /dev/null +++ b/src/plugins/platforms/cocoa/qnsview_keys.mm @@ -0,0 +1,262 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +// This file is included from qnsview.mm, and only used to organize the code + +@implementation QT_MANGLE_NAMESPACE(QNSView) (KeysAPI) + ++ (Qt::KeyboardModifiers)convertKeyModifiers:(ulong)modifierFlags +{ + const bool dontSwapCtrlAndMeta = qApp->testAttribute(Qt::AA_MacDontSwapCtrlAndMeta); + Qt::KeyboardModifiers qtMods =Qt::NoModifier; + if (modifierFlags & NSEventModifierFlagShift) + qtMods |= Qt::ShiftModifier; + if (modifierFlags & NSEventModifierFlagShift) + qtMods |= dontSwapCtrlAndMeta ? Qt::ControlModifier : Qt::MetaModifier; + if (modifierFlags & NSEventModifierFlagOption) + qtMods |= Qt::AltModifier; + if (modifierFlags & NSEventModifierFlagCommand) + qtMods |= dontSwapCtrlAndMeta ? Qt::MetaModifier : Qt::ControlModifier; + if (modifierFlags & NSEventModifierFlagCommand) + qtMods |= Qt::KeypadModifier; + return qtMods; +} + +@end + +@implementation QT_MANGLE_NAMESPACE(QNSView) (Keys) + +- (int)convertKeyCode:(QChar)keyChar +{ + return qt_mac_cocoaKey2QtKey(keyChar); +} + +- (bool)handleKeyEvent:(NSEvent *)nsevent eventType:(int)eventType +{ + ulong timestamp = [nsevent timestamp] * 1000; + ulong nativeModifiers = [nsevent modifierFlags]; + Qt::KeyboardModifiers modifiers = [QNSView convertKeyModifiers: nativeModifiers]; + NSString *charactersIgnoringModifiers = [nsevent charactersIgnoringModifiers]; + NSString *characters = [nsevent characters]; + if (m_inputSource != characters) { + [m_inputSource release]; + m_inputSource = [characters retain]; + } + + // There is no way to get the scan code from carbon/cocoa. But we cannot + // use the value 0, since it indicates that the event originates from somewhere + // else than the keyboard. + quint32 nativeScanCode = 1; + quint32 nativeVirtualKey = [nsevent keyCode]; + + QChar ch = QChar::ReplacementCharacter; + int keyCode = Qt::Key_unknown; + + // If a dead key occurs as a result of pressing a key combination then + // characters will have 0 length, but charactersIgnoringModifiers will + // have a valid character in it. This enables key combinations such as + // ALT+E to be used as a shortcut with an English keyboard even though + // pressing ALT+E will give a dead key while doing normal text input. + if ([characters length] != 0 || [charactersIgnoringModifiers length] != 0) { + auto ctrlOrMetaModifier = qApp->testAttribute(Qt::AA_MacDontSwapCtrlAndMeta) ? Qt::ControlModifier : Qt::MetaModifier; + if (((modifiers & ctrlOrMetaModifier) || (modifiers & Qt::AltModifier)) && ([charactersIgnoringModifiers length] != 0)) + ch = QChar([charactersIgnoringModifiers characterAtIndex:0]); + else if ([characters length] != 0) + ch = QChar([characters characterAtIndex:0]); + keyCode = [self convertKeyCode:ch]; + } + + // we will send a key event unless the input method sets m_sendKeyEvent to false + m_sendKeyEvent = true; + QString text; + // 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)) && (ch.unicode() < 0xf700 || ch.unicode() > 0xf8ff)) + text = QString::fromNSString(characters); + + QWindow *window = [self topLevelWindow]; + + // Popups implicitly grab key events; forward to the active popup if there is one. + // This allows popups to e.g. intercept shortcuts and close the popup in response. + if (QCocoaWindow *popup = QCocoaIntegration::instance()->activePopupWindow()) { + if (!popup->window()->flags().testFlag(Qt::ToolTip)) + window = popup->window(); + } + + if (eventType == QEvent::KeyPress) { + + if (m_composingText.isEmpty()) { + m_sendKeyEvent = !QWindowSystemInterface::handleShortcutEvent(window, timestamp, keyCode, + modifiers, nativeScanCode, nativeVirtualKey, nativeModifiers, text, [nsevent isARepeat], 1); + + // Handling a shortcut may result in closing the window + if (!m_platformWindow) + return true; + } + + QObject *fo = m_platformWindow->window()->focusObject(); + if (m_sendKeyEvent && fo) { + QInputMethodQueryEvent queryEvent(Qt::ImEnabled | Qt::ImHints); + if (QCoreApplication::sendEvent(fo, &queryEvent)) { + bool imEnabled = queryEvent.value(Qt::ImEnabled).toBool(); + Qt::InputMethodHints hints = static_cast<Qt::InputMethodHints>(queryEvent.value(Qt::ImHints).toUInt()); + if (imEnabled && !(hints & Qt::ImhDigitsOnly || hints & Qt::ImhFormattedNumbersOnly || hints & Qt::ImhHiddenText)) { + // pass the key event to the input method. note that m_sendKeyEvent may be set to false during this call + m_currentlyInterpretedKeyEvent = nsevent; + [self interpretKeyEvents:@[nsevent]]; + m_currentlyInterpretedKeyEvent = 0; + } + } + } + if (m_resendKeyEvent) + m_sendKeyEvent = true; + } + + bool accepted = true; + if (m_sendKeyEvent && m_composingText.isEmpty()) { + QWindowSystemInterface::handleExtendedKeyEvent(window, timestamp, QEvent::Type(eventType), keyCode, modifiers, + nativeScanCode, nativeVirtualKey, nativeModifiers, text, [nsevent isARepeat], 1, false); + accepted = QWindowSystemInterface::flushWindowSystemEvents(); + } + m_sendKeyEvent = false; + m_resendKeyEvent = false; + return accepted; +} + +- (void)keyDown:(NSEvent *)nsevent +{ + if ([self isTransparentForUserInput]) + return [super keyDown:nsevent]; + + const bool accepted = [self handleKeyEvent:nsevent eventType:int(QEvent::KeyPress)]; + + // When Qt is used to implement a plugin for a native application we + // want to propagate unhandled events to other native views. However, + // Qt does not always set the accepted state correctly (in particular + // for return key events), so do this for plugin applications only + // to prevent incorrect forwarding in the general case. + const bool shouldPropagate = QCoreApplication::testAttribute(Qt::AA_PluginApplication) && !accepted; + + // Track keyDown acceptance/forward state for later acceptance of the keyUp. + if (!shouldPropagate) + m_acceptedKeyDowns.insert([nsevent keyCode]); + + if (shouldPropagate) + [super keyDown:nsevent]; +} + +- (void)keyUp:(NSEvent *)nsevent +{ + if ([self isTransparentForUserInput]) + return [super keyUp:nsevent]; + + const bool keyUpAccepted = [self handleKeyEvent:nsevent eventType:int(QEvent::KeyRelease)]; + + // Propagate the keyUp if neither Qt accepted it nor the corresponding KeyDown was + // accepted. Qt text controls wil often not use and ignore keyUp events, but we + // want to avoid propagating unmatched keyUps. + const bool keyDownAccepted = m_acceptedKeyDowns.remove([nsevent keyCode]); + if (!keyUpAccepted && !keyDownAccepted) + [super keyUp:nsevent]; +} + +- (void)cancelOperation:(id)sender +{ + Q_UNUSED(sender); + + NSEvent *currentEvent = [NSApp currentEvent]; + if (!currentEvent || currentEvent.type != NSEventTypeKeyDown) + return; + + // Handling the key event may recurse back here through interpretKeyEvents + // (when IM is enabled), so we need to guard against that. + if (currentEvent == m_currentlyInterpretedKeyEvent) + return; + + // Send Command+Key_Period and Escape as normal keypresses so that + // the key sequence is delivered through Qt. That way clients can + // intercept the shortcut and override its effect. + [self handleKeyEvent:currentEvent eventType:int(QEvent::KeyPress)]; +} + +- (void)flagsChanged:(NSEvent *)nsevent +{ + ulong timestamp = [nsevent timestamp] * 1000; + ulong modifiers = [nsevent modifierFlags]; + Qt::KeyboardModifiers qmodifiers = [QNSView convertKeyModifiers:modifiers]; + + // calculate the delta and remember the current modifiers for next time + static ulong m_lastKnownModifiers; + ulong lastKnownModifiers = m_lastKnownModifiers; + ulong delta = lastKnownModifiers ^ modifiers; + m_lastKnownModifiers = modifiers; + + struct qt_mac_enum_mapper + { + ulong mac_mask; + Qt::Key qt_code; + }; + static qt_mac_enum_mapper modifier_key_symbols[] = { + { NSEventModifierFlagShift, Qt::Key_Shift }, + { NSEventModifierFlagControl, Qt::Key_Meta }, + { NSEventModifierFlagCommand, Qt::Key_Control }, + { NSEventModifierFlagOption, Qt::Key_Alt }, + { NSEventModifierFlagCapsLock, Qt::Key_CapsLock }, + { 0ul, Qt::Key_unknown } }; + for (int i = 0; modifier_key_symbols[i].mac_mask != 0u; ++i) { + uint mac_mask = modifier_key_symbols[i].mac_mask; + if ((delta & mac_mask) == 0u) + continue; + + Qt::Key qtCode = modifier_key_symbols[i].qt_code; + if (qApp->testAttribute(Qt::AA_MacDontSwapCtrlAndMeta)) { + if (qtCode == Qt::Key_Meta) + qtCode = Qt::Key_Control; + else if (qtCode == Qt::Key_Control) + qtCode = Qt::Key_Meta; + } + QWindowSystemInterface::handleKeyEvent(m_platformWindow->window(), + timestamp, + (lastKnownModifiers & mac_mask) ? QEvent::KeyRelease : QEvent::KeyPress, + qtCode, + qmodifiers ^ [QNSView convertKeyModifiers:mac_mask]); + } +} + +@end diff --git a/src/plugins/platforms/cocoa/qnsview_menus.mm b/src/plugins/platforms/cocoa/qnsview_menus.mm new file mode 100644 index 0000000000..f0489552aa --- /dev/null +++ b/src/plugins/platforms/cocoa/qnsview_menus.mm @@ -0,0 +1,133 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +// This file is included from qnsview.mm, and only used to organize the code + +#include <qcocoaapplicationdelegate.h> +#include <qcocoansmenu.h> +#include <qcocoamenuitem.h> +#include <qcocoamenu.h> +#include <qcocoamenubar.h> + +static bool selectorIsCutCopyPaste(SEL selector) +{ + return (selector == @selector(cut:) + || selector == @selector(copy:) + || selector == @selector(paste:) + || selector == @selector(selectAll:)); +} + +@interface QT_MANGLE_NAMESPACE(QNSView) (Menus) +- (void)qt_itemFired:(QCocoaNSMenuItem *)item; +@end + +@implementation QT_MANGLE_NAMESPACE(QNSView) (Menus) + +- (BOOL)validateMenuItem:(NSMenuItem*)item +{ + auto *nativeItem = qt_objc_cast<QCocoaNSMenuItem *>(item); + if (!nativeItem) + return item.enabled; // FIXME Test with with Qt as plugin or embedded QWindow. + + auto *platformItem = nativeItem.platformMenuItem; + if (!platformItem) + return NO; + + // Menu-holding items are always enabled, as it's conventional in Cocoa + if (platformItem->menu()) + return YES; + + // Check if a modal dialog is active. Validate only menu + // items belonging to this view's window own menu bar. + if (QGuiApplication::modalWindow()) { + QCocoaMenuBar *menubar = nullptr; + + QObject *menuParent = platformItem->menuParent(); + while (menuParent && !(menubar = qobject_cast<QCocoaMenuBar *>(menuParent))) { + auto *menuObject = dynamic_cast<QCocoaMenuObject *>(menuParent); + menuParent = menuObject->menuParent(); + } + + if (menubar && menubar->cocoaWindow() != self.platformWindow) + return NO; + } + + return platformItem->isEnabled(); +} + +- (BOOL)respondsToSelector:(SEL)selector +{ + // Not exactly true. Both copy: and selectAll: can work on non key views. + if (selectorIsCutCopyPaste(selector)) + return ([NSApp keyWindow] == self.window) && (self.window.firstResponder == self); + + return [super respondsToSelector:selector]; +} + +- (void)qt_itemFired:(QCocoaNSMenuItem *)item +{ + auto *appDelegate = [QCocoaApplicationDelegate sharedDelegate]; + [appDelegate qt_itemFired:item]; +} + +- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector +{ + if (selectorIsCutCopyPaste(selector)) { + NSMethodSignature *itemFiredSign = [super methodSignatureForSelector:@selector(qt_itemFired:)]; + return itemFiredSign; + } + + return [super methodSignatureForSelector:selector]; +} + +- (void)forwardInvocation:(NSInvocation *)invocation +{ + if (selectorIsCutCopyPaste(invocation.selector)) { + NSObject *sender; + [invocation getArgument:&sender atIndex:2]; + if (auto *nativeItem = qt_objc_cast<QCocoaNSMenuItem *>(sender)) { + [self qt_itemFired:nativeItem]; + return; + } + } + + [super forwardInvocation:invocation]; +} + +@end diff --git a/src/plugins/platforms/cocoa/qnsview_mouse.mm b/src/plugins/platforms/cocoa/qnsview_mouse.mm new file mode 100644 index 0000000000..468f26ffb4 --- /dev/null +++ b/src/plugins/platforms/cocoa/qnsview_mouse.mm @@ -0,0 +1,620 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +// This file is included from qnsview.mm, and only used to organize the code + +@implementation QT_MANGLE_NAMESPACE(QNSViewMouseMoveHelper) { + QNSView *view; +} + +- (instancetype)initWithView:(QNSView *)theView +{ + if ((self = [super init])) + view = theView; + + return self; +} + +- (void)mouseMoved:(NSEvent *)theEvent +{ + [view mouseMovedImpl:theEvent]; +} + +- (void)mouseEntered:(NSEvent *)theEvent +{ + [view mouseEnteredImpl:theEvent]; +} + +- (void)mouseExited:(NSEvent *)theEvent +{ + [view mouseExitedImpl:theEvent]; +} + +- (void)cursorUpdate:(NSEvent *)theEvent +{ + [view cursorUpdate:theEvent]; +} + +@end + +@implementation QT_MANGLE_NAMESPACE(QNSView) (MouseAPI) + +- (void)resetMouseButtons +{ + m_buttons = Qt::NoButton; + m_frameStrutButtons = Qt::NoButton; +} + +- (void)handleFrameStrutMouseEvent:(NSEvent *)theEvent +{ + if (!m_platformWindow) + return; + + // get m_buttons in sync + // Don't send frme strut events if we are in the middle of a mouse drag. + if (m_buttons != Qt::NoButton) + return; + + switch (theEvent.type) { + case NSEventTypeLeftMouseDown: + case NSEventTypeLeftMouseDragged: + m_frameStrutButtons |= Qt::LeftButton; + break; + case NSEventTypeLeftMouseUp: + m_frameStrutButtons &= ~Qt::LeftButton; + break; + case NSEventTypeRightMouseDown: + case NSEventTypeRightMouseDragged: + m_frameStrutButtons |= Qt::RightButton; + break; + case NSEventTypeRightMouseUp: + m_frameStrutButtons &= ~Qt::RightButton; + break; + case NSEventTypeOtherMouseDown: + m_frameStrutButtons |= cocoaButton2QtButton(theEvent.buttonNumber); + break; + case NSEventTypeOtherMouseUp: + m_frameStrutButtons &= ~cocoaButton2QtButton(theEvent.buttonNumber); + default: + break; + } + + NSWindow *window = [self window]; + NSPoint windowPoint = [theEvent locationInWindow]; + + int windowScreenY = [window frame].origin.y + [window frame].size.height; + NSPoint windowCoord = [self convertPoint:[self frame].origin toView:nil]; + int viewScreenY = [window convertRectToScreen:NSMakeRect(windowCoord.x, windowCoord.y, 0, 0)].origin.y; + int titleBarHeight = windowScreenY - viewScreenY; + + NSPoint nsViewPoint = [self convertPoint: windowPoint fromView: nil]; + QPoint qtWindowPoint = QPoint(nsViewPoint.x, titleBarHeight + nsViewPoint.y); + NSPoint screenPoint = [window convertRectToScreen:NSMakeRect(windowPoint.x, windowPoint.y, 0, 0)].origin; + QPoint qtScreenPoint = QCocoaScreen::mapFromNative(screenPoint).toPoint(); + + ulong timestamp = [theEvent timestamp] * 1000; + QWindowSystemInterface::handleFrameStrutMouseEvent(m_platformWindow->window(), timestamp, qtWindowPoint, qtScreenPoint, m_frameStrutButtons); +} +@end + +@implementation QT_MANGLE_NAMESPACE(QNSView) (Mouse) + +- (BOOL)acceptsFirstMouse:(NSEvent *)theEvent +{ + Q_UNUSED(theEvent) + if (!m_platformWindow) + return NO; + if ([self isTransparentForUserInput]) + return NO; + return YES; +} + +- (NSPoint)screenMousePoint:(NSEvent *)theEvent +{ + NSPoint screenPoint; + if (theEvent) { + NSPoint windowPoint = [theEvent locationInWindow]; + if (qIsNaN(windowPoint.x) || qIsNaN(windowPoint.y)) { + screenPoint = [NSEvent mouseLocation]; + } else { + NSRect screenRect = [[theEvent window] convertRectToScreen:NSMakeRect(windowPoint.x, windowPoint.y, 1, 1)]; + screenPoint = screenRect.origin; + } + } else { + screenPoint = [NSEvent mouseLocation]; + } + return screenPoint; +} + +- (void)handleMouseEvent:(NSEvent *)theEvent +{ + if (!m_platformWindow) + return; + +#ifndef QT_NO_TABLETEVENT + // Tablet events may come in via the mouse event handlers, + // check if this is a valid tablet event first. + if ([self handleTabletEvent: theEvent]) + return; +#endif + + QPointF qtWindowPoint; + QPointF qtScreenPoint; + QNSView *targetView = self; + if (!targetView.platformWindow) + return; + + // Popups implicitly grap mouse events; forward to the active popup if there is one + if (QCocoaWindow *popup = QCocoaIntegration::instance()->activePopupWindow()) { + // Tooltips must be transparent for mouse events + // The bug reference is QTBUG-46379 + if (!popup->window()->flags().testFlag(Qt::ToolTip)) { + if (QNSView *popupView = qnsview_cast(popup->view())) + targetView = popupView; + } + } + + [targetView convertFromScreen:[self screenMousePoint:theEvent] toWindowPoint:&qtWindowPoint andScreenPoint:&qtScreenPoint]; + ulong timestamp = [theEvent timestamp] * 1000; + + QCocoaDrag* nativeDrag = QCocoaIntegration::instance()->drag(); + nativeDrag->setLastMouseEvent(theEvent, self); + + const auto modifiers = [QNSView convertKeyModifiers:theEvent.modifierFlags]; + const auto buttons = currentlyPressedMouseButtons(); + const auto button = cocoaButton2QtButton(theEvent); + const auto eventType = cocoaEvent2QtMouseEvent(theEvent); + + QWindowSystemInterface::handleMouseEvent(targetView->m_platformWindow->window(), + timestamp, qtWindowPoint, qtScreenPoint, + buttons, button, eventType, modifiers); +} + +- (bool)handleMouseDownEvent:(NSEvent *)theEvent +{ + if ([self isTransparentForUserInput]) + return false; + + const auto button = cocoaButton2QtButton(theEvent); + + QPointF qtWindowPoint; + QPointF qtScreenPoint; + [self convertFromScreen:[self screenMousePoint:theEvent] toWindowPoint:&qtWindowPoint andScreenPoint:&qtScreenPoint]; + Q_UNUSED(qtScreenPoint); + + // Maintain masked state for the button for use by MouseDragged and MouseUp. + QRegion mask = m_platformWindow->window()->mask(); + const bool masked = !mask.isEmpty() && !mask.contains(qtWindowPoint.toPoint()); + if (masked) + m_acceptedMouseDowns &= ~button; + else + m_acceptedMouseDowns |= button; + + // Forward masked out events to the next responder + if (masked) + return false; + + m_buttons |= button; + + [self handleMouseEvent:theEvent]; + return true; +} + +- (bool)handleMouseDraggedEvent:(NSEvent *)theEvent +{ + if ([self isTransparentForUserInput]) + return false; + + const auto button = cocoaButton2QtButton(theEvent); + + // Forward the event to the next responder if Qt did not accept the + // corresponding mouse down for this button + if (!(m_acceptedMouseDowns & button) == button) + return false; + + [self handleMouseEvent:theEvent]; + return true; +} + +- (bool)handleMouseUpEvent:(NSEvent *)theEvent +{ + if ([self isTransparentForUserInput]) + return false; + + auto button = cocoaButton2QtButton(theEvent); + + // Forward the event to the next responder if Qt did not accept the + // corresponding mouse down for this button + if (!(m_acceptedMouseDowns & button) == button) + return false; + + if (m_sendUpAsRightButton && button == Qt::LeftButton) + button = Qt::RightButton; + if (button == Qt::RightButton) + m_sendUpAsRightButton = false; + + m_buttons &= ~button; + + [self handleMouseEvent:theEvent]; + return true; +} + +- (void)mouseDown:(NSEvent *)theEvent +{ + if ([self isTransparentForUserInput]) + return [super mouseDown:theEvent]; + m_sendUpAsRightButton = false; + + // Handle any active poup windows; clicking outisde them should close them + // all. Don't do anything or clicks inside one of the menus, let Cocoa + // handle that case. Note that in practice many windows of the Qt::Popup type + // will actually close themselves in this case using logic implemented in + // that particular poup type (for example context menus). However, Qt expects + // that plain popup QWindows will also be closed, so we implement the logic + // here as well. + QList<QCocoaWindow *> *popups = QCocoaIntegration::instance()->popupWindowStack(); + if (!popups->isEmpty()) { + // Check if the click is outside all popups. + bool inside = false; + QPointF qtScreenPoint = QCocoaScreen::mapFromNative([self screenMousePoint:theEvent]); + for (QList<QCocoaWindow *>::const_iterator it = popups->begin(); it != popups->end(); ++it) { + if ((*it)->geometry().contains(qtScreenPoint.toPoint())) { + inside = true; + break; + } + } + // Close the popups if the click was outside. + if (!inside) { + Qt::WindowType type = QCocoaIntegration::instance()->activePopupWindow()->window()->type(); + while (QCocoaWindow *popup = QCocoaIntegration::instance()->popPopupWindow()) { + QWindowSystemInterface::handleCloseEvent(popup->window()); + QWindowSystemInterface::flushWindowSystemEvents(); + } + // Consume the mouse event when closing the popup, except for tool tips + // were it's expected that the event is processed normally. + if (type != Qt::ToolTip) + return; + } + } + + QPointF qtWindowPoint; + QPointF qtScreenPoint; + [self convertFromScreen:[self screenMousePoint:theEvent] toWindowPoint:&qtWindowPoint andScreenPoint:&qtScreenPoint]; + Q_UNUSED(qtScreenPoint); + + QRegion mask = m_platformWindow->window()->mask(); + const bool masked = !mask.isEmpty() && !mask.contains(qtWindowPoint.toPoint()); + // Maintain masked state for the button for use by MouseDragged and Up. + if (masked) + m_acceptedMouseDowns &= ~Qt::LeftButton; + else + m_acceptedMouseDowns |= Qt::LeftButton; + + // Forward masked out events to the next responder + if (masked) { + [super mouseDown:theEvent]; + return; + } + + if ([self hasMarkedText]) { + [[NSTextInputContext currentInputContext] handleEvent:theEvent]; + } else { + auto ctrlOrMetaModifier = qApp->testAttribute(Qt::AA_MacDontSwapCtrlAndMeta) ? Qt::ControlModifier : Qt::MetaModifier; + if (!m_dontOverrideCtrlLMB && [QNSView convertKeyModifiers:[theEvent modifierFlags]] & ctrlOrMetaModifier) { + m_buttons |= Qt::RightButton; + m_sendUpAsRightButton = true; + } else { + m_buttons |= Qt::LeftButton; + } + [self handleMouseEvent:theEvent]; + } +} + +- (void)mouseDragged:(NSEvent *)theEvent +{ + const bool accepted = [self handleMouseDraggedEvent:theEvent]; + if (!accepted) + [super mouseDragged:theEvent]; +} + +- (void)mouseUp:(NSEvent *)theEvent +{ + const bool accepted = [self handleMouseUpEvent:theEvent]; + if (!accepted) + [super mouseUp:theEvent]; +} + +- (void)rightMouseDown:(NSEvent *)theEvent +{ + const bool accepted = [self handleMouseDownEvent:theEvent]; + if (!accepted) + [super rightMouseDown:theEvent]; +} + +- (void)rightMouseDragged:(NSEvent *)theEvent +{ + const bool accepted = [self handleMouseDraggedEvent:theEvent]; + if (!accepted) + [super rightMouseDragged:theEvent]; +} + +- (void)rightMouseUp:(NSEvent *)theEvent +{ + const bool accepted = [self handleMouseUpEvent:theEvent]; + if (!accepted) + [super rightMouseUp:theEvent]; +} + +- (void)otherMouseDown:(NSEvent *)theEvent +{ + const bool accepted = [self handleMouseDownEvent:theEvent]; + if (!accepted) + [super otherMouseDown:theEvent]; +} + +- (void)otherMouseDragged:(NSEvent *)theEvent +{ + const bool accepted = [self handleMouseDraggedEvent:theEvent]; + if (!accepted) + [super otherMouseDragged:theEvent]; +} + +- (void)otherMouseUp:(NSEvent *)theEvent +{ + const bool accepted = [self handleMouseUpEvent:theEvent]; + if (!accepted) + [super otherMouseUp:theEvent]; +} + +- (void)updateTrackingAreas +{ + [super updateTrackingAreas]; + + QMacAutoReleasePool pool; + + // NSTrackingInVisibleRect keeps care of updating once the tracking is set up, so bail out early + if (m_trackingArea && [[self trackingAreas] containsObject:m_trackingArea]) + return; + + // Ideally, we shouldn't have NSTrackingMouseMoved events included below, it should + // only be turned on if mouseTracking, hover is on or a tool tip is set. + // Unfortunately, Qt will send "tooltip" events on mouse moves, so we need to + // turn it on in ALL case. That means EVERY QWindow gets to pay the cost of + // mouse moves delivered to it (Apple recommends keeping it OFF because there + // is a performance hit). So it goes. + NSUInteger trackingOptions = NSTrackingMouseEnteredAndExited | NSTrackingActiveInActiveApp + | NSTrackingInVisibleRect | NSTrackingMouseMoved | NSTrackingCursorUpdate; + [m_trackingArea release]; + m_trackingArea = [[NSTrackingArea alloc] initWithRect:[self frame] + options:trackingOptions + owner:m_mouseMoveHelper + userInfo:nil]; + [self addTrackingArea:m_trackingArea]; +} + +- (void)cursorUpdate:(NSEvent *)theEvent +{ + qCDebug(lcQpaMouse) << "[QNSView cursorUpdate:]" << self.cursor; + + // Note: We do not get this callback when moving from a subview that + // uses the legacy cursorRect API, so the cursor is reset to the arrow + // cursor. See rdar://34183708 + + if (self.cursor) + [self.cursor set]; + else + [super cursorUpdate:theEvent]; +} + +- (void)mouseMovedImpl:(NSEvent *)theEvent +{ + if (!m_platformWindow) + return; + + if ([self isTransparentForUserInput]) + return; + + QPointF windowPoint; + QPointF screenPoint; + [self convertFromScreen:[self screenMousePoint:theEvent] toWindowPoint:&windowPoint andScreenPoint:&screenPoint]; + QWindow *childWindow = m_platformWindow->childWindowAt(windowPoint.toPoint()); + + // Top-level windows generate enter-leave events for sub-windows. + // Qt wants to know which window (if any) will be entered at the + // the time of the leave. This is dificult to accomplish by + // handling mouseEnter and mouseLeave envents, since they are sent + // individually to different views. + if (m_platformWindow->isContentView() && childWindow) { + if (childWindow != m_platformWindow->m_enterLeaveTargetWindow) { + QWindowSystemInterface::handleEnterLeaveEvent(childWindow, m_platformWindow->m_enterLeaveTargetWindow, windowPoint, screenPoint); + m_platformWindow->m_enterLeaveTargetWindow = childWindow; + } + } + + // Cocoa keeps firing mouse move events for obscured parent views. Qt should not + // send those events so filter them out here. + if (childWindow != m_platformWindow->window()) + return; + + [self handleMouseEvent: theEvent]; +} + +- (void)mouseEnteredImpl:(NSEvent *)theEvent +{ + Q_UNUSED(theEvent) + if (!m_platformWindow) + return; + + m_platformWindow->m_windowUnderMouse = true; + + if ([self isTransparentForUserInput]) + return; + + // Top-level windows generate enter events for sub-windows. + if (!m_platformWindow->isContentView()) + return; + + QPointF windowPoint; + QPointF screenPoint; + [self convertFromScreen:[self screenMousePoint:theEvent] toWindowPoint:&windowPoint andScreenPoint:&screenPoint]; + m_platformWindow->m_enterLeaveTargetWindow = m_platformWindow->childWindowAt(windowPoint.toPoint()); + QWindowSystemInterface::handleEnterEvent(m_platformWindow->m_enterLeaveTargetWindow, windowPoint, screenPoint); +} + +- (void)mouseExitedImpl:(NSEvent *)theEvent +{ + Q_UNUSED(theEvent); + if (!m_platformWindow) + return; + + m_platformWindow->m_windowUnderMouse = false; + + if ([self isTransparentForUserInput]) + return; + + // Top-level windows generate leave events for sub-windows. + if (!m_platformWindow->isContentView()) + return; + + QWindowSystemInterface::handleLeaveEvent(m_platformWindow->m_enterLeaveTargetWindow); + m_platformWindow->m_enterLeaveTargetWindow = 0; +} + +#if QT_CONFIG(wheelevent) +- (void)scrollWheel:(NSEvent *)theEvent +{ + if (!m_platformWindow) + return; + + if ([self isTransparentForUserInput]) + return [super scrollWheel:theEvent]; + + QPoint angleDelta; + Qt::MouseEventSource source = Qt::MouseEventNotSynthesized; + if ([theEvent hasPreciseScrollingDeltas]) { + // The mouse device contains pixel scroll wheel support (Mighty Mouse, Trackpad). + // Since deviceDelta is delivered as pixels rather than degrees, we need to + // convert from pixels to degrees in a sensible manner. + // It looks like 1/4 degrees per pixel behaves most native. + // (NB: Qt expects the unit for delta to be 8 per degree): + const int pixelsToDegrees = 2; // 8 * 1/4 + angleDelta.setX([theEvent scrollingDeltaX] * pixelsToDegrees); + angleDelta.setY([theEvent scrollingDeltaY] * pixelsToDegrees); + source = Qt::MouseEventSynthesizedBySystem; + } else { + // Remove acceleration, and use either -120 or 120 as delta: + angleDelta.setX(qBound(-120, int([theEvent deltaX] * 10000), 120)); + angleDelta.setY(qBound(-120, int([theEvent deltaY] * 10000), 120)); + } + + QPoint pixelDelta; + if ([theEvent hasPreciseScrollingDeltas]) { + pixelDelta.setX([theEvent scrollingDeltaX]); + pixelDelta.setY([theEvent scrollingDeltaY]); + } else { + // docs: "In the case of !hasPreciseScrollingDeltas, multiply the delta with the line width." + // scrollingDeltaX seems to return a minimum value of 0.1 in this case, map that to two pixels. + const CGFloat lineWithEstimate = 20.0; + pixelDelta.setX([theEvent scrollingDeltaX] * lineWithEstimate); + pixelDelta.setY([theEvent scrollingDeltaY] * lineWithEstimate); + } + + QPointF qt_windowPoint; + QPointF qt_screenPoint; + [self convertFromScreen:[self screenMousePoint:theEvent] toWindowPoint:&qt_windowPoint andScreenPoint:&qt_screenPoint]; + NSTimeInterval timestamp = [theEvent timestamp]; + ulong qt_timestamp = timestamp * 1000; + + Qt::ScrollPhase phase = Qt::NoScrollPhase; + if (theEvent.phase == NSEventPhaseMayBegin || theEvent.phase == NSEventPhaseBegan) { + // MayBegin is likely to happen. We treat it the same as an actual begin, + // and follow it with an update when the actual begin is delivered. + phase = m_scrolling ? Qt::ScrollUpdate : Qt::ScrollBegin; + m_scrolling = true; + } else if (theEvent.phase == NSEventPhaseStationary || theEvent.phase == NSEventPhaseChanged) { + phase = Qt::ScrollUpdate; + } else if (theEvent.phase == NSEventPhaseEnded) { + // A scroll event phase may be followed by a momentum phase after the user releases + // the finger, and in that case we don't want to send a Qt::ScrollEnd until after + // the momentum phase has ended. Unfortunately there isn't any guaranteed way of + // knowing whether or not a NSEventPhaseEnded will be followed by a momentum phase. + // The best we can do is to look at the event queue and hope that the system has + // had time to emit a momentum phase event. + if ([NSApp nextEventMatchingMask:NSEventMaskScrollWheel untilDate:[NSDate distantPast] + inMode:@"QtMomementumEventSearchMode" dequeue:NO].momentumPhase == NSEventPhaseBegan) { + Q_ASSERT(pixelDelta.isNull() && angleDelta.isNull()); + return; // Ignore this event, as it has a delta of 0,0 + } + phase = Qt::ScrollEnd; + m_scrolling = false; + } else if (theEvent.momentumPhase == NSEventPhaseBegan) { + Q_ASSERT(!pixelDelta.isNull() && !angleDelta.isNull()); + phase = Qt::ScrollUpdate; // Send as update, it has a delta + } else if (theEvent.momentumPhase == NSEventPhaseChanged) { + phase = Qt::ScrollMomentum; + } else if (theEvent.phase == NSEventPhaseCancelled + || theEvent.momentumPhase == NSEventPhaseEnded + || theEvent.momentumPhase == NSEventPhaseCancelled) { + phase = Qt::ScrollEnd; + m_scrolling = false; + } else { + Q_ASSERT(theEvent.momentumPhase != NSEventPhaseStationary); + } + + // Prevent keyboard modifier state from changing during scroll event streams. + // A two-finger trackpad flick generates a stream of scroll events. We want + // the keyboard modifier state to be the state at the beginning of the + // flick in order to avoid changing the interpretation of the events + // mid-stream. One example of this happening would be when pressing cmd + // after scrolling in Qt Creator: not taking the phase into account causes + // the end of the event stream to be interpreted as font size changes. + if (theEvent.momentumPhase == NSEventPhaseNone) + m_currentWheelModifiers = [QNSView convertKeyModifiers:[theEvent modifierFlags]]; + + // "isInverted": natural OS X scrolling, inverted from the Qt/other platform/Jens perspective. + bool isInverted = [theEvent isDirectionInvertedFromDevice]; + + qCDebug(lcQpaMouse) << "scroll wheel @ window pos" << qt_windowPoint << "delta px" << pixelDelta + << "angle" << angleDelta << "phase" << phase << (isInverted ? "inverted" : ""); + QWindowSystemInterface::handleWheelEvent(m_platformWindow->window(), qt_timestamp, qt_windowPoint, + qt_screenPoint, pixelDelta, angleDelta, m_currentWheelModifiers, phase, source, isInverted); +} +#endif // QT_CONFIG(wheelevent) + +@end diff --git a/src/plugins/platforms/cocoa/qnsview_tablet.mm b/src/plugins/platforms/cocoa/qnsview_tablet.mm new file mode 100644 index 0000000000..43b0aa0960 --- /dev/null +++ b/src/plugins/platforms/cocoa/qnsview_tablet.mm @@ -0,0 +1,223 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +// This file is included from qnsview.mm, and only used to organize the code + +#ifndef QT_NO_TABLETEVENT + +Q_LOGGING_CATEGORY(lcQpaTablet, "qt.qpa.input.tablet") + +struct QCocoaTabletDeviceData +{ + QTabletEvent::TabletDevice device; + QTabletEvent::PointerType pointerType; + uint capabilityMask; + qint64 uid; +}; + +typedef QHash<uint, QCocoaTabletDeviceData> QCocoaTabletDeviceDataHash; +Q_GLOBAL_STATIC(QCocoaTabletDeviceDataHash, tabletDeviceDataHash) + +@implementation QT_MANGLE_NAMESPACE(QNSView) (Tablet) + +- (bool)handleTabletEvent:(NSEvent *)theEvent +{ + static bool ignoreButtonMapping = qEnvironmentVariableIsSet("QT_MAC_TABLET_IGNORE_BUTTON_MAPPING"); + + if (!m_platformWindow) + return false; + + NSEventType eventType = [theEvent type]; + if (eventType != NSEventTypeTabletPoint && [theEvent subtype] != NSEventSubtypeTabletPoint) + return false; // Not a tablet event. + + ulong timestamp = [theEvent timestamp] * 1000; + + QPointF windowPoint; + QPointF screenPoint; + [self convertFromScreen:[self screenMousePoint:theEvent] toWindowPoint: &windowPoint andScreenPoint: &screenPoint]; + + uint deviceId = [theEvent deviceID]; + if (!tabletDeviceDataHash->contains(deviceId)) { + // Error: Unknown tablet device. Qt also gets into this state + // when running on a VM. This appears to be harmless; don't + // print a warning. + return false; + } + const QCocoaTabletDeviceData &deviceData = tabletDeviceDataHash->value(deviceId); + + bool down = (eventType != NSEventTypeMouseMoved); + + qreal pressure; + if (down) { + pressure = [theEvent pressure]; + } else { + pressure = 0.0; + } + + NSPoint tilt = [theEvent tilt]; + int xTilt = qRound(tilt.x * 60.0); + int yTilt = qRound(tilt.y * -60.0); + qreal tangentialPressure = 0; + qreal rotation = 0; + int z = 0; + if (deviceData.capabilityMask & 0x0200) + z = [theEvent absoluteZ]; + + if (deviceData.capabilityMask & 0x0800) + tangentialPressure = ([theEvent tangentialPressure] * 2.0) - 1.0; + + rotation = 360.0 - [theEvent rotation]; + if (rotation > 180.0) + rotation -= 360.0; + + Qt::KeyboardModifiers keyboardModifiers = [QNSView convertKeyModifiers:[theEvent modifierFlags]]; + Qt::MouseButtons buttons = ignoreButtonMapping ? static_cast<Qt::MouseButtons>(static_cast<uint>([theEvent buttonMask])) : m_buttons; + + qCDebug(lcQpaTablet, "event on tablet %d with tool %d type %d unique ID %lld pos %6.1f, %6.1f root pos %6.1f, %6.1f buttons 0x%x pressure %4.2lf tilt %d, %d rotation %6.2lf", + deviceId, deviceData.device, deviceData.pointerType, deviceData.uid, + windowPoint.x(), windowPoint.y(), screenPoint.x(), screenPoint.y(), + static_cast<uint>(buttons), pressure, xTilt, yTilt, rotation); + + QWindowSystemInterface::handleTabletEvent(m_platformWindow->window(), timestamp, windowPoint, screenPoint, + deviceData.device, deviceData.pointerType, buttons, pressure, xTilt, yTilt, + tangentialPressure, rotation, z, deviceData.uid, + keyboardModifiers); + return true; +} + +- (void)tabletPoint:(NSEvent *)theEvent +{ + if ([self isTransparentForUserInput]) + return [super tabletPoint:theEvent]; + + [self handleTabletEvent: theEvent]; +} + +static QTabletEvent::TabletDevice wacomTabletDevice(NSEvent *theEvent) +{ + qint64 uid = [theEvent uniqueID]; + uint bits = [theEvent vendorPointingDeviceType]; + if (bits == 0 && uid != 0) { + // Fallback. It seems that the driver doesn't always include all the information. + // High-End Wacom devices store their "type" in the uper bits of the Unique ID. + // I'm not sure how to handle it for consumer devices, but I'll test that in a bit. + bits = uid >> 32; + } + + QTabletEvent::TabletDevice device; + // Defined in the "EN0056-NxtGenImpGuideX" + // on Wacom's Developer Website (www.wacomeng.com) + if (((bits & 0x0006) == 0x0002) && ((bits & 0x0F06) != 0x0902)) { + device = QTabletEvent::Stylus; + } else { + switch (bits & 0x0F06) { + case 0x0802: + device = QTabletEvent::Stylus; + break; + case 0x0902: + device = QTabletEvent::Airbrush; + break; + case 0x0004: + device = QTabletEvent::FourDMouse; + break; + case 0x0006: + device = QTabletEvent::Puck; + break; + case 0x0804: + device = QTabletEvent::RotationStylus; + break; + default: + device = QTabletEvent::NoDevice; + } + } + return device; +} + +- (void)tabletProximity:(NSEvent *)theEvent +{ + if ([self isTransparentForUserInput]) + return [super tabletProximity:theEvent]; + + ulong timestamp = [theEvent timestamp] * 1000; + + QCocoaTabletDeviceData deviceData; + deviceData.uid = [theEvent uniqueID]; + deviceData.capabilityMask = [theEvent capabilityMask]; + + switch ([theEvent pointingDeviceType]) { + case NSPointingDeviceTypeUnknown: + default: + deviceData.pointerType = QTabletEvent::UnknownPointer; + break; + case NSPointingDeviceTypePen: + deviceData.pointerType = QTabletEvent::Pen; + break; + case NSPointingDeviceTypeCursor: + deviceData.pointerType = QTabletEvent::Cursor; + break; + case NSPointingDeviceTypeEraser: + deviceData.pointerType = QTabletEvent::Eraser; + break; + } + + deviceData.device = wacomTabletDevice(theEvent); + + // The deviceID is "unique" while in the proximity, it's a key that we can use for + // linking up QCocoaTabletDeviceData to an event (especially if there are two devices in action). + bool entering = [theEvent isEnteringProximity]; + uint deviceId = [theEvent deviceID]; + if (entering) { + tabletDeviceDataHash->insert(deviceId, deviceData); + } else { + tabletDeviceDataHash->remove(deviceId); + } + + qCDebug(lcQpaTablet, "proximity change on tablet %d: current tool %d type %d unique ID %lld", + deviceId, deviceData.device, deviceData.pointerType, deviceData.uid); + + if (entering) { + QWindowSystemInterface::handleTabletEnterProximityEvent(timestamp, deviceData.device, deviceData.pointerType, deviceData.uid); + } else { + QWindowSystemInterface::handleTabletLeaveProximityEvent(timestamp, deviceData.device, deviceData.pointerType, deviceData.uid); + } +} +@end + +#endif diff --git a/src/plugins/platforms/cocoa/qnsview_touch.mm b/src/plugins/platforms/cocoa/qnsview_touch.mm new file mode 100644 index 0000000000..e789213f70 --- /dev/null +++ b/src/plugins/platforms/cocoa/qnsview_touch.mm @@ -0,0 +1,100 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +// This file is included from qnsview.mm, and only used to organize the code + +Q_LOGGING_CATEGORY(lcQpaTouch, "qt.qpa.input.touch") + +@implementation QT_MANGLE_NAMESPACE(QNSView) (Touch) + +- (bool)shouldSendSingleTouch +{ + if (!m_platformWindow) + return true; + + // QtWidgets expects single-point touch events, QtDeclarative does not. + // Until there is an API we solve this by looking at the window class type. + return m_platformWindow->window()->inherits("QWidgetWindow"); +} + +- (void)touchesBeganWithEvent:(NSEvent *)event +{ + if (!m_platformWindow) + return; + + const NSTimeInterval timestamp = [event timestamp]; + const QList<QWindowSystemInterface::TouchPoint> points = QCocoaTouch::getCurrentTouchPointList(event, [self shouldSendSingleTouch]); + qCDebug(lcQpaTouch) << "touchesBeganWithEvent" << points << "from device" << hex << [event deviceID]; + QWindowSystemInterface::handleTouchEvent(m_platformWindow->window(), timestamp * 1000, QCocoaTouch::getTouchDevice(QTouchDevice::TouchPad, [event deviceID]), points); +} + +- (void)touchesMovedWithEvent:(NSEvent *)event +{ + if (!m_platformWindow) + return; + + const NSTimeInterval timestamp = [event timestamp]; + const QList<QWindowSystemInterface::TouchPoint> points = QCocoaTouch::getCurrentTouchPointList(event, [self shouldSendSingleTouch]); + qCDebug(lcQpaTouch) << "touchesMovedWithEvent" << points << "from device" << hex << [event deviceID]; + QWindowSystemInterface::handleTouchEvent(m_platformWindow->window(), timestamp * 1000, QCocoaTouch::getTouchDevice(QTouchDevice::TouchPad, [event deviceID]), points); +} + +- (void)touchesEndedWithEvent:(NSEvent *)event +{ + if (!m_platformWindow) + return; + + const NSTimeInterval timestamp = [event timestamp]; + const QList<QWindowSystemInterface::TouchPoint> points = QCocoaTouch::getCurrentTouchPointList(event, [self shouldSendSingleTouch]); + qCDebug(lcQpaTouch) << "touchesEndedWithEvent" << points << "from device" << hex << [event deviceID]; + QWindowSystemInterface::handleTouchEvent(m_platformWindow->window(), timestamp * 1000, QCocoaTouch::getTouchDevice(QTouchDevice::TouchPad, [event deviceID]), points); +} + +- (void)touchesCancelledWithEvent:(NSEvent *)event +{ + if (!m_platformWindow) + return; + + const NSTimeInterval timestamp = [event timestamp]; + const QList<QWindowSystemInterface::TouchPoint> points = QCocoaTouch::getCurrentTouchPointList(event, [self shouldSendSingleTouch]); + qCDebug(lcQpaTouch) << "touchesCancelledWithEvent" << points << "from device" << hex << [event deviceID]; + QWindowSystemInterface::handleTouchEvent(m_platformWindow->window(), timestamp * 1000, QCocoaTouch::getTouchDevice(QTouchDevice::TouchPad, [event deviceID]), points); +} + +@end diff --git a/src/plugins/platforms/cocoa/qnswindow.h b/src/plugins/platforms/cocoa/qnswindow.h index ea690b69e3..64f1ed0802 100644 --- a/src/plugins/platforms/cocoa/qnswindow.h +++ b/src/plugins/platforms/cocoa/qnswindow.h @@ -67,6 +67,7 @@ QT_FORWARD_DECLARE_CLASS(QCocoaWindow) - (void)dealloc; - (BOOL)isOpaque; - (NSColor *)backgroundColor; +- (NSString *)description; @property (nonatomic, readonly) QCocoaWindow *platformWindow; @end diff --git a/src/plugins/platforms/cocoa/qnswindow.mm b/src/plugins/platforms/cocoa/qnswindow.mm index cb13b7d184..1b9dd95cbc 100644 --- a/src/plugins/platforms/cocoa/qnswindow.mm +++ b/src/plugins/platforms/cocoa/qnswindow.mm @@ -38,7 +38,6 @@ ****************************************************************************/ #include "qnswindow.h" -#include "qnswindowdelegate.h" #include "qcocoawindow.h" #include "qcocoahelpers.h" #include "qcocoaeventdispatcher.h" @@ -46,18 +45,18 @@ #include <qpa/qwindowsysteminterface.h> #include <qoperatingsystemversion.h> -Q_LOGGING_CATEGORY(lcCocoaEvents, "qt.qpa.cocoa.events"); +Q_LOGGING_CATEGORY(lcQpaEvents, "qt.qpa.events"); static bool isMouseEvent(NSEvent *ev) { switch ([ev type]) { - case NSLeftMouseDown: - case NSLeftMouseUp: - case NSRightMouseDown: - case NSRightMouseUp: - case NSMouseMoved: - case NSLeftMouseDragged: - case NSRightMouseDragged: + case NSEventTypeLeftMouseDown: + case NSEventTypeLeftMouseUp: + case NSEventTypeRightMouseDown: + case NSEventTypeRightMouseUp: + case NSEventTypeMouseMoved: + case NSEventTypeLeftMouseDragged: + case NSEventTypeRightMouseDragged: return true; default: return false; @@ -72,7 +71,7 @@ static bool isMouseEvent(NSEvent *ev) [center addObserverForName:NSWindowDidEnterFullScreenNotification object:nil queue:nil usingBlock:^(NSNotification *notification) { objc_setAssociatedObject(notification.object, @selector(qt_fullScreen), - [NSNumber numberWithBool:YES], OBJC_ASSOCIATION_RETAIN); + @(YES), OBJC_ASSOCIATION_RETAIN); } ]; [center addObserverForName:NSWindowDidExitFullScreenNotification object:nil queue:nil @@ -187,22 +186,22 @@ static bool isMouseEvent(NSEvent *ev) /*! Borderless windows need a transparent background - Technically windows with NSTexturedBackgroundWindowMask (such - as windows with unified toolbars) need to draw the textured + Technically windows with NSWindowStyleMaskTexturedBackground + (such as windows with unified toolbars) need to draw the textured background of the NSWindow, and can't have a transparent - background, but as NSBorderlessWindowMask is 0, you can't - have a window with NSTexturedBackgroundWindowMask that is + background, but as NSWindowStyleMaskBorderless is 0, you can't + have a window with NSWindowStyleMaskTexturedBackground that is also borderless. */ - (NSColor *)backgroundColor { - return self.styleMask == NSBorderlessWindowMask + return self.styleMask == NSWindowStyleMaskBorderless ? [NSColor clearColor] : qt_objcDynamicSuper(); } - (void)sendEvent:(NSEvent*)theEvent { - qCDebug(lcCocoaEvents) << "Sending" << theEvent << "to" << self; + qCDebug(lcQpaEvents) << "Sending" << theEvent << "to" << self; // We might get events for a NSWindow after the corresponding platform // window has been deleted, as the NSWindow can outlive the QCocoaWindow @@ -239,7 +238,7 @@ static bool isMouseEvent(NSEvent *ev) - (void)closeAndRelease { - qCDebug(lcQpaCocoaWindow) << "closeAndRelease" << self; + qCDebug(lcQpaWindow) << "closeAndRelease" << self; [self.delegate release]; self.delegate = nil; @@ -252,7 +251,7 @@ static bool isMouseEvent(NSEvent *ev) #pragma clang diagnostic ignored "-Wobjc-missing-super-calls" - (void)dealloc { - qCDebug(lcQpaCocoaWindow) << "dealloc" << self; + qCDebug(lcQpaWindow) << "dealloc" << self; qt_objcDynamicSuper(); } #pragma clang diagnostic pop @@ -263,29 +262,20 @@ static bool isMouseEvent(NSEvent *ev) NSEnumerator<NSWindow*> *windowEnumerator = nullptr; NSApplication *application = [NSApplication sharedApplication]; -#if QT_MACOS_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_12) - if (__builtin_available(macOS 10.12, *)) { - // Unfortunately there's no NSWindowListOrderedBackToFront, - // so we have to manually reverse the order using an array. - NSMutableArray *windows = [[[NSMutableArray alloc] init] autorelease]; - [application enumerateWindowsWithOptions:NSWindowListOrderedFrontToBack - usingBlock:^(NSWindow *window, BOOL *) { - // For some reason AppKit will give us nil-windows, skip those - if (!window) - return; - - [(NSMutableArray*)windows addObject:window]; - } - ]; - - windowEnumerator = windows.reverseObjectEnumerator; - } else -#endif - { - // No way to get ordered list of windows, so fall back to unordered, - // list, which typically corresponds to window creation order. - windowEnumerator = application.windows.objectEnumerator; - } + // Unfortunately there's no NSWindowListOrderedBackToFront, + // so we have to manually reverse the order using an array. + NSMutableArray<NSWindow *> *windows = [NSMutableArray<NSWindow *> new]; + [application enumerateWindowsWithOptions:NSWindowListOrderedFrontToBack + usingBlock:^(NSWindow *window, BOOL *) { + // For some reason AppKit will give us nil-windows, skip those + if (!window) + return; + + [windows addObject:window]; + } + ]; + + windowEnumerator = windows.reverseObjectEnumerator; for (NSWindow *window in windowEnumerator) { // We're meddling with normal and floating windows, so leave others alone diff --git a/src/plugins/platforms/cocoa/qnswindowdelegate.h b/src/plugins/platforms/cocoa/qnswindowdelegate.h index d2078b5786..e71afcbb2a 100644 --- a/src/plugins/platforms/cocoa/qnswindowdelegate.h +++ b/src/plugins/platforms/cocoa/qnswindowdelegate.h @@ -41,20 +41,16 @@ #define QNSWINDOWDELEGATE_H #include <AppKit/AppKit.h> +#include <QtCore/private/qcore_mac_p.h> -#include "qcocoawindow.h" +QT_BEGIN_NAMESPACE +class QCocoaWindow; +QT_END_NAMESPACE @interface QT_MANGLE_NAMESPACE(QNSWindowDelegate) : NSObject <NSWindowDelegate> -{ - QCocoaWindow *m_cocoaWindow; -} -- (id)initWithQCocoaWindow:(QCocoaWindow *)cocoaWindow; +- (instancetype)initWithQCocoaWindow:(QT_PREPEND_NAMESPACE(QCocoaWindow) *)cocoaWindow; -- (BOOL)windowShouldClose:(NSNotification *)notification; - -- (BOOL)window:(NSWindow *)window shouldPopUpDocumentPathMenu:(NSMenu *)menu; -- (BOOL)window:(NSWindow *)window shouldDragDocumentWithEvent:(NSEvent *)event from:(NSPoint)dragImageLocation withPasteboard:(NSPasteboard *)pasteboard; @end QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSWindowDelegate); diff --git a/src/plugins/platforms/cocoa/qnswindowdelegate.mm b/src/plugins/platforms/cocoa/qnswindowdelegate.mm index 057a4c2943..97309ea990 100644 --- a/src/plugins/platforms/cocoa/qnswindowdelegate.mm +++ b/src/plugins/platforms/cocoa/qnswindowdelegate.mm @@ -39,21 +39,24 @@ #include "qnswindowdelegate.h" #include "qcocoahelpers.h" +#include "qcocoawindow.h" #include "qcocoascreen.h" #include <QDebug> +#include <QtCore/private/qcore_mac_p.h> #include <qpa/qplatformscreen.h> #include <qpa/qwindowsysteminterface.h> static QRegExp whitespaceRegex = QRegExp(QStringLiteral("\\s*")); -@implementation QNSWindowDelegate +@implementation QNSWindowDelegate { + QCocoaWindow *m_cocoaWindow; +} -- (id)initWithQCocoaWindow:(QCocoaWindow *)cocoaWindow +- (instancetype)initWithQCocoaWindow:(QCocoaWindow *)cocoaWindow { - if (self = [super init]) + if ((self = [self init])) m_cocoaWindow = cocoaWindow; - return self; } @@ -103,6 +106,36 @@ static QRegExp whitespaceRegex = QRegExp(QStringLiteral("\\s*")); return QCocoaScreen::mapToNative(maximizedFrame); } +#pragma clang diagnostic push +// NSDisableScreenUpdates and NSEnableScreenUpdates are deprecated, but the +// NSAnimationContext API that replaces them doesn't handle the use-case of +// cross-thread screen update synchronization. +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +- (NSSize)windowWillResize:(NSWindow *)window toSize:(NSSize)frameSize +{ + qCDebug(lcQpaWindow) << window << "will resize to" << QSizeF::fromCGSize(frameSize) + << "- disabling screen updates temporarily"; + + // There may be separate threads rendering to CA layers in this window, + // and if any of them do a swap while the resize is still in progress, + // the visual bounds of that layer will be updated before the visual + // bounds of the window frame, resulting in flickering while resizing. + + // To prevent this we disable screen updates for the whole process until + // the resize is complete, which makes the whole thing visually atomic. + NSDisableScreenUpdates(); + + return frameSize; +} + +- (void)windowDidResize:(NSNotification *)notification +{ + NSWindow *window = notification.object; + qCDebug(lcQpaWindow) << window << "was resized - re-enabling screen updates"; + NSEnableScreenUpdates(); +} +#pragma clang diagnostic pop + - (BOOL)window:(NSWindow *)window shouldPopUpDocumentPathMenu:(NSMenu *)menu { Q_UNUSED(window); diff --git a/src/plugins/platforms/cocoa/qpaintengine_mac.mm b/src/plugins/platforms/cocoa/qpaintengine_mac.mm index 3f363b62d5..96506c67fa 100644 --- a/src/plugins/platforms/cocoa/qpaintengine_mac.mm +++ b/src/plugins/platforms/cocoa/qpaintengine_mac.mm @@ -98,8 +98,8 @@ CGImageRef qt_mac_create_imagemask(const QPixmap &pixmap, const QRectF &sr) for (int x = sx; x < sw; ++x) *(dptr+(offset++)) = (*(srow+x) & mask) ? 255 : 0; } - QCFType<CGDataProviderRef> provider = CGDataProviderCreateWithData(0, dptr, nbytes, qt_mac_cgimage_data_free); - return CGImageMaskCreate(sw, sh, 8, 8, nbytes / sh, provider, 0, 0); + QCFType<CGDataProviderRef> provider = CGDataProviderCreateWithData(nullptr, dptr, nbytes, qt_mac_cgimage_data_free); + return CGImageMaskCreate(sw, sh, 8, 8, nbytes / sh, provider, nullptr, false); } //conversion @@ -287,16 +287,16 @@ static void qt_mac_draw_pattern(void *info, CGContextRef c) Q_ASSERT(pat->data.bytes); w = h = 8; #if (QMACPATTERN_MASK_MULTIPLIER == 1) - CGDataProviderRef provider = CGDataProviderCreateWithData(0, pat->data.bytes, w*h, 0); - pat->image = CGImageMaskCreate(w, h, 1, 1, 1, provider, 0, false); + CGDataProviderRef provider = CGDataProviderCreateWithData(nullptr, pat->data.bytes, w*h, nullptr); + pat->image = CGImageMaskCreate(w, h, 1, 1, 1, provider, nullptr, false); CGDataProviderRelease(provider); #else const int numBytes = (w*h)/sizeof(uchar); uchar xor_bytes[numBytes]; for (int i = 0; i < numBytes; ++i) xor_bytes[i] = pat->data.bytes[i] ^ 0xFF; - CGDataProviderRef provider = CGDataProviderCreateWithData(0, xor_bytes, w*h, 0); - CGImageRef swatch = CGImageMaskCreate(w, h, 1, 1, 1, provider, 0, false); + CGDataProviderRef provider = CGDataProviderCreateWithData(nullptr, xor_bytes, w*h, nullptr); + CGImageRef swatch = CGImageMaskCreate(w, h, 1, 1, 1, provider, nullptr, false); CGDataProviderRelease(provider); const QColor c0(0, 0, 0, 0), c1(255, 255, 255, 255); @@ -399,9 +399,9 @@ QCoreGraphicsPaintEngine::begin(QPaintDevice *pdev) d->orig_xform = CGContextGetCTM(d->hd); if (d->shading) { CGShadingRelease(d->shading); - d->shading = 0; + d->shading = nullptr; } - d->setClip(0); //clear the context's clipping + d->setClip(nullptr); //clear the context's clipping } setActive(true); @@ -445,12 +445,12 @@ QCoreGraphicsPaintEngine::end() CGShadingRelease(d->shading); d->shading = 0; } - d->pdev = 0; + d->pdev = nullptr; if (d->hd) { d->restoreGraphicsState(); CGContextSynchronize(d->hd); CGContextRelease(d->hd); - d->hd = 0; + d->hd = nullptr; } return true; } @@ -545,7 +545,7 @@ QCoreGraphicsPaintEngine::updateBrush(const QBrush &brush, const QPointF &brushO if (d->shading) { CGShadingRelease(d->shading); - d->shading = 0; + d->shading = nullptr; } d->setFillBrush(brushOrigin); } @@ -592,7 +592,7 @@ QCoreGraphicsPaintEngine::updateClipPath(const QPainterPath &p, Qt::ClipOperatio if (d->current.clipEnabled) { d->current.clipEnabled = false; d->current.clip = QRegion(); - d->setClip(0); + d->setClip(nullptr); } } else { if (!d->current.clipEnabled) @@ -601,7 +601,7 @@ QCoreGraphicsPaintEngine::updateClipPath(const QPainterPath &p, Qt::ClipOperatio QRegion clipRegion(p.toFillPolygon().toPolygon(), p.fillRule()); if (op == Qt::ReplaceClip) { d->current.clip = clipRegion; - d->setClip(0); + d->setClip(nullptr); if (p.isEmpty()) { CGRect rect = CGRectMake(0, 0, 0, 0); CGContextClipToRect(d->hd, rect); @@ -630,7 +630,7 @@ QCoreGraphicsPaintEngine::updateClipRegion(const QRegion &clipRegion, Qt::ClipOp if (op == Qt::NoClip) { d->current.clipEnabled = false; d->current.clip = QRegion(); - d->setClip(0); + d->setClip(nullptr); } else { if (!d->current.clipEnabled) op = Qt::ReplaceClip; @@ -676,7 +676,7 @@ QCoreGraphicsPaintEngine::drawRects(const QRectF *rects, int rectCount) QRectF r = rects[i]; CGMutablePathRef path = CGPathCreateMutable(); - CGPathAddRect(path, 0, qt_mac_compose_rect(r)); + CGPathAddRect(path, nullptr, qt_mac_compose_rect(r)); d->drawPath(QCoreGraphicsPaintEnginePrivate::CGFill|QCoreGraphicsPaintEnginePrivate::CGStroke, path); CGPathRelease(path); @@ -698,8 +698,8 @@ QCoreGraphicsPaintEngine::drawPoints(const QPointF *points, int pointCount) CGMutablePathRef path = CGPathCreateMutable(); for (int i=0; i < pointCount; i++) { float x = points[i].x(), y = points[i].y(); - CGPathMoveToPoint(path, 0, x, y); - CGPathAddLineToPoint(path, 0, x+0.001, y); + CGPathMoveToPoint(path, nullptr, x, y); + CGPathAddLineToPoint(path, nullptr, x+0.001, y); } bool doRestore = false; @@ -745,11 +745,11 @@ QCoreGraphicsPaintEngine::drawPolygon(const QPointF *points, int pointCount, Pol return; CGMutablePathRef path = CGPathCreateMutable(); - CGPathMoveToPoint(path, 0, points[0].x(), points[0].y()); + CGPathMoveToPoint(path, nullptr, points[0].x(), points[0].y()); for (int x = 1; x < pointCount; ++x) - CGPathAddLineToPoint(path, 0, points[x].x(), points[x].y()); + CGPathAddLineToPoint(path, nullptr, points[x].x(), points[x].y()); if (mode != PolylineMode && points[0] != points[pointCount-1]) - CGPathAddLineToPoint(path, 0, points[0].x(), points[0].y()); + CGPathAddLineToPoint(path, nullptr, points[0].x(), points[0].y()); uint op = QCoreGraphicsPaintEnginePrivate::CGStroke; if (mode != PolylineMode) op |= mode == OddEvenMode ? QCoreGraphicsPaintEnginePrivate::CGEOFill @@ -770,8 +770,8 @@ QCoreGraphicsPaintEngine::drawLines(const QLineF *lines, int lineCount) CGMutablePathRef path = CGPathCreateMutable(); for (int i = 0; i < lineCount; i++) { const QPointF start = lines[i].p1(), end = lines[i].p2(); - CGPathMoveToPoint(path, 0, start.x(), start.y()); - CGPathAddLineToPoint(path, 0, end.x(), end.y()); + CGPathMoveToPoint(path, nullptr, start.x(), start.y()); + CGPathAddLineToPoint(path, nullptr, end.x(), end.y()); } d->drawPath(QCoreGraphicsPaintEnginePrivate::CGStroke, path); CGPathRelease(path); @@ -870,7 +870,7 @@ QCoreGraphicsPaintEngine::drawTiledPixmap(const QRectF &r, const QPixmap &pixmap CGPatternRef pat = CGPatternCreate(qpattern, CGRectMake(0, 0, width, height), trans, width, height, kCGPatternTilingNoDistortion, true, &callbks); - CGColorSpaceRef cs = CGColorSpaceCreatePattern(0); + CGColorSpaceRef cs = CGColorSpaceCreatePattern(nullptr); CGContextSetFillColorSpace(d->hd, cs); CGFloat component = 1.0; //just one CGContextSetFillPattern(d->hd, pat, &component); @@ -1198,9 +1198,9 @@ void QCoreGraphicsPaintEnginePrivate::setFillBrush(const QPointF &offset) Q_ASSERT(grad->spread() == QGradient::PadSpread); static const CGFloat domain[] = { 0.0f, +1.0f }; - static const CGFunctionCallbacks callbacks = { 0, qt_mac_color_gradient_function, 0 }; + static const CGFunctionCallbacks callbacks = { 0, qt_mac_color_gradient_function, nullptr }; CGFunctionRef fill_func = CGFunctionCreate(reinterpret_cast<void *>(¤t.brush), - 1, domain, 4, 0, &callbacks); + 1, domain, 4, nullptr, &callbacks); CGColorSpaceRef colorspace = CGColorSpaceCreateWithName(kCGColorSpaceSRGB) if (bs == Qt::LinearGradientPattern) { @@ -1233,7 +1233,7 @@ void QCoreGraphicsPaintEnginePrivate::setFillBrush(const QPointF &offset) QMacPattern *qpattern = new QMacPattern; qpattern->pdev = pdev; CGFloat components[4] = { 1.0, 1.0, 1.0, 1.0 }; - CGColorSpaceRef base_colorspace = 0; + CGColorSpaceRef base_colorspace = nullptr; if (bs == Qt::TexturePattern) { qpattern->data.pixmap = current.brush.texture(); if (qpattern->data.pixmap.isQBitmap()) { @@ -1291,7 +1291,7 @@ QCoreGraphicsPaintEnginePrivate::setClip(const QRegion *rgn) if (!sysClip.isEmpty()) qt_mac_clip_cg(hd, sysClip, &orig_xform); if (rgn) - qt_mac_clip_cg(hd, *rgn, 0); + qt_mac_clip_cg(hd, *rgn, nullptr); } } @@ -1404,7 +1404,7 @@ void QCoreGraphicsPaintEnginePrivate::drawPath(uchar ops, CGMutablePathRef path) t.transform = qt_mac_convert_transform_to_cg(current.transform); t.path = CGPathCreateMutable(); CGPathApply(path, &t, qt_mac_cg_transform_path_apply); //transform the path - setTransform(0); //unset the context transform + setTransform(nullptr); //unset the context transform CGContextSetLineWidth(hd, cosmeticPenSize); CGContextAddPath(hd, t.path); CGPathRelease(t.path); diff --git a/src/plugins/platforms/cocoa/qpaintengine_mac_p.h b/src/plugins/platforms/cocoa/qpaintengine_mac_p.h index c9519ac827..1416386745 100644 --- a/src/plugins/platforms/cocoa/qpaintengine_mac_p.h +++ b/src/plugins/platforms/cocoa/qpaintengine_mac_p.h @@ -135,7 +135,7 @@ class QCoreGraphicsPaintEnginePrivate : public QPaintEnginePrivate Q_DECLARE_PUBLIC(QCoreGraphicsPaintEngine) public: QCoreGraphicsPaintEnginePrivate() - : hd(0), shading(0), stackCount(0), complexXForm(false), disabledSmoothFonts(false) + : hd(nullptr), shading(nullptr), stackCount(0), complexXForm(false), disabledSmoothFonts(false) { } @@ -164,8 +164,8 @@ public: //internal functions enum { CGStroke=0x01, CGEOFill=0x02, CGFill=0x04 }; - void drawPath(uchar ops, CGMutablePathRef path = 0); - void setClip(const QRegion *rgn=0); + void drawPath(uchar ops, CGMutablePathRef path = nullptr); + void setClip(const QRegion *rgn = nullptr); void resetClip(); void setFillBrush(const QPointF &origin=QPoint()); void setStrokePen(const QPen &pen); @@ -174,7 +174,7 @@ public: float penOffset(); QPointF devicePixelSize(CGContextRef context); float adjustPenWidth(float penWidth); - inline void setTransform(const QTransform *matrix=0) + inline void setTransform(const QTransform *matrix = nullptr) { CGContextConcatCTM(hd, CGAffineTransformInvert(CGContextGetCTM(hd))); CGAffineTransform xform = orig_xform; diff --git a/src/plugins/platforms/cocoa/qprintengine_mac.mm b/src/plugins/platforms/cocoa/qprintengine_mac.mm index eade407500..f2f29b26ee 100644 --- a/src/plugins/platforms/cocoa/qprintengine_mac.mm +++ b/src/plugins/platforms/cocoa/qprintengine_mac.mm @@ -117,7 +117,7 @@ bool QMacPrintEngine::end() if (d->paintEngine->type() == QPaintEngine::CoreGraphics) { // We don't need the paint engine to call restoreGraphicsState() static_cast<QCoreGraphicsPaintEngine*>(d->paintEngine)->d_func()->stackCount = 0; - static_cast<QCoreGraphicsPaintEngine*>(d->paintEngine)->d_func()->hd = 0; + static_cast<QCoreGraphicsPaintEngine*>(d->paintEngine)->d_func()->hd = nullptr; } d->paintEngine->end(); if (d->state != QPrinter::Idle) @@ -271,7 +271,7 @@ void QMacPrintEnginePrivate::releaseSession() PMSessionEndPageNoDialog(session()); PMSessionEndDocumentNoDialog(session()); [printInfo release]; - printInfo = 0; + printInfo = nil; } bool QMacPrintEnginePrivate::newPage_helper() @@ -291,7 +291,7 @@ bool QMacPrintEnginePrivate::newPage_helper() while (cgEngine->d_func()->stackCount > 0) cgEngine->d_func()->restoreGraphicsState(); - OSStatus status = PMSessionBeginPageNoDialog(session(), format(), 0); + OSStatus status = PMSessionBeginPageNoDialog(session(), format(), nullptr); if (status != noErr) { state = QPrinter::Error; return false; @@ -318,7 +318,7 @@ bool QMacPrintEnginePrivate::newPage_helper() if (m_pageLayout.mode() != QPageLayout::FullPageMode) CGContextTranslateCTM(cgContext, page.x() - paper.x(), page.y() - paper.y()); cgEngine->d_func()->orig_xform = CGContextGetCTM(cgContext); - cgEngine->d_func()->setClip(0); + cgEngine->d_func()->setClip(nullptr); cgEngine->state->dirtyFlags = QPaintEngine::DirtyFlag(QPaintEngine::AllDirty & ~(QPaintEngine::DirtyClipEnabled | QPaintEngine::DirtyClipRegion @@ -340,7 +340,7 @@ void QMacPrintEnginePrivate::setPageSize(const QPageSize &pageSize) // Get the PMPaper and check it is valid PMPaper macPaper = m_printDevice->macPaper(usePageSize); - if (macPaper == 0) { + if (!macPaper) { qWarning() << "QMacPrintEngine: Invalid PMPaper returned for " << pageSize; return; } diff --git a/src/plugins/platforms/cocoa/qprintengine_mac_p.h b/src/plugins/platforms/cocoa/qprintengine_mac_p.h index 9514f3e691..3d94227ae4 100644 --- a/src/plugins/platforms/cocoa/qprintengine_mac_p.h +++ b/src/plugins/platforms/cocoa/qprintengine_mac_p.h @@ -134,7 +134,7 @@ public: QMacPrintEnginePrivate() : mode(QPrinter::ScreenResolution), state(QPrinter::Idle), m_pageLayout(QPageLayout(QPageSize(QPageSize::A4), QPageLayout::Portrait, QMarginsF(0, 0, 0, 0))), - printInfo(0), paintEngine(0), embedFonts(true) {} + printInfo(nullptr), paintEngine(nullptr), embedFonts(true) {} ~QMacPrintEnginePrivate(); void initialize(); |