| /**************************************************************************** |
| ** |
| ** 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$ |
| ** |
| ****************************************************************************/ |
| |
| #if !defined(QNSWINDOW_PROTOCOL_IMPLMENTATION) |
| |
| #include "qnswindow.h" |
| #include "qcocoawindow.h" |
| #include "qcocoahelpers.h" |
| #include "qcocoaeventdispatcher.h" |
| |
| #include <qpa/qwindowsysteminterface.h> |
| #include <qoperatingsystemversion.h> |
| |
| Q_LOGGING_CATEGORY(lcQpaEvents, "qt.qpa.events"); |
| |
| static bool isMouseEvent(NSEvent *ev) |
| { |
| switch ([ev type]) { |
| case NSEventTypeLeftMouseDown: |
| case NSEventTypeLeftMouseUp: |
| case NSEventTypeRightMouseDown: |
| case NSEventTypeRightMouseUp: |
| case NSEventTypeMouseMoved: |
| case NSEventTypeLeftMouseDragged: |
| case NSEventTypeRightMouseDragged: |
| 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), |
| @(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 QNSWindow |
| #define QNSWINDOW_PROTOCOL_IMPLMENTATION 1 |
| #include "qnswindow.mm" |
| #undef QNSWINDOW_PROTOCOL_IMPLMENTATION |
| |
| + (void)applicationActivationChanged:(NSNotification*)notification |
| { |
| const id sender = self; |
| NSEnumerator<NSWindow*> *windowEnumerator = nullptr; |
| NSApplication *application = [NSApplication sharedApplication]; |
| |
| // 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 |
| 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<QCocoaNSWindow *>(window).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]; |
| } |
| } |
| |
| @end |
| |
| @implementation QNSPanel |
| #define QNSWINDOW_PROTOCOL_IMPLMENTATION 1 |
| #include "qnswindow.mm" |
| #undef QNSWINDOW_PROTOCOL_IMPLMENTATION |
| @end |
| |
| #else // QNSWINDOW_PROTOCOL_IMPLMENTATION |
| |
| // The following content is mixed in to the QNSWindow and QNSPanel classes via includes |
| |
| { |
| // Member variables |
| QPointer<QCocoaWindow> m_platformWindow; |
| } |
| |
| - (instancetype)initWithContentRect:(NSRect)contentRect styleMask:(NSWindowStyleMask)style |
| backing:(NSBackingStoreType)backingStoreType defer:(BOOL)defer screen:(NSScreen *)screen |
| platformWindow:(QCocoaWindow*)window |
| { |
| // Initializing the window will end up in [NSWindow _commonAwake], which calls many |
| // of the getters below. We need to set up the platform window reference first, so |
| // we can properly reflect the window's state during initialization. |
| m_platformWindow = window; |
| |
| return [super initWithContentRect:contentRect styleMask:style backing:backingStoreType defer:defer screen:screen]; |
| } |
| |
| - (QCocoaWindow *)platformWindow |
| { |
| return m_platformWindow; |
| } |
| |
| - (NSString *)description |
| { |
| NSMutableString *description = [NSMutableString stringWithString:[super description]]; |
| |
| #ifndef QT_NO_DEBUG_STREAM |
| QString contentViewDescription; |
| QDebug debug(&contentViewDescription); |
| debug.nospace() << "; contentView=" << qnsview_cast(self.contentView) << ">"; |
| |
| NSRange lastCharacter = [description rangeOfComposedCharacterSequenceAtIndex:description.length - 1]; |
| [description replaceCharactersInRange:lastCharacter withString:contentViewDescription.toNSString()]; |
| #endif |
| |
| return description; |
| } |
| |
| - (BOOL)canBecomeKeyWindow |
| { |
| if (!m_platformWindow) |
| return NO; |
| |
| if (m_platformWindow->shouldRefuseKeyWindowAndFirstResponder()) |
| return NO; |
| |
| if ([self isKindOfClass:[QNSPanel class]]) { |
| // Only tool or dialog windows should become key: |
| Qt::WindowType type = m_platformWindow->window()->type(); |
| if (type == Qt::Tool || type == Qt::Dialog) |
| return YES; |
| |
| return NO; |
| } else { |
| // 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: |
| if (!m_platformWindow || m_platformWindow->window()->transientParent()) |
| canBecomeMain = NO; |
| |
| return canBecomeMain; |
| } |
| |
| - (BOOL)worksWhenModal |
| { |
| if (m_platformWindow && [self isKindOfClass:[QNSPanel class]]) { |
| Qt::WindowType type = m_platformWindow->window()->type(); |
| if (type == Qt::Popup || type == Qt::Dialog || type == Qt::Tool) |
| return YES; |
| } |
| |
| return [super worksWhenModal]; |
| } |
| |
| - (BOOL)isOpaque |
| { |
| return m_platformWindow ? m_platformWindow->isOpaque() : [super isOpaque]; |
| } |
| |
| - (NSColor *)backgroundColor |
| { |
| return self.styleMask == NSWindowStyleMaskBorderless ? |
| [NSColor clearColor] : [super backgroundColor]; |
| } |
| |
| - (void)sendEvent:(NSEvent*)theEvent |
| { |
| 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 |
| // e.g. if being retained by other parts of AppKit, or in an auto-release |
| // pool. We guard against this in QNSView as well, as not all callbacks |
| // come via events, but if they do there's no point in propagating them. |
| if (!m_platformWindow) |
| return; |
| |
| // Prevent deallocation of this NSWindow during event delivery, as we |
| // have logic further below that depends on the window being alive. |
| [[self retain] autorelease]; |
| |
| const char *eventType = object_getClassName(theEvent); |
| if (QWindowSystemInterface::handleNativeEvent(m_platformWindow->window(), |
| QByteArray::fromRawData(eventType, qstrlen(eventType)), theEvent, nullptr)) { |
| return; |
| } |
| |
| [super sendEvent:theEvent]; |
| |
| if (!m_platformWindow) |
| return; // Platform window went away while processing event |
| |
| if (m_platformWindow->frameStrutEventsEnabled() && isMouseEvent(theEvent)) { |
| NSPoint loc = [theEvent locationInWindow]; |
| NSRect windowFrame = [self convertRectFromScreen:self.frame]; |
| NSRect contentFrame = self.contentView.frame; |
| if (NSMouseInRect(loc, windowFrame, NO) && !NSMouseInRect(loc, contentFrame, NO)) |
| [qnsview_cast(m_platformWindow->view()) handleFrameStrutMouseEvent:theEvent]; |
| } |
| } |
| |
| - (void)closeAndRelease |
| { |
| qCDebug(lcQpaWindow) << "Closing and releasing" << self; |
| [self close]; |
| [self release]; |
| } |
| |
| - (void)dealloc |
| { |
| qCDebug(lcQpaWindow) << "Deallocating" << self; |
| self.delegate = nil; |
| |
| [super dealloc]; |
| } |
| |
| #endif |