diff options
author | Tor Arne Vestbø <tor.arne.vestbo@qt.io> | 2017-06-20 15:02:37 +0200 |
---|---|---|
committer | Tor Arne Vestbø <tor.arne.vestbo@qt.io> | 2017-06-21 20:25:26 +0000 |
commit | 3d8a70c0459192d57088ee51d2b1f0cc52d2e5f8 (patch) | |
tree | 460d38942432f8b4a21e63c73f1c4043443db11e /src/plugins/platforms/cocoa/qnswindow.mm | |
parent | 8af7b5ba5abe485ff12b5fb02c58f4c275e6eafe (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.mm | 418 |
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 |