summaryrefslogtreecommitdiffstats
path: root/src/plugins/platforms/cocoa/qnswindow.mm
diff options
context:
space:
mode:
authorTor Arne Vestbø <tor.arne.vestbo@qt.io>2017-06-20 15:02:37 +0200
committerTor Arne Vestbø <tor.arne.vestbo@qt.io>2017-06-21 20:25:26 +0000
commit3d8a70c0459192d57088ee51d2b1f0cc52d2e5f8 (patch)
tree460d38942432f8b4a21e63c73f1c4043443db11e /src/plugins/platforms/cocoa/qnswindow.mm
parent8af7b5ba5abe485ff12b5fb02c58f4c275e6eafe (diff)
macOS: Move QNSWindow implementation into separate file
Change-Id: Ie75419c7ffb7a8fdf78d53e1ea14afee63ef1656 Reviewed-by: Morten Johan Sørvig <morten.sorvig@qt.io>
Diffstat (limited to 'src/plugins/platforms/cocoa/qnswindow.mm')
-rw-r--r--src/plugins/platforms/cocoa/qnswindow.mm418
1 files changed, 418 insertions, 0 deletions
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 <qoperatingsystemversion.h>
+
+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<QCocoaEventDispatcher *>(QGuiApplication::instance()->eventDispatcher());
+ if (cocoaEventDispatcher) {
+ QCocoaEventDispatcherPrivate *cocoaEventDispatcherPrivate = static_cast<QCocoaEventDispatcherPrivate *>(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<NSWindow*> *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<id<QNSWindowProtocol>>(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