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 ®ion) 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 ®ion) 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