1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the plugins of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39#include "qcocoawindow.h"
40#include "qcocoaintegration.h"
41#include "qcocoascreen.h"
42#include "qnswindowdelegate.h"
43#include "qcocoaeventdispatcher.h"
44#ifndef QT_NO_OPENGL
45#include "qcocoaglcontext.h"
46#endif
47#include "qcocoahelpers.h"
48#include "qcocoanativeinterface.h"
49#include "qnsview.h"
50#include "qnswindow.h"
51#include <QtCore/qfileinfo.h>
52#include <QtCore/private/qcore_mac_p.h>
53#include <qwindow.h>
54#include <private/qwindow_p.h>
55#include <qpa/qwindowsysteminterface.h>
56#include <qpa/qplatformscreen.h>
57#include <QtGui/private/qcoregraphics_p.h>
58#include <QtGui/private/qhighdpiscaling_p.h>
59
60#include <AppKit/AppKit.h>
61#include <QuartzCore/QuartzCore.h>
62
63#include <QDebug>
64
65#include <vector>
66
67QT_BEGIN_NAMESPACE
68
69enum {
70    defaultWindowWidth = 160,
71    defaultWindowHeight = 160
72};
73
74Q_LOGGING_CATEGORY(lcCocoaNotifications, "qt.qpa.cocoa.notifications");
75
76static void qRegisterNotificationCallbacks()
77{
78    static const QLatin1String notificationHandlerPrefix(Q_NOTIFICATION_PREFIX);
79
80    NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
81
82    const QMetaObject *metaObject = QMetaType::metaObjectForType(qRegisterMetaType<QCocoaWindow*>());
83    Q_ASSERT(metaObject);
84
85    for (int i = 0; i < metaObject->methodCount(); ++i) {
86        QMetaMethod method = metaObject->method(i);
87        const QString methodTag = QString::fromLatin1(method.tag());
88        if (!methodTag.startsWith(notificationHandlerPrefix))
89            continue;
90
91        const QString notificationName = methodTag.mid(notificationHandlerPrefix.size());
92        [center addObserverForName:notificationName.toNSString() object:nil queue:nil
93            usingBlock:^(NSNotification *notification) {
94
95            QVarLengthArray<QCocoaWindow *, 32> cocoaWindows;
96            if ([notification.object isKindOfClass:[NSWindow class]]) {
97                NSWindow *nsWindow = notification.object;
98                for (const QWindow *window : QGuiApplication::allWindows()) {
99                    if (QCocoaWindow *cocoaWindow = static_cast<QCocoaWindow *>(window->handle()))
100                        if (cocoaWindow->nativeWindow() == nsWindow)
101                            cocoaWindows += cocoaWindow;
102                }
103            } else if ([notification.object isKindOfClass:[NSView class]]) {
104                if (QNSView *qnsView = qnsview_cast(notification.object))
105                    cocoaWindows += qnsView.platformWindow;
106            } else {
107                qCWarning(lcCocoaNotifications) << "Unhandled notifcation"
108                    << notification.name << "for" << notification.object;
109                return;
110            }
111
112            if (lcCocoaNotifications().isDebugEnabled() && !cocoaWindows.isEmpty()) {
113                QVector<QCocoaWindow *> debugWindows;
114                for (QCocoaWindow *cocoaWindow : cocoaWindows)
115                    debugWindows += cocoaWindow;
116                qCDebug(lcCocoaNotifications) << "Forwarding" << qPrintable(notificationName) <<
117                    "to" << debugWindows;
118            }
119
120            // FIXME: Could be a foreign window, look up by iterating top level QWindows
121
122            for (QCocoaWindow *cocoaWindow : cocoaWindows) {
123                if (!method.invoke(cocoaWindow, Qt::DirectConnection)) {
124                    qCWarning(lcQpaWindow) << "Failed to invoke NSNotification callback for"
125                        << notification.name << "on" << cocoaWindow;
126                }
127            }
128        }];
129    }
130}
131Q_CONSTRUCTOR_FUNCTION(qRegisterNotificationCallbacks)
132
133const int QCocoaWindow::NoAlertRequest = -1;
134
135QCocoaWindow::QCocoaWindow(QWindow *win, WId nativeHandle)
136    : QPlatformWindow(win)
137    , m_view(nil)
138    , m_nsWindow(nil)
139    , m_lastReportedWindowState(Qt::WindowNoState)
140    , m_windowModality(Qt::NonModal)
141    , m_windowUnderMouse(false)
142    , m_initialized(false)
143    , m_inSetVisible(false)
144    , m_inSetGeometry(false)
145    , m_inSetStyleMask(false)
146    , m_menubar(nullptr)
147    , m_needsInvalidateShadow(false)
148    , m_frameStrutEventsEnabled(false)
149    , m_registerTouchCount(0)
150    , m_resizableTransientParent(false)
151    , m_alertRequest(NoAlertRequest)
152    , monitor(nil)
153    , m_drawContentBorderGradient(false)
154    , m_topContentBorderThickness(0)
155    , m_bottomContentBorderThickness(0)
156{
157    qCDebug(lcQpaWindow) << "QCocoaWindow::QCocoaWindow" << window();
158
159    if (nativeHandle) {
160        m_view = reinterpret_cast<NSView *>(nativeHandle);
161        [m_view retain];
162    }
163}
164
165void QCocoaWindow::initialize()
166{
167    qCDebug(lcQpaWindow) << "QCocoaWindow::initialize" << window();
168
169    QMacAutoReleasePool pool;
170
171    if (!m_view)
172        m_view = [[QNSView alloc] initWithCocoaWindow:this];
173
174    setGeometry(initialGeometry(window(), windowGeometry(), defaultWindowWidth, defaultWindowHeight));
175
176    recreateWindowIfNeeded();
177    window()->setGeometry(geometry());
178
179    m_initialized = true;
180}
181
182QCocoaWindow::~QCocoaWindow()
183{
184    qCDebug(lcQpaWindow) << "QCocoaWindow::~QCocoaWindow" << window();
185
186    QMacAutoReleasePool pool;
187    [m_nsWindow makeFirstResponder:nil];
188    [m_nsWindow setContentView:nil];
189    if ([m_view superview])
190        [m_view removeFromSuperview];
191
192    removeMonitor();
193
194    // Make sure to disconnect observer in all case if view is valid
195    // to avoid notifications received when deleting when using Qt::AA_NativeWindows attribute
196    if (!isForeignWindow())
197        [[NSNotificationCenter defaultCenter] removeObserver:m_view];
198
199    if (QCocoaIntegration *cocoaIntegration = QCocoaIntegration::instance()) {
200        // While it is unlikely that this window will be in the popup stack
201        // during deletetion we clear any pointers here to make sure.
202        cocoaIntegration->popupWindowStack()->removeAll(this);
203
204#if QT_CONFIG(vulkan)
205        auto vulcanInstance = cocoaIntegration->getCocoaVulkanInstance();
206        if (vulcanInstance)
207            vulcanInstance->destroySurface(m_vulkanSurface);
208#endif
209    }
210
211    [m_view release];
212    [m_nsWindow close];
213    [m_nsWindow release];
214}
215
216QSurfaceFormat QCocoaWindow::format() const
217{
218    QSurfaceFormat format = window()->requestedFormat();
219
220    // Upgrade the default surface format to include an alpha channel. The default RGB format
221    // causes Cocoa to spend an unreasonable amount of time converting it to RGBA internally.
222    if (format.alphaBufferSize() < 0)
223        format.setAlphaBufferSize(8);
224
225    return format;
226}
227
228void QCocoaWindow::setGeometry(const QRect &rectIn)
229{
230    qCDebug(lcQpaWindow) << "QCocoaWindow::setGeometry" << window() << rectIn;
231
232    QBoolBlocker inSetGeometry(m_inSetGeometry, true);
233
234    QRect rect = rectIn;
235    // This means it is a call from QWindow::setFramePosition() and
236    // the coordinates include the frame (size is still the contents rectangle).
237    if (qt_window_private(const_cast<QWindow *>(window()))->positionPolicy
238            == QWindowPrivate::WindowFrameInclusive) {
239        const QMargins margins = frameMargins();
240        rect.moveTopLeft(rect.topLeft() + QPoint(margins.left(), margins.top()));
241    }
242    if (geometry() == rect)
243        return;
244
245    setCocoaGeometry(rect);
246}
247
248bool QCocoaWindow::isForeignWindow() const
249{
250    return ![m_view isKindOfClass:[QNSView class]];
251}
252
253QRect QCocoaWindow::geometry() const
254{
255    // QWindows that are embedded in a NSView hiearchy may be considered
256    // top-level from Qt's point of view but are not from Cocoa's point
257    // of view. Embedded QWindows get global (screen) geometry.
258    if (isEmbedded()) {
259        NSPoint windowPoint = [m_view convertPoint:NSMakePoint(0, 0) toView:nil];
260        NSRect screenRect = [[m_view window] convertRectToScreen:NSMakeRect(windowPoint.x, windowPoint.y, 1, 1)];
261        NSPoint screenPoint = screenRect.origin;
262        QPoint position = QCocoaScreen::mapFromNative(screenPoint).toPoint();
263        QSize size = QRectF::fromCGRect(NSRectToCGRect([m_view bounds])).toRect().size();
264        return QRect(position, size);
265    }
266
267    return QPlatformWindow::geometry();
268}
269
270void QCocoaWindow::setCocoaGeometry(const QRect &rect)
271{
272    qCDebug(lcQpaWindow) << "QCocoaWindow::setCocoaGeometry" << window() << rect;
273    QMacAutoReleasePool pool;
274
275    QPlatformWindow::setGeometry(rect);
276
277    if (isEmbedded()) {
278        if (!isForeignWindow()) {
279            [m_view setFrame:NSMakeRect(0, 0, rect.width(), rect.height())];
280        }
281        return;
282    }
283
284    if (isContentView()) {
285        NSRect bounds = QCocoaScreen::mapToNative(rect);
286        [m_view.window setFrame:[m_view.window frameRectForContentRect:bounds] display:YES animate:NO];
287    } else {
288        [m_view setFrame:NSMakeRect(rect.x(), rect.y(), rect.width(), rect.height())];
289    }
290
291    // will call QPlatformWindow::setGeometry(rect) during resize confirmation (see qnsview.mm)
292}
293
294bool QCocoaWindow::startSystemMove()
295{
296    switch (NSApp.currentEvent.type) {
297    case NSEventTypeLeftMouseDown:
298    case NSEventTypeRightMouseDown:
299    case NSEventTypeOtherMouseDown:
300    case NSEventTypeMouseMoved:
301    case NSEventTypeLeftMouseDragged:
302    case NSEventTypeRightMouseDragged:
303    case NSEventTypeOtherMouseDragged:
304        // The documentation only describes starting a system move
305        // based on mouse down events, but move events also work.
306        [m_view.window performWindowDragWithEvent:NSApp.currentEvent];
307        return true;
308    default:
309        return false;
310    }
311}
312
313void QCocoaWindow::setVisible(bool visible)
314{
315    qCDebug(lcQpaWindow) << "QCocoaWindow::setVisible" << window() << visible;
316
317    QScopedValueRollback<bool> rollback(m_inSetVisible, true);
318
319    QMacAutoReleasePool pool;
320    QCocoaWindow *parentCocoaWindow = nullptr;
321    if (window()->transientParent())
322        parentCocoaWindow = static_cast<QCocoaWindow *>(window()->transientParent()->handle());
323
324    auto eventDispatcher = [] {
325        return static_cast<QCocoaEventDispatcherPrivate *>(QObjectPrivate::get(qApp->eventDispatcher()));
326    };
327
328    if (visible) {
329        // We need to recreate if the modality has changed as the style mask will need updating
330        recreateWindowIfNeeded();
331
332        // We didn't send geometry changes during creation, as that would have confused
333        // Qt, which expects a show-event to be sent before any resize events. But now
334        // that the window is made visible, we know that the show-event has been sent
335        // so we can send the geometry change. FIXME: Get rid of this workaround.
336        handleGeometryChange();
337
338        // Register popup windows. The Cocoa platform plugin will forward mouse events
339        // to them and close them when needed.
340        if (window()->type() == Qt::Popup || window()->type() == Qt::ToolTip)
341            QCocoaIntegration::instance()->pushPopupWindow(this);
342
343        if (parentCocoaWindow) {
344            // The parent window might have moved while this window was hidden,
345            // update the window geometry if there is a parent.
346            setGeometry(windowGeometry());
347
348            if (window()->type() == Qt::Popup) {
349                // QTBUG-30266: a window should not be resizable while a transient popup is open
350                // Since this isn't a native popup, the window manager doesn't close the popup when you click outside
351                NSWindow *nativeParentWindow = parentCocoaWindow->nativeWindow();
352                NSUInteger parentStyleMask = nativeParentWindow.styleMask;
353                if ((m_resizableTransientParent = (parentStyleMask & NSWindowStyleMaskResizable))
354                    && !(nativeParentWindow.styleMask & NSWindowStyleMaskFullScreen))
355                    nativeParentWindow.styleMask &= ~NSWindowStyleMaskResizable;
356            }
357
358        }
359
360        if (isContentView()) {
361            QWindowSystemInterface::flushWindowSystemEvents(QEventLoop::ExcludeUserInputEvents);
362
363            // setWindowState might have been called while the window was hidden and
364            // will not change the NSWindow state in that case. Sync up here:
365            applyWindowState(window()->windowStates());
366
367            if (window()->windowState() != Qt::WindowMinimized) {
368                if (parentCocoaWindow && (window()->modality() == Qt::WindowModal || window()->type() == Qt::Sheet)) {
369                    // Show the window as a sheet
370                    [parentCocoaWindow->nativeWindow() beginSheet:m_view.window completionHandler:nil];
371                } else if (window()->modality() == Qt::ApplicationModal) {
372                    // Show the window as application modal
373                    eventDispatcher()->beginModalSession(window());
374                } else if (m_view.window.canBecomeKeyWindow) {
375                    bool shouldBecomeKeyNow = !NSApp.modalWindow || m_view.window.worksWhenModal;
376
377                    // Panels with becomesKeyOnlyIfNeeded set should not activate until a view
378                    // with needsPanelToBecomeKey, for example a line edit, is clicked.
379                    if ([m_view.window isKindOfClass:[NSPanel class]])
380                        shouldBecomeKeyNow &= !(static_cast<NSPanel*>(m_view.window).becomesKeyOnlyIfNeeded);
381
382                    if (shouldBecomeKeyNow)
383                        [m_view.window makeKeyAndOrderFront:nil];
384                    else
385                        [m_view.window orderFront:nil];
386                } else {
387                    [m_view.window orderFront:nil];
388                }
389
390                // Close popup when clicking outside it
391                if (window()->type() == Qt::Popup && !(parentCocoaWindow && window()->transientParent()->isActive())) {
392                    removeMonitor();
393                    NSEventMask eventMask = NSEventMaskLeftMouseDown | NSEventMaskRightMouseDown
394                                          | NSEventMaskOtherMouseDown | NSEventMaskMouseMoved;
395                    monitor = [NSEvent addGlobalMonitorForEventsMatchingMask:eventMask handler:^(NSEvent *e) {
396                        const auto button = cocoaButton2QtButton(e);
397                        const auto buttons = currentlyPressedMouseButtons();
398                        const auto eventType = cocoaEvent2QtMouseEvent(e);
399                        const auto globalPoint = QCocoaScreen::mapFromNative(NSEvent.mouseLocation);
400                        const auto localPoint = window()->mapFromGlobal(globalPoint.toPoint());
401                        QWindowSystemInterface::handleMouseEvent(window(), localPoint, globalPoint, buttons, button, eventType);
402                    }];
403                }
404            }
405        }
406
407        // In some cases, e.g. QDockWidget, the content view is hidden before moving to its own
408        // Cocoa window, and then shown again. Therefore, we test for the view being hidden even
409        // if it's attached to an NSWindow.
410        if ([m_view isHidden])
411            [m_view setHidden:NO];
412
413    } else {
414        // Window not visible, hide it
415        if (isContentView()) {
416            if (eventDispatcher()->hasModalSession()) {
417                eventDispatcher()->endModalSession(window());
418            } else {
419                if ([m_view.window isSheet]) {
420                    Q_ASSERT_X(parentCocoaWindow, "QCocoaWindow", "Window modal dialog has no transient parent.");
421                    [parentCocoaWindow->nativeWindow() endSheet:m_view.window];
422                }
423            }
424
425            // Note: We do not guard the order out by checking NSWindow.visible, as AppKit will
426            // in some cases, such as when hiding the application, order out and make a window
427            // invisible, but keep it in a list of "hidden windows", that it then restores again
428            // when the application is unhidden. We need to call orderOut explicitly, to bring
429            // the window out of this "hidden list".
430            [m_view.window orderOut:nil];
431
432            if (m_view.window == [NSApp keyWindow] && !eventDispatcher()->hasModalSession()) {
433                // Probably because we call runModalSession: outside [NSApp run] in QCocoaEventDispatcher
434                // (e.g., when show()-ing a modal QDialog instead of exec()-ing it), it can happen that
435                // the current NSWindow is still key after being ordered out. Then, after checking we
436                // don't have any other modal session left, it's safe to make the main window key again.
437                NSWindow *mainWindow = [NSApp mainWindow];
438                if (mainWindow && [mainWindow canBecomeKeyWindow])
439                    [mainWindow makeKeyWindow];
440            }
441        } else {
442            [m_view setHidden:YES];
443        }
444
445        removeMonitor();
446
447        if (window()->type() == Qt::Popup || window()->type() == Qt::ToolTip)
448            QCocoaIntegration::instance()->popupWindowStack()->removeAll(this);
449
450        if (parentCocoaWindow && window()->type() == Qt::Popup) {
451            NSWindow *nativeParentWindow = parentCocoaWindow->nativeWindow();
452            if (m_resizableTransientParent
453                && !(nativeParentWindow.styleMask & NSWindowStyleMaskFullScreen))
454                // A window should not be resizable while a transient popup is open
455                nativeParentWindow.styleMask |= NSWindowStyleMaskResizable;
456        }
457    }
458}
459
460NSInteger QCocoaWindow::windowLevel(Qt::WindowFlags flags)
461{
462    Qt::WindowType type = static_cast<Qt::WindowType>(int(flags & Qt::WindowType_Mask));
463
464    NSInteger windowLevel = NSNormalWindowLevel;
465
466    if (type == Qt::Tool)
467        windowLevel = NSFloatingWindowLevel;
468    else if ((type & Qt::Popup) == Qt::Popup)
469        windowLevel = NSPopUpMenuWindowLevel;
470
471    // StayOnTop window should appear above Tool windows.
472    if (flags & Qt::WindowStaysOnTopHint)
473        windowLevel = NSModalPanelWindowLevel;
474    // Tooltips should appear above StayOnTop windows.
475    if (type == Qt::ToolTip)
476        windowLevel = NSScreenSaverWindowLevel;
477
478    auto *transientParent = window()->transientParent();
479    if (transientParent && transientParent->handle()) {
480        // We try to keep windows in at least the same window level as
481        // their transient parent. Unfortunately this only works when the
482        // window is created. If the window level changes after that, as
483        // a result of a call to setWindowFlags, or by changing the level
484        // of the native window, we will not pick this up, and the window
485        // will be left behind (or in a different window level than) its
486        // parent. We could KVO-observe the window level of our transient
487        // parent, but that requires us to know when the parent goes away
488        // so that we can unregister the observation before the parent is
489        // dealloced, something we can't do for generic NSWindows. Another
490        // way would be to override [NSWindow setLevel:] and notify child
491        // windows about the change, but that doesn't work for foreign
492        // windows, which can still be transient parents via fromWinId().
493        // One area where this problem is apparent is when AppKit tweaks
494        // the window level of modal windows during application activation
495        // and deactivation. Since we don't pick up on these window level
496        // changes in a generic way, we need to add logic explicitly to
497        // re-evaluate the window level after AppKit has done its tweaks.
498
499        auto *transientCocoaWindow = static_cast<QCocoaWindow *>(transientParent->handle());
500        auto *nsWindow = transientCocoaWindow->nativeWindow();
501
502        // We only upgrade the window level for "special" windows, to work
503        // around Qt Designer parenting the designer windows to the widget
504        // palette window (QTBUG-31779). This should be fixed in designer.
505        if (type != Qt::Window)
506            windowLevel = qMax(windowLevel, nsWindow.level);
507    }
508
509    return windowLevel;
510}
511
512NSUInteger QCocoaWindow::windowStyleMask(Qt::WindowFlags flags)
513{
514    const Qt::WindowType type = static_cast<Qt::WindowType>(int(flags & Qt::WindowType_Mask));
515    const bool frameless = (flags & Qt::FramelessWindowHint) || windowIsPopupType(type);
516
517    // Remove zoom button by disabling resize for CustomizeWindowHint windows, except for
518    // Qt::Tool windows (e.g. dock windows) which should always be resizable.
519    const bool resizable = !(flags & Qt::CustomizeWindowHint) || (type == Qt::Tool);
520
521    // Select base window type. Note that the value of NSBorderlessWindowMask is 0.
522    NSUInteger styleMask = (frameless || !resizable) ? NSWindowStyleMaskBorderless : NSWindowStyleMaskResizable;
523
524    if (frameless) {
525        // No further customizations for frameless since there are no window decorations.
526    } else if (flags & Qt::CustomizeWindowHint) {
527        if (flags & Qt::WindowTitleHint)
528            styleMask |= NSWindowStyleMaskTitled;
529        if (flags & Qt::WindowCloseButtonHint)
530            styleMask |= NSWindowStyleMaskClosable;
531        if (flags & Qt::WindowMinimizeButtonHint)
532            styleMask |= NSWindowStyleMaskMiniaturizable;
533        if (flags & Qt::WindowMaximizeButtonHint)
534            styleMask |= NSWindowStyleMaskResizable;
535    } else {
536        styleMask |= NSWindowStyleMaskClosable | NSWindowStyleMaskTitled;
537
538        if (type != Qt::Dialog)
539            styleMask |= NSWindowStyleMaskMiniaturizable;
540    }
541
542    if (type == Qt::Tool)
543        styleMask |= NSWindowStyleMaskUtilityWindow;
544
545    if (m_drawContentBorderGradient)
546        styleMask |= NSWindowStyleMaskTexturedBackground;
547
548    // Don't wipe fullscreen state
549    if (m_view.window.styleMask & NSWindowStyleMaskFullScreen)
550        styleMask |= NSWindowStyleMaskFullScreen;
551
552    return styleMask;
553}
554
555void QCocoaWindow::setWindowZoomButton(Qt::WindowFlags flags)
556{
557    if (!isContentView())
558        return;
559
560    // Disable the zoom (maximize) button for fixed-sized windows and customized
561    // no-WindowMaximizeButtonHint windows. From a Qt perspective it migth be expected
562    // that the button would be removed in the latter case, but disabling it is more
563    // in line with the platform style guidelines.
564    bool fixedSizeNoZoom = (windowMinimumSize().isValid() && windowMaximumSize().isValid()
565                            && windowMinimumSize() == windowMaximumSize());
566    bool customizeNoZoom = ((flags & Qt::CustomizeWindowHint)
567        && !(flags & (Qt::WindowMaximizeButtonHint | Qt::WindowFullscreenButtonHint)));
568    [[m_view.window standardWindowButton:NSWindowZoomButton] setEnabled:!(fixedSizeNoZoom || customizeNoZoom)];
569}
570
571void QCocoaWindow::setWindowFlags(Qt::WindowFlags flags)
572{
573    // Updating the window flags may affect the window's theme frame, which
574    // in the process retains and then autoreleases the NSWindow. To make
575    // sure this doesn't leave lingering releases when there is no pool in
576    // place (e.g. during main(), before exec), we add one locally here.
577    QMacAutoReleasePool pool;
578
579    if (!isContentView())
580        return;
581
582    // While setting style mask we can have handleGeometryChange calls on a content
583    // view with null geometry, reporting an invalid coordinates as a result.
584    m_inSetStyleMask = true;
585    m_view.window.styleMask = windowStyleMask(flags);
586    m_inSetStyleMask = false;
587
588    Qt::WindowType type = static_cast<Qt::WindowType>(int(flags & Qt::WindowType_Mask));
589    if ((type & Qt::Popup) != Qt::Popup && (type & Qt::Dialog) != Qt::Dialog) {
590        NSWindowCollectionBehavior behavior = m_view.window.collectionBehavior;
591        const bool enableFullScreen = m_view.window.qt_fullScreen
592                                    || !(flags & Qt::CustomizeWindowHint)
593                                    || (flags & Qt::WindowFullscreenButtonHint);
594        if (enableFullScreen) {
595            behavior |= NSWindowCollectionBehaviorFullScreenPrimary;
596            behavior &= ~NSWindowCollectionBehaviorFullScreenAuxiliary;
597        } else {
598            behavior |= NSWindowCollectionBehaviorFullScreenAuxiliary;
599            behavior &= ~NSWindowCollectionBehaviorFullScreenPrimary;
600        }
601        m_view.window.collectionBehavior = behavior;
602    }
603
604    // Set styleMask and collectionBehavior before applying window level, as
605    // the window level change will trigger verification of the two properties.
606    m_view.window.level = this->windowLevel(flags);
607
608    m_view.window.hasShadow = !(flags & Qt::NoDropShadowWindowHint);
609
610    if (!(flags & Qt::FramelessWindowHint))
611        setWindowTitle(window()->title());
612
613    setWindowZoomButton(flags);
614
615    // Make window ignore mouse events if WindowTransparentForInput is set.
616    // Note that ignoresMouseEvents has a special initial state where events
617    // are ignored (passed through) based on window transparency, and that
618    // setting the property to false does not return us to that state. Instead,
619    // this makes the window capture all mouse events. Take care to only
620    // set the property if needed. FIXME: recreate window if needed or find
621    // some other way to implement WindowTransparentForInput.
622    bool ignoreMouse = flags & Qt::WindowTransparentForInput;
623    if (m_view.window.ignoresMouseEvents != ignoreMouse)
624        m_view.window.ignoresMouseEvents = ignoreMouse;
625}
626
627// ----------------------- Window state -----------------------
628
629/*!
630    Changes the state of the NSWindow, going in/out of minimize/zoomed/fullscreen
631
632    When this is called from QWindow::setWindowState(), the QWindow state has not been
633    updated yet, so window()->windowState() will reflect the previous state that was
634    reported to QtGui.
635*/
636void QCocoaWindow::setWindowState(Qt::WindowStates state)
637{
638    if (window()->isVisible())
639        applyWindowState(state); // Window state set for hidden windows take effect when show() is called
640}
641
642void QCocoaWindow::applyWindowState(Qt::WindowStates requestedState)
643{
644    if (!isContentView())
645        return;
646
647    const Qt::WindowState currentState = windowState();
648    const Qt::WindowState newState = QWindowPrivate::effectiveState(requestedState);
649
650    if (newState == currentState)
651        return;
652
653    qCDebug(lcQpaWindow) << "Applying" << newState << "to" << window() << "in" << currentState;
654
655    const NSSize contentSize = m_view.frame.size;
656    if (contentSize.width <= 0 || contentSize.height <= 0) {
657        // If content view width or height is 0 then the window animations will crash so
658        // do nothing. We report the current state back to reflect the failed operation.
659        qWarning("invalid window content view size, check your window geometry");
660        handleWindowStateChanged(HandleUnconditionally);
661        return;
662    }
663
664    const NSWindow *nsWindow = m_view.window;
665
666    if (nsWindow.styleMask & NSWindowStyleMaskUtilityWindow
667        && newState & (Qt::WindowMinimized | Qt::WindowFullScreen)) {
668        qWarning() << window()->type() << "windows cannot be made" << newState;
669        handleWindowStateChanged(HandleUnconditionally);
670        return;
671    }
672
673    const id sender = nsWindow;
674
675    // First we need to exit states that can't transition directly to other states
676    switch (currentState) {
677    case Qt::WindowMinimized:
678        [nsWindow deminiaturize:sender];
679        Q_ASSERT_X(windowState() != Qt::WindowMinimized, "QCocoaWindow",
680            "[NSWindow deminiaturize:] is synchronous");
681        break;
682    case Qt::WindowFullScreen: {
683        toggleFullScreen();
684        // Exiting fullscreen is not synchronous, so we need to wait for the
685        // NSWindowDidExitFullScreenNotification before continuing to apply
686        // the new state.
687        return;
688    }
689    default:;
690    }
691
692    // Then we apply the new state if needed
693    if (newState == windowState())
694        return;
695
696    switch (newState) {
697    case Qt::WindowFullScreen:
698        toggleFullScreen();
699        break;
700    case Qt::WindowMaximized:
701        toggleMaximized();
702        break;
703    case Qt::WindowMinimized:
704        [nsWindow miniaturize:sender];
705        break;
706    case Qt::WindowNoState:
707        if (windowState() == Qt::WindowMaximized)
708            toggleMaximized();
709        break;
710    default:
711        Q_UNREACHABLE();
712    }
713}
714
715Qt::WindowState QCocoaWindow::windowState() const
716{
717    // FIXME: Support compound states (Qt::WindowStates)
718
719    NSWindow *window = m_view.window;
720    if (window.miniaturized)
721        return Qt::WindowMinimized;
722    if (window.qt_fullScreen)
723        return Qt::WindowFullScreen;
724    if ((window.zoomed && !isTransitioningToFullScreen())
725        || (m_lastReportedWindowState == Qt::WindowMaximized && isTransitioningToFullScreen()))
726        return Qt::WindowMaximized;
727
728    // Note: We do not report Qt::WindowActive, even if isActive()
729    // is true, as QtGui does not expect this window state to be set.
730
731    return Qt::WindowNoState;
732}
733
734void QCocoaWindow::toggleMaximized()
735{
736    const NSWindow *window = m_view.window;
737
738    // The NSWindow needs to be resizable, otherwise the window will
739    // not be possible to zoom back to non-zoomed state.
740    const bool wasResizable = window.styleMask & NSWindowStyleMaskResizable;
741    window.styleMask |= NSWindowStyleMaskResizable;
742
743    const id sender = window;
744    [window zoom:sender];
745
746    if (!wasResizable)
747        window.styleMask &= ~NSWindowStyleMaskResizable;
748}
749
750void QCocoaWindow::toggleFullScreen()
751{
752    const NSWindow *window = m_view.window;
753
754    // The window needs to have the correct collection behavior for the
755    // toggleFullScreen call to have an effect. The collection behavior
756    // will be reset in windowDidEnterFullScreen/windowDidLeaveFullScreen.
757    window.collectionBehavior |= NSWindowCollectionBehaviorFullScreenPrimary;
758
759    const id sender = window;
760    [window toggleFullScreen:sender];
761}
762
763void QCocoaWindow::windowWillEnterFullScreen()
764{
765    if (!isContentView())
766        return;
767
768    // The NSWindow needs to be resizable, otherwise we'll end up with
769    // the normal window geometry, centered in the middle of the screen
770    // on a black background. The styleMask will be reset below.
771    m_view.window.styleMask |= NSWindowStyleMaskResizable;
772}
773
774bool QCocoaWindow::isTransitioningToFullScreen() const
775{
776    NSWindow *window = m_view.window;
777    return window.styleMask & NSWindowStyleMaskFullScreen && !window.qt_fullScreen;
778}
779
780void QCocoaWindow::windowDidEnterFullScreen()
781{
782    if (!isContentView())
783        return;
784
785    Q_ASSERT_X(m_view.window.qt_fullScreen, "QCocoaWindow",
786        "FullScreen category processes window notifications first");
787
788    // Reset to original styleMask
789    setWindowFlags(window()->flags());
790
791    handleWindowStateChanged();
792}
793
794void QCocoaWindow::windowWillExitFullScreen()
795{
796    if (!isContentView())
797        return;
798
799    // The NSWindow needs to be resizable, otherwise we'll end up with
800    // a weird zoom animation. The styleMask will be reset below.
801    m_view.window.styleMask |= NSWindowStyleMaskResizable;
802}
803
804void QCocoaWindow::windowDidExitFullScreen()
805{
806    if (!isContentView())
807        return;
808
809    Q_ASSERT_X(!m_view.window.qt_fullScreen, "QCocoaWindow",
810        "FullScreen category processes window notifications first");
811
812    // Reset to original styleMask
813    setWindowFlags(window()->flags());
814
815    Qt::WindowState requestedState = window()->windowState();
816
817    // Deliver update of QWindow state
818    handleWindowStateChanged();
819
820    if (requestedState != windowState() && requestedState != Qt::WindowFullScreen) {
821        // We were only going out of full screen as an intermediate step before
822        // progressing into the final step, so re-sync the desired state.
823       applyWindowState(requestedState);
824    }
825}
826
827void QCocoaWindow::windowWillMiniaturize()
828{
829    QCocoaIntegration::instance()->closePopups(window());
830}
831
832void QCocoaWindow::windowDidMiniaturize()
833{
834    if (!isContentView())
835        return;
836
837    handleWindowStateChanged();
838}
839
840void QCocoaWindow::windowDidDeminiaturize()
841{
842    if (!isContentView())
843        return;
844
845    handleWindowStateChanged();
846}
847
848void QCocoaWindow::handleWindowStateChanged(HandleFlags flags)
849{
850    Qt::WindowState currentState = windowState();
851    if (!(flags & HandleUnconditionally) && currentState == m_lastReportedWindowState)
852        return;
853
854    qCDebug(lcQpaWindow) << "QCocoaWindow::handleWindowStateChanged" <<
855        m_lastReportedWindowState << "-->" << currentState;
856
857    QWindowSystemInterface::handleWindowStateChanged<QWindowSystemInterface::SynchronousDelivery>(
858        window(), currentState, m_lastReportedWindowState);
859    m_lastReportedWindowState = currentState;
860}
861
862// ------------------------------------------------------------
863
864void QCocoaWindow::setWindowTitle(const QString &title)
865{
866    if (!isContentView())
867        return;
868
869    QMacAutoReleasePool pool;
870    m_view.window.title = title.toNSString();
871
872    if (title.isEmpty() && !window()->filePath().isEmpty()) {
873        // Clearing the title should restore the default filename
874        setWindowFilePath(window()->filePath());
875    }
876}
877
878void QCocoaWindow::setWindowFilePath(const QString &filePath)
879{
880    if (!isContentView())
881        return;
882
883    QMacAutoReleasePool pool;
884
885    if (window()->title().isNull())
886        [m_view.window setTitleWithRepresentedFilename:filePath.toNSString()];
887    else
888        m_view.window.representedFilename = filePath.toNSString();
889
890    // Changing the file path may affect icon visibility
891    setWindowIcon(window()->icon());
892}
893
894void QCocoaWindow::setWindowIcon(const QIcon &icon)
895{
896    if (!isContentView())
897        return;
898
899    NSButton *iconButton = [m_view.window standardWindowButton:NSWindowDocumentIconButton];
900    if (!iconButton) {
901        // Window icons are only supported on macOS in combination with a document filePath
902        return;
903    }
904
905    QMacAutoReleasePool pool;
906
907    if (icon.isNull()) {
908        iconButton.image = [NSWorkspace.sharedWorkspace iconForFile:m_view.window.representedFilename];
909    } else {
910        // Fall back to a size that looks good on the highest resolution screen available
911        auto fallbackSize = iconButton.frame.size.height * qGuiApp->devicePixelRatio();
912        iconButton.image = [NSImage imageFromQIcon:icon withSize:fallbackSize];
913    }
914}
915
916void QCocoaWindow::setAlertState(bool enabled)
917{
918    if (m_alertRequest == NoAlertRequest && enabled) {
919        m_alertRequest = [NSApp requestUserAttention:NSCriticalRequest];
920    } else if (m_alertRequest != NoAlertRequest && !enabled) {
921        [NSApp cancelUserAttentionRequest:m_alertRequest];
922        m_alertRequest = NoAlertRequest;
923    }
924}
925
926bool QCocoaWindow::isAlertState() const
927{
928    return m_alertRequest != NoAlertRequest;
929}
930
931void QCocoaWindow::raise()
932{
933    qCDebug(lcQpaWindow) << "QCocoaWindow::raise" << window();
934
935    // ### handle spaces (see Qt 4 raise_sys in qwidget_mac.mm)
936    if (isContentView()) {
937        if (m_view.window.visible) {
938            {
939                // Clean up auto-released temp objects from orderFront immediately.
940                // Failure to do so has been observed to cause leaks also beyond any outer
941                // autorelease pool (for example around a complete QWindow
942                // construct-show-raise-hide-delete cycle), counter to expected autoreleasepool
943                // behavior.
944                QMacAutoReleasePool pool;
945                [m_view.window orderFront:m_view.window];
946            }
947            static bool raiseProcess = qt_mac_resolveOption(true, "QT_MAC_SET_RAISE_PROCESS");
948            if (raiseProcess)
949                [NSApp activateIgnoringOtherApps:YES];
950        }
951    } else {
952        [m_view.superview addSubview:m_view positioned:NSWindowAbove relativeTo:nil];
953    }
954}
955
956void QCocoaWindow::lower()
957{
958    qCDebug(lcQpaWindow) << "QCocoaWindow::lower" << window();
959
960    if (isContentView()) {
961        if (m_view.window.visible)
962            [m_view.window orderBack:m_view.window];
963    } else {
964        [m_view.superview addSubview:m_view positioned:NSWindowBelow relativeTo:nil];
965    }
966}
967
968bool QCocoaWindow::isExposed() const
969{
970    return !m_exposedRect.isEmpty();
971}
972
973bool QCocoaWindow::isEmbedded() const
974{
975    // Child QWindows are not embedded
976    if (window()->parent())
977        return false;
978
979    // Top-level QWindows with non-Qt NSWindows are embedded
980    if (m_view.window)
981        return !([m_view.window isKindOfClass:[QNSWindow class]] ||
982                 [m_view.window isKindOfClass:[QNSPanel class]]);
983
984    // The window has no QWindow parent but also no NSWindow,
985    // conservatively reuturn false.
986    return false;
987}
988
989bool QCocoaWindow::isOpaque() const
990{
991    // OpenGL surfaces can be ordered either above(default) or below the NSWindow.
992    // When ordering below the window must be tranclucent.
993    static GLint openglSourfaceOrder = qt_mac_resolveOption(1, "QT_MAC_OPENGL_SURFACE_ORDER");
994
995    bool translucent = window()->format().alphaBufferSize() > 0
996                        || window()->opacity() < 1
997                        || !window()->mask().isEmpty()
998                        || (surface()->supportsOpenGL() && openglSourfaceOrder == -1);
999    return !translucent;
1000}
1001
1002void QCocoaWindow::propagateSizeHints()
1003{
1004    QMacAutoReleasePool pool;
1005    if (!isContentView())
1006        return;
1007
1008    qCDebug(lcQpaWindow) << "QCocoaWindow::propagateSizeHints" << window()
1009                              << "min:" << windowMinimumSize() << "max:" << windowMaximumSize()
1010                              << "increment:" << windowSizeIncrement()
1011                              << "base:" << windowBaseSize();
1012
1013    const NSWindow *window = m_view.window;
1014
1015    // Set the minimum content size.
1016    QSize minimumSize = windowMinimumSize();
1017    if (!minimumSize.isValid()) // minimumSize is (-1, -1) when not set. Make that (0, 0) for Cocoa.
1018        minimumSize = QSize(0, 0);
1019    window.contentMinSize = NSSizeFromCGSize(minimumSize.toCGSize());
1020
1021    // Set the maximum content size.
1022    window.contentMaxSize = NSSizeFromCGSize(windowMaximumSize().toCGSize());
1023
1024    // The window may end up with a fixed size; in this case the zoom button should be disabled.
1025    setWindowZoomButton(this->window()->flags());
1026
1027    // sizeIncrement is observed to take values of (-1, -1) and (0, 0) for windows that should be
1028    // resizable and that have no specific size increment set. Cocoa expects (1.0, 1.0) in this case.
1029    QSize sizeIncrement = windowSizeIncrement();
1030    if (sizeIncrement.isEmpty())
1031        sizeIncrement = QSize(1, 1);
1032    window.resizeIncrements = NSSizeFromCGSize(sizeIncrement.toCGSize());
1033
1034    QRect rect = geometry();
1035    QSize baseSize = windowBaseSize();
1036    if (!baseSize.isNull() && baseSize.isValid())
1037        [window setFrame:NSMakeRect(rect.x(), rect.y(), baseSize.width(), baseSize.height()) display:YES];
1038}
1039
1040void QCocoaWindow::setOpacity(qreal level)
1041{
1042    qCDebug(lcQpaWindow) << "QCocoaWindow::setOpacity" << level;
1043    if (!isContentView())
1044        return;
1045
1046    m_view.window.alphaValue = level;
1047}
1048
1049void QCocoaWindow::setMask(const QRegion &region)
1050{
1051    qCDebug(lcQpaWindow) << "QCocoaWindow::setMask" << window() << region;
1052
1053    if (m_view.layer) {
1054        if (!region.isEmpty()) {
1055            QCFType<CGMutablePathRef> maskPath = CGPathCreateMutable();
1056            for (const QRect &r : region)
1057                CGPathAddRect(maskPath, nullptr, r.toCGRect());
1058            CAShapeLayer *maskLayer = [CAShapeLayer layer];
1059            maskLayer.path = maskPath;
1060            m_view.layer.mask = maskLayer;
1061        } else {
1062            m_view.layer.mask = nil;
1063        }
1064    } else {
1065        if (isContentView()) {
1066            // Setting the mask requires invalidating the NSWindow shadow, but that needs
1067            // to happen after the backingstore has been redrawn, so that AppKit can pick
1068            // up the new window shape based on the backingstore content. Doing a display
1069            // directly here is not an option, as the window might not be exposed at this
1070            // time, and so would not result in an updated backingstore.
1071            m_needsInvalidateShadow = true;
1072            [m_view setNeedsDisplay:YES];
1073        }
1074    }
1075}
1076
1077bool QCocoaWindow::setKeyboardGrabEnabled(bool grab)
1078{
1079    qCDebug(lcQpaWindow) << "QCocoaWindow::setKeyboardGrabEnabled" << window() << grab;
1080    if (!isContentView())
1081        return false;
1082
1083    if (grab && ![m_view.window isKeyWindow])
1084        [m_view.window makeKeyWindow];
1085
1086    return true;
1087}
1088
1089bool QCocoaWindow::setMouseGrabEnabled(bool grab)
1090{
1091    qCDebug(lcQpaWindow) << "QCocoaWindow::setMouseGrabEnabled" << window() << grab;
1092    if (!isContentView())
1093        return false;
1094
1095    if (grab && ![m_view.window isKeyWindow])
1096        [m_view.window makeKeyWindow];
1097
1098    return true;
1099}
1100
1101WId QCocoaWindow::winId() const
1102{
1103    return WId(m_view);
1104}
1105
1106void QCocoaWindow::setParent(const QPlatformWindow *parentWindow)
1107{
1108    qCDebug(lcQpaWindow) << "QCocoaWindow::setParent" << window() << (parentWindow ? parentWindow->window() : 0);
1109
1110    // recreate the window for compatibility
1111    bool unhideAfterRecreate = parentWindow && !isEmbedded() && ![m_view isHidden];
1112    recreateWindowIfNeeded();
1113    if (unhideAfterRecreate)
1114        [m_view setHidden:NO];
1115    setCocoaGeometry(geometry());
1116}
1117
1118NSView *QCocoaWindow::view() const
1119{
1120    return m_view;
1121}
1122
1123NSWindow *QCocoaWindow::nativeWindow() const
1124{
1125    return m_view.window;
1126}
1127
1128void QCocoaWindow::setEmbeddedInForeignView()
1129{
1130    // Release any previosly created NSWindow.
1131    [m_nsWindow closeAndRelease];
1132    m_nsWindow = 0;
1133}
1134
1135// ----------------------- NSView notifications -----------------------
1136
1137void QCocoaWindow::viewDidChangeFrame()
1138{
1139    // Note: When the view is the content view, it would seem redundant
1140    // to deliver geometry changes both from windowDidResize and this
1141    // callback, but in some cases such as when macOS native tabbed
1142    // windows are enabled we may end up with the wrong geometry in
1143    // the initial windowDidResize callback when a new tab is created.
1144    handleGeometryChange();
1145}
1146
1147/*!
1148    Callback for NSViewGlobalFrameDidChangeNotification.
1149
1150    Posted whenever an NSView object that has attached surfaces (that is,
1151    NSOpenGLContext objects) moves to a different screen, or other cases
1152    where the NSOpenGLContext object needs to be updated.
1153*/
1154void QCocoaWindow::viewDidChangeGlobalFrame()
1155{
1156    [m_view setNeedsDisplay:YES];
1157}
1158
1159// ----------------------- NSWindow notifications -----------------------
1160
1161// Note: The following notifications are delivered to every QCocoaWindow
1162// that is a child of the NSWindow that triggered the notification. Each
1163// callback should make sure to filter out notifications if they do not
1164// apply to that QCocoaWindow, e.g. if the window is not a content view.
1165
1166void QCocoaWindow::windowWillMove()
1167{
1168    // Close any open popups on window move
1169    QCocoaIntegration::instance()->closePopups();
1170}
1171
1172void QCocoaWindow::windowDidMove()
1173{
1174    if (!isContentView())
1175        return;
1176
1177    handleGeometryChange();
1178
1179    // Moving a window might bring it out of maximized state
1180    handleWindowStateChanged();
1181}
1182
1183void QCocoaWindow::windowDidResize()
1184{
1185    if (!isContentView())
1186        return;
1187
1188    handleGeometryChange();
1189
1190    if (!m_view.inLiveResize)
1191        handleWindowStateChanged();
1192}
1193
1194void QCocoaWindow::windowDidEndLiveResize()
1195{
1196    if (!isContentView())
1197        return;
1198
1199    handleWindowStateChanged();
1200}
1201
1202void QCocoaWindow::windowDidBecomeKey()
1203{
1204    if (!isContentView())
1205        return;
1206
1207    if (isForeignWindow())
1208        return;
1209
1210    if (m_windowUnderMouse) {
1211        QPointF windowPoint;
1212        QPointF screenPoint;
1213        [qnsview_cast(m_view) convertFromScreen:[NSEvent mouseLocation] toWindowPoint:&windowPoint andScreenPoint:&screenPoint];
1214        QWindowSystemInterface::handleEnterEvent(m_enterLeaveTargetWindow, windowPoint, screenPoint);
1215    }
1216
1217    if (!windowIsPopupType())
1218        QWindowSystemInterface::handleWindowActivated<QWindowSystemInterface::SynchronousDelivery>(window());
1219}
1220
1221void QCocoaWindow::windowDidResignKey()
1222{
1223    if (!isContentView())
1224        return;
1225
1226    if (isForeignWindow())
1227        return;
1228
1229    // The current key window will be non-nil if another window became key. If that
1230    // window is a Qt window, we delay the window activation event until the didBecomeKey
1231    // notification is delivered to the active window, to ensure an atomic update.
1232    NSWindow *newKeyWindow = [NSApp keyWindow];
1233    if (newKeyWindow && newKeyWindow != m_view.window
1234        && [newKeyWindow conformsToProtocol:@protocol(QNSWindowProtocol)])
1235        return;
1236
1237    // Lost key window, go ahead and set the active window to zero
1238    if (!windowIsPopupType())
1239        QWindowSystemInterface::handleWindowActivated<QWindowSystemInterface::SynchronousDelivery>(nullptr);
1240}
1241
1242void QCocoaWindow::windowDidOrderOnScreen()
1243{
1244    [m_view setNeedsDisplay:YES];
1245}
1246
1247void QCocoaWindow::windowDidOrderOffScreen()
1248{
1249    handleExposeEvent(QRegion());
1250}
1251
1252void QCocoaWindow::windowDidChangeOcclusionState()
1253{
1254    bool visible = m_view.window.occlusionState & NSWindowOcclusionStateVisible;
1255    qCDebug(lcQpaWindow) << "QCocoaWindow::windowDidChangeOcclusionState" << window() << "is now" << (visible ? "visible" : "occluded");
1256    if (visible)
1257        [m_view setNeedsDisplay:YES];
1258    else
1259        handleExposeEvent(QRegion());
1260}
1261
1262void QCocoaWindow::windowDidChangeScreen()
1263{
1264    if (!window())
1265        return;
1266
1267    // Note: When a window is resized to 0x0 Cocoa will report the window's screen as nil
1268    auto *currentScreen = QCocoaScreen::get(m_view.window.screen);
1269    auto *previousScreen = static_cast<QCocoaScreen*>(screen());
1270
1271    Q_ASSERT_X(!m_view.window.screen || currentScreen,
1272        "QCocoaWindow", "Failed to get QCocoaScreen for NSScreen");
1273
1274    // Note: The previous screen may be the same as the current screen, either because
1275    // a) the screen was just reconfigured, which still results in AppKit sending an
1276    // NSWindowDidChangeScreenNotification, b) because the previous screen was removed,
1277    // and we ended up calling QWindow::setScreen to move the window, which doesn't
1278    // actually move the window to the new screen, or c) because we've delivered the
1279    // screen change to the top level window, which will make all the child windows
1280    // of that window report the new screen when requested via QWindow::screen().
1281    // We still need to deliver the screen change in all these cases, as the
1282    // device-pixel ratio may have changed, and needs to be delivered to all
1283    // windows, both top level and child windows.
1284
1285    qCDebug(lcQpaWindow) << "Screen changed for" << window() << "from" << previousScreen << "to" << currentScreen;
1286    QWindowSystemInterface::handleWindowScreenChanged<QWindowSystemInterface::SynchronousDelivery>(
1287        window(), currentScreen ? currentScreen->screen() : nullptr);
1288
1289    if (currentScreen && hasPendingUpdateRequest()) {
1290        // Restart display-link on new screen. We need to do this unconditionally,
1291        // since we can't rely on the previousScreen reflecting whether or not the
1292        // window actually moved from one screen to another, or just stayed on the
1293        // same screen.
1294        currentScreen->requestUpdate();
1295    }
1296}
1297
1298void QCocoaWindow::windowWillClose()
1299{
1300    // Close any open popups on window closing.
1301    if (window() && !windowIsPopupType(window()->type()))
1302        QCocoaIntegration::instance()->closePopups();
1303}
1304
1305// ----------------------- NSWindowDelegate callbacks -----------------------
1306
1307bool QCocoaWindow::windowShouldClose()
1308{
1309    qCDebug(lcQpaWindow) << "QCocoaWindow::windowShouldClose" << window();
1310    // This callback should technically only determine if the window
1311    // should (be allowed to) close, but since our QPA API to determine
1312    // that also involves actually closing the window we do both at the
1313    // same time, instead of doing the latter in windowWillClose.
1314    return QWindowSystemInterface::handleCloseEvent<QWindowSystemInterface::SynchronousDelivery>(window());
1315}
1316
1317// ----------------------------- QPA forwarding -----------------------------
1318
1319void QCocoaWindow::handleGeometryChange()
1320{
1321    // Prevent geometry change during initialization, as that will result
1322    // in a resize event, and Qt expects those to come after the show event.
1323    // FIXME: Remove once we've clarified the Qt behavior for this.
1324    if (!m_initialized)
1325        return;
1326
1327    // It can happen that the current NSWindow is nil (if we are changing styleMask
1328    // from/to borderless, and the content view is being re-parented), which results
1329    // in invalid coordinates.
1330    if (m_inSetStyleMask && !m_view.window)
1331        return;
1332
1333    QRect newGeometry;
1334    if (isContentView() && !isEmbedded()) {
1335        // Content views are positioned at (0, 0) in the window, so we resolve via the window
1336        CGRect contentRect = [m_view.window contentRectForFrameRect:m_view.window.frame];
1337
1338        // The result above is in native screen coordinates, so remap to the Qt coordinate system
1339        newGeometry = QCocoaScreen::mapFromNative(contentRect).toRect();
1340    } else {
1341        // QNSView has isFlipped set, so no need to remap the geometry
1342        newGeometry = QRectF::fromCGRect(m_view.frame).toRect();
1343    }
1344
1345    qCDebug(lcQpaWindow) << "QCocoaWindow::handleGeometryChange" << window()
1346                               << "current" << geometry() << "new" << newGeometry;
1347
1348    QWindowSystemInterface::handleGeometryChange(window(), newGeometry);
1349
1350    // Guard against processing window system events during QWindow::setGeometry
1351    // calls, which Qt and Qt applications do not expect.
1352    if (!m_inSetGeometry)
1353        QWindowSystemInterface::flushWindowSystemEvents(QEventLoop::ExcludeUserInputEvents | QEventLoop::ExcludeSocketNotifiers);
1354}
1355
1356void QCocoaWindow::handleExposeEvent(const QRegion &region)
1357{
1358    // Ideally we'd implement isExposed() in terms of these properties,
1359    // plus the occlusionState of the NSWindow, and let the expose event
1360    // pull the exposed state out when needed. However, when the window
1361    // is first shown we receive a drawRect call where the occlusionState
1362    // of the window is still hidden, but we still want to prepare the
1363    // window for display by issuing an expose event to Qt. To work around
1364    // this we don't use the occlusionState directly, but instead base
1365    // the exposed state on the region we get in, which in the case of
1366    // a window being obscured is an empty region, and in the case of
1367    // a drawRect call is a non-null region, even if occlusionState
1368    // is still hidden. This ensures the window is prepared for display.
1369    if (m_view.window.visible && m_view.window.screen
1370            && !geometry().size().isEmpty() && !region.isEmpty()
1371            && !m_view.hiddenOrHasHiddenAncestor) {
1372        m_exposedRect = region.boundingRect();
1373    } else {
1374        m_exposedRect = QRect();
1375    }
1376
1377    qCDebug(lcQpaDrawing) << "QCocoaWindow::handleExposeEvent" << window() << region << "isExposed" << isExposed();
1378    QWindowSystemInterface::handleExposeEvent<QWindowSystemInterface::SynchronousDelivery>(window(), region);
1379}
1380
1381// --------------------------------------------------------------------------
1382
1383bool QCocoaWindow::windowIsPopupType(Qt::WindowType type) const
1384{
1385    if (type == Qt::Widget)
1386        type = window()->type();
1387    if (type == Qt::Tool)
1388        return false; // Qt::Tool has the Popup bit set but isn't, at least on Mac.
1389
1390    return ((type & Qt::Popup) == Qt::Popup);
1391}
1392
1393/*!
1394    Checks if the window is the content view of its immediate NSWindow.
1395
1396    Being the content view of a NSWindow means the QWindow is
1397    the highest accessible NSView object in the window's view
1398    hierarchy.
1399
1400    This is the case if the QWindow is a top level window.
1401*/
1402bool QCocoaWindow::isContentView() const
1403{
1404    return m_view.window.contentView == m_view;
1405}
1406
1407/*!
1408    Recreates (or removes) the NSWindow for this QWindow, if needed.
1409
1410    A QWindow may need a corresponding NSWindow/NSPanel, depending on
1411    whether or not it's a top level or not, window flags, etc.
1412*/
1413void QCocoaWindow::recreateWindowIfNeeded()
1414{
1415    QMacAutoReleasePool pool;
1416
1417    QPlatformWindow *parentWindow = QPlatformWindow::parent();
1418
1419    const bool isEmbeddedView = isEmbedded();
1420    RecreationReasons recreateReason = RecreationNotNeeded;
1421
1422    QCocoaWindow *oldParentCocoaWindow = nullptr;
1423    if (QNSView *qnsView = qnsview_cast(m_view.superview))
1424        oldParentCocoaWindow = qnsView.platformWindow;
1425
1426    if (parentWindow != oldParentCocoaWindow)
1427         recreateReason |= ParentChanged;
1428
1429    if (!m_view.window)
1430        recreateReason |= MissingWindow;
1431
1432    // If the modality has changed the style mask will need updating
1433    if (m_windowModality != window()->modality())
1434        recreateReason |= WindowModalityChanged;
1435
1436    Qt::WindowType type = window()->type();
1437
1438    const bool shouldBeContentView = !parentWindow
1439        && !((type & Qt::SubWindow) == Qt::SubWindow)
1440        && !isEmbeddedView;
1441    if (isContentView() != shouldBeContentView)
1442        recreateReason |= ContentViewChanged;
1443
1444    const bool isPanel = isContentView() && [m_view.window isKindOfClass:[QNSPanel class]];
1445    const bool shouldBePanel = shouldBeContentView &&
1446        ((type & Qt::Popup) == Qt::Popup || (type & Qt::Dialog) == Qt::Dialog);
1447
1448    if (isPanel != shouldBePanel)
1449         recreateReason |= PanelChanged;
1450
1451    qCDebug(lcQpaWindow) << "QCocoaWindow::recreateWindowIfNeeded" << window() << recreateReason;
1452
1453    if (recreateReason == RecreationNotNeeded)
1454        return;
1455
1456    QCocoaWindow *parentCocoaWindow = static_cast<QCocoaWindow *>(parentWindow);
1457
1458    // Remove current window (if any)
1459    if ((isContentView() && !shouldBeContentView) || (recreateReason & PanelChanged)) {
1460        if (m_nsWindow) {
1461            qCDebug(lcQpaWindow) << "Getting rid of existing window" << m_nsWindow;
1462            if (m_nsWindow.observationInfo) {
1463                qCCritical(lcQpaWindow) << m_nsWindow << "has active key-value observers (KVO)!"
1464                    << "These will stop working now that the window is recreated, and will result in exceptions"
1465                    << "when the observers are removed. Break in QCocoaWindow::recreateWindowIfNeeded to debug.";
1466            }
1467            [m_nsWindow closeAndRelease];
1468            if (isContentView() && !isEmbeddedView) {
1469                // We explicitly disassociate m_view from the window's contentView,
1470                // as AppKit does not automatically do this in response to removing
1471                // the view from the NSThemeFrame subview list, so we might end up
1472                // with a NSWindow contentView pointing to a deallocated NSView.
1473                m_view.window.contentView = nil;
1474            }
1475            m_nsWindow = nil;
1476        }
1477    }
1478
1479    if (shouldBeContentView && !m_nsWindow) {
1480        // Move view to new NSWindow if needed
1481        if (auto *newWindow = createNSWindow(shouldBePanel)) {
1482            qCDebug(lcQpaWindow) << "Ensuring that" << m_view << "is content view for" << newWindow;
1483            [m_view setPostsFrameChangedNotifications:NO];
1484            [newWindow setContentView:m_view];
1485            [m_view setPostsFrameChangedNotifications:YES];
1486
1487            m_nsWindow = newWindow;
1488            Q_ASSERT(m_view.window == m_nsWindow);
1489        }
1490    }
1491
1492    if (isEmbeddedView) {
1493        // An embedded window doesn't have its own NSWindow.
1494    } else if (!parentWindow) {
1495        // QPlatformWindow subclasses must sync up with QWindow on creation:
1496        propagateSizeHints();
1497        setWindowFlags(window()->flags());
1498        setWindowTitle(window()->title());
1499        setWindowFilePath(window()->filePath()); // Also sets window icon
1500        setWindowState(window()->windowState());
1501    } else {
1502        // Child windows have no NSWindow, link the NSViews instead.
1503        [parentCocoaWindow->m_view addSubview:m_view];
1504        QRect rect = windowGeometry();
1505        // Prevent setting a (0,0) window size; causes opengl context
1506        // "Invalid Drawable" warnings.
1507        if (rect.isNull())
1508            rect.setSize(QSize(1, 1));
1509        NSRect frame = NSMakeRect(rect.x(), rect.y(), rect.width(), rect.height());
1510        [m_view setFrame:frame];
1511        [m_view setHidden:!window()->isVisible()];
1512    }
1513
1514    const qreal opacity = qt_window_private(window())->opacity;
1515    if (!qFuzzyCompare(opacity, qreal(1.0)))
1516        setOpacity(opacity);
1517
1518    setMask(QHighDpi::toNativeLocalRegion(window()->mask(), window()));
1519
1520    // top-level QWindows may have an attached NSToolBar, call
1521    // update function which will attach to the NSWindow.
1522    if (!parentWindow && !isEmbeddedView)
1523        updateNSToolbar();
1524}
1525
1526void QCocoaWindow::requestUpdate()
1527{
1528    qCDebug(lcQpaDrawing) << "QCocoaWindow::requestUpdate" << window()
1529        << "using" << (updatesWithDisplayLink() ? "display-link" : "timer");
1530
1531    if (updatesWithDisplayLink()) {
1532        static_cast<QCocoaScreen *>(screen())->requestUpdate();
1533    } else {
1534        // Fall back to the un-throttled timer-based callback
1535        QPlatformWindow::requestUpdate();
1536    }
1537}
1538
1539bool QCocoaWindow::updatesWithDisplayLink() const
1540{
1541    // Update via CVDisplayLink if Vsync is enabled
1542    return format().swapInterval() != 0;
1543}
1544
1545void QCocoaWindow::deliverUpdateRequest()
1546{
1547    qCDebug(lcQpaDrawing) << "Delivering update request to" << window();
1548    QPlatformWindow::deliverUpdateRequest();
1549}
1550
1551void QCocoaWindow::requestActivateWindow()
1552{
1553    QMacAutoReleasePool pool;
1554    [m_view.window makeFirstResponder:m_view];
1555    [m_view.window makeKeyWindow];
1556}
1557
1558QCocoaNSWindow *QCocoaWindow::createNSWindow(bool shouldBePanel)
1559{
1560    QMacAutoReleasePool pool;
1561
1562    Qt::WindowType type = window()->type();
1563    Qt::WindowFlags flags = window()->flags();
1564
1565    QRect rect = geometry();
1566
1567    QScreen *targetScreen = nullptr;
1568    for (QScreen *screen : QGuiApplication::screens()) {
1569        if (screen->geometry().contains(rect.topLeft())) {
1570            targetScreen = screen;
1571            break;
1572        }
1573    }
1574
1575    NSWindowStyleMask styleMask = windowStyleMask(flags);
1576
1577    if (!targetScreen) {
1578        qCWarning(lcQpaWindow) << "Window position" << rect << "outside any known screen, using primary screen";
1579        targetScreen = QGuiApplication::primaryScreen();
1580        // Unless the window is created as borderless AppKit won't find a position and
1581        // screen that's close to the requested invalid position, and will always place
1582        // the window on the primary screen.
1583        styleMask = NSWindowStyleMaskBorderless;
1584    }
1585
1586    rect.translate(-targetScreen->geometry().topLeft());
1587    auto *targetCocoaScreen = static_cast<QCocoaScreen *>(targetScreen->handle());
1588    NSRect contentRect = QCocoaScreen::mapToNative(rect, targetCocoaScreen);
1589
1590    if (targetScreen->primaryOrientation() == Qt::PortraitOrientation) {
1591        // The macOS window manager has a bug, where if a screen is rotated, it will not allow
1592        // a window to be created within the area of the screen that has a Y coordinate (I quadrant)
1593        // higher than the height of the screen in its non-rotated state (including a magic padding
1594        // of 24 points), unless the window is created with the NSWindowStyleMaskBorderless style mask.
1595        if (styleMask && (contentRect.origin.y + 24 > targetScreen->geometry().width())) {
1596            qCDebug(lcQpaWindow) << "Window positioned on portrait screen."
1597                << "Adjusting style mask during creation";
1598            styleMask = NSWindowStyleMaskBorderless;
1599        }
1600    }
1601
1602    // Create NSWindow
1603    Class windowClass = shouldBePanel ? [QNSPanel class] : [QNSWindow class];
1604    QCocoaNSWindow *nsWindow = [[windowClass alloc] initWithContentRect:contentRect
1605        // Mask will be updated in setWindowFlags if not the final mask
1606        styleMask:styleMask
1607        // Deferring window creation breaks OpenGL (the GL context is
1608        // set up before the window is shown and needs a proper window)
1609        backing:NSBackingStoreBuffered defer:NO
1610        screen:targetCocoaScreen->nativeScreen()
1611        platformWindow:this];
1612
1613    // The resulting screen can be different from the screen requested if
1614    // for example the application has been assigned to a specific display.
1615    auto resultingScreen = QCocoaScreen::get(nsWindow.screen);
1616
1617    // But may not always be resolved at this point, in which case we fall back
1618    // to the target screen. The real screen will be delivered as a screen change
1619    // when resolved as part of ordering the window on screen.
1620    if (!resultingScreen)
1621        resultingScreen = targetCocoaScreen;
1622
1623    if (resultingScreen->screen() != window()->screen()) {
1624        QWindowSystemInterface::handleWindowScreenChanged<
1625            QWindowSystemInterface::SynchronousDelivery>(window(), resultingScreen->screen());
1626    }
1627
1628    static QSharedPointer<QNSWindowDelegate> sharedDelegate([[QNSWindowDelegate alloc] init],
1629        [](QNSWindowDelegate *delegate) { [delegate release]; });
1630    nsWindow.delegate = sharedDelegate.get();
1631
1632    // Prevent Cocoa from releasing the window on close. Qt
1633    // handles the close event asynchronously and we want to
1634    // make sure that NSWindow stays valid until the
1635    // QCocoaWindow is deleted by Qt.
1636    [nsWindow setReleasedWhenClosed:NO];
1637
1638    if (alwaysShowToolWindow()) {
1639        static dispatch_once_t onceToken;
1640        dispatch_once(&onceToken, ^{
1641            NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
1642            [center addObserver:[QNSWindow class] selector:@selector(applicationActivationChanged:)
1643                name:NSApplicationWillResignActiveNotification object:nil];
1644            [center addObserver:[QNSWindow class] selector:@selector(applicationActivationChanged:)
1645                name:NSApplicationWillBecomeActiveNotification object:nil];
1646        });
1647    }
1648
1649    nsWindow.restorable = NO;
1650    nsWindow.level = windowLevel(flags);
1651    nsWindow.tabbingMode = NSWindowTabbingModeDisallowed;
1652
1653    if (shouldBePanel) {
1654        // Qt::Tool windows hide on app deactivation, unless Qt::WA_MacAlwaysShowToolWindow is set
1655        nsWindow.hidesOnDeactivate = ((type & Qt::Tool) == Qt::Tool) && !alwaysShowToolWindow();
1656
1657        // Make popup windows show on the same desktop as the parent full-screen window
1658        nsWindow.collectionBehavior = NSWindowCollectionBehaviorFullScreenAuxiliary;
1659
1660        if ((type & Qt::Popup) == Qt::Popup) {
1661            nsWindow.hasShadow = YES;
1662            nsWindow.animationBehavior = NSWindowAnimationBehaviorUtilityWindow;
1663        }
1664    }
1665
1666    // Persist modality so we can detect changes later on
1667    m_windowModality = QPlatformWindow::window()->modality();
1668
1669    applyContentBorderThickness(nsWindow);
1670
1671    if (format().colorSpace() == QSurfaceFormat::sRGBColorSpace)
1672        nsWindow.colorSpace = NSColorSpace.sRGBColorSpace;
1673
1674    return nsWindow;
1675}
1676
1677bool QCocoaWindow::alwaysShowToolWindow() const
1678{
1679    return qt_mac_resolveOption(false, window(), "_q_macAlwaysShowToolWindow", "");
1680}
1681
1682void QCocoaWindow::removeMonitor()
1683{
1684    if (!monitor)
1685        return;
1686    [NSEvent removeMonitor:monitor];
1687    monitor = nil;
1688}
1689
1690bool QCocoaWindow::setWindowModified(bool modified)
1691{
1692    if (!isContentView())
1693        return false;
1694
1695    m_view.window.documentEdited = modified;
1696    return true;
1697}
1698
1699void QCocoaWindow::setMenubar(QCocoaMenuBar *mb)
1700{
1701    m_menubar = mb;
1702}
1703
1704QCocoaMenuBar *QCocoaWindow::menubar() const
1705{
1706    return m_menubar;
1707}
1708
1709void QCocoaWindow::setWindowCursor(NSCursor *cursor)
1710{
1711    // Setting a cursor in a foreign view is not supported
1712    if (isForeignWindow())
1713        return;
1714
1715    QNSView *view = qnsview_cast(m_view);
1716    if (cursor == view.cursor)
1717        return;
1718
1719    view.cursor = cursor;
1720
1721    [m_view.window invalidateCursorRectsForView:m_view];
1722}
1723
1724void QCocoaWindow::registerTouch(bool enable)
1725{
1726    m_registerTouchCount += enable ? 1 : -1;
1727    if (enable && m_registerTouchCount == 1)
1728        m_view.allowedTouchTypes |= NSTouchTypeMaskIndirect;
1729    else if (m_registerTouchCount == 0)
1730        m_view.allowedTouchTypes &= ~NSTouchTypeMaskIndirect;
1731}
1732
1733void QCocoaWindow::setContentBorderThickness(int topThickness, int bottomThickness)
1734{
1735    m_topContentBorderThickness = topThickness;
1736    m_bottomContentBorderThickness = bottomThickness;
1737    bool enable = (topThickness > 0 || bottomThickness > 0);
1738    m_drawContentBorderGradient = enable;
1739
1740    applyContentBorderThickness();
1741}
1742
1743void QCocoaWindow::registerContentBorderArea(quintptr identifier, int upper, int lower)
1744{
1745    m_contentBorderAreas.insert(identifier, BorderRange(identifier, upper, lower));
1746    applyContentBorderThickness();
1747}
1748
1749void QCocoaWindow::setContentBorderAreaEnabled(quintptr identifier, bool enable)
1750{
1751    m_enabledContentBorderAreas.insert(identifier, enable);
1752    applyContentBorderThickness();
1753}
1754
1755void QCocoaWindow::setContentBorderEnabled(bool enable)
1756{
1757    m_drawContentBorderGradient = enable;
1758    applyContentBorderThickness();
1759}
1760
1761void QCocoaWindow::applyContentBorderThickness(NSWindow *window)
1762{
1763    if (!window && isContentView())
1764        window = m_view.window;
1765
1766    if (!window)
1767        return;
1768
1769    if (!m_drawContentBorderGradient) {
1770        window.styleMask = window.styleMask & ~NSWindowStyleMaskTexturedBackground;
1771        [window.contentView.superview setNeedsDisplay:YES];
1772        window.titlebarAppearsTransparent = NO;
1773        return;
1774    }
1775
1776    // Find consecutive registered border areas, starting from the top.
1777    std::vector<BorderRange> ranges(m_contentBorderAreas.cbegin(), m_contentBorderAreas.cend());
1778    std::sort(ranges.begin(), ranges.end());
1779    int effectiveTopContentBorderThickness = m_topContentBorderThickness;
1780    for (BorderRange range : ranges) {
1781        // Skip disiabled ranges (typically hidden tool bars)
1782        if (!m_enabledContentBorderAreas.value(range.identifier, false))
1783            continue;
1784
1785        // Is this sub-range adjacent to or overlaping the
1786        // existing total border area range? If so merge
1787        // it into the total range,
1788        if (range.upper <= (effectiveTopContentBorderThickness + 1))
1789            effectiveTopContentBorderThickness = qMax(effectiveTopContentBorderThickness, range.lower);
1790        else
1791            break;
1792    }
1793
1794    int effectiveBottomContentBorderThickness = m_bottomContentBorderThickness;
1795
1796    [window setStyleMask:[window styleMask] | NSWindowStyleMaskTexturedBackground];
1797    window.titlebarAppearsTransparent = YES;
1798
1799    // Setting titlebarAppearsTransparent to YES means that the border thickness has to account
1800    // for the title bar height as well, otherwise sheets will not be presented at the correct
1801    // position, which should be (title bar height + top content border size).
1802    const NSRect frameRect = window.frame;
1803    const NSRect contentRect = [window contentRectForFrameRect:frameRect];
1804    const CGFloat titlebarHeight = frameRect.size.height - contentRect.size.height;
1805    effectiveTopContentBorderThickness += titlebarHeight;
1806
1807    [window setContentBorderThickness:effectiveTopContentBorderThickness forEdge:NSMaxYEdge];
1808    [window setAutorecalculatesContentBorderThickness:NO forEdge:NSMaxYEdge];
1809
1810    [window setContentBorderThickness:effectiveBottomContentBorderThickness forEdge:NSMinYEdge];
1811    [window setAutorecalculatesContentBorderThickness:NO forEdge:NSMinYEdge];
1812
1813    [[[window contentView] superview] setNeedsDisplay:YES];
1814}
1815
1816void QCocoaWindow::updateNSToolbar()
1817{
1818    if (!isContentView())
1819        return;
1820
1821    NSToolbar *toolbar = QCocoaIntegration::instance()->toolbar(window());
1822    const NSWindow *window = m_view.window;
1823
1824    if (window.toolbar == toolbar)
1825       return;
1826
1827    window.toolbar = toolbar;
1828    window.showsToolbarButton = YES;
1829}
1830
1831bool QCocoaWindow::testContentBorderAreaPosition(int position) const
1832{
1833    if (!m_drawContentBorderGradient || !isContentView())
1834        return false;
1835
1836    // Determine if the given y postion (relative to the content area) is inside the
1837    // unified toolbar area. Note that the value returned by contentBorderThicknessForEdge
1838    // includes the title bar height; subtract it.
1839    const int contentBorderThickness = [m_view.window contentBorderThicknessForEdge:NSMaxYEdge];
1840    const NSRect frameRect = m_view.window.frame;
1841    const NSRect contentRect = [m_view.window contentRectForFrameRect:frameRect];
1842    const CGFloat titlebarHeight = frameRect.size.height - contentRect.size.height;
1843    return 0 <= position && position < (contentBorderThickness - titlebarHeight);
1844}
1845
1846qreal QCocoaWindow::devicePixelRatio() const
1847{
1848    // The documented way to observe the relationship between device-independent
1849    // and device pixels is to use one for the convertToBacking functions. Other
1850    // methods such as [NSWindow backingScaleFacor] might not give the correct
1851    // result, for example if setWantsBestResolutionOpenGLSurface is not set or
1852    // or ignored by the OpenGL driver.
1853    NSSize backingSize = [m_view convertSizeToBacking:NSMakeSize(1.0, 1.0)];
1854    return backingSize.height;
1855}
1856
1857QWindow *QCocoaWindow::childWindowAt(QPoint windowPoint)
1858{
1859    QWindow *targetWindow = window();
1860    for (QObject *child : targetWindow->children())
1861        if (QWindow *childWindow = qobject_cast<QWindow *>(child))
1862            if (QPlatformWindow *handle = childWindow->handle())
1863                if (handle->isExposed() && childWindow->geometry().contains(windowPoint))
1864                    targetWindow = static_cast<QCocoaWindow*>(handle)->childWindowAt(windowPoint - childWindow->position());
1865
1866    return targetWindow;
1867}
1868
1869bool QCocoaWindow::shouldRefuseKeyWindowAndFirstResponder()
1870{
1871    // This function speaks up if there's any reason
1872    // to refuse key window or first responder state.
1873
1874    if (window()->flags() & Qt::WindowDoesNotAcceptFocus)
1875        return true;
1876
1877    if (m_inSetVisible) {
1878        QVariant showWithoutActivating = window()->property("_q_showWithoutActivating");
1879        if (showWithoutActivating.isValid() && showWithoutActivating.toBool())
1880            return true;
1881    }
1882
1883    return false;
1884}
1885
1886QPoint QCocoaWindow::bottomLeftClippedByNSWindowOffsetStatic(QWindow *window)
1887{
1888    if (window->handle())
1889        return static_cast<QCocoaWindow *>(window->handle())->bottomLeftClippedByNSWindowOffset();
1890    return QPoint();
1891}
1892
1893QPoint QCocoaWindow::bottomLeftClippedByNSWindowOffset() const
1894{
1895    if (!m_view)
1896        return QPoint();
1897    const NSPoint origin = [m_view isFlipped] ? NSMakePoint(0, [m_view frame].size.height)
1898                                                     : NSMakePoint(0,                                 0);
1899    const NSRect visibleRect = [m_view visibleRect];
1900
1901    return QPoint(visibleRect.origin.x, -visibleRect.origin.y + (origin.y - visibleRect.size.height));
1902}
1903
1904QMargins QCocoaWindow::frameMargins() const
1905{
1906    if (!isContentView())
1907        return QMargins();
1908
1909    NSRect frameW = m_view.window.frame;
1910    NSRect frameC = [m_view.window contentRectForFrameRect:frameW];
1911
1912    return QMargins(frameW.origin.x - frameC.origin.x,
1913        (frameW.origin.y + frameW.size.height) - (frameC.origin.y + frameC.size.height),
1914        (frameW.origin.x + frameW.size.width) - (frameC.origin.x + frameC.size.width),
1915        frameC.origin.y - frameW.origin.y);
1916}
1917
1918void QCocoaWindow::setFrameStrutEventsEnabled(bool enabled)
1919{
1920    m_frameStrutEventsEnabled = enabled;
1921}
1922
1923#ifndef QT_NO_DEBUG_STREAM
1924QDebug operator<<(QDebug debug, const QCocoaWindow *window)
1925{
1926    QDebugStateSaver saver(debug);
1927    debug.nospace();
1928    debug << "QCocoaWindow(" << (const void *)window;
1929    if (window)
1930        debug << ", window=" << window->window();
1931    debug << ')';
1932    return debug;
1933}
1934#endif // !QT_NO_DEBUG_STREAM
1935
1936#include "moc_qcocoawindow.cpp"
1937
1938QT_END_NAMESPACE
1939