diff options
Diffstat (limited to 'src/plugins/platforms/cocoa')
36 files changed, 2093 insertions, 2027 deletions
diff --git a/src/plugins/platforms/cocoa/cocoa.pro b/src/plugins/platforms/cocoa/cocoa.pro index 5a00e1b7ec..5354bdafbc 100644 --- a/src/plugins/platforms/cocoa/cocoa.pro +++ b/src/plugins/platforms/cocoa/cocoa.pro @@ -2,10 +2,12 @@ TARGET = qcocoa SOURCES += main.mm \ qcocoaintegration.mm \ + qcocoascreen.mm \ qcocoatheme.mm \ qcocoabackingstore.mm \ qcocoawindow.mm \ qnsview.mm \ + qnswindow.mm \ qnsviewaccessibility.mm \ qnswindowdelegate.mm \ qcocoanativeinterface.mm \ @@ -34,10 +36,12 @@ SOURCES += main.mm \ messages.cpp HEADERS += qcocoaintegration.h \ + qcocoascreen.h \ qcocoatheme.h \ qcocoabackingstore.h \ qcocoawindow.h \ qnsview.h \ + qnswindow.h \ qnswindowdelegate.h \ qcocoanativeinterface.h \ qcocoaeventdispatcher.h \ @@ -72,12 +76,12 @@ qtConfig(opengl.*) { RESOURCES += qcocoaresources.qrc -LIBS += -framework AppKit -framework Carbon -framework IOKit -lcups +LIBS += -framework AppKit -framework Carbon -framework IOKit -framework QuartzCore -lcups QT += \ core-private gui-private \ accessibility_support-private clipboard_support-private theme_support-private \ - fontdatabase_support-private graphics_support-private cgl_support-private + fontdatabase_support-private graphics_support-private CONFIG += no_app_extension_api_only diff --git a/src/plugins/platforms/cocoa/qcocoaaccessibility.mm b/src/plugins/platforms/cocoa/qcocoaaccessibility.mm index b15c486e9d..8b6dcb35a6 100644 --- a/src/plugins/platforms/cocoa/qcocoaaccessibility.mm +++ b/src/plugins/platforms/cocoa/qcocoaaccessibility.mm @@ -41,8 +41,6 @@ #include <QtGui/qaccessible.h> #include <private/qcore_mac_p.h> -#include <Carbon/Carbon.h> - QT_BEGIN_NAMESPACE #ifndef QT_NO_ACCESSIBILITY @@ -369,7 +367,7 @@ id getValueAttribute(QAccessibleInterface *interface) QString text; if (interface->state().passwordEdit) { // return round password replacement chars - text = QString(end, QChar(kBulletUnicode)); + text = QString(end, QChar(0x2022)); } else { // VoiceOver will read out the entire text string at once when returning // text as a value. For large text edits the size of the returned string diff --git a/src/plugins/platforms/cocoa/qcocoaapplicationdelegate.mm b/src/plugins/platforms/cocoa/qcocoaapplicationdelegate.mm index 35ac7182af..5392804d62 100644 --- a/src/plugins/platforms/cocoa/qcocoaapplicationdelegate.mm +++ b/src/plugins/platforms/cocoa/qcocoaapplicationdelegate.mm @@ -284,7 +284,7 @@ QT_END_NAMESPACE inLaunch = false; if (qEnvironmentVariableIsEmpty("QT_MAC_DISABLE_FOREGROUND_APPLICATION_TRANSFORM")) { - if (QSysInfo::macVersion() >= QSysInfo::MV_10_12) { + if (__builtin_available(macOS 10.12, *)) { // Move the application window to front to avoid launching behind the terminal. // Ignoring other apps is necessary (we must ignore the terminal), but makes // Qt apps play slightly less nice with other apps when lanching from Finder diff --git a/src/plugins/platforms/cocoa/qcocoabackingstore.h b/src/plugins/platforms/cocoa/qcocoabackingstore.h index 5ed455fd71..a0bc204013 100644 --- a/src/plugins/platforms/cocoa/qcocoabackingstore.h +++ b/src/plugins/platforms/cocoa/qcocoabackingstore.h @@ -42,6 +42,8 @@ #include <QtGraphicsSupport/private/qrasterbackingstore_p.h> +#include <private/qcore_mac_p.h> + QT_BEGIN_NAMESPACE class QCocoaBackingStore : public QRasterBackingStore @@ -53,7 +55,9 @@ public: void flush(QWindow *, const QRegion &, const QPoint &) Q_DECL_OVERRIDE; private: + bool windowHasUnifiedToolbar() const; QImage::Format format() const Q_DECL_OVERRIDE; + void redrawRoundedBottomCorners(CGRect) const; }; QT_END_NAMESPACE diff --git a/src/plugins/platforms/cocoa/qcocoabackingstore.mm b/src/plugins/platforms/cocoa/qcocoabackingstore.mm index 1d7ad772dc..57a03905ab 100644 --- a/src/plugins/platforms/cocoa/qcocoabackingstore.mm +++ b/src/plugins/platforms/cocoa/qcocoabackingstore.mm @@ -44,6 +44,8 @@ QT_BEGIN_NAMESPACE +Q_LOGGING_CATEGORY(lcCocoaBackingStore, "qt.qpa.cocoa.backingstore"); + QCocoaBackingStore::QCocoaBackingStore(QWindow *window) : QRasterBackingStore(window) { @@ -51,26 +53,239 @@ QCocoaBackingStore::QCocoaBackingStore(QWindow *window) QCocoaBackingStore::~QCocoaBackingStore() { - if (QCocoaWindow *cocoaWindow = static_cast<QCocoaWindow *>(window()->handle())) - [qnsview_cast(cocoaWindow->view()) clearBackingStore:this]; +} + +bool QCocoaBackingStore::windowHasUnifiedToolbar() const +{ + Q_ASSERT(window()->handle()); + return static_cast<QCocoaWindow *>(window()->handle())->m_drawContentBorderGradient; } QImage::Format QCocoaBackingStore::format() const { - QCocoaWindow *cocoaWindow = static_cast<QCocoaWindow *>(window()->handle()); - if (cocoaWindow && cocoaWindow->m_drawContentBorderGradient) + if (windowHasUnifiedToolbar()) return QImage::Format_ARGB32_Premultiplied; return QRasterBackingStore::format(); } +#if !QT_MACOS_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_12) +static const NSCompositingOperation NSCompositingOperationCopy = NSCompositeCopy; +static const NSCompositingOperation NSCompositingOperationSourceOver = NSCompositeSourceOver; +#endif + +/*! + Flushes the given \a region from the specified \a window onto the + screen. + + The \a window is the top level window represented by this backingstore, + or a non-transient child of that window. + + If the \a window is a child window, the \a region will be in child window + coordinates, and the \a offset will be the child window's offset in relation + to the backingstore's top level window. +*/ void QCocoaBackingStore::flush(QWindow *window, const QRegion ®ion, const QPoint &offset) { if (m_image.isNull()) return; - if (QCocoaWindow *cocoaWindow = static_cast<QCocoaWindow *>(window->handle())) - [qnsview_cast(cocoaWindow->view()) flushBackingStore:this region:region offset:offset]; + // Use local pool so that any stale image references are cleaned up after flushing + QMacAutoReleasePool pool; + + const QWindow *topLevelWindow = this->window(); + + Q_ASSERT(topLevelWindow->handle() && window->handle()); + Q_ASSERT(!topLevelWindow->handle()->isForeignWindow() && !window->handle()->isForeignWindow()); + + QNSView *topLevelView = qnsview_cast(static_cast<QCocoaWindow *>(topLevelWindow->handle())->view()); + QNSView *view = qnsview_cast(static_cast<QCocoaWindow *>(window->handle())->view()); + + if (lcCocoaBackingStore().isDebugEnabled()) { + QString targetViewDescription; + if (view != topLevelView) { + QDebug targetDebug(&targetViewDescription); + targetDebug << "onto" << topLevelView << "at" << offset; + } + qCDebug(lcCocoaBackingStore) << "Flushing" << region << "of" << view << qPrintable(targetViewDescription); + } + + // Prevent potentially costly color conversion by assigning the display color space + // to the backingstore image. This does not copy the underlying image data. + CGColorSpaceRef displayColorSpace = view.window.screen.colorSpace.CGColorSpace; + QCFType<CGImageRef> cgImage = CGImageCreateCopyWithColorSpace( + QCFType<CGImageRef>(m_image.toCGImage()), displayColorSpace); + + if (view.layer) { + // In layer-backed mode, locking focus on a view does not give the right + // view transformation, and doesn't give us a graphics context to render + // via when drawing outside of the display cycle. Instead we tell AppKit + // that we want to update the layer's content, via [NSView wantsUpdateLayer], + // which result in AppKit not creating a backingstore for each layer, and + // we then directly set the layer's backingstore (content) to our backingstore, + // masked to the part of the subview that is relevant. + // FIXME: Figure out if there's a way to do partial updates + view.layer.contents = (__bridge id)static_cast<CGImageRef>(cgImage); + if (view != topLevelView) { + view.layer.contentsRect = CGRectApplyAffineTransform( + [view convertRect:view.bounds toView:topLevelView], + // The contentsRect is in unit coordinate system + CGAffineTransformMakeScale(1.0 / m_image.width(), 1.0 / m_image.height())); + } + return; + } + + // Normally a NSView is drawn via drawRect, as part of the display cycle in the + // main runloop, via setNeedsDisplay and friends. AppKit will lock focus on each + // individual view, starting with the top level and then traversing any subviews, + // calling drawRect for each of them. This pull model results in expose events + // sent to Qt, which result in drawing to the backingstore and flushing it. + // Qt may also decide to paint and flush the backingstore via e.g. timers, + // or other events such as mouse events, in which case we're in a push model. + // If there is no focused view, it means we're in the latter case, and need + // to manually flush the NSWindow after drawing to its graphic context. + const bool drawingOutsideOfDisplayCycle = ![NSView focusView]; + + // We also need to ensure the flushed view has focus, so that the graphics + // context is set up correctly (coordinate system, clipping, etc). Outside + // of the normal display cycle there is no focused view, as explained above, + // so we have to handle it manually. There's also a corner case inside the + // normal display cycle due to way QWidgetBackingStore composits native child + // widgets, where we'll get a flush of a native child during the drawRect of + // its parent/ancestor, and the parent/ancestor being the one locked by AppKit. + // In this case we also need to lock and unlock focus manually. + const bool shouldHandleViewLockManually = [NSView focusView] != view; + if (shouldHandleViewLockManually && ![view lockFocusIfCanDraw]) { + qWarning() << "failed to lock focus of" << view; + return; + } + + const qreal devicePixelRatio = m_image.devicePixelRatio(); + + // If the flushed window is a content view, and not in unified toolbar mode, + // we can get away with copying the backingstore instead of blending. + const NSCompositingOperation compositingOperation = static_cast<QCocoaWindow *>( + window->handle())->isContentView() && !windowHasUnifiedToolbar() ? + NSCompositingOperationCopy : NSCompositingOperationSourceOver; + +#ifdef QT_DEBUG + static bool debugBackingStoreFlush = [[NSUserDefaults standardUserDefaults] + boolForKey:@"QtCocoaDebugBackingStoreFlush"]; +#endif + + // ------------------------------------------------------------------------- + + // The current contexts is typically a NSWindowGraphicsContext, but can be + // NSBitmapGraphicsContext e.g. when debugging the view hierarchy in Xcode. + // If we need to distinguish things here in the future, we can use e.g. + // [NSGraphicsContext drawingToScreen], or the attributes of the context. + NSGraphicsContext *graphicsContext = [NSGraphicsContext currentContext]; + Q_ASSERT_X(graphicsContext, "QCocoaBackingStore", + "Focusing the view should give us a current graphics context"); + + // Create temporary image to use for blitting, without copying image data + NSImage *backingStoreImage = [[[NSImage alloc] initWithCGImage:cgImage size:NSZeroSize] autorelease]; + + QRegion clippedRegion = region; + for (QWindow *w = window; w; w = w->parent()) { + if (!w->mask().isEmpty()) { + clippedRegion &= w == window ? w->mask() + : w->mask().translated(window->mapFromGlobal(w->mapToGlobal(QPoint(0, 0)))); + } + } + + for (const QRect &viewLocalRect : clippedRegion) { + QPoint backingStoreOffset = viewLocalRect.topLeft() + offset; + QRect backingStoreRect(backingStoreOffset * devicePixelRatio, viewLocalRect.size() * devicePixelRatio); + if (graphicsContext.flipped) // Flip backingStoreRect to match graphics context + backingStoreRect.moveTop(m_image.height() - (backingStoreRect.y() + backingStoreRect.height())); + + CGRect viewRect = viewLocalRect.toCGRect(); + + if (windowHasUnifiedToolbar()) + NSDrawWindowBackground(viewRect); + + [backingStoreImage drawInRect:viewRect fromRect:backingStoreRect.toCGRect() + operation:compositingOperation fraction:1.0 respectFlipped:YES hints:nil]; + +#ifdef QT_DEBUG + if (Q_UNLIKELY(debugBackingStoreFlush)) { + [[NSColor colorWithCalibratedRed:drand48() green:drand48() blue:drand48() alpha:0.3] set]; + [NSBezierPath fillRect:viewRect]; + + if (drawingOutsideOfDisplayCycle) { + [[[NSColor magentaColor] colorWithAlphaComponent:0.5] set]; + [NSBezierPath strokeLineFromPoint:viewLocalRect.topLeft().toCGPoint() + toPoint:viewLocalRect.bottomRight().toCGPoint()]; + } + } +#endif + } + + QCocoaWindow *topLevelCocoaWindow = static_cast<QCocoaWindow *>(topLevelWindow->handle()); + if (Q_UNLIKELY(topLevelCocoaWindow->m_needsInvalidateShadow)) { + [topLevelView.window invalidateShadow]; + topLevelCocoaWindow->m_needsInvalidateShadow = false; + } + + // ------------------------------------------------------------------------- + + if (shouldHandleViewLockManually) + [view unlockFocus]; + + if (drawingOutsideOfDisplayCycle) { + redrawRoundedBottomCorners([view convertRect:region.boundingRect().toCGRect() toView:nil]); + [view.window flushWindow]; + } +} + +/* + When drawing outside of the display cycle, which Qt Widget does a lot, + we end up drawing over the NSThemeFrame, losing the rounded corners of + windows in the process. + + To work around this, until we've enabled updates via setNeedsDisplay and/or + enabled layer-backed views, we ask the NSWindow to redraw the bottom corners + if they intersect with the flushed region. + + This is the same logic used internally by e.g [NSView displayIfNeeded], + [NSRulerView _scrollToMatchContentView], and [NSClipView _immediateScrollToPoint:], + as well as the workaround used by WebKit to fix a similar bug: + + https://trac.webkit.org/changeset/85376/webkit +*/ +void QCocoaBackingStore::redrawRoundedBottomCorners(CGRect windowRect) const +{ +#if !defined(QT_APPLE_NO_PRIVATE_APIS) + Q_ASSERT(this->window()->handle()); + NSWindow *window = static_cast<QCocoaWindow *>(this->window()->handle())->nativeWindow(); + + static SEL intersectBottomCornersWithRect = NSSelectorFromString( + [NSString stringWithFormat:@"_%s%s:", "intersectBottomCorners", "WithRect"]); + if (NSMethodSignature *signature = [window methodSignatureForSelector:intersectBottomCornersWithRect]) { + NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature]; + invocation.target = window; + invocation.selector = intersectBottomCornersWithRect; + [invocation setArgument:&windowRect atIndex:2]; + [invocation invoke]; + + NSRect cornerOverlap = NSZeroRect; + [invocation getReturnValue:&cornerOverlap]; + if (!NSIsEmptyRect(cornerOverlap)) { + static SEL maskRoundedBottomCorners = NSSelectorFromString( + [NSString stringWithFormat:@"_%s%s:", "maskRounded", "BottomCorners"]); + if ((signature = [window methodSignatureForSelector:maskRoundedBottomCorners])) { + invocation = [NSInvocation invocationWithMethodSignature:signature]; + invocation.target = window; + invocation.selector = maskRoundedBottomCorners; + [invocation setArgument:&cornerOverlap atIndex:2]; + [invocation invoke]; + } + } + } +#else + Q_UNUSED(windowRect); +#endif } QT_END_NAMESPACE diff --git a/src/plugins/platforms/cocoa/qcocoadrag.h b/src/plugins/platforms/cocoa/qcocoadrag.h index 9ebb090989..c7277a47bf 100644 --- a/src/plugins/platforms/cocoa/qcocoadrag.h +++ b/src/plugins/platforms/cocoa/qcocoadrag.h @@ -55,7 +55,7 @@ public: QCocoaDrag(); ~QCocoaDrag(); - QMimeData *platformDropData() Q_DECL_OVERRIDE; + QMimeData *dragMimeData(); Qt::DropAction drag(QDrag *m_drag) Q_DECL_OVERRIDE; Qt::DropAction defaultAction(Qt::DropActions possibleActions, diff --git a/src/plugins/platforms/cocoa/qcocoadrag.mm b/src/plugins/platforms/cocoa/qcocoadrag.mm index c71e80d191..3bd0b05725 100644 --- a/src/plugins/platforms/cocoa/qcocoadrag.mm +++ b/src/plugins/platforms/cocoa/qcocoadrag.mm @@ -68,7 +68,7 @@ void QCocoaDrag::setLastMouseEvent(NSEvent *event, NSView *view) m_lastView = view; } -QMimeData *QCocoaDrag::platformDropData() +QMimeData *QCocoaDrag::dragMimeData() { if (m_drag) return m_drag->mimeData(); diff --git a/src/plugins/platforms/cocoa/qcocoaeventdispatcher.h b/src/plugins/platforms/cocoa/qcocoaeventdispatcher.h index 70887c41c9..2ffc1395ba 100644 --- a/src/plugins/platforms/cocoa/qcocoaeventdispatcher.h +++ b/src/plugins/platforms/cocoa/qcocoaeventdispatcher.h @@ -177,8 +177,6 @@ public: void maybeCancelWaitForMoreEvents(); void ensureNSAppInitialized(); - void removeQueuedUserInputEvents(int nsWinNumber); - QCFSocketNotifier cfSocketNotifier; QList<void *> queuedUserInputEvents; // NSEvent * CFRunLoopSourceRef postedEventsSource; diff --git a/src/plugins/platforms/cocoa/qcocoaeventdispatcher.mm b/src/plugins/platforms/cocoa/qcocoaeventdispatcher.mm index b22f1b1f54..bf9ba4eccf 100644 --- a/src/plugins/platforms/cocoa/qcocoaeventdispatcher.mm +++ b/src/plugins/platforms/cocoa/qcocoaeventdispatcher.mm @@ -86,7 +86,6 @@ #include <qdebug.h> #include <AppKit/AppKit.h> -#include <Carbon/Carbon.h> QT_BEGIN_NAMESPACE @@ -285,7 +284,7 @@ bool QCocoaEventDispatcher::hasPendingEvents() { extern uint qGlobalPostedEventsCount(); extern bool qt_is_gui_used; //qapplication.cpp - return qGlobalPostedEventsCount() || (qt_is_gui_used && GetNumEventsInQueue(GetMainEventQueue())); + return qGlobalPostedEventsCount() || (qt_is_gui_used && !CFRunLoopIsWaiting(CFRunLoopGetMain())); } static bool IsMouseOrKeyEvent( NSEvent* event ) @@ -900,21 +899,6 @@ void QCocoaEventDispatcherPrivate::processPostedEvents() } } -void QCocoaEventDispatcherPrivate::removeQueuedUserInputEvents(int nsWinNumber) -{ - if (nsWinNumber) { - int eventIndex = queuedUserInputEvents.size(); - - while (--eventIndex >= 0) { - NSEvent * nsevent = static_cast<NSEvent *>(queuedUserInputEvents.at(eventIndex)); - if ([nsevent windowNumber] == nsWinNumber) { - queuedUserInputEvents.removeAt(eventIndex); - [nsevent release]; - } - } - } -} - void QCocoaEventDispatcherPrivate::firstLoopEntry(CFRunLoopObserverRef ref, CFRunLoopActivity activity, void *info) diff --git a/src/plugins/platforms/cocoa/qcocoafiledialoghelper.mm b/src/plugins/platforms/cocoa/qcocoafiledialoghelper.mm index 74148b7cbf..fa123550ef 100644 --- a/src/plugins/platforms/cocoa/qcocoafiledialoghelper.mm +++ b/src/plugins/platforms/cocoa/qcocoafiledialoghelper.mm @@ -163,7 +163,7 @@ QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSOpenSavePanelDelegate); [mSavePanel setDelegate:self]; #if QT_OSX_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_11) - if (QOperatingSystemVersion::current() >= QOperatingSystemVersion::OSXElCapitan) + if (__builtin_available(macOS 10.11, *)) mOpenPanel.accessoryViewDisclosed = YES; #endif @@ -231,7 +231,7 @@ static QString strippedText(QString s) [mOpenPanel beginWithCompletionHandler:^(NSInteger result){ mReturnCode = result; if (mHelper) - mHelper->QNSOpenSavePanelDelegate_panelClosed(result == NSOKButton); + mHelper->QNSOpenSavePanelDelegate_panelClosed(result == NSModalResponseOK); }]; } } @@ -260,12 +260,12 @@ static QString strippedText(QString s) QCocoaMenuBar::resetKnownMenuItemsToQt(); QAbstractEventDispatcher::instance()->interrupt(); - return (mReturnCode == NSOKButton); + return (mReturnCode == NSModalResponseOK); } - (QPlatformDialogHelper::DialogCode)dialogResultCode { - return (mReturnCode == NSOKButton) ? QPlatformDialogHelper::Accepted : QPlatformDialogHelper::Rejected; + return (mReturnCode == NSModalResponseOK) ? QPlatformDialogHelper::Accepted : QPlatformDialogHelper::Rejected; } - (void)showWindowModalSheet:(QWindow *)parent @@ -286,7 +286,7 @@ static QString strippedText(QString s) [mSavePanel beginSheetModalForWindow:nsparent completionHandler:^(NSInteger result){ mReturnCode = result; if (mHelper) - mHelper->QNSOpenSavePanelDelegate_panelClosed(result == NSOKButton); + mHelper->QNSOpenSavePanelDelegate_panelClosed(result == NSModalResponseOK); }]; } diff --git a/src/plugins/platforms/cocoa/qcocoaglcontext.mm b/src/plugins/platforms/cocoa/qcocoaglcontext.mm index 9e688f4d1b..5ed81a7f1b 100644 --- a/src/plugins/platforms/cocoa/qcocoaglcontext.mm +++ b/src/plugins/platforms/cocoa/qcocoaglcontext.mm @@ -42,7 +42,6 @@ #include "qcocoahelpers.h" #include <qdebug.h> #include <QtCore/private/qcore_mac_p.h> -#include <QtCglSupport/private/cglconvenience_p.h> #include <QtPlatformHeaders/qcocoanativecontext.h> #include <dlfcn.h> @@ -154,7 +153,7 @@ QCocoaGLContext::QCocoaGLContext(const QSurfaceFormat &format, QPlatformOpenGLCo QMacAutoReleasePool pool; // For the SG Canvas render thread // create native context for the requested pixel format and share - NSOpenGLPixelFormat *pixelFormat = static_cast <NSOpenGLPixelFormat *>(qcgl_createNSOpenGLPixelFormat(m_format)); + NSOpenGLPixelFormat *pixelFormat = createNSOpenGLPixelFormat(m_format); m_shareContext = share ? static_cast<QCocoaGLContext *>(share)->nsOpenGLContext() : nil; m_context = [[NSOpenGLContext alloc] initWithFormat:pixelFormat shareContext:m_shareContext]; @@ -202,7 +201,6 @@ QVariant QCocoaGLContext::nativeHandle() const return QVariant::fromValue<QCocoaNativeContext>(QCocoaNativeContext(m_context)); } -// Match up with createNSOpenGLPixelFormat! QSurfaceFormat QCocoaGLContext::format() const { return m_format; @@ -220,6 +218,9 @@ void QCocoaGLContext::windowWasHidden() void QCocoaGLContext::swapBuffers(QPlatformSurface *surface) { + if (surface->surface()->surfaceClass() == QSurface::Offscreen) + return; // Nothing to do + QWindow *window = static_cast<QCocoaWindow *>(surface)->window(); setActiveWindow(window); @@ -231,11 +232,13 @@ bool QCocoaGLContext::makeCurrent(QPlatformSurface *surface) Q_ASSERT(surface->surface()->supportsOpenGL()); QMacAutoReleasePool pool; + [m_context makeCurrentContext]; + + if (surface->surface()->surfaceClass() == QSurface::Offscreen) + return true; QWindow *window = static_cast<QCocoaWindow *>(surface)->window(); setActiveWindow(window); - - [m_context makeCurrentContext]; update(); return true; } @@ -362,7 +365,64 @@ void QCocoaGLContext::update() NSOpenGLPixelFormat *QCocoaGLContext::createNSOpenGLPixelFormat(const QSurfaceFormat &format) { - return static_cast<NSOpenGLPixelFormat *>(qcgl_createNSOpenGLPixelFormat(format)); + QVector<NSOpenGLPixelFormatAttribute> attrs; + + if (format.swapBehavior() == QSurfaceFormat::DoubleBuffer + || format.swapBehavior() == QSurfaceFormat::DefaultSwapBehavior) + attrs.append(NSOpenGLPFADoubleBuffer); + else if (format.swapBehavior() == QSurfaceFormat::TripleBuffer) + attrs.append(NSOpenGLPFATripleBuffer); + + + // Select OpenGL profile + attrs << NSOpenGLPFAOpenGLProfile; + if (format.profile() == QSurfaceFormat::CoreProfile) { + if (format.version() >= qMakePair(4, 1)) + attrs << NSOpenGLProfileVersion4_1Core; + else if (format.version() >= qMakePair(3, 2)) + attrs << NSOpenGLProfileVersion3_2Core; + else + attrs << NSOpenGLProfileVersionLegacy; + } else { + attrs << NSOpenGLProfileVersionLegacy; + } + + if (format.depthBufferSize() > 0) + attrs << NSOpenGLPFADepthSize << format.depthBufferSize(); + if (format.stencilBufferSize() > 0) + attrs << NSOpenGLPFAStencilSize << format.stencilBufferSize(); + if (format.alphaBufferSize() > 0) + attrs << NSOpenGLPFAAlphaSize << format.alphaBufferSize(); + if ((format.redBufferSize() > 0) && + (format.greenBufferSize() > 0) && + (format.blueBufferSize() > 0)) { + const int colorSize = format.redBufferSize() + + format.greenBufferSize() + + format.blueBufferSize(); + attrs << NSOpenGLPFAColorSize << colorSize << NSOpenGLPFAMinimumPolicy; + } + + if (format.samples() > 0) { + attrs << NSOpenGLPFAMultisample + << NSOpenGLPFASampleBuffers << (NSOpenGLPixelFormatAttribute) 1 + << NSOpenGLPFASamples << (NSOpenGLPixelFormatAttribute) format.samples(); + } + + if (format.stereo()) + attrs << NSOpenGLPFAStereo; + + attrs << NSOpenGLPFAAllowOfflineRenderers; + + QByteArray useLayer = qgetenv("QT_MAC_WANTS_LAYER"); + if (!useLayer.isEmpty() && useLayer.toInt() > 0) { + // Disable the software rendering fallback. This makes compositing + // OpenGL and raster NSViews using Core Animation layers possible. + attrs << NSOpenGLPFANoRecovery; + } + + attrs << 0; + + return [[NSOpenGLPixelFormat alloc] initWithAttributes:attrs.constData()]; } NSOpenGLContext *QCocoaGLContext::nsOpenGLContext() const diff --git a/src/plugins/platforms/cocoa/qcocoahelpers.h b/src/plugins/platforms/cocoa/qcocoahelpers.h index 4478895538..7810733255 100644 --- a/src/plugins/platforms/cocoa/qcocoahelpers.h +++ b/src/plugins/platforms/cocoa/qcocoahelpers.h @@ -55,6 +55,9 @@ #include <QtGui/qpalette.h> #include <QtGui/qscreen.h> +#include <objc/runtime.h> +#include <objc/message.h> + Q_FORWARD_DECLARE_OBJC_CLASS(QT_MANGLE_NAMESPACE(QNSView)); QT_BEGIN_NAMESPACE @@ -188,5 +191,131 @@ QT_END_NAMESPACE QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSPanelContentsWrapper); +// ------------------------------------------------------------------------- + +// Depending on the ABI of the platform, we may need to use objc_msgSendSuper_stret: +// - http://www.sealiesoftware.com/blog/archive/2008/10/30/objc_explain_objc_msgSend_stret.html +// - https://lists.apple.com/archives/cocoa-dev/2008/Feb/msg02338.html +template <typename T> +struct objc_msgsend_requires_stret +{ static const bool value = +#if defined(Q_PROCESSOR_X86) + // Any return value larger than two registers on i386/x86_64 + sizeof(T) > sizeof(void*) * 2; +#elif defined(Q_PROCESSOR_ARM_32) + // Any return value larger than a single register on arm + sizeof(T) > sizeof(void*); +#elif defined(Q_PROCESSOR_ARM_64) + // Stret not used on arm64 + false; +#endif +}; + +template <> +struct objc_msgsend_requires_stret<void> +{ static const bool value = false; }; + +template <typename ReturnType, typename... Args> +ReturnType qt_msgSendSuper(id receiver, SEL selector, Args... args) +{ + static_assert(!objc_msgsend_requires_stret<ReturnType>::value, + "The given return type requires stret on this platform"); + + typedef ReturnType (*SuperFn)(objc_super *, SEL, Args...); + SuperFn superFn = reinterpret_cast<SuperFn>(objc_msgSendSuper); + objc_super sup = { receiver, [receiver superclass] }; + return superFn(&sup, selector, args...); +} + +template <typename ReturnType, typename... Args> +ReturnType qt_msgSendSuper_stret(id receiver, SEL selector, Args... args) +{ + static_assert(objc_msgsend_requires_stret<ReturnType>::value, + "The given return type does not use stret on this platform"); + + typedef void (*SuperStretFn)(ReturnType *, objc_super *, SEL, Args...); + SuperStretFn superStretFn = reinterpret_cast<SuperStretFn>(objc_msgSendSuper_stret); + + objc_super sup = { receiver, [receiver superclass] }; + ReturnType ret; + superStretFn(&ret, &sup, selector, args...); + return ret; +} + +template<typename... Args> +class QSendSuperHelper { +public: + QSendSuperHelper(id receiver, SEL sel, Args... args) + : m_receiver(receiver), m_selector(sel), m_args(std::make_tuple(args...)), m_sent(false) + { + } + + ~QSendSuperHelper() + { + if (!m_sent) + msgSendSuper<void>(m_args); + } + + template <typename ReturnType> + operator ReturnType() + { +#if defined(QT_DEBUG) + Method method = class_getInstanceMethod(object_getClass(m_receiver), m_selector); + char returnTypeEncoding[256]; + method_getReturnType(method, returnTypeEncoding, sizeof(returnTypeEncoding)); + NSUInteger alignedReturnTypeSize = 0; + NSGetSizeAndAlignment(returnTypeEncoding, nullptr, &alignedReturnTypeSize); + Q_ASSERT(alignedReturnTypeSize == sizeof(ReturnType)); +#endif + m_sent = true; + return msgSendSuper<ReturnType>(m_args); + } + +private: + template <std::size_t... Ts> + struct index {}; + + template <std::size_t N, std::size_t... Ts> + struct gen_seq : gen_seq<N - 1, N - 1, Ts...> {}; + + template <std::size_t... Ts> + struct gen_seq<0, Ts...> : index<Ts...> {}; + + template <typename ReturnType, bool V> + using if_requires_stret = typename std::enable_if<objc_msgsend_requires_stret<ReturnType>::value == V, ReturnType>::type; + + template <typename ReturnType, std::size_t... Is> + if_requires_stret<ReturnType, false> msgSendSuper(std::tuple<Args...>& args, index<Is...>) + { + return qt_msgSendSuper<ReturnType>(m_receiver, m_selector, std::get<Is>(args)...); + } + + template <typename ReturnType, std::size_t... Is> + if_requires_stret<ReturnType, true> msgSendSuper(std::tuple<Args...>& args, index<Is...>) + { + return qt_msgSendSuper_stret<ReturnType>(m_receiver, m_selector, std::get<Is>(args)...); + } + + template <typename ReturnType> + ReturnType msgSendSuper(std::tuple<Args...>& args) + { + return msgSendSuper<ReturnType>(args, gen_seq<sizeof...(Args)>{}); + } + + id m_receiver; + SEL m_selector; + std::tuple<Args...> m_args; + bool m_sent; +}; + +template<typename... Args> +QSendSuperHelper<Args...> qt_objcDynamicSuperHelper(id receiver, SEL selector, Args... args) +{ + return QSendSuperHelper<Args...>(receiver, selector, args...); +} + +// Same as calling super, but the super_class field resolved at runtime instead of compile time +#define qt_objcDynamicSuper(...) qt_objcDynamicSuperHelper(self, _cmd, ##__VA_ARGS__) + #endif //QCOCOAHELPERS_H diff --git a/src/plugins/platforms/cocoa/qcocoahelpers.mm b/src/plugins/platforms/cocoa/qcocoahelpers.mm index 5d0f13c5a9..9f9618177d 100644 --- a/src/plugins/platforms/cocoa/qcocoahelpers.mm +++ b/src/plugins/platforms/cocoa/qcocoahelpers.mm @@ -55,8 +55,6 @@ #include <algorithm> -#include <Carbon/Carbon.h> - QT_BEGIN_NAMESPACE Q_LOGGING_CATEGORY(lcQpaCocoaWindow, "qt.qpa.cocoa.window"); @@ -407,6 +405,7 @@ QT_END_NAMESPACE self.panelContents.needsDisplay = YES; self.needsDisplay = YES; + [super layout]; } @end diff --git a/src/plugins/platforms/cocoa/qcocoaintegration.h b/src/plugins/platforms/cocoa/qcocoaintegration.h index ecdd20c4dc..2fc5156d24 100644 --- a/src/plugins/platforms/cocoa/qcocoaintegration.h +++ b/src/plugins/platforms/cocoa/qcocoaintegration.h @@ -58,60 +58,11 @@ QT_BEGIN_NAMESPACE -class QCocoaScreen : public QPlatformScreen -{ -public: - QCocoaScreen(int screenIndex); - ~QCocoaScreen(); - - // ---------------------------------------------------- - // Virtual methods overridden from QPlatformScreen - QPixmap grabWindow(WId window, int x, int y, int width, int height) const Q_DECL_OVERRIDE; - QRect geometry() const Q_DECL_OVERRIDE { return m_geometry; } - QRect availableGeometry() const Q_DECL_OVERRIDE { return m_availableGeometry; } - int depth() const Q_DECL_OVERRIDE { return m_depth; } - QImage::Format format() const Q_DECL_OVERRIDE { return m_format; } - qreal devicePixelRatio() const Q_DECL_OVERRIDE; - QSizeF physicalSize() const Q_DECL_OVERRIDE { return m_physicalSize; } - QDpi logicalDpi() const Q_DECL_OVERRIDE { return m_logicalDpi; } - qreal refreshRate() const Q_DECL_OVERRIDE { return m_refreshRate; } - QString name() const Q_DECL_OVERRIDE { return m_name; } - QPlatformCursor *cursor() const Q_DECL_OVERRIDE { return m_cursor; } - QWindow *topLevelAt(const QPoint &point) const Q_DECL_OVERRIDE; - QList<QPlatformScreen *> virtualSiblings() const Q_DECL_OVERRIDE { return m_siblings; } - QPlatformScreen::SubpixelAntialiasingType subpixelAntialiasingTypeHint() const Q_DECL_OVERRIDE; - - // ---------------------------------------------------- - // Additional methods - void setVirtualSiblings(const QList<QPlatformScreen *> &siblings) { m_siblings = siblings; } - NSScreen *nativeScreen() const; - void updateGeometry(); - - QPointF mapToNative(const QPointF &pos) const { return flipCoordinate(pos); } - QRectF mapToNative(const QRectF &rect) const { return flipCoordinate(rect); } - QPointF mapFromNative(const QPointF &pos) const { return flipCoordinate(pos); } - QRectF mapFromNative(const QRectF &rect) const { return flipCoordinate(rect); } - -private: - QPointF flipCoordinate(const QPointF &pos) const; - QRectF flipCoordinate(const QRectF &rect) const; +class QCocoaScreen; -public: - int m_screenIndex; - QRect m_geometry; - QRect m_availableGeometry; - QDpi m_logicalDpi; - qreal m_refreshRate; - int m_depth; - QString m_name; - QImage::Format m_format; - QSizeF m_physicalSize; - QCocoaCursor *m_cursor; - QList<QPlatformScreen *> m_siblings; -}; - -class QCocoaIntegration : public QPlatformIntegration +class QCocoaIntegration : public QObject, public QPlatformIntegration { + Q_OBJECT public: enum Option { UseFreeTypeFontEngine = 0x1 @@ -127,6 +78,7 @@ public: bool hasCapability(QPlatformIntegration::Capability cap) const Q_DECL_OVERRIDE; QPlatformWindow *createPlatformWindow(QWindow *window) const Q_DECL_OVERRIDE; QPlatformWindow *createForeignWindow(QWindow *window, WId nativeHandle) const Q_DECL_OVERRIDE; + QPlatformOffscreenSurface *createPlatformOffscreenSurface(QOffscreenSurface *surface) const override; #ifndef QT_NO_OPENGL QPlatformOpenGLContext *createPlatformOpenGLContext(QOpenGLContext *context) const Q_DECL_OVERRIDE; #endif @@ -169,6 +121,9 @@ public: void beep() const Q_DECL_OVERRIDE; +private Q_SLOTS: + void focusWindowChanged(QWindow *); + private: static QCocoaIntegration *mInstance; Options mOptions; diff --git a/src/plugins/platforms/cocoa/qcocoaintegration.mm b/src/plugins/platforms/cocoa/qcocoaintegration.mm index bac49cfad9..55b3805df3 100644 --- a/src/plugins/platforms/cocoa/qcocoaintegration.mm +++ b/src/plugins/platforms/cocoa/qcocoaintegration.mm @@ -51,10 +51,12 @@ #include "qcocoainputcontext.h" #include "qcocoamimetypes.h" #include "qcocoaaccessibility.h" +#include "qcocoascreen.h" #include <qpa/qplatforminputcontextfactory_p.h> #include <qpa/qplatformaccessibility.h> #include <qpa/qplatforminputcontextfactory_p.h> +#include <qpa/qplatformoffscreensurface.h> #include <QtCore/qcoreapplication.h> #include <QtGui/private/qcoregraphics_p.h> @@ -78,221 +80,6 @@ QT_BEGIN_NAMESPACE class QCoreTextFontEngine; class QFontEngineFT; -QCocoaScreen::QCocoaScreen(int screenIndex) - : QPlatformScreen(), m_screenIndex(screenIndex), m_refreshRate(60.0) -{ - updateGeometry(); - m_cursor = new QCocoaCursor; -} - -QCocoaScreen::~QCocoaScreen() -{ - delete m_cursor; -} - -NSScreen *QCocoaScreen::nativeScreen() const -{ - NSArray *screens = [NSScreen screens]; - - // Stale reference, screen configuration has changed - if (m_screenIndex < 0 || (NSUInteger)m_screenIndex >= [screens count]) - return nil; - - return [screens objectAtIndex:m_screenIndex]; -} - -/*! - Flips the Y coordinate of the point between quadrant I and IV. - - The native coordinate system on macOS uses quadrant I, with origin - in bottom left, and Qt uses quadrant IV, with origin in top left. - - By flippig the Y coordinate, we can map the position between the - two coordinate systems. -*/ -QPointF QCocoaScreen::flipCoordinate(const QPointF &pos) const -{ - return QPointF(pos.x(), m_geometry.height() - pos.y()); -} - -/*! - Flips the Y coordinate of the rectangle between quadrant I and IV. - - The native coordinate system on macOS uses quadrant I, with origin - in bottom left, and Qt uses quadrant IV, with origin in top left. - - By flippig the Y coordinate, we can map the rectangle between the - two coordinate systems. -*/ -QRectF QCocoaScreen::flipCoordinate(const QRectF &rect) const -{ - return QRectF(flipCoordinate(rect.topLeft() + QPoint(0, rect.height())), rect.size()); -} - -void QCocoaScreen::updateGeometry() -{ - NSScreen *nsScreen = nativeScreen(); - if (!nsScreen) - return; - - // At this point the geometry is in native coordinates, but the size - // is correct, which we take advantage of next when we map the native - // coordinates to the Qt coordinate system. - m_geometry = QRectF::fromCGRect(NSRectToCGRect(nsScreen.frame)).toRect(); - m_availableGeometry = QRectF::fromCGRect(NSRectToCGRect(nsScreen.visibleFrame)).toRect(); - - // The reference screen for the geometry is always the primary screen, but since - // we may be in the process of creating and registering the primary screen, we - // must special-case that and assign it direcly. - QCocoaScreen *primaryScreen = (nsScreen == [[NSScreen screens] firstObject]) ? - this : static_cast<QCocoaScreen*>(QGuiApplication::primaryScreen()->handle()); - - m_geometry = primaryScreen->mapFromNative(m_geometry).toRect(); - m_availableGeometry = primaryScreen->mapFromNative(m_availableGeometry).toRect(); - - m_format = QImage::Format_RGB32; - m_depth = NSBitsPerPixelFromDepth([nsScreen depth]); - - NSDictionary *devDesc = [nsScreen deviceDescription]; - CGDirectDisplayID dpy = [[devDesc objectForKey:@"NSScreenNumber"] unsignedIntValue]; - CGSize size = CGDisplayScreenSize(dpy); - m_physicalSize = QSizeF(size.width, size.height); - m_logicalDpi.first = 72; - m_logicalDpi.second = 72; - CGDisplayModeRef displayMode = CGDisplayCopyDisplayMode(dpy); - float refresh = CGDisplayModeGetRefreshRate(displayMode); - CGDisplayModeRelease(displayMode); - if (refresh > 0) - m_refreshRate = refresh; - - // Get m_name (brand/model of the monitor) - NSDictionary *deviceInfo = (NSDictionary *)IODisplayCreateInfoDictionary(CGDisplayIOServicePort(dpy), kIODisplayOnlyPreferredName); - NSDictionary *localizedNames = [deviceInfo objectForKey:[NSString stringWithUTF8String:kDisplayProductName]]; - if ([localizedNames count] > 0) - m_name = QString::fromUtf8([[localizedNames objectForKey:[[localizedNames allKeys] objectAtIndex:0]] UTF8String]); - [deviceInfo release]; - - QWindowSystemInterface::handleScreenGeometryChange(screen(), geometry(), availableGeometry()); - QWindowSystemInterface::handleScreenLogicalDotsPerInchChange(screen(), m_logicalDpi.first, m_logicalDpi.second); - QWindowSystemInterface::handleScreenRefreshRateChange(screen(), m_refreshRate); -} - -qreal QCocoaScreen::devicePixelRatio() const -{ - QMacAutoReleasePool pool; - NSScreen *nsScreen = nativeScreen(); - return qreal(nsScreen ? [nsScreen backingScaleFactor] : 1.0); -} - -QPlatformScreen::SubpixelAntialiasingType QCocoaScreen::subpixelAntialiasingTypeHint() const -{ - QPlatformScreen::SubpixelAntialiasingType type = QPlatformScreen::subpixelAntialiasingTypeHint(); - if (type == QPlatformScreen::Subpixel_None) { - // Every OSX machine has RGB pixels unless a peculiar or rotated non-Apple screen is attached - type = QPlatformScreen::Subpixel_RGB; - } - return type; -} - -QWindow *QCocoaScreen::topLevelAt(const QPoint &point) const -{ - NSPoint screenPoint = qt_mac_flipPoint(point); - - // Search (hit test) for the top-level window. [NSWidow windowNumberAtPoint: - // belowWindowWithWindowNumber] may return windows that are not interesting - // to Qt. The search iterates until a suitable window or no window is found. - NSInteger topWindowNumber = 0; - QWindow *window = 0; - do { - // Get the top-most window, below any previously rejected window. - topWindowNumber = [NSWindow windowNumberAtPoint:screenPoint - belowWindowWithWindowNumber:topWindowNumber]; - - // Continue the search if the window does not belong to this process. - NSWindow *nsWindow = [NSApp windowWithWindowNumber:topWindowNumber]; - if (nsWindow == 0) - continue; - - // Continue the search if the window does not belong to Qt. - if (![nsWindow conformsToProtocol:@protocol(QNSWindowProtocol)]) - continue; - - id<QNSWindowProtocol> proto = static_cast<id<QNSWindowProtocol> >(nsWindow); - QCocoaWindow *cocoaWindow = proto.helper.platformWindow; - if (!cocoaWindow) - continue; - window = cocoaWindow->window(); - - // Continue the search if the window is not a top-level window. - if (!window->isTopLevel()) - continue; - - // Stop searching. The current window is the correct window. - break; - } while (topWindowNumber > 0); - - return window; -} - -QPixmap QCocoaScreen::grabWindow(WId window, int x, int y, int width, int height) const -{ - // TODO window should be handled - Q_UNUSED(window) - - const int maxDisplays = 128; // 128 displays should be enough for everyone. - CGDirectDisplayID displays[maxDisplays]; - CGDisplayCount displayCount; - CGRect cgRect; - - if (width < 0 || height < 0) { - // get all displays - cgRect = CGRectInfinite; - } else { - cgRect = CGRectMake(x, y, width, height); - } - const CGDisplayErr err = CGGetDisplaysWithRect(cgRect, maxDisplays, displays, &displayCount); - - if (err && displayCount == 0) - return QPixmap(); - - // calculate pixmap size - QSize windowSize(width, height); - if (width < 0 || height < 0) { - QRect windowRect; - for (uint i = 0; i < displayCount; ++i) { - const CGRect cgRect = CGDisplayBounds(displays[i]); - QRect qRect(cgRect.origin.x, cgRect.origin.y, cgRect.size.width, cgRect.size.height); - windowRect = windowRect.united(qRect); - } - if (width < 0) - windowSize.setWidth(windowRect.width()); - if (height < 0) - windowSize.setHeight(windowRect.height()); - } - - QPixmap windowPixmap(windowSize * devicePixelRatio()); - windowPixmap.fill(Qt::transparent); - - for (uint i = 0; i < displayCount; ++i) { - const CGRect bounds = CGDisplayBounds(displays[i]); - int w = (width < 0 ? bounds.size.width : width) * devicePixelRatio(); - int h = (height < 0 ? bounds.size.height : height) * devicePixelRatio(); - QRect displayRect = QRect(x, y, w, h); - displayRect = displayRect.translated(qRound(-bounds.origin.x), qRound(-bounds.origin.y)); - QCFType<CGImageRef> image = CGDisplayCreateImageForRect(displays[i], - CGRectMake(displayRect.x(), displayRect.y(), displayRect.width(), displayRect.height())); - QPixmap pix(w, h); - pix.fill(Qt::transparent); - CGRect rect = CGRectMake(0, 0, w, h); - QMacCGContext ctx(&pix); - qt_mac_drawCGImage(ctx, &rect, image); - - QPainter painter(&windowPixmap); - painter.drawPixmap(0, 0, pix); - } - return windowPixmap; -} - static QCocoaIntegration::Options parseOptions(const QStringList ¶mList) { QCocoaIntegration::Options options; @@ -355,7 +142,7 @@ QCocoaIntegration::QCocoaIntegration(const QStringList ¶mList) // Move the application window to front to make it take focus, also when launching // from the terminal. On 10.12+ this call has been moved to applicationDidFinishLauching // to work around issues with loss of focus at startup. - if (QSysInfo::macVersion() < QSysInfo::MV_10_12) { + if (QOperatingSystemVersion::current() < QOperatingSystemVersion::MacOSSierra) { // Ignoring other apps is necessary (we must ignore the terminal), but makes // Qt apps play slightly less nice with other apps when lanching from Finder // (See the activateIgnoringOtherApps docs.) @@ -392,6 +179,9 @@ QCocoaIntegration::QCocoaIntegration(const QStringList ¶mList) QMacInternalPasteboardMime::initializeMimeTypes(); QCocoaMimeTypes::initializeMimeTypes(); QWindowSystemInterfacePrivate::TabletEvent::setPlatformSynthesizesMouse(false); + + connect(qGuiApp, &QGuiApplication::focusWindowChanged, + this, &QCocoaIntegration::focusWindowChanged); } QCocoaIntegration::~QCocoaIntegration() @@ -435,6 +225,8 @@ QCocoaIntegration::Options QCocoaIntegration::options() const return mOptions; } +Q_LOGGING_CATEGORY(lcCocoaScreen, "qt.qpa.cocoa.screens"); + /*! \brief Synchronizes the screen list, adds new screens, removes deleted ones */ @@ -474,9 +266,11 @@ void QCocoaIntegration::updateScreens() if (screen) { remainingScreens.remove(screen); screen->updateGeometry(); + qCDebug(lcCocoaScreen) << "Updated properties of" << screen; } else { screen = new QCocoaScreen(i); mScreens.append(screen); + qCDebug(lcCocoaScreen) << "Adding" << screen; screenAdded(screen); } siblings << screen; @@ -493,6 +287,7 @@ void QCocoaIntegration::updateScreens() mScreens.removeOne(screen); // Prevent stale references to NSScreen during destroy screen->m_screenIndex = -1; + qCDebug(lcCocoaScreen) << "Removing" << screen; destroyScreen(screen); } } @@ -545,6 +340,24 @@ QPlatformWindow *QCocoaIntegration::createForeignWindow(QWindow *window, WId nat return new QCocoaWindow(window, nativeHandle); } +class QCocoaOffscreenSurface : public QPlatformOffscreenSurface +{ +public: + QCocoaOffscreenSurface(QOffscreenSurface *offscreenSurface) : QPlatformOffscreenSurface(offscreenSurface) {} + + QSurfaceFormat format() const override + { + Q_ASSERT(offscreenSurface()); + return offscreenSurface()->requestedFormat(); + } + bool isValid() const override { return true; } +}; + +QPlatformOffscreenSurface *QCocoaIntegration::createPlatformOffscreenSurface(QOffscreenSurface *surface) const +{ + return new QCocoaOffscreenSurface(surface); +} + #ifndef QT_NO_OPENGL QPlatformOpenGLContext *QCocoaIntegration::createPlatformOpenGLContext(QOpenGLContext *context) const { @@ -700,4 +513,33 @@ void QCocoaIntegration::beep() const NSBeep(); } +void QCocoaIntegration::focusWindowChanged(QWindow *focusWindow) +{ + // Don't revert icon just because we lost focus + if (!focusWindow) + return; + + static bool hasDefaultApplicationIcon = [](){ + NSImage *genericApplicationIcon = [[NSWorkspace sharedWorkspace] + iconForFileType:NSFileTypeForHFSTypeCode(kGenericApplicationIcon)]; + NSImage *applicationIcon = [NSImage imageNamed:NSImageNameApplicationIcon]; + + NSRect rect = NSMakeRect(0, 0, 32, 32); + return [applicationIcon CGImageForProposedRect:&rect context:nil hints:nil] + == [genericApplicationIcon CGImageForProposedRect:&rect context:nil hints:nil]; + }(); + + // Don't let the window icon override an explicit application icon set in the Info.plist + if (!hasDefaultApplicationIcon) + return; + + // Or an explicit application icon set on QGuiApplication + if (!qGuiApp->windowIcon().isNull()) + return; + + setApplicationIcon(focusWindow->icon()); +} + +#include "moc_qcocoaintegration.cpp" + QT_END_NAMESPACE diff --git a/src/plugins/platforms/cocoa/qcocoakeymapper.h b/src/plugins/platforms/cocoa/qcocoakeymapper.h index 4ba615efeb..a75e275077 100644 --- a/src/plugins/platforms/cocoa/qcocoakeymapper.h +++ b/src/plugins/platforms/cocoa/qcocoakeymapper.h @@ -91,8 +91,6 @@ public: private: QCFType<TISInputSourceRef> currentInputSource; - QLocale keyboardInputLocale; - Qt::LayoutDirection keyboardInputDirection; enum { NullMode, UnicodeMode, OtherMode } keyboard_mode; union { const UCKeyboardLayout *unicode; diff --git a/src/plugins/platforms/cocoa/qcocoakeymapper.mm b/src/plugins/platforms/cocoa/qcocoakeymapper.mm index 1ef7f11011..80140505d1 100644 --- a/src/plugins/platforms/cocoa/qcocoakeymapper.mm +++ b/src/plugins/platforms/cocoa/qcocoakeymapper.mm @@ -72,14 +72,6 @@ static const Qt::KeyboardModifiers ModsTbl[] = { bool qt_mac_eat_unicode_key = false; -Q_GUI_EXPORT void qt_mac_secure_keyboard(bool b) -{ - static bool secure = false; - if (b != secure){ - b ? EnableSecureEventInput() : DisableSecureEventInput(); - secure = b; - } -} /* key maps */ struct qt_mac_enum_mapper @@ -384,18 +376,7 @@ bool QCocoaKeyMapper::updateKeyboard() } currentInputSource = source; keyboard_dead = 0; - CFStringRef iso639Code; - CFArrayRef array = static_cast<CFArrayRef>(TISGetInputSourceProperty(currentInputSource, kTISPropertyInputSourceLanguages)); - iso639Code = static_cast<CFStringRef>(CFArrayGetValueAtIndex(array, 0)); // Actually a RFC3066bis, but it's close enough - - if (iso639Code) { - keyboardInputLocale = QLocale(QString::fromCFString(iso639Code)); - keyboardInputDirection = keyboardInputLocale.textDirection(); - } else { - keyboardInputLocale = QLocale::c(); - keyboardInputDirection = Qt::LeftToRight; - } return true; } diff --git a/src/plugins/platforms/cocoa/qcocoamenu.h b/src/plugins/platforms/cocoa/qcocoamenu.h index 7baaf971f4..5081fc78c6 100644 --- a/src/plugins/platforms/cocoa/qcocoamenu.h +++ b/src/plugins/platforms/cocoa/qcocoamenu.h @@ -55,11 +55,6 @@ public: QCocoaMenu(); ~QCocoaMenu(); - void setTag(quintptr tag) Q_DECL_OVERRIDE - { m_tag = tag; } - quintptr tag() const Q_DECL_OVERRIDE - { return m_tag; } - void insertMenuItem(QPlatformMenuItem *menuItem, QPlatformMenuItem *before) Q_DECL_OVERRIDE; void removeMenuItem(QPlatformMenuItem *menuItem) Q_DECL_OVERRIDE; void syncMenuItem(QPlatformMenuItem *menuItem) Q_DECL_OVERRIDE; @@ -108,7 +103,6 @@ private: QList<QCocoaMenuItem *> m_menuItems; NSMenu *m_nativeMenu; NSMenuItem *m_attachedItem; - quintptr m_tag; int m_updateTimer; bool m_enabled:1; bool m_parentEnabled:1; diff --git a/src/plugins/platforms/cocoa/qcocoamenu.mm b/src/plugins/platforms/cocoa/qcocoamenu.mm index 8bdd0512de..a54284dbae 100644 --- a/src/plugins/platforms/cocoa/qcocoamenu.mm +++ b/src/plugins/platforms/cocoa/qcocoamenu.mm @@ -259,7 +259,6 @@ QT_BEGIN_NAMESPACE QCocoaMenu::QCocoaMenu() : m_attachedItem(0), - m_tag(0), m_updateTimer(0), m_enabled(true), m_parentEnabled(true), diff --git a/src/plugins/platforms/cocoa/qcocoamenuitem.h b/src/plugins/platforms/cocoa/qcocoamenuitem.h index 23f788687c..53862d0484 100644 --- a/src/plugins/platforms/cocoa/qcocoamenuitem.h +++ b/src/plugins/platforms/cocoa/qcocoamenuitem.h @@ -78,11 +78,6 @@ public: QCocoaMenuItem(); ~QCocoaMenuItem(); - void setTag(quintptr tag) Q_DECL_OVERRIDE - { m_tag = tag; } - quintptr tag() const Q_DECL_OVERRIDE - { return m_tag; } - void setText(const QString &text) Q_DECL_OVERRIDE; void setIcon(const QIcon &icon) Q_DECL_OVERRIDE; void setMenu(QPlatformMenu *menu) Q_DECL_OVERRIDE; @@ -129,7 +124,6 @@ private: #ifndef QT_NO_SHORTCUT QKeySequence m_shortcut; #endif - quintptr m_tag; int m_iconSize; bool m_textSynced:1; bool m_isVisible:1; diff --git a/src/plugins/platforms/cocoa/qcocoamenuitem.mm b/src/plugins/platforms/cocoa/qcocoamenuitem.mm index 394e0fb8e4..eaf310ec51 100644 --- a/src/plugins/platforms/cocoa/qcocoamenuitem.mm +++ b/src/plugins/platforms/cocoa/qcocoamenuitem.mm @@ -95,7 +95,6 @@ QCocoaMenuItem::QCocoaMenuItem() : m_itemView(nil), m_menu(NULL), m_role(NoRole), - m_tag(0), m_iconSize(16), m_textSynced(false), m_isVisible(true), diff --git a/src/plugins/platforms/cocoa/qcocoamimetypes.mm b/src/plugins/platforms/cocoa/qcocoamimetypes.mm index 093f86da6e..f7662a92a4 100644 --- a/src/plugins/platforms/cocoa/qcocoamimetypes.mm +++ b/src/plugins/platforms/cocoa/qcocoamimetypes.mm @@ -105,101 +105,9 @@ QList<QByteArray> QMacPasteboardMimeTraditionalMacPlainText::convertFromMime(con return ret; } -class QMacPasteboardMimeTiff : public QMacInternalPasteboardMime { -public: - QMacPasteboardMimeTiff() : QMacInternalPasteboardMime(MIME_ALL) { } - QString convertorName(); - - QString flavorFor(const QString &mime); - QString mimeFor(QString flav); - bool canConvert(const QString &mime, QString flav); - QVariant convertToMime(const QString &mime, QList<QByteArray> data, QString flav); - QList<QByteArray> convertFromMime(const QString &mime, QVariant data, QString flav); -}; - -QString QMacPasteboardMimeTiff::convertorName() -{ - return QLatin1String("Tiff"); -} - -QString QMacPasteboardMimeTiff::flavorFor(const QString &mime) -{ - if (mime.startsWith(QLatin1String("application/x-qt-image"))) - return QLatin1String("public.tiff"); - return QString(); -} - -QString QMacPasteboardMimeTiff::mimeFor(QString flav) -{ - if (flav == QLatin1String("public.tiff")) - return QLatin1String("application/x-qt-image"); - return QString(); -} - -bool QMacPasteboardMimeTiff::canConvert(const QString &mime, QString flav) -{ - return flav == QLatin1String("public.tiff") && mime == QLatin1String("application/x-qt-image"); -} - -QVariant QMacPasteboardMimeTiff::convertToMime(const QString &mime, QList<QByteArray> data, QString flav) -{ - if (data.count() > 1) - qWarning("QMacPasteboardMimeTiff: Cannot handle multiple member data"); - QVariant ret; - if (!canConvert(mime, flav)) - return ret; - const QByteArray &a = data.first(); - QCFType<CGImageRef> image; - QCFType<CFDataRef> tiffData = CFDataCreateWithBytesNoCopy(0, - reinterpret_cast<const UInt8 *>(a.constData()), - a.size(), kCFAllocatorNull); - QCFType<CGImageSourceRef> imageSource = CGImageSourceCreateWithData(tiffData, 0); - image = CGImageSourceCreateImageAtIndex(imageSource, 0, 0); - if (image != 0) - ret = QVariant(qt_mac_toQImage(image)); - return ret; -} - -QList<QByteArray> QMacPasteboardMimeTiff::convertFromMime(const QString &mime, QVariant variant, QString flav) -{ - QList<QByteArray> ret; - if (!canConvert(mime, flav)) - return ret; - - QImage img = qvariant_cast<QImage>(variant); - QCFType<CGImageRef> cgimage = qt_mac_toCGImage(img); - - QCFType<CFMutableDataRef> data = CFDataCreateMutable(0, 0); - QCFType<CGImageDestinationRef> imageDestination = CGImageDestinationCreateWithData(data, kUTTypeTIFF, 1, 0); - if (imageDestination != 0) { - CFTypeRef keys[2]; - QCFType<CFTypeRef> values[2]; - QCFType<CFDictionaryRef> options; - keys[0] = kCGImagePropertyPixelWidth; - keys[1] = kCGImagePropertyPixelHeight; - int width = img.width(); - int height = img.height(); - values[0] = CFNumberCreate(0, kCFNumberIntType, &width); - values[1] = CFNumberCreate(0, kCFNumberIntType, &height); - options = CFDictionaryCreate(0, reinterpret_cast<const void **>(keys), - reinterpret_cast<const void **>(values), 2, - &kCFTypeDictionaryKeyCallBacks, - &kCFTypeDictionaryValueCallBacks); - CGImageDestinationAddImage(imageDestination, cgimage, options); - CGImageDestinationFinalize(imageDestination); - } - QByteArray ar(CFDataGetLength(data), 0); - CFDataGetBytes(data, - CFRangeMake(0, ar.size()), - reinterpret_cast<UInt8 *>(ar.data())); - ret.append(ar); - return ret; -} - void QCocoaMimeTypes::initializeMimeTypes() { new QMacPasteboardMimeTraditionalMacPlainText; - new QMacPasteboardMimeTiff; } QT_END_NAMESPACE diff --git a/src/plugins/platforms/cocoa/qcocoanativeinterface.mm b/src/plugins/platforms/cocoa/qcocoanativeinterface.mm index 26ab07ffaf..8943cb6cd5 100644 --- a/src/plugins/platforms/cocoa/qcocoanativeinterface.mm +++ b/src/plugins/platforms/cocoa/qcocoanativeinterface.mm @@ -103,7 +103,7 @@ void *QCocoaNativeInterface::nativeResourceForWindow(const QByteArray &resourceS return static_cast<QCocoaWindow *>(window->handle())->currentContext()->nsOpenGLContext(); #endif } else if (resourceString == "nswindow") { - return static_cast<QCocoaWindow *>(window->handle())->m_nsWindow; + return static_cast<QCocoaWindow *>(window->handle())->nativeWindow(); } return 0; } diff --git a/src/plugins/platforms/cocoa/qcocoascreen.h b/src/plugins/platforms/cocoa/qcocoascreen.h new file mode 100644 index 0000000000..937002f493 --- /dev/null +++ b/src/plugins/platforms/cocoa/qcocoascreen.h @@ -0,0 +1,112 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QCOCOASCREEN_H +#define QCOCOASCREEN_H + +#include <AppKit/AppKit.h> + +#include "qcocoacursor.h" + +#include <qpa/qplatformintegration.h> + +QT_BEGIN_NAMESPACE + +class QCocoaScreen : public QPlatformScreen +{ +public: + QCocoaScreen(int screenIndex); + ~QCocoaScreen(); + + // ---------------------------------------------------- + // Virtual methods overridden from QPlatformScreen + QPixmap grabWindow(WId window, int x, int y, int width, int height) const Q_DECL_OVERRIDE; + QRect geometry() const Q_DECL_OVERRIDE { return m_geometry; } + QRect availableGeometry() const Q_DECL_OVERRIDE { return m_availableGeometry; } + int depth() const Q_DECL_OVERRIDE { return m_depth; } + QImage::Format format() const Q_DECL_OVERRIDE { return m_format; } + qreal devicePixelRatio() const Q_DECL_OVERRIDE; + QSizeF physicalSize() const Q_DECL_OVERRIDE { return m_physicalSize; } + QDpi logicalDpi() const Q_DECL_OVERRIDE { return m_logicalDpi; } + qreal refreshRate() const Q_DECL_OVERRIDE { return m_refreshRate; } + QString name() const Q_DECL_OVERRIDE { return m_name; } + QPlatformCursor *cursor() const Q_DECL_OVERRIDE { return m_cursor; } + QWindow *topLevelAt(const QPoint &point) const Q_DECL_OVERRIDE; + QList<QPlatformScreen *> virtualSiblings() const Q_DECL_OVERRIDE { return m_siblings; } + QPlatformScreen::SubpixelAntialiasingType subpixelAntialiasingTypeHint() const Q_DECL_OVERRIDE; + + // ---------------------------------------------------- + // Additional methods + void setVirtualSiblings(const QList<QPlatformScreen *> &siblings) { m_siblings = siblings; } + NSScreen *nativeScreen() const; + void updateGeometry(); + + QPointF mapToNative(const QPointF &pos) const { return flipCoordinate(pos); } + QRectF mapToNative(const QRectF &rect) const { return flipCoordinate(rect); } + QPointF mapFromNative(const QPointF &pos) const { return flipCoordinate(pos); } + QRectF mapFromNative(const QRectF &rect) const { return flipCoordinate(rect); } + + static QCocoaScreen *primaryScreen(); + +private: + QPointF flipCoordinate(const QPointF &pos) const; + QRectF flipCoordinate(const QRectF &rect) const; + +public: + int m_screenIndex; + QRect m_geometry; + QRect m_availableGeometry; + QDpi m_logicalDpi; + qreal m_refreshRate; + int m_depth; + QString m_name; + QImage::Format m_format; + QSizeF m_physicalSize; + QCocoaCursor *m_cursor; + QList<QPlatformScreen *> m_siblings; +}; + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug debug, const QCocoaScreen *screen); +#endif + +QT_END_NAMESPACE + +#endif + diff --git a/src/plugins/platforms/cocoa/qcocoascreen.mm b/src/plugins/platforms/cocoa/qcocoascreen.mm new file mode 100644 index 0000000000..f523873bde --- /dev/null +++ b/src/plugins/platforms/cocoa/qcocoascreen.mm @@ -0,0 +1,296 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qcocoascreen.h" + +#include "qcocoawindow.h" +#include "qcocoahelpers.h" + +#include <QtCore/qcoreapplication.h> +#include <QtGui/private/qcoregraphics_p.h> + +#include <IOKit/graphics/IOGraphicsLib.h> + +QT_BEGIN_NAMESPACE + +class QCoreTextFontEngine; +class QFontEngineFT; + +QCocoaScreen::QCocoaScreen(int screenIndex) + : QPlatformScreen(), m_screenIndex(screenIndex), m_refreshRate(60.0) +{ + updateGeometry(); + m_cursor = new QCocoaCursor; +} + +QCocoaScreen::~QCocoaScreen() +{ + delete m_cursor; +} + +NSScreen *QCocoaScreen::nativeScreen() const +{ + NSArray *screens = [NSScreen screens]; + + // Stale reference, screen configuration has changed + if (m_screenIndex < 0 || (NSUInteger)m_screenIndex >= [screens count]) + return nil; + + return [screens objectAtIndex:m_screenIndex]; +} + +/*! + Flips the Y coordinate of the point between quadrant I and IV. + + The native coordinate system on macOS uses quadrant I, with origin + in bottom left, and Qt uses quadrant IV, with origin in top left. + + By flippig the Y coordinate, we can map the position between the + two coordinate systems. +*/ +QPointF QCocoaScreen::flipCoordinate(const QPointF &pos) const +{ + return QPointF(pos.x(), m_geometry.height() - pos.y()); +} + +/*! + Flips the Y coordinate of the rectangle between quadrant I and IV. + + The native coordinate system on macOS uses quadrant I, with origin + in bottom left, and Qt uses quadrant IV, with origin in top left. + + By flippig the Y coordinate, we can map the rectangle between the + two coordinate systems. +*/ +QRectF QCocoaScreen::flipCoordinate(const QRectF &rect) const +{ + return QRectF(flipCoordinate(rect.topLeft() + QPoint(0, rect.height())), rect.size()); +} + +void QCocoaScreen::updateGeometry() +{ + NSScreen *nsScreen = nativeScreen(); + if (!nsScreen) + return; + + // At this point the geometry is in native coordinates, but the size + // is correct, which we take advantage of next when we map the native + // coordinates to the Qt coordinate system. + m_geometry = QRectF::fromCGRect(NSRectToCGRect(nsScreen.frame)).toRect(); + m_availableGeometry = QRectF::fromCGRect(NSRectToCGRect(nsScreen.visibleFrame)).toRect(); + + // The reference screen for the geometry is always the primary screen, but since + // we may be in the process of creating and registering the primary screen, we + // must special-case that and assign it direcly. + QCocoaScreen *primaryScreen = (nsScreen == [[NSScreen screens] firstObject]) ? + this : QCocoaScreen::primaryScreen(); + + m_geometry = primaryScreen->mapFromNative(m_geometry).toRect(); + m_availableGeometry = primaryScreen->mapFromNative(m_availableGeometry).toRect(); + + m_format = QImage::Format_RGB32; + m_depth = NSBitsPerPixelFromDepth([nsScreen depth]); + + NSDictionary *devDesc = [nsScreen deviceDescription]; + CGDirectDisplayID dpy = [[devDesc objectForKey:@"NSScreenNumber"] unsignedIntValue]; + CGSize size = CGDisplayScreenSize(dpy); + m_physicalSize = QSizeF(size.width, size.height); + m_logicalDpi.first = 72; + m_logicalDpi.second = 72; + CGDisplayModeRef displayMode = CGDisplayCopyDisplayMode(dpy); + float refresh = CGDisplayModeGetRefreshRate(displayMode); + CGDisplayModeRelease(displayMode); + if (refresh > 0) + m_refreshRate = refresh; + + // Get m_name (brand/model of the monitor) + NSDictionary *deviceInfo = (NSDictionary *)IODisplayCreateInfoDictionary(CGDisplayIOServicePort(dpy), kIODisplayOnlyPreferredName); + NSDictionary *localizedNames = [deviceInfo objectForKey:[NSString stringWithUTF8String:kDisplayProductName]]; + if ([localizedNames count] > 0) + m_name = QString::fromUtf8([[localizedNames objectForKey:[[localizedNames allKeys] objectAtIndex:0]] UTF8String]); + [deviceInfo release]; + + QWindowSystemInterface::handleScreenGeometryChange(screen(), geometry(), availableGeometry()); + QWindowSystemInterface::handleScreenLogicalDotsPerInchChange(screen(), m_logicalDpi.first, m_logicalDpi.second); + QWindowSystemInterface::handleScreenRefreshRateChange(screen(), m_refreshRate); +} + +qreal QCocoaScreen::devicePixelRatio() const +{ + QMacAutoReleasePool pool; + NSScreen *nsScreen = nativeScreen(); + return qreal(nsScreen ? [nsScreen backingScaleFactor] : 1.0); +} + +QPlatformScreen::SubpixelAntialiasingType QCocoaScreen::subpixelAntialiasingTypeHint() const +{ + QPlatformScreen::SubpixelAntialiasingType type = QPlatformScreen::subpixelAntialiasingTypeHint(); + if (type == QPlatformScreen::Subpixel_None) { + // Every OSX machine has RGB pixels unless a peculiar or rotated non-Apple screen is attached + type = QPlatformScreen::Subpixel_RGB; + } + return type; +} + +QWindow *QCocoaScreen::topLevelAt(const QPoint &point) const +{ + NSPoint screenPoint = qt_mac_flipPoint(point); + + // Search (hit test) for the top-level window. [NSWidow windowNumberAtPoint: + // belowWindowWithWindowNumber] may return windows that are not interesting + // to Qt. The search iterates until a suitable window or no window is found. + NSInteger topWindowNumber = 0; + QWindow *window = 0; + do { + // Get the top-most window, below any previously rejected window. + topWindowNumber = [NSWindow windowNumberAtPoint:screenPoint + belowWindowWithWindowNumber:topWindowNumber]; + + // Continue the search if the window does not belong to this process. + NSWindow *nsWindow = [NSApp windowWithWindowNumber:topWindowNumber]; + if (nsWindow == 0) + continue; + + // Continue the search if the window does not belong to Qt. + if (![nsWindow conformsToProtocol:@protocol(QNSWindowProtocol)]) + continue; + + id<QNSWindowProtocol> proto = static_cast<id<QNSWindowProtocol> >(nsWindow); + QCocoaWindow *cocoaWindow = proto.platformWindow; + if (!cocoaWindow) + continue; + window = cocoaWindow->window(); + + // Continue the search if the window is not a top-level window. + if (!window->isTopLevel()) + continue; + + // Stop searching. The current window is the correct window. + break; + } while (topWindowNumber > 0); + + return window; +} + +QPixmap QCocoaScreen::grabWindow(WId window, int x, int y, int width, int height) const +{ + // TODO window should be handled + Q_UNUSED(window) + + const int maxDisplays = 128; // 128 displays should be enough for everyone. + CGDirectDisplayID displays[maxDisplays]; + CGDisplayCount displayCount; + CGRect cgRect; + + if (width < 0 || height < 0) { + // get all displays + cgRect = CGRectInfinite; + } else { + cgRect = CGRectMake(x, y, width, height); + } + const CGDisplayErr err = CGGetDisplaysWithRect(cgRect, maxDisplays, displays, &displayCount); + + if (err && displayCount == 0) + return QPixmap(); + + // calculate pixmap size + QSize windowSize(width, height); + if (width < 0 || height < 0) { + QRect windowRect; + for (uint i = 0; i < displayCount; ++i) { + const CGRect cgRect = CGDisplayBounds(displays[i]); + QRect qRect(cgRect.origin.x, cgRect.origin.y, cgRect.size.width, cgRect.size.height); + windowRect = windowRect.united(qRect); + } + if (width < 0) + windowSize.setWidth(windowRect.width()); + if (height < 0) + windowSize.setHeight(windowRect.height()); + } + + QPixmap windowPixmap(windowSize * devicePixelRatio()); + windowPixmap.fill(Qt::transparent); + + for (uint i = 0; i < displayCount; ++i) { + const CGRect bounds = CGDisplayBounds(displays[i]); + int w = (width < 0 ? bounds.size.width : width) * devicePixelRatio(); + int h = (height < 0 ? bounds.size.height : height) * devicePixelRatio(); + QRect displayRect = QRect(x, y, w, h); + displayRect = displayRect.translated(qRound(-bounds.origin.x), qRound(-bounds.origin.y)); + QCFType<CGImageRef> image = CGDisplayCreateImageForRect(displays[i], + CGRectMake(displayRect.x(), displayRect.y(), displayRect.width(), displayRect.height())); + QPixmap pix(w, h); + pix.fill(Qt::transparent); + CGRect rect = CGRectMake(0, 0, w, h); + QMacCGContext ctx(&pix); + qt_mac_drawCGImage(ctx, &rect, image); + + QPainter painter(&windowPixmap); + painter.drawPixmap(0, 0, pix); + } + return windowPixmap; +} + +/*! + The screen used as a reference for global window geometry +*/ +QCocoaScreen *QCocoaScreen::primaryScreen() +{ + return static_cast<QCocoaScreen *>(QGuiApplication::primaryScreen()->handle()); +} + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug debug, const QCocoaScreen *screen) +{ + QDebugStateSaver saver(debug); + debug.nospace(); + debug << "QCocoaScreen(" << (const void *)screen; + if (screen) { + debug << ", index=" << screen->m_screenIndex; + debug << ", native=" << screen->nativeScreen(); + debug << ", geometry=" << screen->geometry(); + debug << ", dpr=" << screen->devicePixelRatio(); + debug << ", name=" << screen->name(); + } + debug << ')'; + return debug; +} +#endif // !QT_NO_DEBUG_STREAM + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/cocoa/qcocoasystemsettings.mm b/src/plugins/platforms/cocoa/qcocoasystemsettings.mm index 9ddad7fc7a..7c6f879b18 100644 --- a/src/plugins/platforms/cocoa/qcocoasystemsettings.mm +++ b/src/plugins/platforms/cocoa/qcocoasystemsettings.mm @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2017 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the plugins of the Qt Toolkit. @@ -45,49 +45,8 @@ #include <QtGui/qfont.h> #include <QtGui/private/qcoregraphics_p.h> -#include <Carbon/Carbon.h> - QT_BEGIN_NAMESPACE -QBrush qt_mac_brushForTheme(ThemeBrush brush) -{ - QMacAutoReleasePool pool; - - QCFType<CGColorRef> cgClr = 0; - HIThemeBrushCreateCGColor(brush, &cgClr); - return qt_mac_toQBrush(cgClr); -} - -QColor qt_mac_colorForThemeTextColor(ThemeTextColor themeColor) -{ - // No GetThemeTextColor in 64-bit mode, use hardcoded values: - switch (themeColor) { - case kThemeTextColorAlertActive: - case kThemeTextColorTabFrontActive: - case kThemeTextColorBevelButtonActive: - case kThemeTextColorListView: - case kThemeTextColorPlacardActive: - case kThemeTextColorPopupButtonActive: - case kThemeTextColorPopupLabelActive: - case kThemeTextColorPushButtonActive: - return Qt::black; - case kThemeTextColorAlertInactive: - case kThemeTextColorDialogInactive: - case kThemeTextColorPlacardInactive: - case kThemeTextColorPopupButtonInactive: - case kThemeTextColorPopupLabelInactive: - case kThemeTextColorPushButtonInactive: - case kThemeTextColorTabFrontInactive: - case kThemeTextColorBevelButtonInactive: - case kThemeTextColorMenuItemDisabled: - return QColor(127, 127, 127, 255); - case kThemeTextColorMenuItemSelected: - return Qt::white; - default: - return QColor(0, 0, 0, 255); // ### TODO: Sample color like Qt 4. - } -} - QPalette * qt_mac_createSystemPalette() { QColor qc; @@ -119,7 +78,7 @@ QPalette * qt_mac_createSystemPalette() palette->setBrush(QPalette::Shadow, background.darker(170)); - qc = qt_mac_colorForThemeTextColor(kThemeTextColorDialogActive); + qc = qt_mac_toQColor([NSColor controlTextColor]); palette->setColor(QPalette::Active, QPalette::Text, qc); palette->setColor(QPalette::Active, QPalette::WindowText, qc); palette->setColor(QPalette::Active, QPalette::HighlightedText, qc); @@ -127,7 +86,7 @@ QPalette * qt_mac_createSystemPalette() palette->setColor(QPalette::Inactive, QPalette::WindowText, qc); palette->setColor(QPalette::Inactive, QPalette::HighlightedText, qc); - qc = qt_mac_colorForThemeTextColor(kThemeTextColorDialogInactive); + qc = qt_mac_toQColor([NSColor disabledControlTextColor]); palette->setColor(QPalette::Disabled, QPalette::Text, qc); palette->setColor(QPalette::Disabled, QPalette::WindowText, qc); palette->setColor(QPalette::Disabled, QPalette::HighlightedText, qc); @@ -136,60 +95,66 @@ QPalette * qt_mac_createSystemPalette() } struct QMacPaletteMap { - inline QMacPaletteMap(QPlatformTheme::Palette p, ThemeBrush a, ThemeBrush i) : + inline QMacPaletteMap(QPlatformTheme::Palette p, NSColor *a, NSColor *i) : paletteRole(p), active(a), inactive(i) { } QPlatformTheme::Palette paletteRole; - ThemeBrush active, inactive; + NSColor *active, *inactive; }; +#define MAC_PALETTE_ENTRY(pal, active, inactive) \ + QMacPaletteMap(pal, [NSColor active], [NSColor inactive]) static QMacPaletteMap mac_widget_colors[] = { - QMacPaletteMap(QPlatformTheme::ToolButtonPalette, kThemeTextColorBevelButtonActive, kThemeTextColorBevelButtonInactive), - QMacPaletteMap(QPlatformTheme::ButtonPalette, kThemeTextColorPushButtonActive, kThemeTextColorPushButtonInactive), - QMacPaletteMap(QPlatformTheme::HeaderPalette, kThemeTextColorPushButtonActive, kThemeTextColorPushButtonInactive), - QMacPaletteMap(QPlatformTheme::ComboBoxPalette, kThemeTextColorPopupButtonActive, kThemeTextColorPopupButtonInactive), - QMacPaletteMap(QPlatformTheme::ItemViewPalette, kThemeTextColorListView, kThemeTextColorDialogInactive), - QMacPaletteMap(QPlatformTheme::MessageBoxLabelPalette, kThemeTextColorAlertActive, kThemeTextColorAlertInactive), - QMacPaletteMap(QPlatformTheme::TabBarPalette, kThemeTextColorTabFrontActive, kThemeTextColorTabFrontInactive), - QMacPaletteMap(QPlatformTheme::LabelPalette, kThemeTextColorPlacardActive, kThemeTextColorPlacardInactive), - QMacPaletteMap(QPlatformTheme::GroupBoxPalette, kThemeTextColorPlacardActive, kThemeTextColorPlacardInactive), - QMacPaletteMap(QPlatformTheme::MenuPalette, kThemeTextColorMenuItemActive, kThemeTextColorMenuItemDisabled), - QMacPaletteMap(QPlatformTheme::MenuBarPalette, kThemeTextColorMenuItemActive, kThemeTextColorMenuItemDisabled), - //### TODO: The zeros below gives white-on-black text. - QMacPaletteMap(QPlatformTheme::TextEditPalette, 0, 0), - QMacPaletteMap(QPlatformTheme::TextLineEditPalette, 0, 0), - QMacPaletteMap(QPlatformTheme::NPalettes, 0, 0) }; + MAC_PALETTE_ENTRY(QPlatformTheme::ToolButtonPalette, controlTextColor, disabledControlTextColor), + MAC_PALETTE_ENTRY(QPlatformTheme::ButtonPalette, controlTextColor, disabledControlTextColor), + MAC_PALETTE_ENTRY(QPlatformTheme::HeaderPalette, headerTextColor, disabledControlTextColor), + MAC_PALETTE_ENTRY(QPlatformTheme::ComboBoxPalette, controlTextColor, disabledControlTextColor), + MAC_PALETTE_ENTRY(QPlatformTheme::ItemViewPalette, textColor, disabledControlTextColor), + MAC_PALETTE_ENTRY(QPlatformTheme::MessageBoxLabelPalette, textColor, disabledControlTextColor), + MAC_PALETTE_ENTRY(QPlatformTheme::TabBarPalette, controlTextColor, disabledControlTextColor), + MAC_PALETTE_ENTRY(QPlatformTheme::LabelPalette, textColor, disabledControlTextColor), + MAC_PALETTE_ENTRY(QPlatformTheme::GroupBoxPalette, textColor, disabledControlTextColor), + MAC_PALETTE_ENTRY(QPlatformTheme::MenuPalette, controlTextColor, disabledControlTextColor), + MAC_PALETTE_ENTRY(QPlatformTheme::MenuBarPalette, controlTextColor, disabledControlTextColor), + MAC_PALETTE_ENTRY(QPlatformTheme::TextEditPalette, textColor, disabledControlTextColor), + MAC_PALETTE_ENTRY(QPlatformTheme::TextLineEditPalette, textColor, disabledControlTextColor) +}; +#undef MAC_PALETTE_ENTRY + +static const int mac_widget_colors_count = sizeof(mac_widget_colors) / sizeof(mac_widget_colors[0]); QHash<QPlatformTheme::Palette, QPalette*> qt_mac_createRolePalettes() { QHash<QPlatformTheme::Palette, QPalette*> palettes; QColor qc; - for (int i = 0; mac_widget_colors[i].paletteRole != QPlatformTheme::NPalettes; i++) { + for (int i = 0; i < mac_widget_colors_count; i++) { QPalette &pal = *qt_mac_createSystemPalette(); if (mac_widget_colors[i].active != 0) { - qc = qt_mac_colorForThemeTextColor(mac_widget_colors[i].active); + qc = qt_mac_toQColor(mac_widget_colors[i].active); pal.setColor(QPalette::Active, QPalette::Text, qc); pal.setColor(QPalette::Inactive, QPalette::Text, qc); pal.setColor(QPalette::Active, QPalette::WindowText, qc); pal.setColor(QPalette::Inactive, QPalette::WindowText, qc); pal.setColor(QPalette::Active, QPalette::HighlightedText, qc); pal.setColor(QPalette::Inactive, QPalette::HighlightedText, qc); - qc = qt_mac_colorForThemeTextColor(mac_widget_colors[i].inactive); + qc = qt_mac_toQColor(mac_widget_colors[i].inactive); pal.setColor(QPalette::Disabled, QPalette::Text, qc); pal.setColor(QPalette::Disabled, QPalette::WindowText, qc); pal.setColor(QPalette::Disabled, QPalette::HighlightedText, qc); } if (mac_widget_colors[i].paletteRole == QPlatformTheme::MenuPalette || mac_widget_colors[i].paletteRole == QPlatformTheme::MenuBarPalette) { - pal.setBrush(QPalette::Background, qt_mac_brushForTheme(kThemeBrushMenuBackground)); - qc = qt_mac_colorForThemeTextColor(kThemeTextColorMenuItemActive); + pal.setBrush(QPalette::Highlight, qt_mac_toQColor([NSColor selectedMenuItemColor])); + qc = qt_mac_toQColor([NSColor labelColor]); pal.setBrush(QPalette::ButtonText, qc); - qc = qt_mac_colorForThemeTextColor(kThemeTextColorMenuItemSelected); + pal.setBrush(QPalette::Text, qc); + qc = qt_mac_toQColor([NSColor selectedMenuItemTextColor]); pal.setBrush(QPalette::HighlightedText, qc); - qc = qt_mac_colorForThemeTextColor(kThemeTextColorMenuItemDisabled); + qc = qt_mac_toQColor([NSColor disabledControlTextColor]); pal.setBrush(QPalette::Disabled, QPalette::Text, qc); } else if ((mac_widget_colors[i].paletteRole == QPlatformTheme::ButtonPalette) - || (mac_widget_colors[i].paletteRole == QPlatformTheme::HeaderPalette)) { + || (mac_widget_colors[i].paletteRole == QPlatformTheme::HeaderPalette) + || (mac_widget_colors[i].paletteRole == QPlatformTheme::TabBarPalette)) { pal.setColor(QPalette::Disabled, QPalette::ButtonText, pal.color(QPalette::Disabled, QPalette::Text)); pal.setColor(QPalette::Inactive, QPalette::ButtonText, diff --git a/src/plugins/platforms/cocoa/qcocoatheme.mm b/src/plugins/platforms/cocoa/qcocoatheme.mm index a1c1b379f7..5239864ad8 100644 --- a/src/plugins/platforms/cocoa/qcocoatheme.mm +++ b/src/plugins/platforms/cocoa/qcocoatheme.mm @@ -55,6 +55,7 @@ #include <QtGui/private/qguiapplication_p.h> #include <QtGui/private/qcoregraphics_p.h> #include <QtGui/qpainter.h> +#include <QtGui/qtextformat.h> #include <QtFontDatabaseSupport/private/qcoretextfontdatabase_p.h> #include <QtThemeSupport/private/qabstractfileiconengine_p.h> #include <qpa/qplatformdialoghelper.h> @@ -341,9 +342,11 @@ QVariant QCocoaTheme::themeHint(ThemeHint hint) const case IconPixmapSizes: return QVariant::fromValue(QCocoaFileIconEngine::availableIconSizes()); case QPlatformTheme::PasswordMaskCharacter: - return QVariant(QChar(kBulletUnicode)); + return QVariant(QChar(0x2022)); case QPlatformTheme::UiEffects: return QVariant(int(HoverEffect)); + case QPlatformTheme::SpellCheckUnderlineStyle: + return QVariant(int(QTextCharFormat::DotLine)); default: break; } diff --git a/src/plugins/platforms/cocoa/qcocoawindow.h b/src/plugins/platforms/cocoa/qcocoawindow.h index deba861fcc..6fbe29f683 100644 --- a/src/plugins/platforms/cocoa/qcocoawindow.h +++ b/src/plugins/platforms/cocoa/qcocoawindow.h @@ -50,79 +50,15 @@ #include "qcocoaglcontext.h" #endif #include "qnsview.h" +#include "qnswindow.h" #include "qt_mac_p.h" -QT_FORWARD_DECLARE_CLASS(QCocoaWindow) - -@class QT_MANGLE_NAMESPACE(QNSWindowHelper); - -@protocol QNSWindowProtocol - -@property (nonatomic, readonly) QT_MANGLE_NAMESPACE(QNSWindowHelper) *helper; - -- (void)superSendEvent:(NSEvent *)theEvent; -- (void)closeAndRelease; - -@end - -typedef NSWindow<QNSWindowProtocol> QCocoaNSWindow; - -@interface QT_MANGLE_NAMESPACE(QNSWindowHelper) : NSObject -{ - QCocoaNSWindow *_window; - QPointer<QCocoaWindow> _platformWindow; - BOOL _grabbingMouse; - BOOL _releaseOnMouseUp; -} - -@property (nonatomic, readonly) QCocoaNSWindow *window; -@property (nonatomic, readonly) QCocoaWindow *platformWindow; -@property (nonatomic) BOOL grabbingMouse; -@property (nonatomic) BOOL releaseOnMouseUp; - -- (id)initWithNSWindow:(QCocoaNSWindow *)window platformWindow:(QCocoaWindow *)platformWindow; -- (void)handleWindowEvent:(NSEvent *)theEvent; -- (void) clearWindow; - -@end - -QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSWindowHelper); - -@interface QT_MANGLE_NAMESPACE(QNSWindow) : NSWindow<QNSWindowProtocol> -{ - QNSWindowHelper *_helper; -} - -@property (nonatomic, readonly) QNSWindowHelper *helper; - -- (id)initWithContentRect:(NSRect)contentRect - screen:(NSScreen*)screen - styleMask:(NSUInteger)windowStyle - qPlatformWindow:(QCocoaWindow *)qpw; - -@end - -QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSWindow); - -@interface QT_MANGLE_NAMESPACE(QNSPanel) : NSPanel<QNSWindowProtocol> -{ - QNSWindowHelper *_helper; -} - -@property (nonatomic, readonly) QNSWindowHelper *helper; - -- (id)initWithContentRect:(NSRect)contentRect - screen:(NSScreen*)screen - styleMask:(NSUInteger)windowStyle - qPlatformWindow:(QCocoaWindow *)qpw; - -@end - -QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSPanel); +QT_BEGIN_NAMESPACE -@class QT_MANGLE_NAMESPACE(QNSWindowDelegate); +#ifndef QT_NO_DEBUG_STREAM +class QDebug; +#endif -QT_BEGIN_NAMESPACE // QCocoaWindow // // QCocoaWindow is an NSView (not an NSWindow!) in the sense @@ -159,16 +95,15 @@ public: QCocoaWindow(QWindow *tlw, WId nativeHandle = 0); ~QCocoaWindow(); + void initialize() override; + void setGeometry(const QRect &rect) Q_DECL_OVERRIDE; QRect geometry() const Q_DECL_OVERRIDE; void setCocoaGeometry(const QRect &rect); - void clipChildWindows(); - void clipWindow(const NSRect &clipRect); - void show(bool becauseOfAncestor = false); - void hide(bool becauseOfAncestor = false); + void setVisible(bool visible) Q_DECL_OVERRIDE; void setWindowFlags(Qt::WindowFlags flags) Q_DECL_OVERRIDE; - void setWindowState(Qt::WindowState state) Q_DECL_OVERRIDE; + void setWindowState(Qt::WindowStates state) Q_DECL_OVERRIDE; void setWindowTitle(const QString &title) Q_DECL_OVERRIDE; void setWindowFilePath(const QString &filePath) Q_DECL_OVERRIDE; void setWindowIcon(const QIcon &icon) Q_DECL_OVERRIDE; @@ -188,6 +123,7 @@ public: bool isForeignWindow() const Q_DECL_OVERRIDE; + void requestUpdate() override; void requestActivateWindow() Q_DECL_OVERRIDE; WId winId() const Q_DECL_OVERRIDE; @@ -198,11 +134,12 @@ public: void setEmbeddedInForeignView(bool subwindow); + Q_NOTIFICATION_HANDLER(NSViewFrameDidChangeNotification) void viewDidChangeFrame(); + Q_NOTIFICATION_HANDLER(NSViewGlobalFrameDidChangeNotification) void viewDidChangeGlobalFrame(); + Q_NOTIFICATION_HANDLER(NSWindowWillMoveNotification) void windowWillMove(); Q_NOTIFICATION_HANDLER(NSWindowDidMoveNotification) void windowDidMove(); Q_NOTIFICATION_HANDLER(NSWindowDidResizeNotification) void windowDidResize(); - Q_NOTIFICATION_HANDLER(NSViewFrameDidChangeNotification) void viewDidChangeFrame(); - Q_NOTIFICATION_HANDLER(NSViewGlobalFrameDidChangeNotification) void viewDidChangeGlobalFrame(); Q_NOTIFICATION_HANDLER(NSWindowDidEndLiveResizeNotification) void windowDidEndLiveResize(); Q_NOTIFICATION_HANDLER(NSWindowDidBecomeKeyNotification) void windowDidBecomeKey(); Q_NOTIFICATION_HANDLER(NSWindowDidResignKeyNotification) void windowDidResignKey(); @@ -212,8 +149,8 @@ public: Q_NOTIFICATION_HANDLER(NSWindowDidEnterFullScreenNotification) void windowDidEnterFullScreen(); Q_NOTIFICATION_HANDLER(NSWindowWillExitFullScreenNotification) void windowWillExitFullScreen(); Q_NOTIFICATION_HANDLER(NSWindowDidExitFullScreenNotification) void windowDidExitFullScreen(); - Q_NOTIFICATION_HANDLER(NSWindowDidOrderOffScreenNotification) void windowDidOrderOffScreen(); Q_NOTIFICATION_HANDLER(NSWindowDidOrderOnScreenAndFinishAnimatingNotification) void windowDidOrderOnScreen(); + Q_NOTIFICATION_HANDLER(NSWindowDidOrderOffScreenNotification) void windowDidOrderOffScreen(); Q_NOTIFICATION_HANDLER(NSWindowDidChangeOcclusionStateNotification) void windowDidChangeOcclusionState(); Q_NOTIFICATION_HANDLER(NSWindowDidChangeScreenNotification) void windowDidChangeScreen(); Q_NOTIFICATION_HANDLER(NSWindowWillCloseNotification) void windowWillClose(); @@ -221,11 +158,8 @@ public: bool windowShouldClose(); bool windowIsPopupType(Qt::WindowType type = Qt::Widget) const; - void reportCurrentWindowState(bool unconditionally = false); - NSInteger windowLevel(Qt::WindowFlags flags); NSUInteger windowStyleMask(Qt::WindowFlags flags); - void setWindowShadow(Qt::WindowFlags flags); void setWindowZoomButton(Qt::WindowFlags flags); #ifndef QT_NO_OPENGL @@ -242,8 +176,6 @@ public: void setMenubar(QCocoaMenuBar *mb); QCocoaMenuBar *menubar() const; - NSCursor *effectiveWindowCursor() const; - void applyEffectiveWindowCursor(); void setWindowCursor(NSCursor *cursor); void registerTouch(bool enable); @@ -252,14 +184,10 @@ public: void setContentBorderAreaEnabled(quintptr identifier, bool enable); void setContentBorderEnabled(bool enable); bool testContentBorderAreaPosition(int position) const; - void applyContentBorderThickness(NSWindow *window); + void applyContentBorderThickness(NSWindow *window = nullptr); void updateNSToolbar(); qreal devicePixelRatio() const Q_DECL_OVERRIDE; - bool isWindowExposable(); - void exposeWindow(); - void obscureWindow(); - void updateExposedGeometry(); QWindow *childWindowAt(QPoint windowPoint); bool shouldRefuseKeyWindowAndFirstResponder(); @@ -271,7 +199,6 @@ public: ParentChanged = 0x1, MissingWindow = 0x2, WindowModalityChanged = 0x4, - ChildNSWindowChanged = 0x8, ContentViewChanged = 0x10, PanelChanged = 0x20, }; @@ -279,20 +206,13 @@ public: Q_FLAG(RecreationReasons) protected: - bool isChildNSWindow() const; - bool isContentView() const; - - void foreachChildNSWindow(void (^block)(QCocoaWindow *)); - void recreateWindowIfNeeded(); - QCocoaNSWindow *createNSWindow(bool shouldBeChildNSWindow, bool shouldBePanel); + QCocoaNSWindow *createNSWindow(bool shouldBePanel); QRect nativeWindowGeometry() const; - void reinsertChildWindow(QCocoaWindow *child); - void removeChildWindow(QCocoaWindow *child); Qt::WindowState windowState() const; - void applyWindowState(Qt::WindowState newState); + void applyWindowState(Qt::WindowStates newState); void toggleMaximized(); void toggleFullScreen(); bool isTransitioningToFullScreen() const; @@ -302,24 +222,34 @@ public: // for QNSView friend class QCocoaBackingStore; friend class QCocoaNativeInterface; + bool isContentView() const; + bool alwaysShowToolWindow() const; void removeMonitor(); + enum HandleFlags { + NoHandleFlags = 0, + HandleUnconditionally = 1 + }; + + void handleGeometryChange(); + void handleWindowStateChanged(HandleFlags flags = NoHandleFlags); + void handleExposeEvent(const QRegion ®ion); + NSView *m_view; QCocoaNSWindow *m_nsWindow; - QPointer<QCocoaWindow> m_forwardWindow; // TODO merge to one variable if possible bool m_viewIsEmbedded; // true if the m_view is actually embedded in a "foreign" NSView hiearchy bool m_viewIsToBeEmbedded; // true if the m_view is intended to be embedded in a "foreign" NSView hiearchy Qt::WindowFlags m_windowFlags; - Qt::WindowState m_lastReportedWindowState; + Qt::WindowStates m_lastReportedWindowState; Qt::WindowModality m_windowModality; QPointer<QWindow> m_enterLeaveTargetWindow; bool m_windowUnderMouse; - bool m_inConstructor; + bool m_initialized; bool m_inSetVisible; bool m_inSetGeometry; bool m_inSetStyleMask; @@ -327,18 +257,14 @@ public: // for QNSView QCocoaGLContext *m_glContext; #endif QCocoaMenuBar *m_menubar; - NSCursor *m_windowCursor; + + bool m_needsInvalidateShadow; bool m_hasModalSession; bool m_frameStrutEventsEnabled; - bool m_geometryUpdateExposeAllowed; - bool m_isExposed; - QRect m_exposedGeometry; - qreal m_exposedDevicePixelRatio; + QRect m_exposedRect; int m_registerTouchCount; bool m_resizableTransientParent; - bool m_hiddenByClipping; - bool m_hiddenByAncestor; static const int NoAlertRequest; NSInteger m_alertRequest; @@ -359,10 +285,12 @@ public: // for QNSView }; QHash<quintptr, BorderRange> m_contentBorderAreas; // identifer -> uppper/lower QHash<quintptr, bool> m_enabledContentBorderAreas; // identifer -> enabled state (true/false) - - bool m_hasWindowFilePath; }; +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug debug, const QCocoaWindow *window); +#endif + QT_END_NAMESPACE #endif // QCOCOAWINDOW_H diff --git a/src/plugins/platforms/cocoa/qcocoawindow.mm b/src/plugins/platforms/cocoa/qcocoawindow.mm index 5cd4beb4f0..d1f19f2de9 100644 --- a/src/plugins/platforms/cocoa/qcocoawindow.mm +++ b/src/plugins/platforms/cocoa/qcocoawindow.mm @@ -38,6 +38,7 @@ ****************************************************************************/ #include "qcocoawindow.h" #include "qcocoaintegration.h" +#include "qcocoascreen.h" #include "qnswindowdelegate.h" #include "qcocoaeventdispatcher.h" #ifndef QT_NO_OPENGL @@ -46,6 +47,7 @@ #include "qcocoahelpers.h" #include "qcocoanativeinterface.h" #include "qnsview.h" +#include "qnswindow.h" #include <QtCore/qfileinfo.h> #include <QtCore/private/qcore_mac_p.h> #include <qwindow.h> @@ -53,34 +55,22 @@ #include <qpa/qwindowsysteminterface.h> #include <qpa/qplatformscreen.h> #include <QtGui/private/qcoregraphics_p.h> +#include <QtGui/private/qhighdpiscaling_p.h> #include <AppKit/AppKit.h> +#include <QuartzCore/QuartzCore.h> #include <QDebug> #include <vector> +QT_BEGIN_NAMESPACE + enum { defaultWindowWidth = 160, defaultWindowHeight = 160 }; -static bool isMouseEvent(NSEvent *ev) -{ - switch ([ev type]) { - case NSLeftMouseDown: - case NSLeftMouseUp: - case NSRightMouseDown: - case NSRightMouseUp: - case NSMouseMoved: - case NSLeftMouseDragged: - case NSRightMouseDragged: - return true; - default: - return false; - } -} - static void qt_closePopups() { while (QCocoaWindow *popup = QCocoaIntegration::instance()->popPopupWindow()) { @@ -89,372 +79,7 @@ static void qt_closePopups() } } -@interface NSWindow (FullScreenProperty) -@property(readonly) BOOL qt_fullScreen; -@end - -@implementation NSWindow (FullScreenProperty) - -+ (void)load -{ - NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; - [center addObserverForName:NSWindowDidEnterFullScreenNotification object:nil queue:nil - usingBlock:^(NSNotification *notification) { - objc_setAssociatedObject(notification.object, @selector(qt_fullScreen), - [NSNumber numberWithBool:YES], OBJC_ASSOCIATION_RETAIN); - } - ]; - [center addObserverForName:NSWindowDidExitFullScreenNotification object:nil queue:nil - usingBlock:^(NSNotification *notification) { - objc_setAssociatedObject(notification.object, @selector(qt_fullScreen), - nil, OBJC_ASSOCIATION_RETAIN); - } - ]; -} - -- (BOOL)qt_fullScreen -{ - NSNumber *number = objc_getAssociatedObject(self, @selector(qt_fullScreen)); - return [number boolValue]; -} -@end - -@implementation QNSWindowHelper - -@synthesize window = _window; -@synthesize grabbingMouse = _grabbingMouse; -@synthesize releaseOnMouseUp = _releaseOnMouseUp; - -- (QCocoaWindow *)platformWindow -{ - return _platformWindow.data(); -} - -- (id)initWithNSWindow:(QCocoaNSWindow *)window platformWindow:(QCocoaWindow *)platformWindow -{ - self = [super init]; - if (self) { - _window = window; - _platformWindow = platformWindow; - - _window.delegate = [[QNSWindowDelegate alloc] initWithQCocoaWindow:_platformWindow]; - - // Prevent Cocoa from releasing the window on close. Qt - // handles the close event asynchronously and we want to - // make sure that m_nsWindow stays valid until the - // QCocoaWindow is deleted by Qt. - [_window setReleasedWhenClosed:NO]; - } - - return self; -} - -- (void)handleWindowEvent:(NSEvent *)theEvent -{ - QCocoaWindow *pw = self.platformWindow; - if (pw && pw->m_forwardWindow) { - if (theEvent.type == NSLeftMouseUp || theEvent.type == NSLeftMouseDragged) { - QNSView *forwardView = qnsview_cast(pw->view()); - if (theEvent.type == NSLeftMouseUp) { - [forwardView mouseUp:theEvent]; - pw->m_forwardWindow.clear(); - } else { - [forwardView mouseDragged:theEvent]; - } - } - if (pw->window()->isTopLevel() && theEvent.type == NSLeftMouseDown) { - pw->m_forwardWindow.clear(); - } - } - - if (theEvent.type == NSLeftMouseDown) { - self.grabbingMouse = YES; - } else if (theEvent.type == NSLeftMouseUp) { - self.grabbingMouse = NO; - if (self.releaseOnMouseUp) { - [self detachFromPlatformWindow]; - [self.window release]; - return; - } - } - - // The call to -[NSWindow sendEvent] may result in the window being deleted - // (e.g., when closing the window by pressing the title bar close button). - [self retain]; - [self.window superSendEvent:theEvent]; - bool windowStillAlive = self.window != nil; // We need to read before releasing - [self release]; - if (!windowStillAlive) - return; - - if (!self.window.delegate) - return; // Already detached, pending NSAppKitDefined event - - if (pw && pw->frameStrutEventsEnabled() && isMouseEvent(theEvent)) { - NSPoint loc = [theEvent locationInWindow]; - NSRect windowFrame = [self.window convertRectFromScreen:[self.window frame]]; - NSRect contentFrame = [[self.window contentView] frame]; - if (NSMouseInRect(loc, windowFrame, NO) && !NSMouseInRect(loc, contentFrame, NO)) - [qnsview_cast(pw->view()) handleFrameStrutMouseEvent:theEvent]; - } -} - -- (void)detachFromPlatformWindow -{ - _platformWindow.clear(); - [self.window.delegate release]; - self.window.delegate = nil; -} - -- (void)clearWindow -{ - if (_window) { - QCocoaEventDispatcher *cocoaEventDispatcher = qobject_cast<QCocoaEventDispatcher *>(QGuiApplication::instance()->eventDispatcher()); - if (cocoaEventDispatcher) { - QCocoaEventDispatcherPrivate *cocoaEventDispatcherPrivate = static_cast<QCocoaEventDispatcherPrivate *>(QObjectPrivate::get(cocoaEventDispatcher)); - cocoaEventDispatcherPrivate->removeQueuedUserInputEvents([_window windowNumber]); - } - - _window = nil; - } -} - -- (void)dealloc -{ - _window = nil; - _platformWindow.clear(); - [super dealloc]; -} - -@end - -@implementation QNSWindow - -@synthesize helper = _helper; - -- (id)initWithContentRect:(NSRect)contentRect - screen:(NSScreen*)screen - styleMask:(NSUInteger)windowStyle - qPlatformWindow:(QCocoaWindow *)qpw -{ - self = [super initWithContentRect:contentRect - styleMask:windowStyle - backing:NSBackingStoreBuffered - defer:NO screen:screen]; // Deferring window creation breaks OpenGL (the GL context is - // set up before the window is shown and needs a proper window) - - if (self) { - _helper = [[QNSWindowHelper alloc] initWithNSWindow:self platformWindow:qpw]; - } - return self; -} - -- (BOOL)canBecomeKeyWindow -{ - // Prevent child NSWindows from becoming the key window in - // order keep the active apperance of the top-level window. - QCocoaWindow *pw = self.helper.platformWindow; - if (!pw || !pw->window()->isTopLevel()) - return NO; - - if (pw->shouldRefuseKeyWindowAndFirstResponder()) - return NO; - - // The default implementation returns NO for title-bar less windows, - // override and return yes here to make sure popup windows such as - // the combobox popup can become the key window. - return YES; -} - -- (BOOL)canBecomeMainWindow -{ - BOOL canBecomeMain = YES; // By default, windows can become the main window - - // Windows with a transient parent (such as combobox popup windows) - // cannot become the main window: - QCocoaWindow *pw = self.helper.platformWindow; - if (!pw || !pw->window()->isTopLevel() || pw->window()->transientParent()) - canBecomeMain = NO; - - return canBecomeMain; -} - -- (void) sendEvent: (NSEvent*) theEvent -{ - [self.helper handleWindowEvent:theEvent]; -} - -- (void)superSendEvent:(NSEvent *)theEvent -{ - [super sendEvent:theEvent]; -} - -- (void)closeAndRelease -{ - qCDebug(lcQpaCocoaWindow) << "closeAndRelease" << self; - - [self close]; - - if (self.helper.grabbingMouse) { - self.helper.releaseOnMouseUp = YES; - } else { - [self.helper detachFromPlatformWindow]; - [self release]; - } -} - -- (void)dealloc -{ - [_helper clearWindow]; - [_helper release]; - _helper = nil; - [super dealloc]; -} - -@end - -@implementation QNSPanel - -@synthesize helper = _helper; - -+ (void)applicationActivationChanged:(NSNotification*)notification -{ - const id sender = self; - NSEnumerator<NSWindow*> *windowEnumerator = nullptr; - NSApplication *application = [NSApplication sharedApplication]; - -#if QT_MACOS_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_12) - if (QOperatingSystemVersion::current() >= QOperatingSystemVersion::MacOSSierra) { - // Unfortunately there's no NSWindowListOrderedBackToFront, - // so we have to manually reverse the order using an array. - NSMutableArray *windows = [[[NSMutableArray alloc] init] autorelease]; - [application enumerateWindowsWithOptions:NSWindowListOrderedFrontToBack - usingBlock:^(NSWindow *window, BOOL *) { - // For some reason AppKit will give us nil-windows, skip those - if (!window) - return; - - [(NSMutableArray*)windows addObject:window]; - } - ]; - - windowEnumerator = windows.reverseObjectEnumerator; - } else -#endif - { - // No way to get ordered list of windows, so fall back to unordered, - // list, which typically corresponds to window creation order. - windowEnumerator = application.windows.objectEnumerator; - } - - for (NSWindow *window in windowEnumerator) { - // We're meddling with normal and floating windows, so leave others alone - if (!(window.level == NSNormalWindowLevel || window.level == NSFloatingWindowLevel)) - continue; - - // Windows that hide automatically will keep their NSFloatingWindowLevel, - // and hence be on top of the window stack. We don't want to affect these - // windows, as otherwise we might end up with key windows being ordered - // behind these auto-hidden windows when activating the application by - // clicking on a new tool window. - if (window.hidesOnDeactivate) - continue; - - if ([window conformsToProtocol:@protocol(QNSWindowProtocol)]) { - QCocoaWindow *cocoaWindow = static_cast<id<QNSWindowProtocol>>(window).helper.platformWindow; - window.level = notification.name == NSApplicationWillResignActiveNotification ? - NSNormalWindowLevel : cocoaWindow->windowLevel(cocoaWindow->window()->flags()); - } - - // The documentation says that "when a window enters a new level, it’s ordered - // in front of all its peers in that level", but that doesn't seem to be the - // case in practice. To keep the order correct after meddling with the window - // levels, we explicitly order each window to the front. Since we are iterating - // the windows in back-to-front order, this is okey. The call also triggers AppKit - // to re-evaluate the level in relation to windows from other applications, - // working around an issue where our tool windows would stay on top of other - // application windows if activation was transferred to another application by - // clicking on it instead of via the application switcher or Dock. Finally, we - // do this re-ordering for all windows (except auto-hiding ones), otherwise we would - // end up triggering a bug in AppKit where the tool windows would disappear behind - // the application window. - [window orderFront:sender]; - } -} - -- (id)initWithContentRect:(NSRect)contentRect - screen:(NSScreen*)screen - styleMask:(NSUInteger)windowStyle - qPlatformWindow:(QCocoaWindow *)qpw -{ - self = [super initWithContentRect:contentRect - styleMask:windowStyle - backing:NSBackingStoreBuffered - defer:NO screen:screen]; // Deferring window creation breaks OpenGL (the GL context is - // set up before the window is shown and needs a proper window) - - if (self) { - _helper = [[QNSWindowHelper alloc] initWithNSWindow:self platformWindow:qpw]; - - if (qpw->alwaysShowToolWindow()) { - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; - [center addObserver:[self class] selector:@selector(applicationActivationChanged:) - name:NSApplicationWillResignActiveNotification object:nil]; - [center addObserver:[self class] selector:@selector(applicationActivationChanged:) - name:NSApplicationWillBecomeActiveNotification object:nil]; - }); - } - } - return self; -} - -- (BOOL)canBecomeKeyWindow -{ - QCocoaWindow *pw = self.helper.platformWindow; - if (!pw) - return NO; - - if (pw->shouldRefuseKeyWindowAndFirstResponder()) - return NO; - - // Only tool or dialog windows should become key: - Qt::WindowType type = pw->window()->type(); - if (type == Qt::Tool || type == Qt::Dialog) - return YES; - - return NO; -} - -- (void) sendEvent: (NSEvent*) theEvent -{ - [self.helper handleWindowEvent:theEvent]; -} - -- (void)superSendEvent:(NSEvent *)theEvent -{ - [super sendEvent:theEvent]; -} - -- (void)closeAndRelease -{ - qCDebug(lcQpaCocoaWindow) << "closeAndRelease" << self; - - [self.helper detachFromPlatformWindow]; - [self close]; - [self release]; -} - -- (void)dealloc -{ - [_helper clearWindow]; - [_helper release]; - _helper = nil; - [super dealloc]; -} - -@end +Q_LOGGING_CATEGORY(lcCocoaNotifications, "qt.qpa.cocoa.notifications"); static void qRegisterNotificationCallbacks() { @@ -475,38 +100,43 @@ static void qRegisterNotificationCallbacks() [center addObserverForName:notificationName.toNSString() object:nil queue:nil usingBlock:^(NSNotification *notification) { - NSView *view = nullptr; + QVarLengthArray<QCocoaWindow *, 32> cocoaWindows; if ([notification.object isKindOfClass:[NSWindow class]]) { - NSWindow *window = notification.object; - // Only top level NSWindows should notify their QNSViews - if (window.parentWindow) - return; - - if (!window.contentView) - return; - - view = window.contentView; + NSWindow *nsWindow = notification.object; + for (const QWindow *window : QGuiApplication::allWindows()) { + if (QCocoaWindow *cocoaWindow = static_cast<QCocoaWindow *>(window->handle())) + if (cocoaWindow->nativeWindow() == nsWindow) + cocoaWindows += cocoaWindow; + } } else if ([notification.object isKindOfClass:[NSView class]]) { - view = notification.object; + if (QNSView *qnsView = qnsview_cast(notification.object)) + cocoaWindows += qnsView.platformWindow; } else { - qCWarning(lcQpaCocoaWindow) << "Unhandled notifcation" + qCWarning(lcCocoaNotifications) << "Unhandled notifcation" << notification.name << "for" << notification.object; return; } - Q_ASSERT(view); - QCocoaWindow *cocoaWindow = nullptr; - if (QNSView *qnsView = qnsview_cast(view)) - cocoaWindow = qnsView.platformWindow; + if (lcCocoaNotifications().isDebugEnabled()) { + if (cocoaWindows.isEmpty()) { + qCDebug(lcCocoaNotifications) << "Could not find forwarding target for" << + qPrintable(notificationName) << "from" << notification.object; + } else { + QVector<QCocoaWindow *> debugWindows; + for (QCocoaWindow *cocoaWindow : cocoaWindows) + debugWindows += cocoaWindow; + qCDebug(lcCocoaNotifications) << "Forwarding" << qPrintable(notificationName) << + "to" << debugWindows; + } + } // FIXME: Could be a foreign window, look up by iterating top level QWindows - if (!cocoaWindow) - return; - - if (!method.invoke(cocoaWindow, Qt::DirectConnection)) { - qCWarning(lcQpaCocoaWindow) << "Failed to invoke NSNotification callback for" - << notification.name << "on" << cocoaWindow; + for (QCocoaWindow *cocoaWindow : cocoaWindows) { + if (!method.invoke(cocoaWindow, Qt::DirectConnection)) { + qCWarning(lcQpaCocoaWindow) << "Failed to invoke NSNotification callback for" + << notification.name << "on" << cocoaWindow; + } } }]; } @@ -515,8 +145,8 @@ Q_CONSTRUCTOR_FUNCTION(qRegisterNotificationCallbacks) const int QCocoaWindow::NoAlertRequest = -1; -QCocoaWindow::QCocoaWindow(QWindow *tlw, WId nativeHandle) - : QPlatformWindow(tlw) +QCocoaWindow::QCocoaWindow(QWindow *win, WId nativeHandle) + : QPlatformWindow(win) , m_view(nil) , m_nsWindow(0) , m_viewIsEmbedded(false) @@ -524,7 +154,7 @@ QCocoaWindow::QCocoaWindow(QWindow *tlw, WId nativeHandle) , m_lastReportedWindowState(Qt::WindowNoState) , m_windowModality(Qt::NonModal) , m_windowUnderMouse(false) - , m_inConstructor(true) + , m_initialized(false) , m_inSetVisible(false) , m_inSetGeometry(false) , m_inSetStyleMask(false) @@ -532,42 +162,44 @@ QCocoaWindow::QCocoaWindow(QWindow *tlw, WId nativeHandle) , m_glContext(0) #endif , m_menubar(0) - , m_windowCursor(0) + , m_needsInvalidateShadow(false) , m_hasModalSession(false) , m_frameStrutEventsEnabled(false) - , m_geometryUpdateExposeAllowed(false) - , m_isExposed(false) , m_registerTouchCount(0) , m_resizableTransientParent(false) - , m_hiddenByClipping(false) - , m_hiddenByAncestor(false) , m_alertRequest(NoAlertRequest) , monitor(nil) , m_drawContentBorderGradient(false) , m_topContentBorderThickness(0) , m_bottomContentBorderThickness(0) - , m_hasWindowFilePath(false) { qCDebug(lcQpaCocoaWindow) << "QCocoaWindow::QCocoaWindow" << window(); - QMacAutoReleasePool pool; - if (nativeHandle) { m_view = reinterpret_cast<NSView *>(nativeHandle); [m_view retain]; - } else { + } +} + +void QCocoaWindow::initialize() +{ + qCDebug(lcQpaCocoaWindow) << "QCocoaWindow::initialize" << window(); + + QMacAutoReleasePool pool; + + if (!m_view) { m_view = [[QNSView alloc] initWithCocoaWindow:this]; // Enable high-dpi OpenGL for retina displays. Enabling has the side // effect that Cocoa will start calling glViewport(0, 0, width, height), // overriding any glViewport calls in application code. This is usually not a // problem, except if the appilcation wants to have a "custom" viewport. // (like the hellogl example) - if (tlw->supportsOpenGL()) { - BOOL enable = qt_mac_resolveOption(YES, tlw, "_q_mac_wantsBestResolutionOpenGLSurface", + if (window()->supportsOpenGL()) { + BOOL enable = qt_mac_resolveOption(YES, window(), "_q_mac_wantsBestResolutionOpenGLSurface", "QT_MAC_WANTS_BEST_RESOLUTION_OPENGL_SURFACE"); [m_view setWantsBestResolutionOpenGLSurface:enable]; } - BOOL enable = qt_mac_resolveOption(NO, tlw, "_q_mac_wantsLayer", + BOOL enable = qt_mac_resolveOption(NO, window(), "_q_mac_wantsLayer", "QT_MAC_WANTS_LAYER"); [m_view setWantsLayer:enable]; } @@ -575,10 +207,11 @@ QCocoaWindow::QCocoaWindow(QWindow *tlw, WId nativeHandle) setGeometry(initialGeometry(window(), windowGeometry(), defaultWindowWidth, defaultWindowHeight)); recreateWindowIfNeeded(); - tlw->setGeometry(geometry()); - if (tlw->isTopLevel()) - setWindowIcon(tlw->icon()); - m_inConstructor = false; + window()->setGeometry(geometry()); + if (window()->isTopLevel()) + setWindowIcon(window()->icon()); + + m_initialized = true; } QCocoaWindow::~QCocoaWindow() @@ -588,10 +221,7 @@ QCocoaWindow::~QCocoaWindow() QMacAutoReleasePool pool; [m_nsWindow makeFirstResponder:nil]; [m_nsWindow setContentView:nil]; - [m_nsWindow.helper detachFromPlatformWindow]; - if (m_view.window.parentWindow) - [m_view.window.parentWindow removeChildWindow:m_view.window]; - else if ([m_view superview]) + if ([m_view superview]) [m_view removeFromSuperview]; removeMonitor(); @@ -607,13 +237,8 @@ QCocoaWindow::~QCocoaWindow() QCocoaIntegration::instance()->popupWindowStack()->removeAll(this); } - foreachChildNSWindow(^(QCocoaWindow *childWindow) { - [m_nsWindow removeChildWindow:childWindow->m_nsWindow]; - }); - [m_view release]; [m_nsWindow release]; - [m_windowCursor release]; } QSurfaceFormat QCocoaWindow::format() const @@ -674,134 +299,29 @@ void QCocoaWindow::setCocoaGeometry(const QRect &rect) qCDebug(lcQpaCocoaWindow) << "QCocoaWindow::setCocoaGeometry" << window() << rect; QMacAutoReleasePool pool; + QPlatformWindow::setGeometry(rect); + if (m_viewIsEmbedded) { if (!isForeignWindow()) { [m_view setFrame:NSMakeRect(0, 0, rect.width(), rect.height())]; - } else { - QPlatformWindow::setGeometry(rect); } return; } - if (isChildNSWindow()) { - QPlatformWindow::setGeometry(rect); - NSWindow *parentNSWindow = m_view.window.parentWindow; - NSRect parentWindowFrame = [parentNSWindow contentRectForFrameRect:parentNSWindow.frame]; - clipWindow(parentWindowFrame); - - // call this here: updateGeometry in qnsview.mm is a no-op for this case - QWindowSystemInterface::handleGeometryChange(window(), rect); - QWindowSystemInterface::handleExposeEvent(window(), QRect(QPoint(0, 0), rect.size())); - } else if (m_nsWindow) { + if (isContentView()) { NSRect bounds = qt_mac_flipRect(rect); - [m_nsWindow setFrame:[m_nsWindow frameRectForContentRect:bounds] display:YES animate:NO]; + [m_view.window setFrame:[m_view.window frameRectForContentRect:bounds] display:YES animate:NO]; } else { [m_view setFrame:NSMakeRect(rect.x(), rect.y(), rect.width(), rect.height())]; } - if (isForeignWindow()) - QPlatformWindow::setGeometry(rect); - // will call QPlatformWindow::setGeometry(rect) during resize confirmation (see qnsview.mm) } -void QCocoaWindow::clipChildWindows() -{ - foreachChildNSWindow(^(QCocoaWindow *childWindow) { - childWindow->clipWindow(m_nsWindow.frame); - }); -} - -void QCocoaWindow::clipWindow(const NSRect &clipRect) -{ - if (!isChildNSWindow()) - return; - - NSRect clippedWindowRect = NSZeroRect; - if (!NSIsEmptyRect(clipRect)) { - NSRect windowFrame = qt_mac_flipRect(QRect(window()->mapToGlobal(QPoint(0, 0)), geometry().size())); - clippedWindowRect = NSIntersectionRect(windowFrame, clipRect); - // Clipping top/left offsets the content. Move it back. - NSPoint contentViewOffset = NSMakePoint(qMax(CGFloat(0), NSMinX(clippedWindowRect) - NSMinX(windowFrame)), - qMax(CGFloat(0), NSMaxY(windowFrame) - NSMaxY(clippedWindowRect))); - [m_view setBoundsOrigin:contentViewOffset]; - } - - if (NSIsEmptyRect(clippedWindowRect)) { - if (!m_hiddenByClipping) { - // We dont call hide() here as we will recurse further down - [m_nsWindow orderOut:nil]; - m_hiddenByClipping = true; - } - } else { - [m_nsWindow setFrame:clippedWindowRect display:YES animate:NO]; - if (m_hiddenByClipping) { - m_hiddenByClipping = false; - if (!m_hiddenByAncestor) { - [m_nsWindow orderFront:nil]; - static_cast<QCocoaWindow *>(QPlatformWindow::parent())->reinsertChildWindow(this); - } - } - } - - // recurse - foreachChildNSWindow(^(QCocoaWindow *childWindow) { - childWindow->clipWindow(clippedWindowRect); - }); -} - -void QCocoaWindow::hide(bool becauseOfAncestor) -{ - bool visible = [m_nsWindow isVisible]; - - if (!m_hiddenByAncestor && !visible) // Already explicitly hidden - return; - if (m_hiddenByAncestor && becauseOfAncestor) // Trying to hide some child again - return; - - m_hiddenByAncestor = becauseOfAncestor; - - if (!visible) // Could have been clipped before - return; - - foreachChildNSWindow(^(QCocoaWindow *childWindow) { - childWindow->hide(true); - }); - - [m_nsWindow orderOut:nil]; -} - -void QCocoaWindow::show(bool becauseOfAncestor) -{ - if ([m_nsWindow isVisible]) - return; - - if (m_view.window.parentWindow && !m_view.window.parentWindow.visible) { - m_hiddenByAncestor = true; // Parent still hidden, don't show now - } else if ((becauseOfAncestor == m_hiddenByAncestor) // Was NEITHER explicitly hidden - && !m_hiddenByClipping) { // ... NOR clipped - if (isChildNSWindow()) { - m_hiddenByAncestor = false; - setCocoaGeometry(windowGeometry()); - } - if (!m_hiddenByClipping) { // setCocoaGeometry() can change the clipping status - [m_nsWindow orderFront:nil]; - if (isChildNSWindow()) - static_cast<QCocoaWindow *>(QPlatformWindow::parent())->reinsertChildWindow(this); - foreachChildNSWindow(^(QCocoaWindow *childWindow) { - childWindow->show(true); - }); - } - } -} - void QCocoaWindow::setVisible(bool visible) { qCDebug(lcQpaCocoaWindow) << "QCocoaWindow::setVisible" << window() << visible; - if (isChildNSWindow() && m_hiddenByClipping) - return; - m_inSetVisible = true; QMacAutoReleasePool pool; @@ -813,6 +333,12 @@ void QCocoaWindow::setVisible(bool visible) // We need to recreate if the modality has changed as the style mask will need updating recreateWindowIfNeeded(); + // We didn't send geometry changes during creation, as that would have confused + // Qt, which expects a show-event to be sent before any resize events. But now + // that the window is made visible, we know that the show-event has been sent + // so we can send the geometry change. FIXME: Get rid of this workaround. + handleGeometryChange(); + // Register popup windows. The Cocoa platform plugin will forward mouse events // to them and close them when needed. if (window()->type() == Qt::Popup || window()->type() == Qt::ToolTip) @@ -826,34 +352,28 @@ void QCocoaWindow::setVisible(bool visible) if (window()->type() == Qt::Popup) { // QTBUG-30266: a window should not be resizable while a transient popup is open // Since this isn't a native popup, the window manager doesn't close the popup when you click outside - NSUInteger parentStyleMask = [parentCocoaWindow->m_nsWindow styleMask]; + NSWindow *nativeParentWindow = parentCocoaWindow->nativeWindow(); + NSUInteger parentStyleMask = nativeParentWindow.styleMask; if ((m_resizableTransientParent = (parentStyleMask & NSResizableWindowMask)) - && !([parentCocoaWindow->m_nsWindow styleMask] & NSFullScreenWindowMask)) - [parentCocoaWindow->m_nsWindow setStyleMask:parentStyleMask & ~NSResizableWindowMask]; + && !(nativeParentWindow.styleMask & NSFullScreenWindowMask)) + nativeParentWindow.styleMask &= ~NSResizableWindowMask; } } - // This call is here to handle initial window show correctly: - // - top-level windows need to have backing store content ready when the - // window is shown, sendin the expose event here makes that more likely. - // - QNSViews for child windows are initialy not hidden and won't get the - // viewDidUnhide message. - exposeWindow(); - - if (m_nsWindow) { + if (isContentView()) { QWindowSystemInterface::flushWindowSystemEvents(QEventLoop::ExcludeUserInputEvents); // setWindowState might have been called while the window was hidden and // will not change the NSWindow state in that case. Sync up here: - applyWindowState(window()->windowState()); + applyWindowState(window()->windowStates()); if (window()->windowState() != Qt::WindowMinimized) { if ((window()->modality() == Qt::WindowModal || window()->type() == Qt::Sheet) && parentCocoaWindow) { // show the window as a sheet - [parentCocoaWindow->m_nsWindow beginSheet:m_nsWindow completionHandler:nil]; + [parentCocoaWindow->nativeWindow() beginSheet:m_view.window completionHandler:nil]; } else if (window()->modality() != Qt::NonModal) { // show the window as application modal QCocoaEventDispatcher *cocoaEventDispatcher = qobject_cast<QCocoaEventDispatcher *>(QGuiApplication::instance()->eventDispatcher()); @@ -861,28 +381,24 @@ void QCocoaWindow::setVisible(bool visible) QCocoaEventDispatcherPrivate *cocoaEventDispatcherPrivate = static_cast<QCocoaEventDispatcherPrivate *>(QObjectPrivate::get(cocoaEventDispatcher)); cocoaEventDispatcherPrivate->beginModalSession(window()); m_hasModalSession = true; - } else if ([m_nsWindow canBecomeKeyWindow]) { + } else if ([m_view.window canBecomeKeyWindow]) { QCocoaEventDispatcher *cocoaEventDispatcher = qobject_cast<QCocoaEventDispatcher *>(QGuiApplication::instance()->eventDispatcher()); QCocoaEventDispatcherPrivate *cocoaEventDispatcherPrivate = 0; if (cocoaEventDispatcher) cocoaEventDispatcherPrivate = static_cast<QCocoaEventDispatcherPrivate *>(QObjectPrivate::get(cocoaEventDispatcher)); if (cocoaEventDispatcherPrivate && cocoaEventDispatcherPrivate->cocoaModalSessionStack.isEmpty()) - [m_nsWindow makeKeyAndOrderFront:nil]; + [m_view.window makeKeyAndOrderFront:nil]; else - [m_nsWindow orderFront:nil]; - - foreachChildNSWindow(^(QCocoaWindow *childWindow) { - childWindow->show(true); - }); + [m_view.window orderFront:nil]; } else { - show(); + [m_view.window orderFront:nil]; } // We want the events to properly reach the popup, dialog, and tool if ((window()->type() == Qt::Popup || window()->type() == Qt::Dialog || window()->type() == Qt::Tool) - && [m_nsWindow isKindOfClass:[NSPanel class]]) { - [(NSPanel *)m_nsWindow setWorksWhenModal:YES]; + && [m_view.window isKindOfClass:[NSPanel class]]) { + ((NSPanel *)m_view.window).worksWhenModal = YES; if (!(parentCocoaWindow && window()->transientParent()->isActive()) && window()->type() == Qt::Popup) { removeMonitor(); monitor = [NSEvent addGlobalMonitorForEventsMatchingMask:NSLeftMouseDownMask|NSRightMouseDownMask|NSOtherMouseDownMask|NSMouseMovedMask handler:^(NSEvent *e) { @@ -909,20 +425,21 @@ void QCocoaWindow::setVisible(bool visible) QCocoaEventDispatcherPrivate *cocoaEventDispatcherPrivate = 0; if (cocoaEventDispatcher) cocoaEventDispatcherPrivate = static_cast<QCocoaEventDispatcherPrivate *>(QObjectPrivate::get(cocoaEventDispatcher)); - if (m_nsWindow) { + if (isContentView()) { if (m_hasModalSession) { if (cocoaEventDispatcherPrivate) cocoaEventDispatcherPrivate->endModalSession(window()); m_hasModalSession = false; } else { - if ([m_nsWindow isSheet]) { + if ([m_view.window isSheet]) { Q_ASSERT_X(parentCocoaWindow, "QCocoaWindow", "Window modal dialog has no transient parent."); - [parentCocoaWindow->m_nsWindow endSheet:m_nsWindow]; + [parentCocoaWindow->nativeWindow() endSheet:m_view.window]; } } - hide(); - if (m_nsWindow == [NSApp keyWindow] + [m_view.window orderOut:nil]; + + if (m_view.window == [NSApp keyWindow] && !(cocoaEventDispatcherPrivate && cocoaEventDispatcherPrivate->currentModalSession())) { // Probably because we call runModalSession: outside [NSApp run] in QCocoaEventDispatcher // (e.g., when show()-ing a modal QDialog instead of exec()-ing it), it can happen that @@ -941,10 +458,11 @@ void QCocoaWindow::setVisible(bool visible) QCocoaIntegration::instance()->popupWindowStack()->removeAll(this); if (parentCocoaWindow && window()->type() == Qt::Popup) { + NSWindow *nativeParentWindow = parentCocoaWindow->nativeWindow(); if (m_resizableTransientParent - && !([parentCocoaWindow->m_nsWindow styleMask] & NSFullScreenWindowMask)) - // QTBUG-30266: a window should not be resizable while a transient popup is open - [parentCocoaWindow->m_nsWindow setStyleMask:[parentCocoaWindow->m_nsWindow styleMask] | NSResizableWindowMask]; + && !(nativeParentWindow.styleMask & NSFullScreenWindowMask)) + // A window should not be resizable while a transient popup is open + nativeParentWindow.styleMask |= NSResizableWindowMask; } } @@ -974,7 +492,7 @@ NSInteger QCocoaWindow::windowLevel(Qt::WindowFlags flags) const QWindow * const transientParent = window()->transientParent(); const QCocoaWindow * const transientParentWindow = transientParent ? static_cast<QCocoaWindow *>(transientParent->handle()) : 0; if (transientParentWindow) - windowLevel = qMax([transientParentWindow->m_nsWindow level], windowLevel); + windowLevel = qMax([transientParentWindow->nativeWindow() level], windowLevel); } return windowLevel; @@ -982,70 +500,46 @@ NSInteger QCocoaWindow::windowLevel(Qt::WindowFlags flags) NSUInteger QCocoaWindow::windowStyleMask(Qt::WindowFlags flags) { - Qt::WindowType type = static_cast<Qt::WindowType>(int(flags & Qt::WindowType_Mask)); - NSInteger styleMask = NSBorderlessWindowMask; - if (flags & Qt::FramelessWindowHint) - return styleMask; - if ((type & Qt::Popup) == Qt::Popup) { - if (!windowIsPopupType(type)) { - styleMask = NSUtilityWindowMask | NSResizableWindowMask; - if (!(flags & Qt::CustomizeWindowHint)) { - styleMask |= NSClosableWindowMask | NSMiniaturizableWindowMask | NSTitledWindowMask; - } else { - if (flags & Qt::WindowTitleHint) - styleMask |= NSTitledWindowMask; - if (flags & Qt::WindowCloseButtonHint) - styleMask |= NSClosableWindowMask; - if (flags & Qt::WindowMinimizeButtonHint) - styleMask |= NSMiniaturizableWindowMask; - } - } + const Qt::WindowType type = static_cast<Qt::WindowType>(int(flags & Qt::WindowType_Mask)); + const bool frameless = (flags & Qt::FramelessWindowHint) || windowIsPopupType(type); + + // Select base window type. + NSUInteger styleMask = frameless ? NSBorderlessWindowMask : NSResizableWindowMask; + + if (frameless) { + // No further customizations for frameless since there are no window decorations. + } else if (flags & Qt::CustomizeWindowHint) { + if (flags & Qt::WindowTitleHint) + styleMask |= NSTitledWindowMask; + if (flags & Qt::WindowCloseButtonHint) + styleMask |= NSClosableWindowMask; + if (flags & Qt::WindowMinimizeButtonHint) + styleMask |= NSMiniaturizableWindowMask; } else { - if (type == Qt::Window && !(flags & Qt::CustomizeWindowHint)) { - styleMask = (NSResizableWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask | NSTitledWindowMask); - } else if (type == Qt::Dialog) { - if (flags & Qt::CustomizeWindowHint) { - if (flags & Qt::WindowMaximizeButtonHint) - styleMask = NSResizableWindowMask; - if (flags & Qt::WindowTitleHint) - styleMask |= NSTitledWindowMask; - if (flags & Qt::WindowCloseButtonHint) - styleMask |= NSClosableWindowMask; - if (flags & Qt::WindowMinimizeButtonHint) - styleMask |= NSMiniaturizableWindowMask; - } else { - styleMask = NSResizableWindowMask | NSClosableWindowMask | NSTitledWindowMask; - } - } else { - if (flags & Qt::WindowMaximizeButtonHint) - styleMask |= NSResizableWindowMask; - if (flags & Qt::WindowTitleHint) - styleMask |= NSTitledWindowMask; - if (flags & Qt::WindowCloseButtonHint) - styleMask |= NSClosableWindowMask; - if (flags & Qt::WindowMinimizeButtonHint) - styleMask |= NSMiniaturizableWindowMask; - } + styleMask |= NSClosableWindowMask | NSTitledWindowMask; + + if (type != Qt::Dialog) + styleMask |= NSMiniaturizableWindowMask; } + if (type == Qt::Tool) + styleMask |= NSUtilityWindowMask; + if (m_drawContentBorderGradient) styleMask |= NSTexturedBackgroundWindowMask; // Don't wipe fullscreen state - if (m_nsWindow.styleMask & NSFullScreenWindowMask) + if (m_view.window.styleMask & NSFullScreenWindowMask) styleMask |= NSFullScreenWindowMask; return styleMask; } -void QCocoaWindow::setWindowShadow(Qt::WindowFlags flags) -{ - bool keepShadow = !(flags & Qt::NoDropShadowWindowHint); - [m_nsWindow setHasShadow:(keepShadow ? YES : NO)]; -} - void QCocoaWindow::setWindowZoomButton(Qt::WindowFlags flags) { + if (!isContentView()) + return; + // Disable the zoom (maximize) button for fixed-sized windows and customized // no-WindowMaximizeButtonHint windows. From a Qt perspective it migth be expected // that the button would be removed in the latter case, but disabling it is more @@ -1054,28 +548,27 @@ void QCocoaWindow::setWindowZoomButton(Qt::WindowFlags flags) && windowMinimumSize() == windowMaximumSize()); bool customizeNoZoom = ((flags & Qt::CustomizeWindowHint) && !(flags & (Qt::WindowMaximizeButtonHint | Qt::WindowFullscreenButtonHint))); - [[m_nsWindow standardWindowButton:NSWindowZoomButton] setEnabled:!(fixedSizeNoZoom || customizeNoZoom)]; + [[m_view.window standardWindowButton:NSWindowZoomButton] setEnabled:!(fixedSizeNoZoom || customizeNoZoom)]; } void QCocoaWindow::setWindowFlags(Qt::WindowFlags flags) { - if (m_nsWindow && !isChildNSWindow()) { - NSUInteger styleMask = windowStyleMask(flags); - NSInteger level = this->windowLevel(flags); - // While setting style mask we can have -updateGeometry calls on a content + if (isContentView()) { + // While setting style mask we can have handleGeometryChange calls on a content // view with null geometry, reporting an invalid coordinates as a result. m_inSetStyleMask = true; - [m_nsWindow setStyleMask:styleMask]; + m_view.window.styleMask = windowStyleMask(flags); m_inSetStyleMask = false; - [m_nsWindow setLevel:level]; - setWindowShadow(flags); - if (!(flags & Qt::FramelessWindowHint)) { + m_view.window.level = this->windowLevel(flags); + + m_view.window.hasShadow = !(flags & Qt::NoDropShadowWindowHint); + + if (!(flags & Qt::FramelessWindowHint)) setWindowTitle(window()->title()); - } Qt::WindowType type = window()->type(); if ((type & Qt::Popup) != Qt::Popup && (type & Qt::Dialog) != Qt::Dialog) { - NSWindowCollectionBehavior behavior = [m_nsWindow collectionBehavior]; + NSWindowCollectionBehavior behavior = m_view.window.collectionBehavior; if (flags & Qt::WindowFullscreenButtonHint) { behavior |= NSWindowCollectionBehaviorFullScreenPrimary; behavior &= ~NSWindowCollectionBehaviorFullScreenAuxiliary; @@ -1083,28 +576,26 @@ void QCocoaWindow::setWindowFlags(Qt::WindowFlags flags) behavior |= NSWindowCollectionBehaviorFullScreenAuxiliary; behavior &= ~NSWindowCollectionBehaviorFullScreenPrimary; } - [m_nsWindow setCollectionBehavior:behavior]; + m_view.window.collectionBehavior = behavior; } setWindowZoomButton(flags); - } - // Make window ignore mouse events if WindowTransparentForInput is set. - // Note that ignoresMouseEvents has a special initial state where events - // are ignored (passed through) based on window transparency, and that - // setting the property to false does not return us to that state. Instead, - // this makes the window capture all mouse events. Take care to only - // set the property if needed. FIXME: recreate window if needed or find - // some other way to implement WindowTransparentForInput. - if (m_nsWindow) { + // Make window ignore mouse events if WindowTransparentForInput is set. + // Note that ignoresMouseEvents has a special initial state where events + // are ignored (passed through) based on window transparency, and that + // setting the property to false does not return us to that state. Instead, + // this makes the window capture all mouse events. Take care to only + // set the property if needed. FIXME: recreate window if needed or find + // some other way to implement WindowTransparentForInput. bool ignoreMouse = flags & Qt::WindowTransparentForInput; - if (m_nsWindow.ignoresMouseEvents != ignoreMouse) - m_nsWindow.ignoresMouseEvents = ignoreMouse; + if (m_view.window.ignoresMouseEvents != ignoreMouse) + m_view.window.ignoresMouseEvents = ignoreMouse; } m_windowFlags = flags; } -void QCocoaWindow::setWindowState(Qt::WindowState state) +void QCocoaWindow::setWindowState(Qt::WindowStates state) { if (window()->isVisible()) applyWindowState(state); // Window state set for hidden windows take effect when show() is called @@ -1112,45 +603,54 @@ void QCocoaWindow::setWindowState(Qt::WindowState state) void QCocoaWindow::setWindowTitle(const QString &title) { - QMacAutoReleasePool pool; - if (!m_nsWindow) + if (!isContentView()) return; - CFStringRef windowTitle = title.toCFString(); - [m_nsWindow setTitle: const_cast<NSString *>(reinterpret_cast<const NSString *>(windowTitle))]; - CFRelease(windowTitle); + QMacAutoReleasePool pool; + m_view.window.title = title.toNSString(); + + if (title.isEmpty() && !window()->filePath().isEmpty()) { + // Clearing the title should restore the default filename + setWindowFilePath(window()->filePath()); + } } void QCocoaWindow::setWindowFilePath(const QString &filePath) { - QMacAutoReleasePool pool; - if (!m_nsWindow) + if (!isContentView()) return; - QFileInfo fi(filePath); - [m_nsWindow setRepresentedFilename:fi.exists() ? filePath.toNSString() : @""]; - m_hasWindowFilePath = fi.exists(); + QMacAutoReleasePool pool; + + if (window()->title().isNull()) + [m_view.window setTitleWithRepresentedFilename:filePath.toNSString()]; + else + m_view.window.representedFilename = filePath.toNSString(); + + // Changing the file path may affect icon visibility + setWindowIcon(window()->icon()); } void QCocoaWindow::setWindowIcon(const QIcon &icon) { - QMacAutoReleasePool pool; + if (!isContentView()) + return; - NSButton *iconButton = [m_nsWindow standardWindowButton:NSWindowDocumentIconButton]; - if (iconButton == nil) { - if (icon.isNull()) - return; - NSString *title = window()->title().toNSString(); - [m_nsWindow setRepresentedURL:[NSURL fileURLWithPath:title]]; - iconButton = [m_nsWindow standardWindowButton:NSWindowDocumentIconButton]; + NSButton *iconButton = [m_view.window standardWindowButton:NSWindowDocumentIconButton]; + if (!iconButton) { + // Window icons are only supported on macOS in combination with a document filePath + return; } + + QMacAutoReleasePool pool; + if (icon.isNull()) { - [iconButton setImage:nil]; + NSWorkspace *workspace = [NSWorkspace sharedWorkspace]; + [iconButton setImage:[workspace iconForFile:m_view.window.representedFilename]]; } else { QPixmap pixmap = icon.pixmap(QSize(22, 22)); NSImage *image = static_cast<NSImage *>(qt_mac_create_nsimage(pixmap)); - [iconButton setImage:image]; - [image release]; + [iconButton setImage:[image autorelease]]; } } @@ -1174,34 +674,22 @@ void QCocoaWindow::raise() qCDebug(lcQpaCocoaWindow) << "QCocoaWindow::raise" << window(); // ### handle spaces (see Qt 4 raise_sys in qwidget_mac.mm) - if (!m_nsWindow) + if (!isContentView()) return; - if (isChildNSWindow()) { - if (m_hiddenByClipping) - return; - } - if ([m_nsWindow isVisible]) { - if (isChildNSWindow()) { - // -[NSWindow orderFront:] doesn't work with attached windows. - // The only solution is to remove and add the child window. - // This will place it on top of all the other NSWindows. - NSWindow *parentNSWindow = m_view.window.parentWindow; - [parentNSWindow removeChildWindow:m_nsWindow]; - [parentNSWindow addChildWindow:m_nsWindow ordered:NSWindowAbove]; - } else { - { - // Clean up autoreleased temp objects from orderFront immediately. - // Failure to do so has been observed to cause leaks also beyond any outer - // autorelease pool (for example around a complete QWindow - // construct-show-raise-hide-delete cyle), counter to expected autoreleasepool - // behavior. - QMacAutoReleasePool pool; - [m_nsWindow orderFront: m_nsWindow]; - } - static bool raiseProcess = qt_mac_resolveOption(true, "QT_MAC_SET_RAISE_PROCESS"); - if (raiseProcess) { - [NSApp activateIgnoringOtherApps:YES]; - } + + if (m_view.window.visible) { + { + // Clean up autoreleased temp objects from orderFront immediately. + // Failure to do so has been observed to cause leaks also beyond any outer + // autorelease pool (for example around a complete QWindow + // construct-show-raise-hide-delete cyle), counter to expected autoreleasepool + // behavior. + QMacAutoReleasePool pool; + [m_view.window orderFront:m_view.window]; + } + static bool raiseProcess = qt_mac_resolveOption(true, "QT_MAC_SET_RAISE_PROCESS"); + if (raiseProcess) { + [NSApp activateIgnoringOtherApps:YES]; } } } @@ -1209,34 +697,16 @@ void QCocoaWindow::raise() void QCocoaWindow::lower() { qCDebug(lcQpaCocoaWindow) << "QCocoaWindow::lower" << window(); - if (!m_nsWindow) + if (!isContentView()) return; - if (isChildNSWindow()) { - if (m_hiddenByClipping) - return; - } - if ([m_nsWindow isVisible]) { - if (isChildNSWindow()) { - // -[NSWindow orderBack:] doesn't work with attached windows. - // The only solution is to remove and add all the child windows except this one. - // This will keep the current window at the bottom while adding the others on top of it, - // hopefully in the same order (this is not documented anywhere in the Cocoa documentation). - NSWindow *parentNSWindow = m_view.window.parentWindow; - NSArray *children = [parentNSWindow.childWindows copy]; - for (NSWindow *child in children) - if (m_nsWindow != child) { - [parentNSWindow removeChildWindow:child]; - [parentNSWindow addChildWindow:child ordered:NSWindowAbove]; - } - } else { - [m_nsWindow orderBack: m_nsWindow]; - } - } + + if (m_view.window.visible) + [m_view.window orderBack:m_view.window]; } bool QCocoaWindow::isExposed() const { - return m_isExposed; + return !m_exposedRect.isEmpty(); } bool QCocoaWindow::isOpaque() const @@ -1247,7 +717,7 @@ bool QCocoaWindow::isOpaque() const bool translucent = window()->format().alphaBufferSize() > 0 || window()->opacity() < 1 - || [qnsview_cast(m_view) hasMask] + || !window()->mask().isEmpty() || (surface()->supportsOpenGL() && openglSourfaceOrder == -1); return !translucent; } @@ -1255,24 +725,24 @@ bool QCocoaWindow::isOpaque() const void QCocoaWindow::propagateSizeHints() { QMacAutoReleasePool pool; - if (!m_nsWindow) + if (!isContentView()) return; - qCDebug(lcQpaCocoaWindow) << "QCocoaWindow::propagateSizeHints" << window() << "\n" - << " min/max" << windowMinimumSize() << windowMaximumSize() - << "size increment" << windowSizeIncrement() - << " basesize" << windowBaseSize() - << " geometry" << windowGeometry(); + qCDebug(lcQpaCocoaWindow) << "QCocoaWindow::propagateSizeHints" << window() + << "min:" << windowMinimumSize() << "max:" << windowMaximumSize() + << "increment:" << windowSizeIncrement() + << "base:" << windowBaseSize(); + + const NSWindow *window = m_view.window; // Set the minimum content size. - const QSize minimumSize = windowMinimumSize(); + QSize minimumSize = windowMinimumSize(); if (!minimumSize.isValid()) // minimumSize is (-1, -1) when not set. Make that (0, 0) for Cocoa. - [m_nsWindow setContentMinSize : NSMakeSize(0.0, 0.0)]; - [m_nsWindow setContentMinSize : NSMakeSize(minimumSize.width(), minimumSize.height())]; + minimumSize = QSize(0, 0); + window.contentMinSize = NSSizeFromCGSize(minimumSize.toCGSize()); // Set the maximum content size. - const QSize maximumSize = windowMaximumSize(); - [m_nsWindow setContentMaxSize : NSMakeSize(maximumSize.width(), maximumSize.height())]; + window.contentMaxSize = NSSizeFromCGSize(windowMaximumSize().toCGSize()); // The window may end up with a fixed size; in this case the zoom button should be disabled. setWindowZoomButton(m_windowFlags); @@ -1282,42 +752,65 @@ void QCocoaWindow::propagateSizeHints() QSize sizeIncrement = windowSizeIncrement(); if (sizeIncrement.isEmpty()) sizeIncrement = QSize(1, 1); - [m_nsWindow setResizeIncrements:NSSizeFromCGSize(sizeIncrement.toCGSize())]; + window.resizeIncrements = NSSizeFromCGSize(sizeIncrement.toCGSize()); QRect rect = geometry(); QSize baseSize = windowBaseSize(); - if (!baseSize.isNull() && baseSize.isValid()) { - [m_nsWindow setFrame:NSMakeRect(rect.x(), rect.y(), baseSize.width(), baseSize.height()) display:YES]; - } + if (!baseSize.isNull() && baseSize.isValid()) + [window setFrame:NSMakeRect(rect.x(), rect.y(), baseSize.width(), baseSize.height()) display:YES]; } void QCocoaWindow::setOpacity(qreal level) { qCDebug(lcQpaCocoaWindow) << "QCocoaWindow::setOpacity" << level; - if (m_nsWindow) { - [m_nsWindow setAlphaValue:level]; - [m_nsWindow setOpaque: isOpaque()]; - } + if (!isContentView()) + return; + + m_view.window.alphaValue = level; } void QCocoaWindow::setMask(const QRegion ®ion) { qCDebug(lcQpaCocoaWindow) << "QCocoaWindow::setMask" << window() << region; - if (m_nsWindow) - [m_nsWindow setBackgroundColor:[NSColor clearColor]]; - [qnsview_cast(m_view) setMaskRegion:®ion]; - [m_nsWindow setOpaque:isOpaque()]; + if (m_view.layer) { + if (!region.isEmpty()) { + QCFType<CGMutablePathRef> maskPath = CGPathCreateMutable(); + for (const QRect &r : region) + CGPathAddRect(maskPath, nullptr, r.toCGRect()); + CAShapeLayer *maskLayer = [CAShapeLayer layer]; + maskLayer.path = maskPath; + m_view.layer.mask = maskLayer; + } else { + m_view.layer.mask = nil; + } + } + + if (isContentView()) { + // Setting the mask requires invalidating the NSWindow shadow, but that needs + // to happen after the backingstore has been redrawn, so that AppKit can pick + // up the new window shape based on the backingstore content. Doing a display + // directly here is not an option, as the window might not be exposed at this + // time, and so would not result in an updated backingstore. + m_needsInvalidateShadow = true; + [m_view setNeedsDisplay:YES]; + + // FIXME: [NSWindow invalidateShadow] has no effect when in layer-backed mode, + // so if the mask is changed after the initial mask is applied, it will not + // result in any visual change to the shadow. This is an Apple bug, and there + // may be ways to work around it, such as calling setFrame on the window to + // trigger some internal invalidation, but that needs more research. + } } bool QCocoaWindow::setKeyboardGrabEnabled(bool grab) { qCDebug(lcQpaCocoaWindow) << "QCocoaWindow::setKeyboardGrabEnabled" << window() << grab; - if (!m_nsWindow) + if (!isContentView()) return false; - if (grab && ![m_nsWindow isKeyWindow]) - [m_nsWindow makeKeyWindow]; + if (grab && ![m_view.window isKeyWindow]) + [m_view.window makeKeyWindow]; return true; } @@ -1325,11 +818,11 @@ bool QCocoaWindow::setKeyboardGrabEnabled(bool grab) bool QCocoaWindow::setMouseGrabEnabled(bool grab) { qCDebug(lcQpaCocoaWindow) << "QCocoaWindow::setMouseGrabEnabled" << window() << grab; - if (!m_nsWindow) + if (!isContentView()) return false; - if (grab && ![m_nsWindow isKeyWindow]) - [m_nsWindow makeKeyWindow]; + if (grab && ![m_view.window isKeyWindow]) + [m_view.window makeKeyWindow]; return true; } @@ -1358,7 +851,7 @@ NSView *QCocoaWindow::view() const NSWindow *QCocoaWindow::nativeWindow() const { - return m_nsWindow; + return m_view.window; } void QCocoaWindow::setEmbeddedInForeignView(bool embedded) @@ -1369,8 +862,32 @@ void QCocoaWindow::setEmbeddedInForeignView(bool embedded) m_nsWindow = 0; } +// ----------------------- NSView notifications ----------------------- + +void QCocoaWindow::viewDidChangeFrame() +{ + handleGeometryChange(); +} + +/*! + Callback for NSViewGlobalFrameDidChangeNotification. + + Posted whenever an NSView object that has attached surfaces (that is, + NSOpenGLContext objects) moves to a different screen, or other cases + where the NSOpenGLContext object needs to be updated. +*/ +void QCocoaWindow::viewDidChangeGlobalFrame() +{ + [m_view setNeedsDisplay:YES]; +} + // ----------------------- NSWindow notifications ----------------------- +// Note: The following notifications are delivered to every QCocoaWindow +// that is a child of the NSWindow that triggered the notification. Each +// callback should make sure to filter out notifications if they do not +// apply to that QCocoaWindow, e.g. if the window is not a content view. + void QCocoaWindow::windowWillMove() { // Close any open popups on window move @@ -1379,54 +896,39 @@ void QCocoaWindow::windowWillMove() void QCocoaWindow::windowDidMove() { - if (isChildNSWindow()) + if (!isContentView()) return; - [qnsview_cast(m_view) updateGeometry]; + handleGeometryChange(); // Moving a window might bring it out of maximized state - reportCurrentWindowState(); + handleWindowStateChanged(); } void QCocoaWindow::windowDidResize() { - if (!m_nsWindow) - return; - - if (isChildNSWindow()) + if (!isContentView()) return; - clipChildWindows(); - [qnsview_cast(m_view) updateGeometry]; + handleGeometryChange(); if (!m_view.inLiveResize) - reportCurrentWindowState(); -} - -void QCocoaWindow::viewDidChangeFrame() -{ - [qnsview_cast(m_view) updateGeometry]; -} - -/*! - Callback for NSViewGlobalFrameDidChangeNotification. - - Posted whenever an NSView object that has attached surfaces (that is, - NSOpenGLContext objects) moves to a different screen, or other cases - where the NSOpenGLContext object needs to be updated. -*/ -void QCocoaWindow::viewDidChangeGlobalFrame() -{ - updateExposedGeometry(); + handleWindowStateChanged(); } void QCocoaWindow::windowDidEndLiveResize() { - reportCurrentWindowState(); + if (!isContentView()) + return; + + handleWindowStateChanged(); } void QCocoaWindow::windowDidBecomeKey() { + if (!isContentView()) + return; + if (isForeignWindow()) return; @@ -1443,6 +945,9 @@ void QCocoaWindow::windowDidBecomeKey() void QCocoaWindow::windowDidResignKey() { + if (!isContentView()) + return; + if (isForeignWindow()) return; @@ -1459,43 +964,61 @@ void QCocoaWindow::windowDidResignKey() void QCocoaWindow::windowDidMiniaturize() { - reportCurrentWindowState(); + if (!isContentView()) + return; + + handleWindowStateChanged(); } void QCocoaWindow::windowDidDeminiaturize() { - reportCurrentWindowState(); + if (!isContentView()) + return; + + handleWindowStateChanged(); } void QCocoaWindow::windowWillEnterFullScreen() { + if (!isContentView()) + return; + // The NSWindow needs to be resizable, otherwise we'll end up with // the normal window geometry, centered in the middle of the screen // on a black background. The styleMask will be reset below. - m_nsWindow.styleMask |= NSResizableWindowMask; + m_view.window.styleMask |= NSResizableWindowMask; } void QCocoaWindow::windowDidEnterFullScreen() { - Q_ASSERT_X(m_nsWindow.qt_fullScreen, "QCocoaWindow", + if (!isContentView()) + return; + + Q_ASSERT_X(m_view.window.qt_fullScreen, "QCocoaWindow", "FullScreen category processes window notifications first"); // Reset to original styleMask setWindowFlags(m_windowFlags); - reportCurrentWindowState(); + handleWindowStateChanged(); } void QCocoaWindow::windowWillExitFullScreen() { + if (!isContentView()) + return; + // The NSWindow needs to be resizable, otherwise we'll end up with // a weird zoom animation. The styleMask will be reset below. - m_nsWindow.styleMask |= NSResizableWindowMask; + m_view.window.styleMask |= NSResizableWindowMask; } void QCocoaWindow::windowDidExitFullScreen() { - Q_ASSERT_X(!m_nsWindow.qt_fullScreen, "QCocoaWindow", + if (!isContentView()) + return; + + Q_ASSERT_X(!m_view.window.qt_fullScreen, "QCocoaWindow", "FullScreen category processes window notifications first"); // Reset to original styleMask @@ -1504,7 +1027,7 @@ void QCocoaWindow::windowDidExitFullScreen() Qt::WindowState requestedState = window()->windowState(); // Deliver update of QWindow state - reportCurrentWindowState(); + handleWindowStateChanged(); if (requestedState != windowState() && requestedState != Qt::WindowFullScreen) { // We were only going out of full screen as an intermediate step before @@ -1513,30 +1036,22 @@ void QCocoaWindow::windowDidExitFullScreen() } } -void QCocoaWindow::windowDidOrderOffScreen() +void QCocoaWindow::windowDidOrderOnScreen() { - obscureWindow(); + [m_view setNeedsDisplay:YES]; } -void QCocoaWindow::windowDidOrderOnScreen() +void QCocoaWindow::windowDidOrderOffScreen() { - exposeWindow(); + handleExposeEvent(QRegion()); } void QCocoaWindow::windowDidChangeOcclusionState() { - // Several unit tests expect paint and/or expose events for windows that are - // sometimes (unpredictably) occluded and some unit tests depend on QWindow::isExposed. - // Don't send Expose/Obscure events when running under QTestLib. - static const bool onTestLib = qt_mac_resolveOption(false, "QT_QTESTLIB_RUNNING"); - if (!onTestLib) { - if ((NSUInteger)[m_view.window occlusionState] & NSWindowOcclusionStateVisible) { - exposeWindow(); - } else { - // Send Obscure events on window occlusion to stop animations. - obscureWindow(); - } - } + if (m_view.window.occlusionState & NSWindowOcclusionStateVisible) + [m_view setNeedsDisplay:YES]; + else + handleExposeEvent(QRegion()); } void QCocoaWindow::windowDidChangeScreen() @@ -1546,8 +1061,6 @@ void QCocoaWindow::windowDidChangeScreen() if (QCocoaScreen *cocoaScreen = QCocoaIntegration::instance()->screenForNSScreen(m_view.window.screen)) QWindowSystemInterface::handleWindowScreenChanged(window(), cocoaScreen->screen()); - - updateExposedGeometry(); } void QCocoaWindow::windowWillClose() @@ -1572,6 +1085,111 @@ bool QCocoaWindow::windowShouldClose() return accepted; } +// ----------------------------- QPA forwarding ----------------------------- + +void QCocoaWindow::handleGeometryChange() +{ + // Prevent geometry change during initialization, as that will result + // in a resize event, and Qt expects those to come after the show event. + // FIXME: Remove once we've clarified the Qt behavior for this. + if (!m_initialized) + return; + + // Don't send the geometry change if the QWindow is designated to be + // embedded in a foreign view hierarchy but has not actually been + // embedded yet - it's too early. + if (m_viewIsToBeEmbedded && !m_viewIsEmbedded) + return; + + // It can happen that the current NSWindow is nil (if we are changing styleMask + // from/to borderless, and the content view is being re-parented), which results + // in invalid coordinates. + if (m_inSetStyleMask && !m_view.window) + return; + + const bool isEmbedded = m_viewIsToBeEmbedded || m_viewIsEmbedded; + + QRect newGeometry; + if (isContentView() && !isEmbedded) { + // Content views are positioned at (0, 0) in the window, so we resolve via the window + CGRect contentRect = [m_view.window contentRectForFrameRect:m_view.window.frame]; + + // The result above is in native screen coordinates, so remap to the Qt coordinate system + newGeometry = QCocoaScreen::primaryScreen()->mapFromNative(QRectF::fromCGRect(contentRect)).toRect(); + } else { + // QNSView has isFlipped set, so no need to remap the geometry + newGeometry = QRectF::fromCGRect(m_view.frame).toRect(); + } + + qCDebug(lcQpaCocoaWindow) << "QCocoaWindow::handleGeometryChange" << window() + << "current" << geometry() << "new" << newGeometry; + + QWindowSystemInterface::handleGeometryChange(window(), newGeometry); + + // Guard against processing window system events during QWindow::setGeometry + // calls, which Qt and Qt applications do not expect. + if (!m_inSetGeometry) + QWindowSystemInterface::flushWindowSystemEvents(); +} + +void QCocoaWindow::handleExposeEvent(const QRegion ®ion) +{ + const QRect previouslyExposedRect = m_exposedRect; + + // Ideally we'd implement isExposed() in terms of these properties, + // plus the occlusionState of the NSWindow, and let the expose event + // pull the exposed state out when needed. However, when the window + // is first shown we receive a drawRect call where the occlusionState + // of the window is still hidden, but we still want to prepare the + // window for display by issuing an expose event to Qt. To work around + // this we don't use the occlusionState directly, but instead base + // the exposed state on the region we get in, which in the case of + // a window being obscured is an empty region, and in the case of + // a drawRect call is a non-null region, even if occlusionState + // is still hidden. This ensures the window is prepared for display. + if (m_view.window.visible && m_view.window.screen + && !geometry().size().isEmpty() && !region.isEmpty() + && !m_view.hiddenOrHasHiddenAncestor) { + m_exposedRect = region.boundingRect(); + } else { + m_exposedRect = QRect(); + } + + QWindowPrivate *windowPrivate = qt_window_private(window()); + if (windowPrivate->updateRequestPending) { + // We can only deliver update request events when the window is exposed, + // and we also have to make sure we deliver any change to the exposed + // rect as a real expose event (including going from non-exposed to + // exposed). FIXME: Should this logic live in QGuiApplication? + if (isExposed() && m_exposedRect == previouslyExposedRect) { + qCDebug(lcQpaCocoaWindow) << "QCocoaWindow::handleExposeEvent" << window() << region << "as update request"; + windowPrivate->deliverUpdateRequest(); + return; + } else { + // Since updateRequestPending is still set, we will issue a deferred setNeedsDisplay + // from drawRect and get back into this code on the next display cycle, delivering + // the pending update request. + } + } + + qCDebug(lcQpaCocoaWindow) << "QCocoaWindow::handleExposeEvent" << window() << region << "isExposed" << isExposed(); + QWindowSystemInterface::handleExposeEvent<QWindowSystemInterface::SynchronousDelivery>(window(), region); +} + +void QCocoaWindow::handleWindowStateChanged(HandleFlags flags) +{ + Qt::WindowState currentState = windowState(); + if (!(flags & HandleUnconditionally) && currentState == m_lastReportedWindowState) + return; + + qCDebug(lcQpaCocoaWindow) << "QCocoaWindow::handleWindowStateChanged" << + m_lastReportedWindowState << "-->" << currentState; + + QWindowSystemInterface::handleWindowStateChanged<QWindowSystemInterface::SynchronousDelivery>( + window(), currentState, m_lastReportedWindowState); + m_lastReportedWindowState = currentState; +} + // -------------------------------------------------------------------------- bool QCocoaWindow::windowIsPopupType(Qt::WindowType type) const @@ -1597,26 +1215,13 @@ QCocoaGLContext *QCocoaWindow::currentContext() const #endif /*! - Checks if the window is a non-top level QWindow with a NSWindow. - - \sa _q_platform_MacUseNSWindow, QT_MAC_USE_NSWINDOW -*/ -bool QCocoaWindow::isChildNSWindow() const -{ - return m_view.window.parentWindow != nil; -} - -/*! Checks if the window is the content view of its immediate NSWindow. Being the content view of a NSWindow means the QWindow is the highest accessible NSView object in the window's view hierarchy. - This can only happen in two cases, either if the QWindow is - itself a top level window, or if it's a child NSWindow. - - \sa isChildNSWindow + This is the case if the QWindow is a top level window. */ bool QCocoaWindow::isContentView() const { @@ -1624,33 +1229,16 @@ bool QCocoaWindow::isContentView() const } /*! - Iterates child NSWindows that have a corresponding QCocoaWindow. -*/ -void QCocoaWindow::foreachChildNSWindow(void (^block)(QCocoaWindow *)) -{ - NSArray *windows = m_view.window.childWindows; - [windows enumerateObjectsUsingBlock:^(NSWindow *window, NSUInteger index, BOOL *stop) { - Q_UNUSED(index); - Q_UNUSED(stop); - if (QNSView *view = qnsview_cast(window.contentView)) - block(view.platformWindow); - }]; -} - -/*! Recreates (or removes) the NSWindow for this QWindow, if needed. - A QWindow may need a corresponding NSWindow, depending on whether - or not it's a top level or not (or explicitly set to be a child - NSWindow), whether it is a NSPanel or not, etc. + A QWindow may need a corresponding NSWindow/NSPanel, depending on + whether or not it's a top level or not, window flags, etc. */ void QCocoaWindow::recreateWindowIfNeeded() { QMacAutoReleasePool pool; QPlatformWindow *parentWindow = QPlatformWindow::parent(); - qCDebug(lcQpaCocoaWindow) << "QCocoaWindow::recreateWindowIfNeeded" << window() - << "parent" << (parentWindow ? parentWindow->window() : 0); RecreationReasons recreateReason = RecreationNotNeeded; @@ -1668,79 +1256,56 @@ void QCocoaWindow::recreateWindowIfNeeded() if (m_windowModality != window()->modality()) recreateReason |= WindowModalityChanged; - const bool shouldBeChildNSWindow = parentWindow && qt_mac_resolveOption(NO, - window(), "_q_platform_MacUseNSWindow", "QT_MAC_USE_NSWINDOW"); - - if (isChildNSWindow() != shouldBeChildNSWindow) - recreateReason |= ChildNSWindowChanged; - - const bool shouldBeContentView = (!parentWindow && !m_viewIsEmbedded) || shouldBeChildNSWindow; + const bool shouldBeContentView = !parentWindow && !(m_viewIsToBeEmbedded || m_viewIsEmbedded); if (isContentView() != shouldBeContentView) recreateReason |= ContentViewChanged; Qt::WindowType type = window()->type(); const bool isPanel = isContentView() && [m_view.window isKindOfClass:[QNSPanel class]]; - const bool shouldBePanel = shouldBeContentView && !shouldBeChildNSWindow && + const bool shouldBePanel = shouldBeContentView && ((type & Qt::Popup) == Qt::Popup || (type & Qt::Dialog) == Qt::Dialog); if (isPanel != shouldBePanel) recreateReason |= PanelChanged; - if (recreateReason == RecreationNotNeeded) { - qCDebug(lcQpaCocoaWindow) << "No need to recreate NSWindow"; - return; - } + qCDebug(lcQpaCocoaWindow) << "QCocoaWindow::recreateWindowIfNeeded" << window() << recreateReason; - qCDebug(lcQpaCocoaWindow) << "Reconfiguring NSWindow due to" << recreateReason; + if (recreateReason == RecreationNotNeeded) + return; QCocoaWindow *parentCocoaWindow = static_cast<QCocoaWindow *>(parentWindow); - if (shouldBeChildNSWindow) { - QWindow *parentQWindow = parentWindow->window(); - // Ensure that all parents in the hierarchy are also child NSWindows - if (!parentQWindow->property("_q_platform_MacUseNSWindow").toBool()) { - parentQWindow->setProperty("_q_platform_MacUseNSWindow", QVariant(true)); - parentCocoaWindow->recreateWindowIfNeeded(); - } - } - // Remove current window (if any) if ((isContentView() && !shouldBeContentView) || (recreateReason & PanelChanged)) { - qCDebug(lcQpaCocoaWindow) << "Getting rid of existing window" << m_nsWindow; - [m_nsWindow closeAndRelease]; - if (isChildNSWindow()) - [m_view.window.parentWindow removeChildWindow:m_view.window]; - if (isContentView()) { - // We explicitly disassociate m_view from the window's contentView, - // as AppKit does not automatically do this in response to removing - // the view from the NSThemeFrame subview list, so we might end up - // with a NSWindow contentView pointing to a deallocated NSView. - m_view.window.contentView = nil; + if (m_nsWindow) { + qCDebug(lcQpaCocoaWindow) << "Getting rid of existing window" << m_nsWindow; + [m_nsWindow closeAndRelease]; + if (isContentView()) { + // We explicitly disassociate m_view from the window's contentView, + // as AppKit does not automatically do this in response to removing + // the view from the NSThemeFrame subview list, so we might end up + // with a NSWindow contentView pointing to a deallocated NSView. + m_view.window.contentView = nil; + } + m_nsWindow = 0; } - m_nsWindow = 0; } if (shouldBeContentView) { bool noPreviousWindow = m_nsWindow == 0; + QCocoaNSWindow *newWindow = nullptr; if (noPreviousWindow) - m_nsWindow = createNSWindow(shouldBeChildNSWindow, shouldBePanel); - - if (m_view.window.parentWindow) { - if (!shouldBeChildNSWindow || (recreateReason & ParentChanged)) - [m_view.window.parentWindow removeChildWindow:m_view.window]; - m_forwardWindow = oldParentCocoaWindow; - } + newWindow = createNSWindow(shouldBePanel); // Move view to new NSWindow if needed - if (m_nsWindow.contentView != m_view) { - qCDebug(lcQpaCocoaWindow) << "Ensuring that view is content view for" << m_nsWindow; + if (newWindow) { + qCDebug(lcQpaCocoaWindow) << "Ensuring that" << m_view << "is content view for" << newWindow; [m_view setPostsFrameChangedNotifications:NO]; - [m_view retain]; - if (m_view.superview) // m_view comes from another NSWindow - [m_view removeFromSuperview]; - [m_nsWindow setContentView:m_view]; - [m_view release]; + [newWindow setContentView:m_view]; [m_view setPostsFrameChangedNotifications:YES]; + + m_nsWindow = newWindow; + Q_ASSERT(m_view.window == m_nsWindow); } } @@ -1751,21 +1316,10 @@ void QCocoaWindow::recreateWindowIfNeeded() propagateSizeHints(); setWindowFlags(window()->flags()); setWindowTitle(window()->title()); + setWindowFilePath(window()->filePath()); setWindowState(window()->windowState()); - } else if (shouldBeChildNSWindow) { - if (!m_hiddenByClipping) { - [parentCocoaWindow->m_nsWindow addChildWindow:m_nsWindow ordered:NSWindowAbove]; - parentCocoaWindow->reinsertChildWindow(this); - } - - // Set properties after the window has been made a child NSWindow - setCocoaGeometry(windowGeometry()); - setWindowFlags(window()->flags()); } else { // Child windows have no NSWindow, link the NSViews instead. - if ([m_view superview]) - [m_view removeFromSuperview]; - [parentCocoaWindow->m_view addSubview:m_view]; QRect rect = windowGeometry(); // Prevent setting a (0,0) window size; causes opengl context @@ -1781,29 +1335,18 @@ void QCocoaWindow::recreateWindowIfNeeded() if (!qFuzzyCompare(opacity, qreal(1.0))) setOpacity(opacity); + setMask(QHighDpi::toNativeLocalRegion(window()->mask(), window())); + // top-level QWindows may have an attached NSToolBar, call // update function which will attach to the NSWindow. if (!parentWindow) updateNSToolbar(); } -void QCocoaWindow::reinsertChildWindow(QCocoaWindow *child) +void QCocoaWindow::requestUpdate() { - const QObjectList &childWindows = window()->children(); - int childIndex = childWindows.indexOf(child->window()); - Q_ASSERT(childIndex != -1); - - for (int i = childIndex; i < childWindows.size(); ++i) { - QWindow *window = static_cast<QWindow *>(childWindows.at(i)); - QCocoaWindow *cocoaWindow = static_cast<QCocoaWindow *>(window->handle()); - if (!cocoaWindow) - continue; - - NSWindow *nsChild = cocoaWindow->m_nsWindow; - if (i != childIndex) - [m_nsWindow removeChildWindow:nsChild]; - [m_nsWindow addChildWindow:nsChild ordered:NSWindowAbove]; - } + qCDebug(lcQpaCocoaWindow) << "QCocoaWindow::requestUpdate" << window(); + [m_view setNeedsDisplay:YES]; } void QCocoaWindow::requestActivateWindow() @@ -1813,10 +1356,8 @@ void QCocoaWindow::requestActivateWindow() [window makeKeyWindow]; } -QCocoaNSWindow *QCocoaWindow::createNSWindow(bool shouldBeChildNSWindow, bool shouldBePanel) +QCocoaNSWindow *QCocoaWindow::createNSWindow(bool shouldBePanel) { - qCDebug(lcQpaCocoaWindow) << "createNSWindow" << shouldBeChildNSWindow << shouldBePanel; - QMacAutoReleasePool pool; QRect rect = geometry(); @@ -1848,44 +1389,58 @@ QCocoaNSWindow *QCocoaWindow::createNSWindow(bool shouldBeChildNSWindow, bool sh // Create NSWindow Class windowClass = shouldBePanel ? [QNSPanel class] : [QNSWindow class]; - NSUInteger styleMask = shouldBeChildNSWindow ? NSBorderlessWindowMask : windowStyleMask(flags); - QCocoaNSWindow *window = [[windowClass alloc] initWithContentRect:frame - screen:cocoaScreen->nativeScreen() styleMask:styleMask qPlatformWindow:this]; - - window.restorable = NO; - window.level = shouldBeChildNSWindow ? NSNormalWindowLevel : windowLevel(flags); - - if (!isOpaque()) { - window.backgroundColor = [NSColor clearColor]; - window.opaque = NO; + QCocoaNSWindow *nsWindow = [[windowClass alloc] initWithContentRect:frame + styleMask:windowStyleMask(flags) + // Deferring window creation breaks OpenGL (the GL context is + // set up before the window is shown and needs a proper window) + backing:NSBackingStoreBuffered defer:NO + screen:cocoaScreen->nativeScreen()]; + + Q_ASSERT_X(nsWindow.screen == cocoaScreen->nativeScreen(), "QCocoaWindow", + "Resulting NSScreen should match the requested NSScreen"); + + nsWindow.delegate = [[QNSWindowDelegate alloc] initWithQCocoaWindow:this]; + + // Prevent Cocoa from releasing the window on close. Qt + // handles the close event asynchronously and we want to + // make sure that NSWindow stays valid until the + // QCocoaWindow is deleted by Qt. + [nsWindow setReleasedWhenClosed:NO]; + + if (alwaysShowToolWindow()) { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; + [center addObserver:[QNSWindow class] selector:@selector(applicationActivationChanged:) + name:NSApplicationWillResignActiveNotification object:nil]; + [center addObserver:[QNSWindow class] selector:@selector(applicationActivationChanged:) + name:NSApplicationWillBecomeActiveNotification object:nil]; + }); } - Q_ASSERT(!(shouldBePanel && shouldBeChildNSWindow)); + if (targetScreen != window()->screen()) + QWindowSystemInterface::handleWindowScreenChanged(window(), targetScreen); + + nsWindow.restorable = NO; + nsWindow.level = windowLevel(flags); if (shouldBePanel) { // Qt::Tool windows hide on app deactivation, unless Qt::WA_MacAlwaysShowToolWindow is set - window.hidesOnDeactivate = ((type & Qt::Tool) == Qt::Tool) && !alwaysShowToolWindow(); + nsWindow.hidesOnDeactivate = ((type & Qt::Tool) == Qt::Tool) && !alwaysShowToolWindow(); // Make popup windows show on the same desktop as the parent full-screen window - window.collectionBehavior = NSWindowCollectionBehaviorFullScreenAuxiliary; + nsWindow.collectionBehavior = NSWindowCollectionBehaviorFullScreenAuxiliary; if ((type & Qt::Popup) == Qt::Popup) { - window.hasShadow = YES; - window.animationBehavior = NSWindowAnimationBehaviorUtilityWindow; + nsWindow.hasShadow = YES; + nsWindow.animationBehavior = NSWindowAnimationBehaviorUtilityWindow; } - } else if (shouldBeChildNSWindow) { - window.collectionBehavior = - NSWindowCollectionBehaviorManaged - | NSWindowCollectionBehaviorIgnoresCycle - | NSWindowCollectionBehaviorFullScreenAuxiliary; - window.hasShadow = NO; - window.animationBehavior = NSWindowAnimationBehaviorNone; } // Persist modality so we can detect changes later on m_windowModality = QPlatformWindow::window()->modality(); - applyContentBorderThickness(window); + applyContentBorderThickness(nsWindow); // Prevent CoreGraphics RGB32 -> RGB64 backing store conversions on deep color // displays by forcing 8-bit components, unless a deep color format has been @@ -1898,11 +1453,11 @@ QCocoaNSWindow *QCocoaWindow::createNSWindow(bool shouldBeChildNSWindow, bool sh surfaceFormat.blueBufferSize() > 8; bool usesLayer = view().layer; if (usesCoreGraphics && !usesDeepColor && !usesLayer) { - [window setDynamicDepthLimit:NO]; - [window setDepthLimit:NSWindowDepthTwentyfourBitRGB]; + [nsWindow setDynamicDepthLimit:NO]; + [nsWindow setDepthLimit:NSWindowDepthTwentyfourBitRGB]; } - return window; + return nsWindow; } bool QCocoaWindow::alwaysShowToolWindow() const @@ -1921,10 +1476,10 @@ void QCocoaWindow::removeMonitor() // Returns the current global screen geometry for the nswindow associated with this window. QRect QCocoaWindow::nativeWindowGeometry() const { - if (!m_nsWindow || isChildNSWindow()) + if (!isContentView()) return geometry(); - NSRect rect = [m_nsWindow frame]; + NSRect rect = m_view.window.frame; QPlatformScreen *onScreen = QPlatformScreen::platformScreenForWindow(window()); int flippedY = onScreen->geometry().height() - rect.origin.y - rect.size.height; // account for nswindow inverted y. QRect qRect = QRect(rect.origin.x, flippedY, rect.size.width, rect.size.height); @@ -1938,13 +1493,15 @@ QRect QCocoaWindow::nativeWindowGeometry() const updated yet, so window()->windowState() will reflect the previous state that was reported to QtGui. */ -void QCocoaWindow::applyWindowState(Qt::WindowState newState) +void QCocoaWindow::applyWindowState(Qt::WindowStates requestedState) { - const Qt::WindowState currentState = windowState(); - if (newState == currentState) + if (!isContentView()) return; - if (!m_nsWindow) + const Qt::WindowState currentState = windowState(); + const Qt::WindowState newState = QWindowPrivate::effectiveState(requestedState); + + if (newState == currentState) return; const NSSize contentSize = m_view.frame.size; @@ -1952,23 +1509,25 @@ void QCocoaWindow::applyWindowState(Qt::WindowState newState) // If content view width or height is 0 then the window animations will crash so // do nothing. We report the current state back to reflect the failed operation. qWarning("invalid window content view size, check your window geometry"); - reportCurrentWindowState(true); + handleWindowStateChanged(HandleUnconditionally); return; } - if (m_nsWindow.styleMask & NSUtilityWindowMask) { - // Utility panels cannot be fullscreen - qWarning() << window()->type() << "windows can not be made full screen"; - reportCurrentWindowState(true); + const NSWindow *nsWindow = m_view.window; + + if (nsWindow.styleMask & NSUtilityWindowMask + && newState & (Qt::WindowMinimized | Qt::WindowFullScreen)) { + qWarning() << window()->type() << "windows can not be made" << newState; + handleWindowStateChanged(HandleUnconditionally); return; } - const id sender = m_nsWindow; + const id sender = nsWindow; // First we need to exit states that can't transition directly to other states switch (currentState) { case Qt::WindowMinimized: - [m_nsWindow deminiaturize:sender]; + [nsWindow deminiaturize:sender]; Q_ASSERT_X(windowState() != Qt::WindowMinimized, "QCocoaWindow", "[NSWindow deminiaturize:] is synchronous"); break; @@ -1994,7 +1553,7 @@ void QCocoaWindow::applyWindowState(Qt::WindowState newState) toggleMaximized(); break; case Qt::WindowMinimized: - [m_nsWindow miniaturize:sender]; + [nsWindow miniaturize:sender]; break; case Qt::WindowNoState: if (windowState() == Qt::WindowMaximized) @@ -2007,27 +1566,31 @@ void QCocoaWindow::applyWindowState(Qt::WindowState newState) void QCocoaWindow::toggleMaximized() { + const NSWindow *window = m_view.window; + // The NSWindow needs to be resizable, otherwise the window will // not be possible to zoom back to non-zoomed state. - const bool wasResizable = m_nsWindow.styleMask & NSResizableWindowMask; - m_nsWindow.styleMask |= NSResizableWindowMask; + const bool wasResizable = window.styleMask & NSResizableWindowMask; + window.styleMask |= NSResizableWindowMask; - const id sender = m_nsWindow; - [m_nsWindow zoom:sender]; + const id sender = window; + [window zoom:sender]; if (!wasResizable) - m_nsWindow.styleMask &= ~NSResizableWindowMask; + window.styleMask &= ~NSResizableWindowMask; } void QCocoaWindow::toggleFullScreen() { + const NSWindow *window = m_view.window; + // The window needs to have the correct collection behavior for the // toggleFullScreen call to have an effect. The collection behavior // will be reset in windowDidEnterFullScreen/windowDidLeaveFullScreen. - m_nsWindow.collectionBehavior |= NSWindowCollectionBehaviorFullScreenPrimary; + window.collectionBehavior |= NSWindowCollectionBehaviorFullScreenPrimary; - const id sender = m_nsWindow; - [m_nsWindow toggleFullScreen:sender]; + const id sender = window; + [window toggleFullScreen:sender]; } bool QCocoaWindow::isTransitioningToFullScreen() const @@ -2055,22 +1618,12 @@ Qt::WindowState QCocoaWindow::windowState() const return Qt::WindowNoState; } -void QCocoaWindow::reportCurrentWindowState(bool unconditionally) -{ - Qt::WindowState currentState = windowState(); - if (!unconditionally && currentState == m_lastReportedWindowState) - return; - - QWindowSystemInterface::handleWindowStateChanged<QWindowSystemInterface::SynchronousDelivery>( - window(), currentState, m_lastReportedWindowState); - m_lastReportedWindowState = currentState; -} - bool QCocoaWindow::setWindowModified(bool modified) { - if (!m_nsWindow) + if (!isContentView()) return false; - [m_nsWindow setDocumentEdited:(modified?YES:NO)]; + + m_view.window.documentEdited = modified; return true; } @@ -2084,51 +1637,19 @@ QCocoaMenuBar *QCocoaWindow::menubar() const return m_menubar; } -// Finds the effective cursor for this window by walking up the -// ancestor chain (including this window) until a set cursor is -// found. Returns nil if there is not set cursor. -NSCursor *QCocoaWindow::effectiveWindowCursor() const -{ - - if (m_windowCursor) - return m_windowCursor; - if (!QPlatformWindow::parent()) - return nil; - return static_cast<QCocoaWindow *>(QPlatformWindow::parent())->effectiveWindowCursor(); -} - -// Applies the cursor as returned by effectiveWindowCursor(), handles -// the special no-cursor-set case by setting the arrow cursor. -void QCocoaWindow::applyEffectiveWindowCursor() -{ - NSCursor *effectiveCursor = effectiveWindowCursor(); - if (effectiveCursor) { - [effectiveCursor set]; - } else { - // We wold like to _unset_ the cursor here; but there is no such - // API. Fall back to setting the default arrow cursor. - [[NSCursor arrowCursor] set]; - } -} - void QCocoaWindow::setWindowCursor(NSCursor *cursor) { - if (m_windowCursor == cursor) + // Setting a cursor in a foreign view is not supported + if (isForeignWindow()) return; - // Setting a cursor in a foregin view is not supported. - if (isForeignWindow()) + QNSView *view = qnsview_cast(m_view); + if (cursor == view.cursor) return; - [m_windowCursor release]; - m_windowCursor = cursor; - [m_windowCursor retain]; + view.cursor = cursor; - // The installed view tracking area (see QNSView updateTrackingAreas) will - // handle cursor updates on mouse enter/leave. Handle the case where the - // mouse is on the this window by changing the cursor immediately. - if (m_windowUnderMouse) - applyEffectiveWindowCursor(); + [m_view.window invalidateCursorRectsForView:m_view]; } void QCocoaWindow::registerTouch(bool enable) @@ -2147,35 +1668,38 @@ void QCocoaWindow::setContentBorderThickness(int topThickness, int bottomThickne bool enable = (topThickness > 0 || bottomThickness > 0); m_drawContentBorderGradient = enable; - applyContentBorderThickness(m_nsWindow); + applyContentBorderThickness(); } void QCocoaWindow::registerContentBorderArea(quintptr identifier, int upper, int lower) { m_contentBorderAreas.insert(identifier, BorderRange(identifier, upper, lower)); - applyContentBorderThickness(m_nsWindow); + applyContentBorderThickness(); } void QCocoaWindow::setContentBorderAreaEnabled(quintptr identifier, bool enable) { m_enabledContentBorderAreas.insert(identifier, enable); - applyContentBorderThickness(m_nsWindow); + applyContentBorderThickness(); } void QCocoaWindow::setContentBorderEnabled(bool enable) { m_drawContentBorderGradient = enable; - applyContentBorderThickness(m_nsWindow); + applyContentBorderThickness(); } void QCocoaWindow::applyContentBorderThickness(NSWindow *window) { + if (!window && isContentView()) + window = m_view.window; + if (!window) return; if (!m_drawContentBorderGradient) { - [window setStyleMask:[window styleMask] & ~NSTexturedBackgroundWindowMask]; - [[[window contentView] superview] setNeedsDisplay:YES]; + window.styleMask = window.styleMask & ~NSTexturedBackgroundWindowMask; + [window.contentView.superview setNeedsDisplay:YES]; window.titlebarAppearsTransparent = NO; return; } @@ -2214,22 +1738,23 @@ void QCocoaWindow::applyContentBorderThickness(NSWindow *window) void QCocoaWindow::updateNSToolbar() { - if (!m_nsWindow) + if (!isContentView()) return; NSToolbar *toolbar = QCocoaIntegration::instance()->toolbar(window()); + const NSWindow *window = m_view.window; - if ([m_nsWindow toolbar] == toolbar) + if (window.toolbar == toolbar) return; - [m_nsWindow setToolbar: toolbar]; - [m_nsWindow setShowsToolbarButton:YES]; + window.toolbar = toolbar; + window.showsToolbarButton = YES; } bool QCocoaWindow::testContentBorderAreaPosition(int position) const { - return m_nsWindow && m_drawContentBorderGradient && - 0 <= position && position < [m_nsWindow contentBorderThicknessForEdge: NSMaxYEdge]; + return isContentView() && m_drawContentBorderGradient && + 0 <= position && position < [m_view.window contentBorderThicknessForEdge:NSMaxYEdge]; } qreal QCocoaWindow::devicePixelRatio() const @@ -2243,85 +1768,6 @@ qreal QCocoaWindow::devicePixelRatio() const return backingSize.height; } -// Returns whether the window can be expose, which it can -// if it is on screen and has a valid geometry. -bool QCocoaWindow::isWindowExposable() -{ - QSize size = geometry().size(); - bool validGeometry = (size.width() > 0 && size.height() > 0); - bool validScreen = ([[m_view window] screen] != 0); - bool nonHiddenSuperView = ![[m_view superview] isHidden]; - return (validGeometry && validScreen && nonHiddenSuperView); -} - -// Exposes the window by posting an expose event to QWindowSystemInterface -void QCocoaWindow::exposeWindow() -{ - m_geometryUpdateExposeAllowed = true; - - if (!isWindowExposable()) - return; - - if (window()->isTopLevel()) { - // Update the QWindow's screen property. This property is set - // to QGuiApplication::primaryScreen() at QWindow construciton - // time, and we won't get a NSWindowDidChangeScreenNotification - // on show. The case where the window is initially displayed - // on a non-primary screen needs special handling here. - if (QCocoaScreen *cocoaScreen = QCocoaIntegration::instance()->screenForNSScreen(m_nsWindow.screen)) - window()->setScreen(cocoaScreen->screen()); - } - - if (!m_isExposed) { - m_isExposed = true; - m_exposedGeometry = geometry(); - m_exposedDevicePixelRatio = devicePixelRatio(); - QRect geometry(QPoint(0, 0), m_exposedGeometry.size()); - qCDebug(lcQpaCocoaWindow) << "QCocoaWindow: exposeWindow" << window() << geometry; - QWindowSystemInterface::handleExposeEvent(window(), geometry); - } -} - -// Obscures the window by posting an empty expose event to QWindowSystemInterface -void QCocoaWindow::obscureWindow() -{ - if (m_isExposed) { - m_geometryUpdateExposeAllowed = false; - m_isExposed = false; - - qCDebug(lcQpaCocoaWindow) << "QCocoaWindow::obscureWindow" << window(); - QWindowSystemInterface::handleExposeEvent(window(), QRegion()); - } -} - -// Updates window geometry by posting an expose event to QWindowSystemInterface -void QCocoaWindow::updateExposedGeometry() -{ - // updateExposedGeometry is not allowed to send the initial expose. If you want - // that call exposeWindow(); - if (!m_geometryUpdateExposeAllowed) - return; - - // Do not send incorrect exposes in case the window is not even visible yet. - // We might get here as a result of a resize() from QWidget's show(), for instance. - if (!window()->isVisible()) - return; - - if (!isWindowExposable()) - return; - - if (m_exposedGeometry.size() == geometry().size() && m_exposedDevicePixelRatio == devicePixelRatio()) - return; - - m_isExposed = true; - m_exposedGeometry = geometry(); - m_exposedDevicePixelRatio = devicePixelRatio(); - - QRect geometry(QPoint(0, 0), m_exposedGeometry.size()); - qCDebug(lcQpaCocoaWindow) << "QCocoaWindow::updateExposedGeometry" << window() << geometry; - QWindowSystemInterface::handleExposeEvent(window(), geometry); -} - QWindow *QCocoaWindow::childWindowAt(QPoint windowPoint) { QWindow *targetWindow = window(); @@ -2371,8 +1817,11 @@ QPoint QCocoaWindow::bottomLeftClippedByNSWindowOffset() const QMargins QCocoaWindow::frameMargins() const { - NSRect frameW = [m_nsWindow frame]; - NSRect frameC = [m_nsWindow contentRectForFrameRect:frameW]; + if (!isContentView()) + return QMargins(); + + NSRect frameW = m_view.window.frame; + NSRect frameC = [m_view.window contentRectForFrameRect:frameW]; return QMargins(frameW.origin.x - frameC.origin.x, (frameW.origin.y + frameW.size.height) - (frameC.origin.y + frameC.size.height), @@ -2385,4 +1834,19 @@ void QCocoaWindow::setFrameStrutEventsEnabled(bool enabled) m_frameStrutEventsEnabled = enabled; } +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug debug, const QCocoaWindow *window) +{ + QDebugStateSaver saver(debug); + debug.nospace(); + debug << "QCocoaWindow(" << (const void *)window; + if (window) + debug << ", window=" << window->window(); + debug << ')'; + return debug; +} +#endif // !QT_NO_DEBUG_STREAM + #include "moc_qcocoawindow.cpp" + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/cocoa/qmultitouch_mac.mm b/src/plugins/platforms/cocoa/qmultitouch_mac.mm index f0ea3b1e66..79f8af7783 100644 --- a/src/plugins/platforms/cocoa/qmultitouch_mac.mm +++ b/src/plugins/platforms/cocoa/qmultitouch_mac.mm @@ -39,10 +39,14 @@ #include "qmultitouch_mac_p.h" #include "qcocoahelpers.h" +#include <private/qtouchdevice_p.h> QT_BEGIN_NAMESPACE +Q_LOGGING_CATEGORY(lcInputDevices, "qt.qpa.input.devices") + QHash<qint64, QCocoaTouch*> QCocoaTouch::_currentTouches; +QHash<quint64, QTouchDevice*> QCocoaTouch::_touchDevices; QPointF QCocoaTouch::_screenReferencePos; QPointF QCocoaTouch::_trackpadReferencePos; int QCocoaTouch::_idAssignmentCount = 0; @@ -209,4 +213,19 @@ QCocoaTouch::getCurrentTouchPointList(NSEvent *event, bool acceptSingleTouch) return touchPoints.values(); } +QTouchDevice *QCocoaTouch::getTouchDevice(QTouchDevice::DeviceType type, quint64 id) +{ + QTouchDevice *ret = _touchDevices.value(id); + if (!ret) { + ret = new QTouchDevice; + ret->setType(type); + ret->setCapabilities(QTouchDevice::Position | QTouchDevice::NormalizedPosition | QTouchDevice::MouseEmulation); + QWindowSystemInterface::registerTouchDevice(ret); + _touchDevices.insert(id, ret); + qCDebug(lcInputDevices) << "touch device" << id << "of type" << type + << "registered as Qt device" << QTouchDevicePrivate::get(ret)->id; + } + return ret; +} + QT_END_NAMESPACE diff --git a/src/plugins/platforms/cocoa/qmultitouch_mac_p.h b/src/plugins/platforms/cocoa/qmultitouch_mac_p.h index 77af86c9c7..044bcd1882 100644 --- a/src/plugins/platforms/cocoa/qmultitouch_mac_p.h +++ b/src/plugins/platforms/cocoa/qmultitouch_mac_p.h @@ -66,8 +66,10 @@ class QCocoaTouch public: static QList<QWindowSystemInterface::TouchPoint> getCurrentTouchPointList(NSEvent *event, bool acceptSingleTouch); static void setMouseInDraggingState(bool inDraggingState); + static QTouchDevice *getTouchDevice(QTouchDevice::DeviceType type, quint64 id); private: + static QHash<quint64, QTouchDevice*> _touchDevices; static QHash<qint64, QCocoaTouch*> _currentTouches; static QPointF _screenReferencePos; static QPointF _trackpadReferencePos; diff --git a/src/plugins/platforms/cocoa/qnsview.h b/src/plugins/platforms/cocoa/qnsview.h index 384f14ba3a..f8903725a6 100644 --- a/src/plugins/platforms/cocoa/qnsview.h +++ b/src/plugins/platforms/cocoa/qnsview.h @@ -51,19 +51,12 @@ QT_BEGIN_NAMESPACE class QCocoaWindow; -class QCocoaBackingStore; class QCocoaGLContext; QT_END_NAMESPACE Q_FORWARD_DECLARE_OBJC_CLASS(QT_MANGLE_NAMESPACE(QNSViewMouseMoveHelper)); @interface QT_MANGLE_NAMESPACE(QNSView) : NSView <NSTextInputClient> { - QCocoaBackingStore* m_backingStore; - QPoint m_backingStoreOffset; - QRegion m_maskRegion; - CGImageRef m_maskImage; - uchar *m_maskData; - bool m_shouldInvalidateWindowShadow; QPointer<QCocoaWindow> m_platformWindow; NSTrackingArea *m_trackingArea; Qt::MouseButtons m_buttons; @@ -73,6 +66,7 @@ Q_FORWARD_DECLARE_OBJC_CLASS(QT_MANGLE_NAMESPACE(QNSViewMouseMoveHelper)); QPointer<QObject> m_composingFocusObject; bool m_sendKeyEvent; QStringList *currentCustomDragTypes; + bool m_dontOverrideCtrlLMB; bool m_sendUpAsRightButton; Qt::KeyboardModifiers currentWheelModifiers; #ifndef QT_NO_OPENGL @@ -89,28 +83,22 @@ Q_FORWARD_DECLARE_OBJC_CLASS(QT_MANGLE_NAMESPACE(QNSViewMouseMoveHelper)); QSet<quint32> m_acceptedKeyDowns; } +@property (nonatomic, retain) NSCursor *cursor; + - (id)init; - (id)initWithCocoaWindow:(QCocoaWindow *)platformWindow; #ifndef QT_NO_OPENGL - (void)setQCocoaGLContext:(QCocoaGLContext *)context; #endif -- (void)flushBackingStore:(QCocoaBackingStore *)backingStore region:(const QRegion &)region offset:(QPoint)offset; -- (void)clearBackingStore:(QCocoaBackingStore *)backingStore; -- (void)setMaskRegion:(const QRegion *)region; -- (void)invalidateWindowShadowIfNeeded; - (void)drawRect:(NSRect)dirtyRect; -- (void)drawBackingStoreUsingCoreGraphics:(NSRect)dirtyRect; -- (void)updateGeometry; - (void)textInputContextKeyboardSelectionDidChangeNotification : (NSNotification *) textInputContextKeyboardSelectionDidChangeNotification; - (void)viewDidHide; -- (void)viewDidUnhide; - (void)removeFromSuperview; - (void)cancelComposingText; - (BOOL)isFlipped; - (BOOL)acceptsFirstResponder; - (BOOL)becomeFirstResponder; -- (BOOL)hasMask; - (BOOL)isOpaque; - (void)convertFromScreen:(NSPoint)mouseLocation toWindowPoint:(QPointF *)qtWindowPoint andScreenPoint:(QPointF *)qtScreenPoint; diff --git a/src/plugins/platforms/cocoa/qnsview.mm b/src/plugins/platforms/cocoa/qnsview.mm index 2e64204fb7..c888551729 100644 --- a/src/plugins/platforms/cocoa/qnsview.mm +++ b/src/plugins/platforms/cocoa/qnsview.mm @@ -53,6 +53,7 @@ #include <QtCore/qsysinfo.h> #include <private/qguiapplication_p.h> #include <private/qcoregraphics_p.h> +#include <private/qwindow_p.h> #include "qcocoabackingstore.h" #ifndef QT_NO_OPENGL #include "qcocoaglcontext.h" @@ -69,16 +70,6 @@ Q_LOGGING_CATEGORY(lcQpaGestures, "qt.qpa.input.gestures") #endif Q_LOGGING_CATEGORY(lcQpaTablet, "qt.qpa.input.tablet") -static QTouchDevice *touchDevice = 0; - -static bool _q_dontOverrideCtrlLMB = false; - -@interface NSEvent (Qt_Compile_Leopard_DeviceDelta) - - (CGFloat)deviceDeltaX; - - (CGFloat)deviceDeltaY; - - (CGFloat)deviceDeltaZ; -@end - @interface QT_MANGLE_NAMESPACE(QNSViewMouseMoveHelper) : NSObject { QNSView *view; @@ -121,7 +112,7 @@ static bool _q_dontOverrideCtrlLMB = false; - (void)cursorUpdate:(NSEvent *)theEvent { - [self cursorUpdate:theEvent]; + [view cursorUpdate:theEvent]; } @end @@ -133,17 +124,9 @@ static bool _q_dontOverrideCtrlLMB = false; @implementation QT_MANGLE_NAMESPACE(QNSView) -+ (void)initialize -{ - _q_dontOverrideCtrlLMB = qt_mac_resolveOption(false, "QT_MAC_DONT_OVERRIDE_CTRL_LMB"); -} - - (id) init { if (self = [super initWithFrame:NSZeroRect]) { - m_backingStore = 0; - m_maskImage = 0; - m_shouldInvalidateWindowShadow = false; m_buttons = Qt::NoButton; m_acceptedMouseDowns = Qt::NoButton; m_frameStrutButtons = Qt::NoButton; @@ -153,6 +136,7 @@ static bool _q_dontOverrideCtrlLMB = false; m_shouldSetGLContextinDrawRect = false; #endif currentCustomDragTypes = 0; + m_dontOverrideCtrlLMB = false; m_sendUpAsRightButton = false; m_inputSource = 0; m_mouseMoveHelper = [[QT_MANGLE_NAMESPACE(QNSViewMouseMoveHelper) alloc] initWithView:self]; @@ -160,28 +144,19 @@ static bool _q_dontOverrideCtrlLMB = false; m_scrolling = false; m_updatingDrag = false; m_currentlyInterpretedKeyEvent = 0; - - if (!touchDevice) { - touchDevice = new QTouchDevice; - touchDevice->setType(QTouchDevice::TouchPad); - touchDevice->setCapabilities(QTouchDevice::Position | QTouchDevice::NormalizedPosition | QTouchDevice::MouseEmulation); - QWindowSystemInterface::registerTouchDevice(touchDevice); - } - m_isMenuView = false; self.focusRingType = NSFocusRingTypeNone; + self.cursor = nil; } return self; } - (void)dealloc { - CGImageRelease(m_maskImage); if (m_trackingArea) { [self removeTrackingArea:m_trackingArea]; [m_trackingArea release]; } - m_maskImage = 0; [m_inputSource release]; [[NSNotificationCenter defaultCenter] removeObserver:self]; [m_mouseMoveHelper release]; @@ -199,6 +174,7 @@ static bool _q_dontOverrideCtrlLMB = false; m_platformWindow = platformWindow; m_sendKeyEvent = false; + m_dontOverrideCtrlLMB = qt_mac_resolveOption(false, platformWindow->window(), "_q_platform_MacDontOverrideCtrlLMB", "QT_MAC_DONT_OVERRIDE_CTRL_LMB"); m_trackingArea = nil; #ifdef QT_COCOA_ENABLE_ACCESSIBILITY_INSPECTOR @@ -226,6 +202,22 @@ static bool _q_dontOverrideCtrlLMB = false; return self; } +- (NSString *)description +{ + NSMutableString *description = [NSMutableString stringWithString:[super description]]; + +#ifndef QT_NO_DEBUG_STREAM + QString platformWindowDescription; + QDebug debug(&platformWindowDescription); + debug.nospace() << "; " << m_platformWindow << ">"; + + NSRange lastCharacter = [description rangeOfComposedCharacterSequenceAtIndex:description.length - 1]; + [description replaceCharactersInRange:lastCharacter withString:platformWindowDescription.toNSString()]; +#endif + + return description; +} + #ifndef QT_NO_OPENGL - (void) setQCocoaGLContext:(QCocoaGLContext *)context { @@ -249,18 +241,13 @@ static bool _q_dontOverrideCtrlLMB = false; if ([self superview]) { m_platformWindow->m_viewIsEmbedded = true; QWindowSystemInterface::handleGeometryChange(m_platformWindow->window(), m_platformWindow->geometry()); - m_platformWindow->updateExposedGeometry(); + [self setNeedsDisplay:YES]; QWindowSystemInterface::flushWindowSystemEvents(); } else { m_platformWindow->m_viewIsEmbedded = false; } } -- (void)viewDidMoveToWindow -{ - m_backingStore = Q_NULLPTR; -} - - (QWindow *)topLevelWindow { if (!m_platformWindow) @@ -279,77 +266,6 @@ static bool _q_dontOverrideCtrlLMB = false; return focusWindow; } -- (void)updateGeometry -{ - if (!m_platformWindow) - return; - - QRect geometry; - - if (self.window.parentWindow) { - return; -#if 0 - //geometry = QRectF::fromCGRect([self frame]).toRect(); - qDebug() << "nsview updateGeometry" << m_platformWindow->window(); - QRect screenRect = QRectF::fromCGRect([m_platformWindow->m_nsWindow convertRectToScreen:[self frame]]).toRect(); - qDebug() << "screenRect" << screenRect; - - screenRect.moveTop(qt_mac_flipYCoordinate(screenRect.y() + screenRect.height())); - geometry = QRect(m_platformWindow->window()->parent()->mapFromGlobal(screenRect.topLeft()), screenRect.size()); - qDebug() << "geometry" << geometry; -#endif - //geometry = QRect(screenRect.origin.x, qt_mac_flipYCoordinate(screenRect.origin.y + screenRect.size.height), screenRect.size.width, screenRect.size.height); - } else if (m_platformWindow->m_nsWindow) { - // top level window, get window rect and flip y. - NSRect rect = [self frame]; - NSRect windowRect = [[self window] frame]; - geometry = QRect(windowRect.origin.x, qt_mac_flipYCoordinate(windowRect.origin.y + rect.size.height), rect.size.width, rect.size.height); - } else if (m_platformWindow->m_viewIsToBeEmbedded) { - // embedded child window, use the frame rect ### merge with case below - geometry = QRectF::fromCGRect(NSRectToCGRect([self bounds])).toRect(); - } else { - // child window, use the frame rect - geometry = QRectF::fromCGRect(NSRectToCGRect([self frame])).toRect(); - } - - if (m_platformWindow->m_nsWindow && geometry == m_platformWindow->geometry()) - return; - - const bool isResize = geometry.size() != m_platformWindow->geometry().size(); - - // It can happen that self.window is nil (if we are changing - // styleMask from/to borderless and content view is being re-parented) - // - this results in an invalid coordinates. - if (m_platformWindow->m_inSetStyleMask && !self.window) - return; - - qCDebug(lcQpaCocoaWindow) << "[QNSView udpateGeometry:]" << m_platformWindow->window() - << "current" << m_platformWindow->geometry() << "new" << geometry; - - // Call setGeometry on QPlatformWindow. (not on QCocoaWindow, - // doing that will initiate a geometry change it and possibly create - // an infinite loop when this notification is triggered again.) - m_platformWindow->QPlatformWindow::setGeometry(geometry); - - // Don't send the geometry change if the QWindow is designated to be - // embedded in a foreign view hiearchy but has not actually been - // embedded yet - it's too early. - if (m_platformWindow->m_viewIsToBeEmbedded && !m_platformWindow->m_viewIsEmbedded) - return; - - // Send a geometry change event to Qt, if it's ready to handle events - if (!m_platformWindow->m_inConstructor) { - QWindowSystemInterface::handleGeometryChange(m_platformWindow->window(), geometry); - m_platformWindow->updateExposedGeometry(); - // Guard against processing window system events during QWindow::setGeometry - // calles, which Qt and Qt applications do not excpect. - if (!m_platformWindow->m_inSetGeometry) - QWindowSystemInterface::flushWindowSystemEvents(); - else if (isResize) - m_backingStore = 0; - } -} - - (void)textInputContextKeyboardSelectionDidChangeNotification : (NSNotification *) textInputContextKeyboardSelectionDidChangeNotification { Q_UNUSED(textInputContextKeyboardSelectionDidChangeNotification) @@ -361,12 +277,13 @@ static bool _q_dontOverrideCtrlLMB = false; - (void)viewDidHide { - m_platformWindow->obscureWindow(); -} + if (!m_platformWindow->isExposed()) + return; -- (void)viewDidUnhide -{ - m_platformWindow->exposeWindow(); + m_platformWindow->handleExposeEvent(QRegion()); + + // Note: setNeedsDisplay is automatically called for + // viewDidUnhide so no reason to override it here. } - (void)removeFromSuperview @@ -375,35 +292,6 @@ static bool _q_dontOverrideCtrlLMB = false; [super removeFromSuperview]; } -- (void) flushBackingStore:(QCocoaBackingStore *)backingStore region:(const QRegion &)region offset:(QPoint)offset -{ - qCDebug(lcQpaCocoaWindow) << "[QNSView flushBackingStore:]" << m_platformWindow->window() << region.rectCount() << region.boundingRect() << offset; - - m_backingStore = backingStore; - m_backingStoreOffset = offset * m_backingStore->paintDevice()->devicePixelRatio(); - - // Prevent buildup of NSDisplayCycle objects during setNeedsDisplayInRect, which - // would normally be released as part of the root runloop's autorelease pool, but - // can be kept alive during repeated painting which starve the root runloop. - // FIXME: Move this to the event dispatcher, to cover more cases of starvation. - // FIXME: Figure out if there's a way to detect and/or prevent runloop starvation. - QMacAutoReleasePool pool; - - for (const QRect &rect : region) - [self setNeedsDisplayInRect:NSMakeRect(rect.x(), rect.y(), rect.width(), rect.height())]; -} - -- (void)clearBackingStore:(QCocoaBackingStore *)backingStore -{ - if (backingStore == m_backingStore) - m_backingStore = 0; -} - -- (BOOL) hasMask -{ - return !m_maskRegion.isEmpty(); -} - - (BOOL) isOpaque { if (!m_platformWindow) @@ -411,49 +299,21 @@ static bool _q_dontOverrideCtrlLMB = false; return m_platformWindow->isOpaque(); } -- (void) setMaskRegion:(const QRegion *)region -{ - m_shouldInvalidateWindowShadow = true; - m_maskRegion = *region; - if (m_maskImage) - CGImageRelease(m_maskImage); - if (region->isEmpty()) { - m_maskImage = 0; - return; - } - - const QRect &rect = region->boundingRect(); - QImage tmp(rect.size(), QImage::Format_RGB32); - tmp.fill(Qt::white); - QPainter p(&tmp); - p.setClipRegion(*region); - p.fillRect(rect, Qt::black); - p.end(); - QImage maskImage = QImage(rect.size(), QImage::Format_Indexed8); - for (int y=0; y<rect.height(); ++y) { - const uint *src = (const uint *) tmp.constScanLine(y); - uchar *dst = maskImage.scanLine(y); - for (int x=0; x<rect.width(); ++x) { - dst[x] = src[x] & 0xff; - } - } - m_maskImage = qt_mac_toCGImageMask(maskImage); -} - -- (void)invalidateWindowShadowIfNeeded -{ - if (m_shouldInvalidateWindowShadow && m_platformWindow->m_nsWindow) { - [m_platformWindow->m_nsWindow invalidateShadow]; - m_shouldInvalidateWindowShadow = false; - } -} - - (void)drawRect:(NSRect)dirtyRect { + Q_UNUSED(dirtyRect); + if (!m_platformWindow) return; - qCDebug(lcQpaCocoaWindow) << "[QNSView drawRect:]" << m_platformWindow->window() << QRectF::fromCGRect(NSRectToCGRect(dirtyRect)); + QRegion exposedRegion; + const NSRect *dirtyRects; + NSInteger numDirtyRects; + [self getRectsBeingDrawn:&dirtyRects count:&numDirtyRects]; + for (int i = 0; i < numDirtyRects; ++i) + exposedRegion += QRectF::fromCGRect(dirtyRects[i]).toRect(); + + qCDebug(lcQpaCocoaWindow) << "[QNSView drawRect:]" << m_platformWindow->window() << exposedRegion; #ifndef QT_NO_OPENGL if (m_glContext && m_shouldSetGLContextinDrawRect) { @@ -462,85 +322,44 @@ static bool _q_dontOverrideCtrlLMB = false; } #endif - if (m_platformWindow->m_drawContentBorderGradient) - NSDrawWindowBackground(dirtyRect); - - if (m_backingStore) - [self drawBackingStoreUsingCoreGraphics:dirtyRect]; + m_platformWindow->handleExposeEvent(exposedRegion); + + if (qt_window_private(m_platformWindow->window())->updateRequestPending) { + // A call to QWindow::requestUpdate was issued during the expose event, or we + // had to deliver a real expose event and still need to deliver the update. + // But AppKit will reset the needsDisplay state of the view after completing + // the current display cycle, so we need to defer the request to redisplay. + // FIXME: Perhaps this should be a trigger to enable CADisplayLink? + qCDebug(lcQpaCocoaWindow) << "[QNSView drawRect:] issuing deferred setNeedsDisplay due to pending update request"; + dispatch_async(dispatch_get_main_queue (), ^{ + [self setNeedsDisplay:YES]; + }); + } +} - [self invalidateWindowShadowIfNeeded]; +- (BOOL)wantsUpdateLayer +{ + return YES; } -// Draws the backing store content to the QNSView using Core Graphics. -// This function assumes that the QNSView is in a configuration that -// supports Core Graphics, such as "classic" mode or layer mode with -// the default layer. -- (void)drawBackingStoreUsingCoreGraphics:(NSRect)dirtyRect +- (void)updateLayer { - if (!m_backingStore) + if (!m_platformWindow) return; - // Calculate source and target rects. The target rect is the dirtyRect: - CGRect dirtyWindowRect = NSRectToCGRect(dirtyRect); - - // The backing store source rect will be larger on retina displays. - // Scale dirtyRect by the device pixel ratio: - const qreal devicePixelRatio = m_backingStore->paintDevice()->devicePixelRatio(); - CGRect dirtyBackingRect = CGRectMake(dirtyRect.origin.x * devicePixelRatio, - dirtyRect.origin.y * devicePixelRatio, - dirtyRect.size.width * devicePixelRatio, - dirtyRect.size.height * devicePixelRatio); - - NSGraphicsContext *nsGraphicsContext = [NSGraphicsContext currentContext]; - CGContextRef cgContext = (CGContextRef) [nsGraphicsContext graphicsPort]; - - // Translate coordiate system from CoreGraphics (bottom-left) to NSView (top-left): - CGContextSaveGState(cgContext); - int dy = dirtyWindowRect.origin.y + CGRectGetMaxY(dirtyWindowRect); - - CGContextTranslateCTM(cgContext, 0, dy); - CGContextScaleCTM(cgContext, 1, -1); - - // If a mask is set, modify the sub image accordingly: - CGImageRef subMask = 0; - if (m_maskImage) { - subMask = CGImageCreateWithImageInRect(m_maskImage, dirtyWindowRect); - CGContextClipToMask(cgContext, dirtyWindowRect, subMask); - } - - // Clip out and draw the correct sub image from the (shared) backingstore: - CGRect backingStoreRect = CGRectMake( - dirtyBackingRect.origin.x + m_backingStoreOffset.x(), - dirtyBackingRect.origin.y + m_backingStoreOffset.y(), - dirtyBackingRect.size.width, - dirtyBackingRect.size.height - ); - CGImageRef bsCGImage = qt_mac_toCGImage(m_backingStore->toImage()); - - // Prevent potentially costly color conversion by assiging the display - // color space to the backingstore image. - CGImageRef displayColorSpaceImage = CGImageCreateCopyWithColorSpace(bsCGImage, - self.window.screen.colorSpace.CGColorSpace); - - CGImageRef cleanImg = CGImageCreateWithImageInRect(displayColorSpaceImage, backingStoreRect); + qCDebug(lcQpaCocoaWindow) << "[QNSView updateLayer]" << m_platformWindow->window(); - // Optimization: Copy frame buffer content instead of blending for - // top-level windows where Qt fills the entire window content area. - // (But don't overpaint the title-bar gradient) - if (m_platformWindow->m_nsWindow && !m_platformWindow->m_drawContentBorderGradient) - CGContextSetBlendMode(cgContext, kCGBlendModeCopy); - - CGContextDrawImage(cgContext, dirtyWindowRect, cleanImg); + // FIXME: Find out if there's a way to resolve the dirty rect like in drawRect: + m_platformWindow->handleExposeEvent(QRectF::fromCGRect(self.bounds).toRect()); +} - // Clean-up: - CGContextRestoreGState(cgContext); - CGImageRelease(cleanImg); - CGImageRelease(subMask); - CGImageRelease(bsCGImage); - CGImageRelease(displayColorSpaceImage); +- (void)viewDidChangeBackingProperties +{ + if (self.layer) + self.layer.contentsScale = self.window.backingScaleFactor; } -- (BOOL) isFlipped +- (BOOL)isFlipped { return YES; } @@ -661,12 +480,6 @@ static bool _q_dontOverrideCtrlLMB = false; QPointF qtWindowPoint; QPointF qtScreenPoint; QNSView *targetView = self; - if (m_platformWindow && m_platformWindow->m_forwardWindow) { - if (theEvent.type == NSLeftMouseDragged || theEvent.type == NSLeftMouseUp) - targetView = qnsview_cast(m_platformWindow->m_forwardWindow->view()); - else - m_platformWindow->m_forwardWindow.clear(); - } if (!targetView.platformWindow) return; @@ -760,7 +573,8 @@ static bool _q_dontOverrideCtrlLMB = false; Q_UNUSED(qtScreenPoint); // Maintain masked state for the button for use by MouseDragged and MouseUp. - const bool masked = [self hasMask] && !m_maskRegion.contains(qtWindowPoint.toPoint()); + QRegion mask = m_platformWindow->window()->mask(); + const bool masked = !mask.isEmpty() && !mask.contains(qtWindowPoint.toPoint()); if (masked) m_acceptedMouseDowns &= ~button; else @@ -858,8 +672,8 @@ static bool _q_dontOverrideCtrlLMB = false; [self convertFromScreen:[self screenMousePoint:theEvent] toWindowPoint:&qtWindowPoint andScreenPoint:&qtScreenPoint]; Q_UNUSED(qtScreenPoint); - const bool masked = [self hasMask] && !m_maskRegion.contains(qtWindowPoint.toPoint()); - + QRegion mask = m_platformWindow->window()->mask(); + const bool masked = !mask.isEmpty() && !mask.contains(qtWindowPoint.toPoint()); // Maintain masked state for the button for use by MouseDragged and Up. if (masked) m_acceptedMouseDowns &= ~Qt::LeftButton; @@ -875,7 +689,7 @@ static bool _q_dontOverrideCtrlLMB = false; if ([self hasMarkedText]) { [[NSTextInputContext currentInputContext] handleEvent:theEvent]; } else { - if (!_q_dontOverrideCtrlLMB && [QNSView convertKeyModifiers:[theEvent modifierFlags]] & Qt::MetaModifier) { + if (!m_dontOverrideCtrlLMB && [QNSView convertKeyModifiers:[theEvent modifierFlags]] & Qt::MetaModifier) { m_buttons |= Qt::RightButton; m_sendUpAsRightButton = true; } else { @@ -972,8 +786,16 @@ static bool _q_dontOverrideCtrlLMB = false; - (void)cursorUpdate:(NSEvent *)theEvent { - Q_UNUSED(theEvent); - m_platformWindow->applyEffectiveWindowCursor(); + qCDebug(lcQpaCocoaWindow) << "[QNSView cursorUpdate:]" << self.cursor; + + // Note: We do not get this callback when moving from a subview that + // uses the legacy cursorRect API, so the cursor is reset to the arrow + // cursor. See rdar://34183708 + + if (self.cursor) + [self.cursor set]; + else + [super cursorUpdate:theEvent]; } - (void)mouseMovedImpl:(NSEvent *)theEvent @@ -994,7 +816,7 @@ static bool _q_dontOverrideCtrlLMB = false; // the time of the leave. This is dificult to accomplish by // handling mouseEnter and mouseLeave envents, since they are sent // individually to different views. - if (m_platformWindow->m_nsWindow && childWindow) { + if (m_platformWindow->isContentView() && childWindow) { if (childWindow != m_platformWindow->m_enterLeaveTargetWindow) { QWindowSystemInterface::handleEnterLeaveEvent(childWindow, m_platformWindow->m_enterLeaveTargetWindow, windowPoint, screenPoint); m_platformWindow->m_enterLeaveTargetWindow = childWindow; @@ -1021,7 +843,7 @@ static bool _q_dontOverrideCtrlLMB = false; return; // Top-level windows generate enter events for sub-windows. - if (!m_platformWindow->m_nsWindow) + if (!m_platformWindow->isContentView()) return; QPointF windowPoint; @@ -1043,7 +865,7 @@ static bool _q_dontOverrideCtrlLMB = false; return; // Top-level windows generate leave events for sub-windows. - if (!m_platformWindow->m_nsWindow) + if (!m_platformWindow->isContentView()) return; QWindowSystemInterface::handleLeaveEvent(m_platformWindow->m_enterLeaveTargetWindow); @@ -1240,8 +1062,8 @@ static QTabletEvent::TabletDevice wacomTabletDevice(NSEvent *theEvent) const NSTimeInterval timestamp = [event timestamp]; const QList<QWindowSystemInterface::TouchPoint> points = QCocoaTouch::getCurrentTouchPointList(event, [self shouldSendSingleTouch]); - qCDebug(lcQpaTouch) << "touchesBeganWithEvent" << points; - QWindowSystemInterface::handleTouchEvent(m_platformWindow->window(), timestamp * 1000, touchDevice, points); + qCDebug(lcQpaTouch) << "touchesBeganWithEvent" << points << "from device" << hex << [event deviceID]; + QWindowSystemInterface::handleTouchEvent(m_platformWindow->window(), timestamp * 1000, QCocoaTouch::getTouchDevice(QTouchDevice::TouchPad, [event deviceID]), points); } - (void)touchesMovedWithEvent:(NSEvent *)event @@ -1251,8 +1073,8 @@ static QTabletEvent::TabletDevice wacomTabletDevice(NSEvent *theEvent) const NSTimeInterval timestamp = [event timestamp]; const QList<QWindowSystemInterface::TouchPoint> points = QCocoaTouch::getCurrentTouchPointList(event, [self shouldSendSingleTouch]); - qCDebug(lcQpaTouch) << "touchesMovedWithEvent" << points; - QWindowSystemInterface::handleTouchEvent(m_platformWindow->window(), timestamp * 1000, touchDevice, points); + qCDebug(lcQpaTouch) << "touchesMovedWithEvent" << points << "from device" << hex << [event deviceID]; + QWindowSystemInterface::handleTouchEvent(m_platformWindow->window(), timestamp * 1000, QCocoaTouch::getTouchDevice(QTouchDevice::TouchPad, [event deviceID]), points); } - (void)touchesEndedWithEvent:(NSEvent *)event @@ -1262,8 +1084,8 @@ static QTabletEvent::TabletDevice wacomTabletDevice(NSEvent *theEvent) const NSTimeInterval timestamp = [event timestamp]; const QList<QWindowSystemInterface::TouchPoint> points = QCocoaTouch::getCurrentTouchPointList(event, [self shouldSendSingleTouch]); - qCDebug(lcQpaTouch) << "touchesEndedWithEvent" << points; - QWindowSystemInterface::handleTouchEvent(m_platformWindow->window(), timestamp * 1000, touchDevice, points); + qCDebug(lcQpaTouch) << "touchesEndedWithEvent" << points << "from device" << hex << [event deviceID]; + QWindowSystemInterface::handleTouchEvent(m_platformWindow->window(), timestamp * 1000, QCocoaTouch::getTouchDevice(QTouchDevice::TouchPad, [event deviceID]), points); } - (void)touchesCancelledWithEvent:(NSEvent *)event @@ -1273,8 +1095,8 @@ static QTabletEvent::TabletDevice wacomTabletDevice(NSEvent *theEvent) const NSTimeInterval timestamp = [event timestamp]; const QList<QWindowSystemInterface::TouchPoint> points = QCocoaTouch::getCurrentTouchPointList(event, [self shouldSendSingleTouch]); - qCDebug(lcQpaTouch) << "touchesCancelledWithEvent" << points; - QWindowSystemInterface::handleTouchEvent(m_platformWindow->window(), timestamp * 1000, touchDevice, points); + qCDebug(lcQpaTouch) << "touchesCancelledWithEvent" << points << "from device" << hex << [event deviceID]; + QWindowSystemInterface::handleTouchEvent(m_platformWindow->window(), timestamp * 1000, QCocoaTouch::getTouchDevice(QTouchDevice::TouchPad, [event deviceID]), points); } #ifndef QT_NO_GESTURES @@ -1304,12 +1126,12 @@ static QTabletEvent::TabletDevice wacomTabletDevice(NSEvent *theEvent) if ([self handleGestureAsBeginEnd:event]) return; - qCDebug(lcQpaGestures) << "magnifyWithEvent" << [event magnification]; + qCDebug(lcQpaGestures) << "magnifyWithEvent" << [event magnification] << "from device" << hex << [event deviceID]; const NSTimeInterval timestamp = [event timestamp]; QPointF windowPoint; QPointF screenPoint; [self convertFromScreen:[self screenMousePoint:event] toWindowPoint:&windowPoint andScreenPoint:&screenPoint]; - QWindowSystemInterface::handleGestureEventWithRealValue(m_platformWindow->window(), timestamp, Qt::ZoomNativeGesture, + QWindowSystemInterface::handleGestureEventWithRealValue(m_platformWindow->window(), QCocoaTouch::getTouchDevice(QTouchDevice::TouchPad, [event deviceID]), timestamp, Qt::ZoomNativeGesture, [event magnification], windowPoint, screenPoint); } @@ -1319,12 +1141,12 @@ static QTabletEvent::TabletDevice wacomTabletDevice(NSEvent *theEvent) return; static bool zoomIn = true; - qCDebug(lcQpaGestures) << "smartMagnifyWithEvent" << zoomIn; + qCDebug(lcQpaGestures) << "smartMagnifyWithEvent" << zoomIn << "from device" << hex << [event deviceID]; const NSTimeInterval timestamp = [event timestamp]; QPointF windowPoint; QPointF screenPoint; [self convertFromScreen:[self screenMousePoint:event] toWindowPoint:&windowPoint andScreenPoint:&screenPoint]; - QWindowSystemInterface::handleGestureEventWithRealValue(m_platformWindow->window(), timestamp, Qt::SmartZoomNativeGesture, + QWindowSystemInterface::handleGestureEventWithRealValue(m_platformWindow->window(), QCocoaTouch::getTouchDevice(QTouchDevice::TouchPad, [event deviceID]), timestamp, Qt::SmartZoomNativeGesture, zoomIn ? 1.0f : 0.0f, windowPoint, screenPoint); zoomIn = !zoomIn; } @@ -1341,7 +1163,7 @@ static QTabletEvent::TabletDevice wacomTabletDevice(NSEvent *theEvent) QPointF windowPoint; QPointF screenPoint; [self convertFromScreen:[self screenMousePoint:event] toWindowPoint:&windowPoint andScreenPoint:&screenPoint]; - QWindowSystemInterface::handleGestureEventWithRealValue(m_platformWindow->window(), timestamp, Qt::RotateNativeGesture, + QWindowSystemInterface::handleGestureEventWithRealValue(m_platformWindow->window(), QCocoaTouch::getTouchDevice(QTouchDevice::TouchPad, [event deviceID]), timestamp, Qt::RotateNativeGesture, -[event rotation], windowPoint, screenPoint); } @@ -1350,7 +1172,7 @@ static QTabletEvent::TabletDevice wacomTabletDevice(NSEvent *theEvent) if (!m_platformWindow) return; - qCDebug(lcQpaGestures) << "swipeWithEvent" << [event deltaX] << [event deltaY]; + qCDebug(lcQpaGestures) << "swipeWithEvent" << [event deltaX] << [event deltaY] << "from device" << hex << [event deviceID]; const NSTimeInterval timestamp = [event timestamp]; QPointF windowPoint; QPointF screenPoint; @@ -1366,7 +1188,7 @@ static QTabletEvent::TabletDevice wacomTabletDevice(NSEvent *theEvent) else if ([event deltaY] == -1) angle = 270.0f; - QWindowSystemInterface::handleGestureEventWithRealValue(m_platformWindow->window(), timestamp, Qt::SwipeNativeGesture, + QWindowSystemInterface::handleGestureEventWithRealValue(m_platformWindow->window(), QCocoaTouch::getTouchDevice(QTouchDevice::TouchPad, [event deviceID]), timestamp, Qt::SwipeNativeGesture, angle, windowPoint, screenPoint); } @@ -1379,8 +1201,8 @@ static QTabletEvent::TabletDevice wacomTabletDevice(NSEvent *theEvent) QPointF windowPoint; QPointF screenPoint; [self convertFromScreen:[self screenMousePoint:event] toWindowPoint:&windowPoint andScreenPoint:&screenPoint]; - qCDebug(lcQpaGestures) << "beginGestureWithEvent @" << windowPoint; - QWindowSystemInterface::handleGestureEvent(m_platformWindow->window(), timestamp, Qt::BeginNativeGesture, + qCDebug(lcQpaGestures) << "beginGestureWithEvent @" << windowPoint << "from device" << hex << [event deviceID]; + QWindowSystemInterface::handleGestureEvent(m_platformWindow->window(), QCocoaTouch::getTouchDevice(QTouchDevice::TouchPad, [event deviceID]), timestamp, Qt::BeginNativeGesture, windowPoint, screenPoint); } @@ -1389,12 +1211,12 @@ static QTabletEvent::TabletDevice wacomTabletDevice(NSEvent *theEvent) if (!m_platformWindow) return; - qCDebug(lcQpaGestures) << "endGestureWithEvent"; + qCDebug(lcQpaGestures) << "endGestureWithEvent" << "from device" << hex << [event deviceID]; const NSTimeInterval timestamp = [event timestamp]; QPointF windowPoint; QPointF screenPoint; [self convertFromScreen:[self screenMousePoint:event] toWindowPoint:&windowPoint andScreenPoint:&screenPoint]; - QWindowSystemInterface::handleGestureEvent(m_platformWindow->window(), timestamp, Qt::EndNativeGesture, + QWindowSystemInterface::handleGestureEvent(m_platformWindow->window(), QCocoaTouch::getTouchDevice(QTouchDevice::TouchPad, [event deviceID]), timestamp, Qt::EndNativeGesture, windowPoint, screenPoint); } #endif // QT_NO_GESTURES @@ -2126,7 +1948,7 @@ static QPoint mapWindowCoordinates(QWindow *source, QWindow *target, QPoint poin QCocoaDrag* nativeDrag = QCocoaIntegration::instance()->drag(); if (nativeDrag->currentDrag()) { // The drag was started from within the application - response = QWindowSystemInterface::handleDrag(target, nativeDrag->platformDropData(), mapWindowCoordinates(m_platformWindow->window(), target, qt_windowPoint), qtAllowed); + response = QWindowSystemInterface::handleDrag(target, nativeDrag->dragMimeData(), mapWindowCoordinates(m_platformWindow->window(), target, qt_windowPoint), qtAllowed); [self updateCursorFromDragResponse:response drag:nativeDrag]; } else { QCocoaDropData mimeData([sender draggingPasteboard]); @@ -2170,7 +1992,7 @@ static QPoint mapWindowCoordinates(QWindow *source, QWindow *target, QPoint poin QCocoaDrag* nativeDrag = QCocoaIntegration::instance()->drag(); if (nativeDrag->currentDrag()) { // The drag was started from within the application - response = QWindowSystemInterface::handleDrop(target, nativeDrag->platformDropData(), mapWindowCoordinates(m_platformWindow->window(), target, qt_windowPoint), qtAllowed); + response = QWindowSystemInterface::handleDrop(target, nativeDrag->dragMimeData(), mapWindowCoordinates(m_platformWindow->window(), target, qt_windowPoint), qtAllowed); } else { QCocoaDropData mimeData([sender draggingPasteboard]); response = QWindowSystemInterface::handleDrop(target, &mimeData, mapWindowCoordinates(m_platformWindow->window(), target, qt_windowPoint), qtAllowed); diff --git a/src/plugins/platforms/cocoa/qnswindow.h b/src/plugins/platforms/cocoa/qnswindow.h new file mode 100644 index 0000000000..1258fddb31 --- /dev/null +++ b/src/plugins/platforms/cocoa/qnswindow.h @@ -0,0 +1,74 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QNSWINDOW_H +#define QNSWINDOW_H + +#include <qglobal.h> +#include <QPointer> +#include "qt_mac_p.h" + +#include <AppKit/AppKit.h> + +QT_FORWARD_DECLARE_CLASS(QCocoaWindow) + +@interface NSWindow (FullScreenProperty) +@property(readonly) BOOL qt_fullScreen; +@end + +@protocol QNSWindowProtocol +@optional +- (BOOL)canBecomeKeyWindow; +- (void)sendEvent:(NSEvent*)theEvent; +- (void)closeAndRelease; +- (void)dealloc; +- (BOOL)isOpaque; +- (NSColor *)backgroundColor; +@property (nonatomic, readonly) QCocoaWindow *platformWindow; +@end + +typedef NSWindow<QNSWindowProtocol> QCocoaNSWindow; + +@interface QT_MANGLE_NAMESPACE(QNSWindow) : NSWindow<QNSWindowProtocol> @end +QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSWindow); + +@interface QT_MANGLE_NAMESPACE(QNSPanel) : NSPanel<QNSWindowProtocol> @end +QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSPanel); + +#endif // QNSWINDOW_H diff --git a/src/plugins/platforms/cocoa/qnswindow.mm b/src/plugins/platforms/cocoa/qnswindow.mm new file mode 100644 index 0000000000..e846fa043c --- /dev/null +++ b/src/plugins/platforms/cocoa/qnswindow.mm @@ -0,0 +1,324 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qnswindow.h" +#include "qnswindowdelegate.h" +#include "qcocoawindow.h" +#include "qcocoahelpers.h" +#include "qcocoaeventdispatcher.h" + +#include <qpa/qwindowsysteminterface.h> +#include <qoperatingsystemversion.h> + +Q_LOGGING_CATEGORY(lcCocoaEvents, "qt.qpa.cocoa.events"); + +static bool isMouseEvent(NSEvent *ev) +{ + switch ([ev type]) { + case NSLeftMouseDown: + case NSLeftMouseUp: + case NSRightMouseDown: + case NSRightMouseUp: + case NSMouseMoved: + case NSLeftMouseDragged: + case NSRightMouseDragged: + return true; + default: + return false; + } +} + +@implementation NSWindow (FullScreenProperty) + ++ (void)load +{ + NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; + [center addObserverForName:NSWindowDidEnterFullScreenNotification object:nil queue:nil + usingBlock:^(NSNotification *notification) { + objc_setAssociatedObject(notification.object, @selector(qt_fullScreen), + [NSNumber numberWithBool:YES], OBJC_ASSOCIATION_RETAIN); + } + ]; + [center addObserverForName:NSWindowDidExitFullScreenNotification object:nil queue:nil + usingBlock:^(NSNotification *notification) { + objc_setAssociatedObject(notification.object, @selector(qt_fullScreen), + nil, OBJC_ASSOCIATION_RETAIN); + } + ]; +} + +- (BOOL)qt_fullScreen +{ + NSNumber *number = objc_getAssociatedObject(self, @selector(qt_fullScreen)); + return [number boolValue]; +} +@end + +#define super USE_qt_objcDynamicSuper_INSTEAD + +@implementation QNSWindow + ++ (void)load +{ + const Class windowClass = [self class]; + const Class panelClass = [QNSPanel class]; + + unsigned int methodDescriptionsCount; + objc_method_description *methods = protocol_copyMethodDescriptionList( + objc_getProtocol("QNSWindowProtocol"), NO, YES, &methodDescriptionsCount); + + for (unsigned int i = 0; i < methodDescriptionsCount; ++i) { + objc_method_description method = methods[i]; + class_addMethod(panelClass, method.name, + class_getMethodImplementation(windowClass, method.name), + method.types); + } + + free(methods); +} + +- (QCocoaWindow *)platformWindow +{ + return qnsview_cast(self.contentView).platformWindow; +} + +- (NSString *)description +{ + NSMutableString *description = [NSMutableString stringWithString:qt_objcDynamicSuper()]; + +#ifndef QT_NO_DEBUG_STREAM + QString contentViewDescription; + QDebug debug(&contentViewDescription); + debug.nospace() << "; contentView=" << qnsview_cast(self.contentView) << ">"; + + NSRange lastCharacter = [description rangeOfComposedCharacterSequenceAtIndex:description.length - 1]; + [description replaceCharactersInRange:lastCharacter withString:contentViewDescription.toNSString()]; +#endif + + return description; +} + +- (BOOL)canBecomeKeyWindow +{ + QCocoaWindow *pw = self.platformWindow; + if (!pw) + return NO; + + if (pw->shouldRefuseKeyWindowAndFirstResponder()) + return NO; + + if ([self isKindOfClass:[QNSPanel class]]) { + // Only tool or dialog windows should become key: + Qt::WindowType type = pw->window()->type(); + if (type == Qt::Tool || type == Qt::Dialog) + return YES; + + return NO; + } else { + // The default implementation returns NO for title-bar less windows, + // override and return yes here to make sure popup windows such as + // the combobox popup can become the key window. + return YES; + } +} + +- (BOOL)canBecomeMainWindow +{ + BOOL canBecomeMain = YES; // By default, windows can become the main window + + // Windows with a transient parent (such as combobox popup windows) + // cannot become the main window: + QCocoaWindow *pw = self.platformWindow; + if (!pw || pw->window()->transientParent()) + canBecomeMain = NO; + + return canBecomeMain; +} + +- (BOOL)isOpaque +{ + return self.platformWindow ? + self.platformWindow->isOpaque() : qt_objcDynamicSuper(); +} + +/*! + Borderless windows need a transparent background + + Technically windows with NSTexturedBackgroundWindowMask (such + as windows with unified toolbars) need to draw the textured + background of the NSWindow, and can't have a transparent + background, but as NSBorderlessWindowMask is 0, you can't + have a window with NSTexturedBackgroundWindowMask that is + also borderless. +*/ +- (NSColor *)backgroundColor +{ + return self.styleMask == NSBorderlessWindowMask + ? [NSColor clearColor] : qt_objcDynamicSuper(); +} + +- (void)sendEvent:(NSEvent*)theEvent +{ + qCDebug(lcCocoaEvents) << "Sending" << theEvent << "to" << self; + + // We might get events for a NSWindow after the corresponding platform + // window has been deleted, as the NSWindow can outlive the QCocoaWindow + // e.g. if being retained by other parts of AppKit, or in an auto-release + // pool. We guard against this in QNSView as well, as not all callbacks + // come via events, but if they do there's no point in propagating them. + if (!self.platformWindow) + return; + + // Prevent deallocation of this NSWindow during event delivery, as we + // have logic further below that depends on the window being alive. + [[self retain] autorelease]; + + const char *eventType = object_getClassName(theEvent); + if (QWindowSystemInterface::handleNativeEvent(self.platformWindow->window(), + QByteArray::fromRawData(eventType, qstrlen(eventType)), theEvent, nullptr)) { + return; + } + + qt_objcDynamicSuper(theEvent); + + if (!self.platformWindow) + return; // Platform window went away while processing event + + QCocoaWindow *pw = self.platformWindow; + if (pw->frameStrutEventsEnabled() && isMouseEvent(theEvent)) { + NSPoint loc = [theEvent locationInWindow]; + NSRect windowFrame = [self convertRectFromScreen:self.frame]; + NSRect contentFrame = self.contentView.frame; + if (NSMouseInRect(loc, windowFrame, NO) && !NSMouseInRect(loc, contentFrame, NO)) + [qnsview_cast(pw->view()) handleFrameStrutMouseEvent:theEvent]; + } +} + +- (void)closeAndRelease +{ + qCDebug(lcQpaCocoaWindow) << "closeAndRelease" << self; + + [self.delegate release]; + self.delegate = nil; + + [self close]; + [self release]; +} + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wobjc-missing-super-calls" +- (void)dealloc +{ + qCDebug(lcQpaCocoaWindow) << "dealloc" << self; + qt_objcDynamicSuper(); +} +#pragma clang diagnostic pop + ++ (void)applicationActivationChanged:(NSNotification*)notification +{ + const id sender = self; + NSEnumerator<NSWindow*> *windowEnumerator = nullptr; + NSApplication *application = [NSApplication sharedApplication]; + +#if QT_MACOS_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_12) + if (__builtin_available(macOS 10.12, *)) { + // Unfortunately there's no NSWindowListOrderedBackToFront, + // so we have to manually reverse the order using an array. + NSMutableArray *windows = [[[NSMutableArray alloc] init] autorelease]; + [application enumerateWindowsWithOptions:NSWindowListOrderedFrontToBack + usingBlock:^(NSWindow *window, BOOL *) { + // For some reason AppKit will give us nil-windows, skip those + if (!window) + return; + + [(NSMutableArray*)windows addObject:window]; + } + ]; + + windowEnumerator = windows.reverseObjectEnumerator; + } else +#endif + { + // No way to get ordered list of windows, so fall back to unordered, + // list, which typically corresponds to window creation order. + windowEnumerator = application.windows.objectEnumerator; + } + + for (NSWindow *window in windowEnumerator) { + // We're meddling with normal and floating windows, so leave others alone + if (!(window.level == NSNormalWindowLevel || window.level == NSFloatingWindowLevel)) + continue; + + // Windows that hide automatically will keep their NSFloatingWindowLevel, + // and hence be on top of the window stack. We don't want to affect these + // windows, as otherwise we might end up with key windows being ordered + // behind these auto-hidden windows when activating the application by + // clicking on a new tool window. + if (window.hidesOnDeactivate) + continue; + + if ([window conformsToProtocol:@protocol(QNSWindowProtocol)]) { + QCocoaWindow *cocoaWindow = static_cast<QCocoaNSWindow *>(window).platformWindow; + window.level = notification.name == NSApplicationWillResignActiveNotification ? + NSNormalWindowLevel : cocoaWindow->windowLevel(cocoaWindow->window()->flags()); + } + + // The documentation says that "when a window enters a new level, it’s ordered + // in front of all its peers in that level", but that doesn't seem to be the + // case in practice. To keep the order correct after meddling with the window + // levels, we explicitly order each window to the front. Since we are iterating + // the windows in back-to-front order, this is okey. The call also triggers AppKit + // to re-evaluate the level in relation to windows from other applications, + // working around an issue where our tool windows would stay on top of other + // application windows if activation was transferred to another application by + // clicking on it instead of via the application switcher or Dock. Finally, we + // do this re-ordering for all windows (except auto-hiding ones), otherwise we would + // end up triggering a bug in AppKit where the tool windows would disappear behind + // the application window. + [window orderFront:sender]; + } +} + +@end + +@implementation QNSPanel +// Implementation shared with QNSWindow, see +[QNSWindow load] above +@end + +#undef super diff --git a/src/plugins/platforms/cocoa/qnswindowdelegate.mm b/src/plugins/platforms/cocoa/qnswindowdelegate.mm index 1224d138d9..cdecd86dfb 100644 --- a/src/plugins/platforms/cocoa/qnswindowdelegate.mm +++ b/src/plugins/platforms/cocoa/qnswindowdelegate.mm @@ -44,6 +44,8 @@ #include <qpa/qplatformscreen.h> #include <qpa/qwindowsysteminterface.h> +static QRegExp whitespaceRegex = QRegExp(QStringLiteral("\\s*")); + @implementation QNSWindowDelegate - (id)initWithQCocoaWindow:(QCocoaWindow *)cocoaWindow @@ -98,7 +100,10 @@ { Q_UNUSED(window); Q_UNUSED(menu); - return m_cocoaWindow && m_cocoaWindow->m_hasWindowFilePath; + + // Only pop up document path if the filename is non-empty. We allow whitespace, to + // allow faking a window icon by setting the file path to a single space character. + return !whitespaceRegex.exactMatch(m_cocoaWindow->window()->filePath()); } - (BOOL)window:(NSWindow *)window shouldDragDocumentWithEvent:(NSEvent *)event from:(NSPoint)dragImageLocation withPasteboard:(NSPasteboard *)pasteboard @@ -107,6 +112,9 @@ Q_UNUSED(event); Q_UNUSED(dragImageLocation); Q_UNUSED(pasteboard); - return m_cocoaWindow && m_cocoaWindow->m_hasWindowFilePath; + + // Only allow drag if the filename is non-empty. We allow whitespace, to + // allow faking a window icon by setting the file path to a single space. + return !whitespaceRegex.exactMatch(m_cocoaWindow->window()->filePath()); } @end |