From 3d8a70c0459192d57088ee51d2b1f0cc52d2e5f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tor=20Arne=20Vestb=C3=B8?= Date: Tue, 20 Jun 2017 15:02:37 +0200 Subject: macOS: Move QNSWindow implementation into separate file MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Change-Id: Ie75419c7ffb7a8fdf78d53e1ea14afee63ef1656 Reviewed-by: Morten Johan Sørvig --- src/plugins/platforms/cocoa/cocoa.pro | 2 + src/plugins/platforms/cocoa/qcocoawindow.h | 71 +---- src/plugins/platforms/cocoa/qcocoawindow.mm | 384 +------------------------ src/plugins/platforms/cocoa/qnswindow.h | 124 +++++++++ src/plugins/platforms/cocoa/qnswindow.mm | 418 ++++++++++++++++++++++++++++ 5 files changed, 546 insertions(+), 453 deletions(-) create mode 100644 src/plugins/platforms/cocoa/qnswindow.h create mode 100644 src/plugins/platforms/cocoa/qnswindow.mm diff --git a/src/plugins/platforms/cocoa/cocoa.pro b/src/plugins/platforms/cocoa/cocoa.pro index 62935210be..4d05fb4a46 100644 --- a/src/plugins/platforms/cocoa/cocoa.pro +++ b/src/plugins/platforms/cocoa/cocoa.pro @@ -6,6 +6,7 @@ OBJECTIVE_SOURCES += main.mm \ qcocoabackingstore.mm \ qcocoawindow.mm \ qnsview.mm \ + qnswindow.mm \ qnsviewaccessibility.mm \ qnswindowdelegate.mm \ qcocoanativeinterface.mm \ @@ -42,6 +43,7 @@ HEADERS += qcocoaintegration.h \ qcocoabackingstore.h \ qcocoawindow.h \ qnsview.h \ + qnswindow.h \ qnswindowdelegate.h \ qcocoanativeinterface.h \ qcocoaeventdispatcher.h \ diff --git a/src/plugins/platforms/cocoa/qcocoawindow.h b/src/plugins/platforms/cocoa/qcocoawindow.h index 0833aa785f..f897df4f8a 100644 --- a/src/plugins/platforms/cocoa/qcocoawindow.h +++ b/src/plugins/platforms/cocoa/qcocoawindow.h @@ -50,78 +50,9 @@ #include "qcocoaglcontext.h" #endif #include "qnsview.h" +#include "qnswindow.h" #include "qt_mac_p.h" -QT_FORWARD_DECLARE_CLASS(QCocoaWindow) - -@class QT_MANGLE_NAMESPACE(QNSWindowHelper); - -@protocol QNSWindowProtocol - -@property (nonatomic, readonly) QT_MANGLE_NAMESPACE(QNSWindowHelper) *helper; - -- (void)superSendEvent:(NSEvent *)theEvent; -- (void)closeAndRelease; - -@end - -typedef NSWindow QCocoaNSWindow; - -@interface QT_MANGLE_NAMESPACE(QNSWindowHelper) : NSObject -{ - QCocoaNSWindow *_window; - QPointer _platformWindow; - BOOL _grabbingMouse; - BOOL _releaseOnMouseUp; -} - -@property (nonatomic, readonly) QCocoaNSWindow *window; -@property (nonatomic, readonly) QCocoaWindow *platformWindow; -@property (nonatomic) BOOL grabbingMouse; -@property (nonatomic) BOOL releaseOnMouseUp; - -- (id)initWithNSWindow:(QCocoaNSWindow *)window platformWindow:(QCocoaWindow *)platformWindow; -- (void)handleWindowEvent:(NSEvent *)theEvent; -- (void) clearWindow; - -@end - -QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSWindowHelper); - -@interface QT_MANGLE_NAMESPACE(QNSWindow) : NSWindow -{ - QNSWindowHelper *_helper; -} - -@property (nonatomic, readonly) QNSWindowHelper *helper; - -- (id)initWithContentRect:(NSRect)contentRect - screen:(NSScreen*)screen - styleMask:(NSUInteger)windowStyle - qPlatformWindow:(QCocoaWindow *)qpw; - -@end - -QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSWindow); - -@interface QT_MANGLE_NAMESPACE(QNSPanel) : NSPanel -{ - QNSWindowHelper *_helper; -} - -@property (nonatomic, readonly) QNSWindowHelper *helper; - -- (id)initWithContentRect:(NSRect)contentRect - screen:(NSScreen*)screen - styleMask:(NSUInteger)windowStyle - qPlatformWindow:(QCocoaWindow *)qpw; - -@end - -QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSPanel); - -@class QT_MANGLE_NAMESPACE(QNSWindowDelegate); - QT_BEGIN_NAMESPACE // QCocoaWindow // diff --git a/src/plugins/platforms/cocoa/qcocoawindow.mm b/src/plugins/platforms/cocoa/qcocoawindow.mm index 2cdcb4b503..e6ef443701 100644 --- a/src/plugins/platforms/cocoa/qcocoawindow.mm +++ b/src/plugins/platforms/cocoa/qcocoawindow.mm @@ -46,6 +46,7 @@ #include "qcocoahelpers.h" #include "qcocoanativeinterface.h" #include "qnsview.h" +#include "qnswindow.h" #include #include #include @@ -65,22 +66,6 @@ enum { defaultWindowHeight = 160 }; -static bool isMouseEvent(NSEvent *ev) -{ - switch ([ev type]) { - case NSLeftMouseDown: - case NSLeftMouseUp: - case NSRightMouseDown: - case NSRightMouseUp: - case NSMouseMoved: - case NSLeftMouseDragged: - case NSRightMouseDragged: - return true; - default: - return false; - } -} - static void qt_closePopups() { while (QCocoaWindow *popup = QCocoaIntegration::instance()->popPopupWindow()) { @@ -89,373 +74,6 @@ static void qt_closePopups() } } -@interface NSWindow (FullScreenProperty) -@property(readonly) BOOL qt_fullScreen; -@end - -@implementation NSWindow (FullScreenProperty) - -+ (void)load -{ - NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; - [center addObserverForName:NSWindowDidEnterFullScreenNotification object:nil queue:nil - usingBlock:^(NSNotification *notification) { - objc_setAssociatedObject(notification.object, @selector(qt_fullScreen), - [NSNumber numberWithBool:YES], OBJC_ASSOCIATION_RETAIN); - } - ]; - [center addObserverForName:NSWindowDidExitFullScreenNotification object:nil queue:nil - usingBlock:^(NSNotification *notification) { - objc_setAssociatedObject(notification.object, @selector(qt_fullScreen), - nil, OBJC_ASSOCIATION_RETAIN); - } - ]; -} - -- (BOOL)qt_fullScreen -{ - NSNumber *number = objc_getAssociatedObject(self, @selector(qt_fullScreen)); - return [number boolValue]; -} -@end - -@implementation QNSWindowHelper - -@synthesize window = _window; -@synthesize grabbingMouse = _grabbingMouse; -@synthesize releaseOnMouseUp = _releaseOnMouseUp; - -- (QCocoaWindow *)platformWindow -{ - return _platformWindow.data(); -} - -- (id)initWithNSWindow:(QCocoaNSWindow *)window platformWindow:(QCocoaWindow *)platformWindow -{ - self = [super init]; - if (self) { - _window = window; - _platformWindow = platformWindow; - - _window.delegate = [[QNSWindowDelegate alloc] initWithQCocoaWindow:_platformWindow]; - - // Prevent Cocoa from releasing the window on close. Qt - // handles the close event asynchronously and we want to - // make sure that NSWindow stays valid until the - // QCocoaWindow is deleted by Qt. - [_window setReleasedWhenClosed:NO]; - } - - return self; -} - -- (void)handleWindowEvent:(NSEvent *)theEvent -{ - QCocoaWindow *pw = self.platformWindow; - if (pw && pw->m_forwardWindow) { - if (theEvent.type == NSLeftMouseUp || theEvent.type == NSLeftMouseDragged) { - QNSView *forwardView = qnsview_cast(pw->view()); - if (theEvent.type == NSLeftMouseUp) { - [forwardView mouseUp:theEvent]; - pw->m_forwardWindow.clear(); - } else { - [forwardView mouseDragged:theEvent]; - } - } - if (pw->window()->isTopLevel() && theEvent.type == NSLeftMouseDown) { - pw->m_forwardWindow.clear(); - } - } - - if (theEvent.type == NSLeftMouseDown) { - self.grabbingMouse = YES; - } else if (theEvent.type == NSLeftMouseUp) { - self.grabbingMouse = NO; - if (self.releaseOnMouseUp) { - [self detachFromPlatformWindow]; - [self.window release]; - return; - } - } - - // The call to -[NSWindow sendEvent] may result in the window being deleted - // (e.g., when closing the window by pressing the title bar close button). - [self retain]; - [self.window superSendEvent:theEvent]; - bool windowStillAlive = self.window != nil; // We need to read before releasing - [self release]; - if (!windowStillAlive) - return; - - if (!self.platformWindow) - return; // Platform window went away while processing event - - if (pw && pw->frameStrutEventsEnabled() && isMouseEvent(theEvent)) { - NSPoint loc = [theEvent locationInWindow]; - NSRect windowFrame = [self.window convertRectFromScreen:[self.window frame]]; - NSRect contentFrame = [[self.window contentView] frame]; - if (NSMouseInRect(loc, windowFrame, NO) && !NSMouseInRect(loc, contentFrame, NO)) - [qnsview_cast(pw->view()) handleFrameStrutMouseEvent:theEvent]; - } -} - -- (void)detachFromPlatformWindow -{ - _platformWindow.clear(); - [self.window.delegate release]; - self.window.delegate = nil; -} - -- (void)clearWindow -{ - if (_window) { - QCocoaEventDispatcher *cocoaEventDispatcher = qobject_cast(QGuiApplication::instance()->eventDispatcher()); - if (cocoaEventDispatcher) { - QCocoaEventDispatcherPrivate *cocoaEventDispatcherPrivate = static_cast(QObjectPrivate::get(cocoaEventDispatcher)); - cocoaEventDispatcherPrivate->removeQueuedUserInputEvents([_window windowNumber]); - } - - _window = nil; - } -} - -- (void)dealloc -{ - _window = nil; - _platformWindow.clear(); - [super dealloc]; -} - -@end - -@implementation QNSWindow - -@synthesize helper = _helper; - -- (id)initWithContentRect:(NSRect)contentRect - screen:(NSScreen*)screen - styleMask:(NSUInteger)windowStyle - qPlatformWindow:(QCocoaWindow *)qpw -{ - self = [super initWithContentRect:contentRect - styleMask:windowStyle - backing:NSBackingStoreBuffered - defer:NO screen:screen]; // Deferring window creation breaks OpenGL (the GL context is - // set up before the window is shown and needs a proper window) - - if (self) { - _helper = [[QNSWindowHelper alloc] initWithNSWindow:self platformWindow:qpw]; - } - return self; -} - -- (BOOL)canBecomeKeyWindow -{ - // Prevent child NSWindows from becoming the key window in - // order keep the active apperance of the top-level window. - QCocoaWindow *pw = self.helper.platformWindow; - if (!pw || !pw->window()->isTopLevel()) - return NO; - - if (pw->shouldRefuseKeyWindowAndFirstResponder()) - return NO; - - // The default implementation returns NO for title-bar less windows, - // override and return yes here to make sure popup windows such as - // the combobox popup can become the key window. - return YES; -} - -- (BOOL)canBecomeMainWindow -{ - BOOL canBecomeMain = YES; // By default, windows can become the main window - - // Windows with a transient parent (such as combobox popup windows) - // cannot become the main window: - QCocoaWindow *pw = self.helper.platformWindow; - if (!pw || !pw->window()->isTopLevel() || pw->window()->transientParent()) - canBecomeMain = NO; - - return canBecomeMain; -} - -- (void) sendEvent: (NSEvent*) theEvent -{ - [self.helper handleWindowEvent:theEvent]; -} - -- (void)superSendEvent:(NSEvent *)theEvent -{ - [super sendEvent:theEvent]; -} - -- (void)closeAndRelease -{ - qCDebug(lcQpaCocoaWindow) << "closeAndRelease" << self; - - [self close]; - - if (self.helper.grabbingMouse) { - self.helper.releaseOnMouseUp = YES; - } else { - [self.helper detachFromPlatformWindow]; - [self release]; - } -} - -- (void)dealloc -{ - [_helper clearWindow]; - [_helper release]; - _helper = nil; - [super dealloc]; -} - -@end - -@implementation QNSPanel - -@synthesize helper = _helper; - -+ (void)applicationActivationChanged:(NSNotification*)notification -{ - const id sender = self; - NSEnumerator *windowEnumerator = nullptr; - NSApplication *application = [NSApplication sharedApplication]; - -#if QT_MACOS_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_12) - if (QOperatingSystemVersion::current() >= QOperatingSystemVersion::MacOSSierra) { - // 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; - } - - for (NSWindow *window in windowEnumerator) { - // We're meddling with normal and floating windows, so leave others alone - if (!(window.level == NSNormalWindowLevel || window.level == NSFloatingWindowLevel)) - continue; - - // Windows that hide automatically will keep their NSFloatingWindowLevel, - // and hence be on top of the window stack. We don't want to affect these - // windows, as otherwise we might end up with key windows being ordered - // behind these auto-hidden windows when activating the application by - // clicking on a new tool window. - if (window.hidesOnDeactivate) - continue; - - if ([window conformsToProtocol:@protocol(QNSWindowProtocol)]) { - QCocoaWindow *cocoaWindow = static_cast>(window).helper.platformWindow; - window.level = notification.name == NSApplicationWillResignActiveNotification ? - NSNormalWindowLevel : cocoaWindow->windowLevel(cocoaWindow->window()->flags()); - } - - // The documentation says that "when a window enters a new level, it’s ordered - // in front of all its peers in that level", but that doesn't seem to be the - // case in practice. To keep the order correct after meddling with the window - // levels, we explicitly order each window to the front. Since we are iterating - // the windows in back-to-front order, this is okey. The call also triggers AppKit - // to re-evaluate the level in relation to windows from other applications, - // working around an issue where our tool windows would stay on top of other - // application windows if activation was transferred to another application by - // clicking on it instead of via the application switcher or Dock. Finally, we - // do this re-ordering for all windows (except auto-hiding ones), otherwise we would - // end up triggering a bug in AppKit where the tool windows would disappear behind - // the application window. - [window orderFront:sender]; - } -} - -- (id)initWithContentRect:(NSRect)contentRect - screen:(NSScreen*)screen - styleMask:(NSUInteger)windowStyle - qPlatformWindow:(QCocoaWindow *)qpw -{ - self = [super initWithContentRect:contentRect - styleMask:windowStyle - backing:NSBackingStoreBuffered - defer:NO screen:screen]; // Deferring window creation breaks OpenGL (the GL context is - // set up before the window is shown and needs a proper window) - - if (self) { - _helper = [[QNSWindowHelper alloc] initWithNSWindow:self platformWindow:qpw]; - - if (qpw->alwaysShowToolWindow()) { - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; - [center addObserver:[self class] selector:@selector(applicationActivationChanged:) - name:NSApplicationWillResignActiveNotification object:nil]; - [center addObserver:[self class] selector:@selector(applicationActivationChanged:) - name:NSApplicationWillBecomeActiveNotification object:nil]; - }); - } - } - return self; -} - -- (BOOL)canBecomeKeyWindow -{ - QCocoaWindow *pw = self.helper.platformWindow; - if (!pw) - return NO; - - if (pw->shouldRefuseKeyWindowAndFirstResponder()) - return NO; - - // Only tool or dialog windows should become key: - Qt::WindowType type = pw->window()->type(); - if (type == Qt::Tool || type == Qt::Dialog) - return YES; - - return NO; -} - -- (void) sendEvent: (NSEvent*) theEvent -{ - [self.helper handleWindowEvent:theEvent]; -} - -- (void)superSendEvent:(NSEvent *)theEvent -{ - [super sendEvent:theEvent]; -} - -- (void)closeAndRelease -{ - qCDebug(lcQpaCocoaWindow) << "closeAndRelease" << self; - - [self.helper detachFromPlatformWindow]; - [self close]; - [self release]; -} - -- (void)dealloc -{ - [_helper clearWindow]; - [_helper release]; - _helper = nil; - [super dealloc]; -} - -@end - static void qRegisterNotificationCallbacks() { static const QLatin1String notificationHandlerPrefix(Q_NOTIFICATION_PREFIX); diff --git a/src/plugins/platforms/cocoa/qnswindow.h b/src/plugins/platforms/cocoa/qnswindow.h new file mode 100644 index 0000000000..580564aeee --- /dev/null +++ b/src/plugins/platforms/cocoa/qnswindow.h @@ -0,0 +1,124 @@ +/**************************************************************************** +** +** Copyright (C) 2017 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$ +** +****************************************************************************/ + +#ifndef QNSWINDOW_H +#define QNSWINDOW_H + +#include +#include +#include "qt_mac_p.h" + +#include + +QT_FORWARD_DECLARE_CLASS(QCocoaWindow) + +@interface NSWindow (FullScreenProperty) +@property(readonly) BOOL qt_fullScreen; +@end + +@class QT_MANGLE_NAMESPACE(QNSWindowHelper); + +@protocol QNSWindowProtocol + +@property (nonatomic, readonly) QT_MANGLE_NAMESPACE(QNSWindowHelper) *helper; + +- (void)superSendEvent:(NSEvent *)theEvent; +- (void)closeAndRelease; + +@end + +typedef NSWindow QCocoaNSWindow; + +@interface QT_MANGLE_NAMESPACE(QNSWindowHelper) : NSObject +{ + QCocoaNSWindow *_window; + QPointer _platformWindow; + BOOL _grabbingMouse; + BOOL _releaseOnMouseUp; +} + +@property (nonatomic, readonly) QCocoaNSWindow *window; +@property (nonatomic, readonly) QCocoaWindow *platformWindow; +@property (nonatomic) BOOL grabbingMouse; +@property (nonatomic) BOOL releaseOnMouseUp; + +- (id)initWithNSWindow:(QCocoaNSWindow *)window platformWindow:(QCocoaWindow *)platformWindow; +- (void)handleWindowEvent:(NSEvent *)theEvent; +- (void)clearWindow; +- (void)detachFromPlatformWindow; + +@end + +QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSWindowHelper); + +@interface QT_MANGLE_NAMESPACE(QNSWindow) : NSWindow +{ + QNSWindowHelper *_helper; +} + +@property (nonatomic, readonly) QNSWindowHelper *helper; + +- (id)initWithContentRect:(NSRect)contentRect + screen:(NSScreen*)screen + styleMask:(NSUInteger)windowStyle + qPlatformWindow:(QCocoaWindow *)qpw; + +@end + +QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSWindow); + +@interface QT_MANGLE_NAMESPACE(QNSPanel) : NSPanel +{ + QNSWindowHelper *_helper; +} + +@property (nonatomic, readonly) QNSWindowHelper *helper; + +- (id)initWithContentRect:(NSRect)contentRect + screen:(NSScreen*)screen + styleMask:(NSUInteger)windowStyle + qPlatformWindow:(QCocoaWindow *)qpw; + +@end + +QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSPanel); + +@class QT_MANGLE_NAMESPACE(QNSWindowDelegate); + +#endif // QNSWINDOW_H diff --git a/src/plugins/platforms/cocoa/qnswindow.mm b/src/plugins/platforms/cocoa/qnswindow.mm new file mode 100644 index 0000000000..13bc13b5bc --- /dev/null +++ b/src/plugins/platforms/cocoa/qnswindow.mm @@ -0,0 +1,418 @@ +/**************************************************************************** +** +** Copyright (C) 2017 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$ +** +****************************************************************************/ + +#include "qnswindow.h" +#include "qnswindowdelegate.h" +#include "qcocoawindow.h" +#include "qcocoahelpers.h" +#include "qcocoaeventdispatcher.h" + +#include + +static bool isMouseEvent(NSEvent *ev) +{ + switch ([ev type]) { + case NSLeftMouseDown: + case NSLeftMouseUp: + case NSRightMouseDown: + case NSRightMouseUp: + case NSMouseMoved: + case NSLeftMouseDragged: + case NSRightMouseDragged: + return true; + default: + return false; + } +} + +@implementation NSWindow (FullScreenProperty) + ++ (void)load +{ + NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; + [center addObserverForName:NSWindowDidEnterFullScreenNotification object:nil queue:nil + usingBlock:^(NSNotification *notification) { + objc_setAssociatedObject(notification.object, @selector(qt_fullScreen), + [NSNumber numberWithBool:YES], OBJC_ASSOCIATION_RETAIN); + } + ]; + [center addObserverForName:NSWindowDidExitFullScreenNotification object:nil queue:nil + usingBlock:^(NSNotification *notification) { + objc_setAssociatedObject(notification.object, @selector(qt_fullScreen), + nil, OBJC_ASSOCIATION_RETAIN); + } + ]; +} + +- (BOOL)qt_fullScreen +{ + NSNumber *number = objc_getAssociatedObject(self, @selector(qt_fullScreen)); + return [number boolValue]; +} +@end + +@implementation QNSWindowHelper + +@synthesize window = _window; +@synthesize grabbingMouse = _grabbingMouse; +@synthesize releaseOnMouseUp = _releaseOnMouseUp; + +- (QCocoaWindow *)platformWindow +{ + return _platformWindow.data(); +} + +- (id)initWithNSWindow:(QCocoaNSWindow *)window platformWindow:(QCocoaWindow *)platformWindow +{ + if (self = [super init]) { + _window = window; + _platformWindow = platformWindow; + + _window.delegate = [[QNSWindowDelegate alloc] initWithQCocoaWindow:_platformWindow]; + + // Prevent Cocoa from releasing the window on close. Qt + // handles the close event asynchronously and we want to + // make sure that NSWindow stays valid until the + // QCocoaWindow is deleted by Qt. + [_window setReleasedWhenClosed:NO]; + } + + return self; +} + +- (void)handleWindowEvent:(NSEvent *)theEvent +{ + QCocoaWindow *pw = self.platformWindow; + if (pw && pw->m_forwardWindow) { + if (theEvent.type == NSLeftMouseUp || theEvent.type == NSLeftMouseDragged) { + QNSView *forwardView = qnsview_cast(pw->view()); + if (theEvent.type == NSLeftMouseUp) { + [forwardView mouseUp:theEvent]; + pw->m_forwardWindow.clear(); + } else { + [forwardView mouseDragged:theEvent]; + } + } + if (pw->window()->isTopLevel() && theEvent.type == NSLeftMouseDown) { + pw->m_forwardWindow.clear(); + } + } + + if (theEvent.type == NSLeftMouseDown) { + self.grabbingMouse = YES; + } else if (theEvent.type == NSLeftMouseUp) { + self.grabbingMouse = NO; + if (self.releaseOnMouseUp) { + [self detachFromPlatformWindow]; + [self.window release]; + return; + } + } + + // The call to -[NSWindow sendEvent] may result in the window being deleted + // (e.g., when closing the window by pressing the title bar close button). + [self retain]; + [self.window superSendEvent:theEvent]; + bool windowStillAlive = self.window != nil; // We need to read before releasing + [self release]; + if (!windowStillAlive) + return; + + if (!self.platformWindow) + return; // Platform window went away while processing event + + if (pw && pw->frameStrutEventsEnabled() && isMouseEvent(theEvent)) { + NSPoint loc = [theEvent locationInWindow]; + NSRect windowFrame = [self.window convertRectFromScreen:[self.window frame]]; + NSRect contentFrame = [[self.window contentView] frame]; + if (NSMouseInRect(loc, windowFrame, NO) && !NSMouseInRect(loc, contentFrame, NO)) + [qnsview_cast(pw->view()) handleFrameStrutMouseEvent:theEvent]; + } +} + +- (void)detachFromPlatformWindow +{ + _platformWindow.clear(); + [self.window.delegate release]; + self.window.delegate = nil; +} + +- (void)clearWindow +{ + if (_window) { + QCocoaEventDispatcher *cocoaEventDispatcher = qobject_cast(QGuiApplication::instance()->eventDispatcher()); + if (cocoaEventDispatcher) { + QCocoaEventDispatcherPrivate *cocoaEventDispatcherPrivate = static_cast(QObjectPrivate::get(cocoaEventDispatcher)); + cocoaEventDispatcherPrivate->removeQueuedUserInputEvents([_window windowNumber]); + } + + _window = nil; + } +} + +- (void)dealloc +{ + _window = nil; + _platformWindow.clear(); + [super dealloc]; +} + +@end + +// Deferring window creation breaks OpenGL (the GL context is +// set up before the window is shown and needs a proper window) +static const bool kNoDefer = NO; + +@implementation QNSWindow + +@synthesize helper = _helper; + +- (id)initWithContentRect:(NSRect)contentRect + screen:(NSScreen*)screen + styleMask:(NSUInteger)windowStyle + qPlatformWindow:(QCocoaWindow *)qpw +{ + if (self = [super initWithContentRect:contentRect styleMask:windowStyle + backing:NSBackingStoreBuffered defer:kNoDefer screen:screen]) { + _helper = [[QNSWindowHelper alloc] initWithNSWindow:self platformWindow:qpw]; + } + return self; +} + +- (BOOL)canBecomeKeyWindow +{ + // Prevent child NSWindows from becoming the key window in + // order keep the active apperance of the top-level window. + QCocoaWindow *pw = self.helper.platformWindow; + if (!pw || !pw->window()->isTopLevel()) + return NO; + + if (pw->shouldRefuseKeyWindowAndFirstResponder()) + return NO; + + // The default implementation returns NO for title-bar less windows, + // override and return yes here to make sure popup windows such as + // the combobox popup can become the key window. + return YES; +} + +- (BOOL)canBecomeMainWindow +{ + BOOL canBecomeMain = YES; // By default, windows can become the main window + + // Windows with a transient parent (such as combobox popup windows) + // cannot become the main window: + QCocoaWindow *pw = self.helper.platformWindow; + if (!pw || !pw->window()->isTopLevel() || pw->window()->transientParent()) + canBecomeMain = NO; + + return canBecomeMain; +} + +- (void)sendEvent:(NSEvent*)theEvent +{ + [self.helper handleWindowEvent:theEvent]; +} + +- (void)superSendEvent:(NSEvent *)theEvent +{ + [super sendEvent:theEvent]; +} + +- (void)closeAndRelease +{ + qCDebug(lcQpaCocoaWindow) << "closeAndRelease" << self; + + [self close]; + + if (self.helper.grabbingMouse) { + self.helper.releaseOnMouseUp = YES; + } else { + [self.helper detachFromPlatformWindow]; + [self release]; + } +} + +- (void)dealloc +{ + [_helper clearWindow]; + [_helper release]; + _helper = nil; + [super dealloc]; +} + +@end + +@implementation QNSPanel + +@synthesize helper = _helper; + ++ (void)applicationActivationChanged:(NSNotification*)notification +{ + const id sender = self; + NSEnumerator *windowEnumerator = nullptr; + NSApplication *application = [NSApplication sharedApplication]; + +#if QT_MACOS_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_12) + if (QOperatingSystemVersion::current() >= QOperatingSystemVersion::MacOSSierra) { + // 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; + } + + for (NSWindow *window in windowEnumerator) { + // We're meddling with normal and floating windows, so leave others alone + if (!(window.level == NSNormalWindowLevel || window.level == NSFloatingWindowLevel)) + continue; + + // Windows that hide automatically will keep their NSFloatingWindowLevel, + // and hence be on top of the window stack. We don't want to affect these + // windows, as otherwise we might end up with key windows being ordered + // behind these auto-hidden windows when activating the application by + // clicking on a new tool window. + if (window.hidesOnDeactivate) + continue; + + if ([window conformsToProtocol:@protocol(QNSWindowProtocol)]) { + QCocoaWindow *cocoaWindow = static_cast>(window).helper.platformWindow; + window.level = notification.name == NSApplicationWillResignActiveNotification ? + NSNormalWindowLevel : cocoaWindow->windowLevel(cocoaWindow->window()->flags()); + } + + // The documentation says that "when a window enters a new level, it’s ordered + // in front of all its peers in that level", but that doesn't seem to be the + // case in practice. To keep the order correct after meddling with the window + // levels, we explicitly order each window to the front. Since we are iterating + // the windows in back-to-front order, this is okey. The call also triggers AppKit + // to re-evaluate the level in relation to windows from other applications, + // working around an issue where our tool windows would stay on top of other + // application windows if activation was transferred to another application by + // clicking on it instead of via the application switcher or Dock. Finally, we + // do this re-ordering for all windows (except auto-hiding ones), otherwise we would + // end up triggering a bug in AppKit where the tool windows would disappear behind + // the application window. + [window orderFront:sender]; + } +} + +- (id)initWithContentRect:(NSRect)contentRect + screen:(NSScreen*)screen + styleMask:(NSUInteger)windowStyle + qPlatformWindow:(QCocoaWindow *)qpw +{ + if (self = [super initWithContentRect:contentRect styleMask:windowStyle + backing:NSBackingStoreBuffered defer:kNoDefer screen:screen]) { + _helper = [[QNSWindowHelper alloc] initWithNSWindow:self platformWindow:qpw]; + + if (qpw->alwaysShowToolWindow()) { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; + [center addObserver:[self class] selector:@selector(applicationActivationChanged:) + name:NSApplicationWillResignActiveNotification object:nil]; + [center addObserver:[self class] selector:@selector(applicationActivationChanged:) + name:NSApplicationWillBecomeActiveNotification object:nil]; + }); + } + } + return self; +} + +- (BOOL)canBecomeKeyWindow +{ + QCocoaWindow *pw = self.helper.platformWindow; + if (!pw) + return NO; + + if (pw->shouldRefuseKeyWindowAndFirstResponder()) + return NO; + + // Only tool or dialog windows should become key: + Qt::WindowType type = pw->window()->type(); + if (type == Qt::Tool || type == Qt::Dialog) + return YES; + + return NO; +} + +- (void)sendEvent:(NSEvent*)theEvent +{ + [self.helper handleWindowEvent:theEvent]; +} + +- (void)superSendEvent:(NSEvent *)theEvent +{ + [super sendEvent:theEvent]; +} + +- (void)closeAndRelease +{ + qCDebug(lcQpaCocoaWindow) << "closeAndRelease" << self; + + [self.helper detachFromPlatformWindow]; + [self close]; + [self release]; +} + +- (void)dealloc +{ + [_helper clearWindow]; + [_helper release]; + _helper = nil; + [super dealloc]; +} + +@end -- cgit v1.2.3