summaryrefslogtreecommitdiffstats
path: root/src/plugins/platforms/cocoa
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/platforms/cocoa')
-rw-r--r--src/plugins/platforms/cocoa/CMakeLists.txt12
-rw-r--r--src/plugins/platforms/cocoa/cocoa.pro4
-rw-r--r--src/plugins/platforms/cocoa/qcocoaaccessibilityelement.mm60
-rw-r--r--src/plugins/platforms/cocoa/qcocoaapplicationdelegate.mm56
-rw-r--r--src/plugins/platforms/cocoa/qcocoabackingstore.h52
-rw-r--r--src/plugins/platforms/cocoa/qcocoabackingstore.mm543
-rw-r--r--src/plugins/platforms/cocoa/qcocoaeventdispatcher.h2
-rw-r--r--src/plugins/platforms/cocoa/qcocoaeventdispatcher.mm40
-rw-r--r--src/plugins/platforms/cocoa/qcocoafiledialoghelper.mm1
-rw-r--r--src/plugins/platforms/cocoa/qcocoaglcontext.mm14
-rw-r--r--src/plugins/platforms/cocoa/qcocoahelpers.h31
-rw-r--r--src/plugins/platforms/cocoa/qcocoahelpers.mm91
-rw-r--r--src/plugins/platforms/cocoa/qcocoaintegration.h1
-rw-r--r--src/plugins/platforms/cocoa/qcocoaintegration.mm61
-rw-r--r--src/plugins/platforms/cocoa/qcocoamenu.mm3
-rw-r--r--src/plugins/platforms/cocoa/qcocoamenuitem.h1
-rw-r--r--src/plugins/platforms/cocoa/qcocoamenuitem.mm18
-rw-r--r--src/plugins/platforms/cocoa/qcocoaprintdevice.h2
-rw-r--r--src/plugins/platforms/cocoa/qcocoaprintdevice.mm4
-rw-r--r--src/plugins/platforms/cocoa/qcocoascreen.mm106
-rw-r--r--src/plugins/platforms/cocoa/qcocoasystemsettings.mm10
-rw-r--r--src/plugins/platforms/cocoa/qcocoasystemtrayicon.mm23
-rw-r--r--src/plugins/platforms/cocoa/qcocoatheme.mm27
-rw-r--r--src/plugins/platforms/cocoa/qcocoawindow.h2
-rw-r--r--src/plugins/platforms/cocoa/qcocoawindow.mm198
-rw-r--r--src/plugins/platforms/cocoa/qiosurfacegraphicsbuffer.h77
-rw-r--r--src/plugins/platforms/cocoa/qiosurfacegraphicsbuffer.mm188
-rw-r--r--src/plugins/platforms/cocoa/qmacclipboard.mm28
-rw-r--r--src/plugins/platforms/cocoa/qnsview.mm107
-rw-r--r--src/plugins/platforms/cocoa/qnsview_complextext.mm4
-rw-r--r--src/plugins/platforms/cocoa/qnsview_dragging.mm111
-rw-r--r--src/plugins/platforms/cocoa/qnsview_drawing.mm70
-rw-r--r--src/plugins/platforms/cocoa/qnsview_keys.mm2
-rw-r--r--src/plugins/platforms/cocoa/qnsview_mouse.mm108
-rw-r--r--src/plugins/platforms/cocoa/qnswindow.h10
-rw-r--r--src/plugins/platforms/cocoa/qnswindow.mm226
-rw-r--r--src/plugins/platforms/cocoa/qnswindowdelegate.h3
-rw-r--r--src/plugins/platforms/cocoa/qnswindowdelegate.mm70
-rw-r--r--src/plugins/platforms/cocoa/qpaintengine_mac.mm3
39 files changed, 1627 insertions, 742 deletions
diff --git a/src/plugins/platforms/cocoa/CMakeLists.txt b/src/plugins/platforms/cocoa/CMakeLists.txt
index 2f1ae9a565..0b08d49390 100644
--- a/src/plugins/platforms/cocoa/CMakeLists.txt
+++ b/src/plugins/platforms/cocoa/CMakeLists.txt
@@ -1,3 +1,5 @@
+# Generated from cocoa.pro.
+
# special case:
find_package(Cups)
find_package(WrapOpenGL)
@@ -38,6 +40,7 @@ add_qt_plugin(qcocoa
qcocoasystemtrayicon.h qcocoasystemtrayicon.mm
qcocoatheme.h qcocoatheme.mm
qcocoawindow.h qcocoawindow.mm
+ qiosurfacegraphicsbuffer.h qiosurfacegraphicsbuffer.mm
qmacclipboard.h qmacclipboard.mm
qmultitouch_mac.mm qmultitouch_mac_p.h
qnsview.h qnsview.mm
@@ -57,6 +60,7 @@ add_qt_plugin(qcocoa
${FWCoreServices}
${FWCoreVideo}
${FWIOKit}
+ ${FWIOSurface}
${FWMetal}
${FWQuartzCore}
Cups::Cups
@@ -76,7 +80,7 @@ add_qt_resource(qcocoa "qcocoaresources" PREFIX "/qt-project.org/mac/cursors" FI
images/waitcursor.png)
-#### Keys ignored in scope 1:.:.:./cocoa.pro:<TRUE>:
+#### Keys ignored in scope 1:.:.:cocoa.pro:<TRUE>:
# CONFIG = "no_app_extension_api_only"
# OTHER_FILES = "cocoa.json"
# PLUGIN_CLASS_NAME = "QCocoaIntegrationPlugin"
@@ -89,7 +93,7 @@ extend_target(qcocoa CONDITION QT_FEATURE_opengl # special case
SOURCES
qcocoaglcontext.h qcocoaglcontext.mm
LIBRARIES
- WrapOpenGL
+ WrapOpenGL # special case
)
extend_target(qcocoa CONDITION QT_FEATURE_vulkan
@@ -115,7 +119,7 @@ extend_target(qcocoa CONDITION TARGET Qt::Widgets
Qt::Widgets
)
-#### Keys ignored in scope 5:.:.:./cocoa.pro:TARGET Qt::Widgets:
+#### Keys ignored in scope 5:.:.:cocoa.pro:TARGET Qt::Widgets:
# QT_FOR_CONFIG = "widgets"
extend_target(qcocoa CONDITION (TARGET Qt::Widgets) AND (QT_FEATURE_colordialog)
@@ -133,5 +137,5 @@ extend_target(qcocoa CONDITION (TARGET Qt::Widgets) AND (QT_FEATURE_fontdialog)
qcocoafontdialoghelper.h qcocoafontdialoghelper.mm
)
-#### Keys ignored in scope 9:.:.:./cocoa.pro:NOT TARGET___equals____ss_QT_DEFAULT_QPA_PLUGIN:
+#### Keys ignored in scope 9:.:.:cocoa.pro:NOT TARGET___equals____ss_QT_DEFAULT_QPA_PLUGIN:
# PLUGIN_EXTENDS = "-"
diff --git a/src/plugins/platforms/cocoa/cocoa.pro b/src/plugins/platforms/cocoa/cocoa.pro
index 8d65cf328f..083b7c1655 100644
--- a/src/plugins/platforms/cocoa/cocoa.pro
+++ b/src/plugins/platforms/cocoa/cocoa.pro
@@ -33,6 +33,7 @@ SOURCES += main.mm \
qcocoaintrospection.mm \
qcocoakeymapper.mm \
qcocoamimetypes.mm \
+ qiosurfacegraphicsbuffer.mm \
messages.cpp
HEADERS += qcocoaintegration.h \
@@ -67,6 +68,7 @@ HEADERS += qcocoaintegration.h \
qcocoaintrospection.h \
qcocoakeymapper.h \
messages.h \
+ qiosurfacegraphicsbuffer.h \
qcocoamimetypes.h
qtConfig(opengl.*) {
@@ -81,7 +83,7 @@ qtConfig(vulkan) {
RESOURCES += qcocoaresources.qrc
-LIBS += -framework AppKit -framework CoreServices -framework Carbon -framework IOKit -framework QuartzCore -framework CoreVideo -framework Metal -lcups
+LIBS += -framework AppKit -framework CoreServices -framework Carbon -framework IOKit -framework QuartzCore -framework CoreVideo -framework Metal -framework IOSurface -lcups
QT += \
core-private gui-private \
diff --git a/src/plugins/platforms/cocoa/qcocoaaccessibilityelement.mm b/src/plugins/platforms/cocoa/qcocoaaccessibilityelement.mm
index 03dc895ffb..f0ef70e3a3 100644
--- a/src/plugins/platforms/cocoa/qcocoaaccessibilityelement.mm
+++ b/src/plugins/platforms/cocoa/qcocoaaccessibilityelement.mm
@@ -51,6 +51,19 @@ QT_USE_NAMESPACE
#ifndef QT_NO_ACCESSIBILITY
+/**
+ * Converts between absolute character offsets and line numbers of a
+ * QAccessibleTextInterface. Works in exactly one of two modes:
+ *
+ * - Pass *line == -1 in order to get a line containing character at the given
+ * *offset
+ * - Pass *offset == -1 in order to get the offset of first character of the
+ * given *line
+ *
+ * You can optionally also pass non-NULL `start` and `end`, which will in both
+ * modes be filled with the offset of the first and last characters of the
+ * relevant line.
+ */
static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *offset, NSUInteger *start = 0, NSUInteger *end = 0)
{
Q_ASSERT(*line == -1 || *offset == -1);
@@ -100,7 +113,6 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of
}
@implementation QMacAccessibilityElement {
- NSString *role;
QAccessible::Id axid;
}
@@ -110,9 +122,6 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of
self = [super init];
if (self) {
axid = anId;
- QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
- Q_ASSERT(iface);
- role = QCocoaAccessible::macRole(iface);
}
return self;
@@ -228,7 +237,7 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of
return [attributes autorelease];
}
-- (id)parentElement {
+- (id)accessibilityParent {
QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
if (!iface || !iface->isValid())
return nil;
@@ -241,7 +250,7 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of
if (QAccessibleInterface *parent = iface->parent()) {
if (parent->role() != QAccessible::Application) {
QAccessible::Id parentId = QAccessible::uniqueId(parent);
- return [QMacAccessibilityElement elementWithId: parentId];
+ return NSAccessibilityUnignoredAncestor([QMacAccessibilityElement elementWithId: parentId]);
}
}
@@ -249,12 +258,18 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of
QPlatformWindow *platformWindow = window->handle();
if (platformWindow) {
QCocoaWindow *win = static_cast<QCocoaWindow*>(platformWindow);
- return qnsview_cast(win->view());
+ return NSAccessibilityUnignoredAncestor(qnsview_cast(win->view()));
}
}
return nil;
}
+- (NSRect)accessibilityFrame {
+ QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
+ if (!iface || !iface->isValid())
+ return NSZeroRect;
+ return QCocoaScreen::mapToNative(iface->rect());
+}
- (id) minValueAttribute:(QAccessibleInterface*)iface {
if (QAccessibleValueInterface *val = iface->valueInterface())
@@ -276,11 +291,12 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of
}
if ([attribute isEqualToString:NSAccessibilityRoleAttribute]) {
- return role;
+ return QCocoaAccessible::macRole(iface);
} else if ([attribute isEqualToString:NSAccessibilitySubroleAttribute]) {
return QCocoaAccessible::macSubrole(iface);
} else if ([attribute isEqualToString:NSAccessibilityRoleDescriptionAttribute]) {
- return NSAccessibilityRoleDescription(role, [self accessibilityAttributeValue:NSAccessibilitySubroleAttribute]);
+ return NSAccessibilityRoleDescription(QCocoaAccessible::macRole(iface),
+ [self accessibilityAttributeValue:NSAccessibilitySubroleAttribute]);
} else if ([attribute isEqualToString:NSAccessibilityChildrenAttribute]) {
return QCocoaAccessible::unignoredChildren(iface);
} else if ([attribute isEqualToString:NSAccessibilityFocusedAttribute]) {
@@ -288,13 +304,13 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of
id focusedElement = [NSApp accessibilityAttributeValue:NSAccessibilityFocusedUIElementAttribute];
return @([focusedElement isEqual:self]);
} else if ([attribute isEqualToString:NSAccessibilityParentAttribute]) {
- return NSAccessibilityUnignoredAncestor([self parentElement]);
+ return self.accessibilityParent;
} else if ([attribute isEqualToString:NSAccessibilityWindowAttribute]) {
// We're in the same window as our parent.
- return [[self parentElement] accessibilityAttributeValue:NSAccessibilityWindowAttribute];
+ return [[self accessibilityParent] accessibilityAttributeValue:NSAccessibilityWindowAttribute];
} else if ([attribute isEqualToString:NSAccessibilityTopLevelUIElementAttribute]) {
// We're in the same top level element as our parent.
- return [[self parentElement] accessibilityAttributeValue:NSAccessibilityTopLevelUIElementAttribute];
+ return [[self accessibilityParent] accessibilityAttributeValue:NSAccessibilityTopLevelUIElementAttribute];
} else if ([attribute isEqualToString:NSAccessibilityPositionAttribute]) {
// The position in points of the element's lower-left corner in screen-relative coordinates
QPointF qtPosition = QRectF(iface->rect()).bottomLeft();
@@ -345,16 +361,13 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of
return [NSValue valueWithRange: NSMakeRange(0, 0)];
} else if ([attribute isEqualToString:NSAccessibilityVisibleCharacterRangeAttribute]) {
// FIXME This is not correct and may impact performance for big texts
- return [NSValue valueWithRange: NSMakeRange(0, iface->textInterface()->characterCount())];
-
+ if (QAccessibleTextInterface *text = iface->textInterface())
+ return [NSValue valueWithRange: NSMakeRange(0, text->characterCount())];
+ return [NSValue valueWithRange: NSMakeRange(0, iface->text(QAccessible::Name).length())];
} else if ([attribute isEqualToString:NSAccessibilityInsertionPointLineNumberAttribute]) {
if (QAccessibleTextInterface *text = iface->textInterface()) {
- int line = 0; // true for all single line edits
- if (iface->state().multiLine) {
- int position = text->cursorPosition();
- convertLineOffset(text, &line, &position);
- }
- return @(line);
+ int position = text->cursorPosition();
+ return [self accessibilityAttributeValue:NSAccessibilityLineForIndexParameterizedAttribute forParameter:@(position)];
}
return nil;
} else if ([attribute isEqualToString:NSAccessibilityMinValueAttribute]) {
@@ -410,8 +423,11 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of
int index = [parameter intValue];
if (index < 0 || index > iface->textInterface()->characterCount())
return nil;
- int line = -1;
- convertLineOffset(iface->textInterface(), &line, &index);
+ int line = 0; // true for all single line edits
+ if (iface->state().multiLine) {
+ line = -1;
+ convertLineOffset(iface->textInterface(), &line, &index);
+ }
return @(line);
}
if ([attribute isEqualToString: NSAccessibilityRangeForLineParameterizedAttribute]) {
diff --git a/src/plugins/platforms/cocoa/qcocoaapplicationdelegate.mm b/src/plugins/platforms/cocoa/qcocoaapplicationdelegate.mm
index 221a8b0866..2cf6672da9 100644
--- a/src/plugins/platforms/cocoa/qcocoaapplicationdelegate.mm
+++ b/src/plugins/platforms/cocoa/qcocoaapplicationdelegate.mm
@@ -86,6 +86,7 @@
#include <private/qguiapplication_p.h>
#include "qt_mac_p.h"
#include <qpa/qwindowsysteminterface.h>
+#include <qwindowdefs.h>
QT_USE_NAMESPACE
@@ -114,22 +115,10 @@ QT_USE_NAMESPACE
self = [super init];
if (self) {
inLaunch = true;
- [[NSNotificationCenter defaultCenter]
- addObserver:self
- selector:@selector(updateScreens:)
- name:NSApplicationDidChangeScreenParametersNotification
- object:NSApp];
}
return self;
}
-- (void)updateScreens:(NSNotification *)notification
-{
- Q_UNUSED(notification);
- if (QCocoaIntegration *ci = QCocoaIntegration::instance())
- ci->updateScreens();
-}
-
- (void)dealloc
{
[_dockMenu release];
@@ -309,25 +298,6 @@ QT_USE_NAMESPACE
return NO; // Someday qApp->quitOnLastWindowClosed(); when QApp and NSApp work closer together.
}
-- (void)applicationWillHide:(NSNotification *)notification
-{
- if (reflectionDelegate
- && [reflectionDelegate respondsToSelector:@selector(applicationWillHide:)]) {
- [reflectionDelegate applicationWillHide:notification];
- }
-
- // When the application is hidden Qt will hide the popup windows associated with
- // it when it has lost the activation for the application. However, when it gets
- // to this point it believes the popup windows to be hidden already due to the
- // fact that the application itself is hidden, which will cause a problem when
- // the application is made visible again.
- const QWindowList topLevelWindows = QGuiApplication::topLevelWindows();
- for (QWindow *topLevelWindow : qAsConst(topLevelWindows)) {
- if ((topLevelWindow->type() & Qt::Popup) == Qt::Popup && topLevelWindow->isVisible())
- topLevelWindow->hide();
- }
-}
-
- (void)applicationDidBecomeActive:(NSNotification *)notification
{
if (reflectionDelegate
@@ -335,21 +305,6 @@ QT_USE_NAMESPACE
[reflectionDelegate applicationDidBecomeActive:notification];
QWindowSystemInterface::handleApplicationStateChanged(Qt::ApplicationActive);
-/*
- onApplicationChangedActivation(true);
-
- if (!QWidget::mouseGrabber()){
- // Update enter/leave immidiatly, don't wait for a move event. But only
- // if no grab exists (even if the grab points to this widget, it seems, ref X11)
- QPoint qlocal, qglobal;
- QWidget *widgetUnderMouse = 0;
- qt_mac_getTargetForMouseEvent(0, QEvent::Enter, qlocal, qglobal, 0, &widgetUnderMouse);
- QApplicationPrivate::dispatchEnterLeave(widgetUnderMouse, 0);
- qt_last_mouse_receiver = widgetUnderMouse;
- qt_last_native_mouse_receiver = widgetUnderMouse ?
- (widgetUnderMouse->internalWinId() ? widgetUnderMouse : widgetUnderMouse->nativeParentWidget()) : 0;
- }
-*/
}
- (void)applicationDidResignActive:(NSNotification *)notification
@@ -359,15 +314,6 @@ QT_USE_NAMESPACE
[reflectionDelegate applicationDidResignActive:notification];
QWindowSystemInterface::handleApplicationStateChanged(Qt::ApplicationInactive);
-/*
- onApplicationChangedActivation(false);
-
- if (!QWidget::mouseGrabber())
- QApplicationPrivate::dispatchEnterLeave(0, qt_last_mouse_receiver);
- qt_last_mouse_receiver = 0;
- qt_last_native_mouse_receiver = 0;
- qt_button_down = 0;
-*/
}
- (BOOL)applicationShouldHandleReopen:(NSApplication *)theApplication hasVisibleWindows:(BOOL)flag
diff --git a/src/plugins/platforms/cocoa/qcocoabackingstore.h b/src/plugins/platforms/cocoa/qcocoabackingstore.h
index b4cd506513..508f24d578 100644
--- a/src/plugins/platforms/cocoa/qcocoabackingstore.h
+++ b/src/plugins/platforms/cocoa/qcocoabackingstore.h
@@ -44,13 +44,16 @@
#include <private/qcore_mac_p.h>
+#include <QScopedPointer>
+#include "qiosurfacegraphicsbuffer.h"
+
QT_BEGIN_NAMESPACE
-class QCocoaBackingStore : public QRasterBackingStore
+class QNSWindowBackingStore : public QRasterBackingStore
{
public:
- QCocoaBackingStore(QWindow *window);
- ~QCocoaBackingStore();
+ QNSWindowBackingStore(QWindow *window);
+ ~QNSWindowBackingStore();
void flush(QWindow *, const QRegion &, const QPoint &) override;
@@ -60,6 +63,49 @@ private:
void redrawRoundedBottomCorners(CGRect) const;
};
+class QCALayerBackingStore : public QPlatformBackingStore
+{
+public:
+ QCALayerBackingStore(QWindow *window);
+ ~QCALayerBackingStore();
+
+ void resize(const QSize &size, const QRegion &staticContents) override;
+
+ void beginPaint(const QRegion &region) override;
+ QPaintDevice *paintDevice() override;
+ void endPaint() override;
+
+ void flush(QWindow *, const QRegion &, const QPoint &) override;
+ void composeAndFlush(QWindow *window, const QRegion &region, const QPoint &offset,
+ QPlatformTextureList *textures, bool translucentBackground) override;
+
+ QPlatformGraphicsBuffer *graphicsBuffer() const override;
+
+private:
+ QSize m_requestedSize;
+ QRegion m_paintedRegion;
+
+ class GraphicsBuffer : public QIOSurfaceGraphicsBuffer
+ {
+ public:
+ GraphicsBuffer(const QSize &size, qreal devicePixelRatio,
+ const QPixelFormat &format, QCFType<CGColorSpaceRef> colorSpace);
+
+ QRegion dirtyRegion; // In unscaled coordinates
+ QImage *asImage();
+
+ private:
+ qreal m_devicePixelRatio;
+ QImage m_image;
+ };
+
+ void ensureBackBuffer();
+ bool recreateBackBufferIfNeeded();
+ bool prepareForFlush();
+
+ std::list<std::unique_ptr<GraphicsBuffer>> m_buffers;
+};
+
QT_END_NAMESPACE
#endif
diff --git a/src/plugins/platforms/cocoa/qcocoabackingstore.mm b/src/plugins/platforms/cocoa/qcocoabackingstore.mm
index 81a0a7d040..8e4e928bc5 100644
--- a/src/plugins/platforms/cocoa/qcocoabackingstore.mm
+++ b/src/plugins/platforms/cocoa/qcocoabackingstore.mm
@@ -42,24 +42,28 @@
#include "qcocoawindow.h"
#include "qcocoahelpers.h"
+#include <QtCore/qmath.h>
+
+#include <QuartzCore/CATransaction.h>
+
QT_BEGIN_NAMESPACE
-QCocoaBackingStore::QCocoaBackingStore(QWindow *window)
+QNSWindowBackingStore::QNSWindowBackingStore(QWindow *window)
: QRasterBackingStore(window)
{
}
-QCocoaBackingStore::~QCocoaBackingStore()
+QNSWindowBackingStore::~QNSWindowBackingStore()
{
}
-bool QCocoaBackingStore::windowHasUnifiedToolbar() const
+bool QNSWindowBackingStore::windowHasUnifiedToolbar() const
{
Q_ASSERT(window()->handle());
return static_cast<QCocoaWindow *>(window()->handle())->m_drawContentBorderGradient;
}
-QImage::Format QCocoaBackingStore::format() const
+QImage::Format QNSWindowBackingStore::format() const
{
if (windowHasUnifiedToolbar())
return QImage::Format_ARGB32_Premultiplied;
@@ -78,7 +82,7 @@ QImage::Format QCocoaBackingStore::format() const
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 &region, const QPoint &offset)
+void QNSWindowBackingStore::flush(QWindow *window, const QRegion &region, const QPoint &offset)
{
if (m_image.isNull())
return;
@@ -103,131 +107,113 @@ void QCocoaBackingStore::flush(QWindow *window, const QRegion &region, const QPo
qCDebug(lcQpaBackingStore) << "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) {
- const CGSize topLevelSize = topLevelView.bounds.size;
- view.layer.contentsRect = CGRectApplyAffineTransform(
- [view convertRect:view.bounds toView:topLevelView],
- // The contentsRect is in unit coordinate system
- CGAffineTransformMakeScale(1.0 / topLevelSize.width, 1.0 / topLevelSize.height));
- }
- } else {
- // 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;
- }
+ // 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();
+ const qreal devicePixelRatio = m_image.devicePixelRatio();
- // If the flushed window is a content view, and we're filling the drawn area
- // completely, or it doesn't have a window background we need to preserve,
- // we can get away with copying instead of blending the backing store.
- QCocoaWindow *cocoaWindow = static_cast<QCocoaWindow *>(window->handle());
- const NSCompositingOperation compositingOperation = cocoaWindow->isContentView()
- && (cocoaWindow->isOpaque() || view.window.backgroundColor == NSColor.clearColor)
- ? NSCompositingOperationCopy : NSCompositingOperationSourceOver;
+ // If the flushed window is a content view, and we're filling the drawn area
+ // completely, or it doesn't have a window background we need to preserve,
+ // we can get away with copying instead of blending the backing store.
+ QCocoaWindow *cocoaWindow = static_cast<QCocoaWindow *>(window->handle());
+ const NSCompositingOperation compositingOperation = cocoaWindow->isContentView()
+ && (cocoaWindow->isOpaque() || view.window.backgroundColor == NSColor.clearColor)
+ ? NSCompositingOperationCopy : NSCompositingOperationSourceOver;
#ifdef QT_DEBUG
- static bool debugBackingStoreFlush = [[NSUserDefaults standardUserDefaults]
- boolForKey:@"QtCocoaDebugBackingStoreFlush"];
+ 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");
+ // 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];
+ // 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);
- 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))));
- }
+ // 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()));
+ 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();
+ CGRect viewRect = viewLocalRect.toCGRect();
- if (windowHasUnifiedToolbar())
- NSDrawWindowBackground(viewRect);
+ if (windowHasUnifiedToolbar())
+ NSDrawWindowBackground(viewRect);
- [backingStoreImage drawInRect:viewRect fromRect:backingStoreRect.toCGRect()
- operation:compositingOperation fraction:1.0 respectFlipped:YES hints:nil];
+ [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()];
- }
+ 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
}
+#endif
+ }
- // -------------------------------------------------------------------------
+ // -------------------------------------------------------------------------
- if (shouldHandleViewLockManually)
- [view unlockFocus];
+ if (shouldHandleViewLockManually)
+ [view unlockFocus];
- if (drawingOutsideOfDisplayCycle) {
- redrawRoundedBottomCorners([view convertRect:region.boundingRect().toCGRect() toView:nil]);
- [view.window flushWindow];
- }
+ if (drawingOutsideOfDisplayCycle) {
+ redrawRoundedBottomCorners([view convertRect:region.boundingRect().toCGRect() toView:nil]);
+ [view.window flushWindow];
}
- // Done flushing to either CALayer or NSWindow backingstore
+
+ // Done flushing to NSWindow backingstore
QCocoaWindow *topLevelCocoaWindow = static_cast<QCocoaWindow *>(topLevelWindow->handle());
if (Q_UNLIKELY(topLevelCocoaWindow->m_needsInvalidateShadow)) {
@@ -251,7 +237,7 @@ void QCocoaBackingStore::flush(QWindow *window, const QRegion &region, const QPo
https://trac.webkit.org/changeset/85376/webkit
*/
-void QCocoaBackingStore::redrawRoundedBottomCorners(CGRect windowRect) const
+void QNSWindowBackingStore::redrawRoundedBottomCorners(CGRect windowRect) const
{
#if !defined(QT_APPLE_NO_PRIVATE_APIS)
Q_ASSERT(this->window()->handle());
@@ -285,4 +271,345 @@ void QCocoaBackingStore::redrawRoundedBottomCorners(CGRect windowRect) const
#endif
}
+// ----------------------------------------------------------------------------
+
+// https://stackoverflow.com/a/52722575/2761869
+template<class R>
+struct backwards_t {
+ R r;
+ constexpr auto begin() const { using std::rbegin; return rbegin(r); }
+ constexpr auto begin() { using std::rbegin; return rbegin(r); }
+ constexpr auto end() const { using std::rend; return rend(r); }
+ constexpr auto end() { using std::rend; return rend(r); }
+};
+template<class R>
+constexpr backwards_t<R> backwards(R&& r) { return {std::forward<R>(r)}; }
+
+QCALayerBackingStore::QCALayerBackingStore(QWindow *window)
+ : QPlatformBackingStore(window)
+{
+ qCDebug(lcQpaBackingStore) << "Creating QCALayerBackingStore for" << window;
+ m_buffers.resize(1);
+}
+
+QCALayerBackingStore::~QCALayerBackingStore()
+{
+}
+
+void QCALayerBackingStore::resize(const QSize &size, const QRegion &staticContents)
+{
+ qCDebug(lcQpaBackingStore) << "Resize requested to" << size;
+
+ if (!staticContents.isNull())
+ qCWarning(lcQpaBackingStore) << "QCALayerBackingStore does not support static contents";
+
+ m_requestedSize = size;
+}
+
+void QCALayerBackingStore::beginPaint(const QRegion &region)
+{
+ Q_UNUSED(region);
+
+ QMacAutoReleasePool pool;
+
+ qCInfo(lcQpaBackingStore) << "Beginning paint of" << region << "into backingstore of" << m_requestedSize;
+
+ ensureBackBuffer(); // Find an unused back buffer, or reserve space for a new one
+
+ const bool bufferWasRecreated = recreateBackBufferIfNeeded();
+
+ m_buffers.back()->lock(QPlatformGraphicsBuffer::SWWriteAccess);
+
+ // Although undocumented, QBackingStore::beginPaint expects the painted region
+ // to be cleared before use if the window has a surface format with an alpha.
+ // Fresh IOSurfaces are already cleared, so we don't need to clear those.
+ if (!bufferWasRecreated && window()->format().hasAlpha()) {
+ qCDebug(lcQpaBackingStore) << "Clearing" << region << "before use";
+ QPainter painter(m_buffers.back()->asImage());
+ painter.setCompositionMode(QPainter::CompositionMode_Source);
+ for (const QRect &rect : region)
+ painter.fillRect(rect, Qt::transparent);
+ }
+
+ m_paintedRegion += region;
+}
+
+void QCALayerBackingStore::ensureBackBuffer()
+{
+ if (window()->format().swapBehavior() == QSurfaceFormat::SingleBuffer)
+ return;
+
+ // The current back buffer may have been assigned to a layer in a previous flush,
+ // but we deferred the swap. Do it now if the surface has been picked up by CA.
+ if (m_buffers.back() && m_buffers.back()->isInUse() && m_buffers.back() != m_buffers.front()) {
+ qCInfo(lcQpaBackingStore) << "Back buffer has been picked up by CA, swapping to front";
+ std::swap(m_buffers.back(), m_buffers.front());
+ }
+
+ if (Q_UNLIKELY(lcQpaBackingStore().isDebugEnabled())) {
+ // ┌───────┬───────┬───────┬─────┬──────┐
+ // │ front ┊ spare ┊ spare ┊ ... ┊ back │
+ // └───────┴───────┴───────┴─────┴──────┘
+ for (const auto &buffer : m_buffers) {
+ qCDebug(lcQpaBackingStore).nospace() << " "
+ << (buffer == m_buffers.front() ? "front" :
+ buffer == m_buffers.back() ? " back" :
+ "spare"
+ ) << ": " << buffer.get();
+ }
+ }
+
+ // Ensure our back buffer is ready to draw into. If not, find a buffer that
+ // is not in use, or reserve space for a new buffer if none can be found.
+ for (auto &buffer : backwards(m_buffers)) {
+ if (!buffer || !buffer->isInUse()) {
+ // Buffer is okey to use, swap if necessary
+ if (buffer != m_buffers.back())
+ std::swap(buffer, m_buffers.back());
+ qCDebug(lcQpaBackingStore) << "Using back buffer" << m_buffers.back().get();
+
+ static const int kMaxSwapChainDepth = 3;
+ if (m_buffers.size() > kMaxSwapChainDepth) {
+ qCDebug(lcQpaBackingStore) << "Reducing swap chain depth to" << kMaxSwapChainDepth;
+ m_buffers.erase(std::next(m_buffers.begin(), 1), std::prev(m_buffers.end(), 2));
+ }
+
+ break;
+ } else if (buffer == m_buffers.front()) {
+ // We've exhausted the available buffers, make room for a new one
+ const int swapChainDepth = m_buffers.size() + 1;
+ qCDebug(lcQpaBackingStore) << "Available buffers exhausted, increasing swap chain depth to" << swapChainDepth;
+ m_buffers.resize(swapChainDepth);
+ break;
+ }
+ }
+
+ Q_ASSERT(!m_buffers.back() || !m_buffers.back()->isInUse());
+}
+
+// Disabled until performance issue on 5K iMac Pro has been investigated further,
+// as rounding up during resize will typically result in full screen buffer sizes
+// and low frame rate also for smaller window sizes.
+#define USE_LAZY_BUFFER_ALLOCATION_DURING_LIVE_WINDOW_RESIZE 0
+
+bool QCALayerBackingStore::recreateBackBufferIfNeeded()
+{
+ const qreal devicePixelRatio = window()->devicePixelRatio();
+ QSize requestedBufferSize = m_requestedSize * devicePixelRatio;
+
+ const NSView *backingStoreView = static_cast<QCocoaWindow *>(window()->handle())->view();
+ Q_UNUSED(backingStoreView);
+
+ auto bufferSizeMismatch = [&](const QSize requested, const QSize actual) {
+#if USE_LAZY_BUFFER_ALLOCATION_DURING_LIVE_WINDOW_RESIZE
+ if (backingStoreView.inLiveResize) {
+ // Prevent over-eager buffer allocation during window resize by reusing larger buffers
+ return requested.width() > actual.width() || requested.height() > actual.height();
+ }
+#endif
+ return requested != actual;
+ };
+
+ if (!m_buffers.back() || bufferSizeMismatch(requestedBufferSize, m_buffers.back()->size())) {
+#if USE_LAZY_BUFFER_ALLOCATION_DURING_LIVE_WINDOW_RESIZE
+ if (backingStoreView.inLiveResize) {
+ // Prevent over-eager buffer allocation during window resize by rounding up
+ QSize nativeScreenSize = window()->screen()->geometry().size() * devicePixelRatio;
+ requestedBufferSize = QSize(qNextPowerOfTwo(requestedBufferSize.width()),
+ qNextPowerOfTwo(requestedBufferSize.height())).boundedTo(nativeScreenSize);
+ }
+#endif
+
+ qCInfo(lcQpaBackingStore) << "Creating surface of" << requestedBufferSize
+ << "based on requested" << m_requestedSize << "and dpr =" << devicePixelRatio;
+
+ static auto pixelFormat = QImage::toPixelFormat(QImage::Format_ARGB32_Premultiplied);
+
+ NSView *view = static_cast<QCocoaWindow *>(window()->handle())->view();
+ auto colorSpace = QCFType<CGColorSpaceRef>::constructFromGet(view.window.screen.colorSpace.CGColorSpace);
+
+ m_buffers.back().reset(new GraphicsBuffer(requestedBufferSize, devicePixelRatio, pixelFormat, colorSpace));
+ return true;
+ }
+
+ return false;
+}
+
+QPaintDevice *QCALayerBackingStore::paintDevice()
+{
+ Q_ASSERT(m_buffers.back());
+ return m_buffers.back()->asImage();
+}
+
+void QCALayerBackingStore::endPaint()
+{
+ qCInfo(lcQpaBackingStore) << "Paint ended with painted region" << m_paintedRegion;
+ m_buffers.back()->unlock();
+}
+
+void QCALayerBackingStore::flush(QWindow *flushedWindow, const QRegion &region, const QPoint &offset)
+{
+ Q_UNUSED(region);
+ Q_UNUSED(offset);
+
+ if (!prepareForFlush())
+ return;
+
+ QMacAutoReleasePool pool;
+
+ NSView *backingStoreView = static_cast<QCocoaWindow *>(window()->handle())->view();
+ NSView *flushedView = static_cast<QCocoaWindow *>(flushedWindow->handle())->view();
+
+ id backBufferSurface = (__bridge id)m_buffers.back()->surface();
+ if (flushedView.layer.contents == backBufferSurface) {
+ // We've managed to paint to the back buffer again before Core Animation had time
+ // to flush the transaction and persist the layer changes to the window server.
+ // The layer already knows about the back buffer, and we don't need to re-apply
+ // it to pick up the surface changes, so bail out early.
+ qCInfo(lcQpaBackingStore).nospace() << "Skipping flush of " << flushedView
+ << ", layer already reflects back buffer";
+ return;
+ }
+
+ // Trigger a new display cycle if there isn't one. This ensures that our layer updates
+ // are committed as part of a display-cycle instead of on the next runloop pass. This
+ // means CA won't try to throttle us if we flush too fast, and we'll coalesce our flush
+ // with other pending view and layer updates.
+ backingStoreView.window.viewsNeedDisplay = YES;
+
+ if (window()->format().swapBehavior() == QSurfaceFormat::SingleBuffer) {
+ // The private API [CALayer reloadValueForKeyPath:@"contents"] would be preferable,
+ // but barring any side effects or performance issues we opt for the hammer for now.
+ flushedView.layer.contents = nil;
+ }
+
+ qCInfo(lcQpaBackingStore) << "Flushing" << backBufferSurface
+ << "to" << flushedView.layer << "of" << flushedView;
+
+ flushedView.layer.contents = backBufferSurface;
+
+ if (flushedView != backingStoreView) {
+ const CGSize backingStoreSize = backingStoreView.bounds.size;
+ flushedView.layer.contentsRect = CGRectApplyAffineTransform(
+ [flushedView convertRect:flushedView.bounds toView:backingStoreView],
+ // The contentsRect is in unit coordinate system
+ CGAffineTransformMakeScale(1.0 / backingStoreSize.width, 1.0 / backingStoreSize.height));
+ }
+
+ // Since we may receive multiple flushes before a new frame is started, we do not
+ // swap any buffers just yet. Instead we check in the next beginPaint if the layer's
+ // surface is in use, and if so swap to an unused surface as the new back buffer.
+
+ // Note: Ideally CoreAnimation would mark a surface as in use the moment we assign
+ // it to a layer, but as that's not the case we may end up painting to the same back
+ // buffer once more if we are painting faster than CA can ship the surfaces over to
+ // the window server.
+}
+
+void QCALayerBackingStore::composeAndFlush(QWindow *window, const QRegion &region, const QPoint &offset,
+ QPlatformTextureList *textures, bool translucentBackground)
+{
+ if (!prepareForFlush())
+ return;
+
+ QPlatformBackingStore::composeAndFlush(window, region, offset, textures, translucentBackground);
+}
+
+QPlatformGraphicsBuffer *QCALayerBackingStore::graphicsBuffer() const
+{
+ return m_buffers.back().get();
+}
+
+bool QCALayerBackingStore::prepareForFlush()
+{
+ if (!m_buffers.back()) {
+ qCWarning(lcQpaBackingStore) << "Tried to flush backingstore without painting to it first";
+ return false;
+ }
+
+ // Update dirty state of buffers based on what was painted. The back buffer will be
+ // less dirty, since we painted to it, while other buffers will become more dirty.
+ // This allows us to minimize copies between front and back buffers on swap in the
+ // cases where the painted region overlaps with the previous frame (front buffer).
+ for (const auto &buffer : m_buffers) {
+ if (buffer == m_buffers.back())
+ buffer->dirtyRegion -= m_paintedRegion;
+ else
+ buffer->dirtyRegion += m_paintedRegion;
+ }
+
+ // After painting, the back buffer is only guaranteed to have content for the painted
+ // region, and may still have dirty areas that need to be synced up with the front buffer,
+ // if we have one. We know that the front buffer is always up to date.
+ if (!m_buffers.back()->dirtyRegion.isEmpty() && m_buffers.front() != m_buffers.back()) {
+ QRegion preserveRegion = m_buffers.back()->dirtyRegion;
+ qCDebug(lcQpaBackingStore) << "Preserving" << preserveRegion << "from front to back buffer";
+
+ m_buffers.front()->lock(QPlatformGraphicsBuffer::SWReadAccess);
+ const QImage *frontBuffer = m_buffers.front()->asImage();
+
+ const QRect frontSurfaceBounds(QPoint(0, 0), m_buffers.front()->size());
+ const qreal sourceDevicePixelRatio = frontBuffer->devicePixelRatio();
+
+ m_buffers.back()->lock(QPlatformGraphicsBuffer::SWWriteAccess);
+ QPainter painter(m_buffers.back()->asImage());
+ painter.setCompositionMode(QPainter::CompositionMode_Source);
+
+ // Let painter operate in device pixels, to make it easier to compare coordinates
+ const qreal targetDevicePixelRatio = painter.device()->devicePixelRatio();
+ painter.scale(1.0 / targetDevicePixelRatio, 1.0 / targetDevicePixelRatio);
+
+ for (const QRect &rect : preserveRegion) {
+ QRect sourceRect(rect.topLeft() * sourceDevicePixelRatio, rect.size() * sourceDevicePixelRatio);
+ QRect targetRect(rect.topLeft() * targetDevicePixelRatio, rect.size() * targetDevicePixelRatio);
+
+#ifdef QT_DEBUG
+ if (Q_UNLIKELY(!frontSurfaceBounds.contains(sourceRect.bottomRight()))) {
+ qCWarning(lcQpaBackingStore) << "Front buffer too small to preserve"
+ << QRegion(sourceRect).subtracted(frontSurfaceBounds);
+ }
+#endif
+ painter.drawImage(targetRect, *frontBuffer, sourceRect);
+ }
+
+ m_buffers.back()->unlock();
+ m_buffers.front()->unlock();
+
+ // The back buffer is now completely in sync, ready to be presented
+ m_buffers.back()->dirtyRegion = QRegion();
+ }
+
+ // Prepare for another round of painting
+ m_paintedRegion = QRegion();
+
+ return true;
+}
+
+// ----------------------------------------------------------------------------
+
+QCALayerBackingStore::GraphicsBuffer::GraphicsBuffer(const QSize &size, qreal devicePixelRatio,
+ const QPixelFormat &format, QCFType<CGColorSpaceRef> colorSpace)
+ : QIOSurfaceGraphicsBuffer(size, format, colorSpace)
+ , dirtyRegion(0, 0, size.width() / devicePixelRatio, size.height() / devicePixelRatio)
+ , m_devicePixelRatio(devicePixelRatio)
+{
+}
+
+QImage *QCALayerBackingStore::GraphicsBuffer::asImage()
+{
+ if (m_image.isNull()) {
+ qCDebug(lcQpaBackingStore) << "Setting up paint device for" << this;
+ CFRetain(surface());
+ m_image = QImage(data(), size().width(), size().height(),
+ bytesPerLine(), QImage::toImageFormat(format()),
+ QImageCleanupFunction(CFRelease), surface());
+ m_image.setDevicePixelRatio(m_devicePixelRatio);
+ }
+
+ Q_ASSERT_X(m_image.constBits() == data(), "QCALayerBackingStore",
+ "IOSurfaces should have have a fixed location in memory once created");
+
+ return &m_image;
+}
+
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/cocoa/qcocoaeventdispatcher.h b/src/plugins/platforms/cocoa/qcocoaeventdispatcher.h
index ebf33cf4e2..9771cd0289 100644
--- a/src/plugins/platforms/cocoa/qcocoaeventdispatcher.h
+++ b/src/plugins/platforms/cocoa/qcocoaeventdispatcher.h
@@ -168,10 +168,10 @@ public:
uint processEventsCalled;
NSModalSession currentModalSessionCached;
NSModalSession currentModalSession();
- void updateChildrenWorksWhenModal();
void temporarilyStopAllModalSessions();
void beginModalSession(QWindow *widget);
void endModalSession(QWindow *widget);
+ bool hasModalSession() const;
void cleanupModalSessions();
void cancelWaitForMoreEvents();
diff --git a/src/plugins/platforms/cocoa/qcocoaeventdispatcher.mm b/src/plugins/platforms/cocoa/qcocoaeventdispatcher.mm
index b0f2b6d940..84ffadea83 100644
--- a/src/plugins/platforms/cocoa/qcocoaeventdispatcher.mm
+++ b/src/plugins/platforms/cocoa/qcocoaeventdispatcher.mm
@@ -672,43 +672,9 @@ NSModalSession QCocoaEventDispatcherPrivate::currentModalSession()
return currentModalSessionCached;
}
-static void setChildrenWorksWhenModal(QWindow *window, bool worksWhenModal)
+bool QCocoaEventDispatcherPrivate::hasModalSession() const
{
- Q_UNUSED(window)
- Q_UNUSED(worksWhenModal)
-
- // For NSPanels (but not NSWindows, sadly), we can set the flag
- // worksWhenModal, so that they are active even when they are not modal.
-/*
- ### not ported
- QList<QDialog *> dialogs = window->findChildren<QDialog *>();
- for (int i=0; i<dialogs.size(); ++i){
- NSWindow *window = qt_mac_window_for(dialogs[i]);
- if (window && [window isKindOfClass:[NSPanel class]]) {
- [static_cast<NSPanel *>(window) setWorksWhenModal:worksWhenModal];
- if (worksWhenModal && [window isVisible]){
- [window orderFront:window];
- }
- }
- }
-*/
-}
-
-void QCocoaEventDispatcherPrivate::updateChildrenWorksWhenModal()
-{
- // Make the dialog children of the window
- // active. And make the dialog children of
- // the previous modal dialog unactive again:
- QMacAutoReleasePool pool;
- int size = cocoaModalSessionStack.size();
- if (size > 0){
- if (QWindow *prevModal = cocoaModalSessionStack[size-1].window)
- setChildrenWorksWhenModal(prevModal, true);
- if (size > 1){
- if (QWindow *prevModal = cocoaModalSessionStack[size-2].window)
- setChildrenWorksWhenModal(prevModal, false);
- }
- }
+ return !cocoaModalSessionStack.isEmpty();
}
void QCocoaEventDispatcherPrivate::cleanupModalSessions()
@@ -743,7 +709,6 @@ void QCocoaEventDispatcherPrivate::cleanupModalSessions()
cocoaModalSessionStack.remove(i);
}
- updateChildrenWorksWhenModal();
cleanupModalSessionsNeeded = false;
}
@@ -764,7 +729,6 @@ void QCocoaEventDispatcherPrivate::beginModalSession(QWindow *window)
// stopped in cleanupModalSessions()).
QCocoaModalSessionInfo info = {window, nullptr, nullptr};
cocoaModalSessionStack.push(info);
- updateChildrenWorksWhenModal();
currentModalSessionCached = nullptr;
}
diff --git a/src/plugins/platforms/cocoa/qcocoafiledialoghelper.mm b/src/plugins/platforms/cocoa/qcocoafiledialoghelper.mm
index e7243ec250..d1695ea860 100644
--- a/src/plugins/platforms/cocoa/qcocoafiledialoghelper.mm
+++ b/src/plugins/platforms/cocoa/qcocoafiledialoghelper.mm
@@ -55,7 +55,6 @@
#include <qbuffer.h>
#include <qdebug.h>
#include <qstringlist.h>
-#include <qtextcodec.h>
#include <qvarlengtharray.h>
#include <stdlib.h>
#include <qabstracteventdispatcher.h>
diff --git a/src/plugins/platforms/cocoa/qcocoaglcontext.mm b/src/plugins/platforms/cocoa/qcocoaglcontext.mm
index 1e1b3907ed..fe1fc31553 100644
--- a/src/plugins/platforms/cocoa/qcocoaglcontext.mm
+++ b/src/plugins/platforms/cocoa/qcocoaglcontext.mm
@@ -86,7 +86,7 @@ QCocoaGLContext::QCocoaGLContext(QOpenGLContext *context)
}
m_context = nativeHandle.value<QCocoaNativeContext>().context();
if (!m_context) {
- qCWarning(lcQpaOpenGLContext, "QCocoaNativeContext's NSOpenGLContext can not be null");
+ qCWarning(lcQpaOpenGLContext, "QCocoaNativeContext's NSOpenGLContext cannot be null");
return;
}
@@ -216,8 +216,13 @@ NSOpenGLPixelFormat *QCocoaGLContext::pixelFormatForSurfaceFormat(const QSurface
<< NSOpenGLPFASamples << NSOpenGLPixelFormatAttribute(format.samples());
}
- // Allow rendering on GPUs without a connected display
- attrs << NSOpenGLPFAAllowOfflineRenderers;
+ //Workaround for problems with Chromium and offline renderers on the lat 2013 MacPros.
+ //FIXME: Think if this could be solved via QSurfaceFormat in the future.
+ static bool offlineRenderersAllowed = qEnvironmentVariableIsEmpty("QT_MAC_PRO_WEBENGINE_WORKAROUND");
+ if (offlineRenderersAllowed) {
+ // Allow rendering on GPUs without a connected display
+ attrs << NSOpenGLPFAAllowOfflineRenderers;
+ }
// FIXME: Pull this information out of the NSView
QByteArray useLayer = qgetenv("QT_MAC_WANTS_LAYER");
@@ -414,7 +419,8 @@ bool QCocoaGLContext::setDrawable(QPlatformSurface *surface)
// have the same effect as an update.
// Now we are ready to associate the view with the context
- if ((m_context.view = view) != view) {
+ m_context.view = view;
+ if (m_context.view != view) {
qCInfo(lcQpaOpenGLContext) << "Failed to set" << view << "as drawable for" << m_context;
m_updateObservers.clear();
return false;
diff --git a/src/plugins/platforms/cocoa/qcocoahelpers.h b/src/plugins/platforms/cocoa/qcocoahelpers.h
index 953bf331bb..69aa7937b6 100644
--- a/src/plugins/platforms/cocoa/qcocoahelpers.h
+++ b/src/plugins/platforms/cocoa/qcocoahelpers.h
@@ -52,6 +52,7 @@
//
#include "qt_mac_p.h"
#include <private/qguiapplication_p.h>
+#include <QtCore/qoperatingsystemversion.h>
#include <QtGui/qpalette.h>
#include <QtGui/qscreen.h>
@@ -60,6 +61,8 @@
Q_FORWARD_DECLARE_OBJC_CLASS(QT_MANGLE_NAMESPACE(QNSView));
+struct mach_header;
+
QT_BEGIN_NAMESPACE
Q_DECLARE_LOGGING_CATEGORY(lcQpaWindow)
@@ -173,6 +176,34 @@ T qt_mac_resolveOption(const T &fallback, QWindow *window, const QByteArray &pro
return fallback;
}
+// -------------------------------------------------------------------------
+
+#if !defined(Q_PROCESSOR_X86_64)
+#error "32-bit builds are not supported"
+#endif
+
+class QMacVersion
+{
+public:
+ enum VersionTarget {
+ ApplicationBinary,
+ QtLibraries
+ };
+
+ static QOperatingSystemVersion buildSDK(VersionTarget target = ApplicationBinary);
+ static QOperatingSystemVersion deploymentTarget(VersionTarget target = ApplicationBinary);
+ static QOperatingSystemVersion currentRuntime();
+
+private:
+ QMacVersion() = default;
+ using VersionTuple = QPair<QOperatingSystemVersion, QOperatingSystemVersion>;
+ static VersionTuple versionsForImage(const mach_header *machHeader);
+ static VersionTuple applicationVersion();
+ static VersionTuple libraryVersion();
+};
+
+// -------------------------------------------------------------------------
+
QT_END_NAMESPACE
// @compatibility_alias doesn't work with protocols
diff --git a/src/plugins/platforms/cocoa/qcocoahelpers.mm b/src/plugins/platforms/cocoa/qcocoahelpers.mm
index 0f5ddfa49a..9c705616ba 100644
--- a/src/plugins/platforms/cocoa/qcocoahelpers.mm
+++ b/src/plugins/platforms/cocoa/qcocoahelpers.mm
@@ -55,11 +55,14 @@
#include <algorithm>
+#include <mach-o/dyld.h>
+#include <dlfcn.h>
+
QT_BEGIN_NAMESPACE
Q_LOGGING_CATEGORY(lcQpaWindow, "qt.qpa.window");
Q_LOGGING_CATEGORY(lcQpaDrawing, "qt.qpa.drawing");
-Q_LOGGING_CATEGORY(lcQpaMouse, "qt.qpa.input.mouse");
+Q_LOGGING_CATEGORY(lcQpaMouse, "qt.qpa.input.mouse", QtCriticalMsg);
Q_LOGGING_CATEGORY(lcQpaScreen, "qt.qpa.screen");
//
@@ -368,6 +371,92 @@ QString qt_mac_removeAmpersandEscapes(QString s)
return QPlatformTheme::removeMnemonics(s).trimmed();
}
+// -------------------------------------------------------------------------
+
+#if !defined(Q_PROCESSOR_X86_64)
+#error "32-bit builds are not supported"
+#endif
+
+QOperatingSystemVersion QMacVersion::buildSDK(VersionTarget target)
+{
+ switch (target) {
+ case ApplicationBinary: return applicationVersion().second;
+ case QtLibraries: return libraryVersion().second;
+ }
+ Q_UNREACHABLE();
+}
+
+QOperatingSystemVersion QMacVersion::deploymentTarget(VersionTarget target)
+{
+ switch (target) {
+ case ApplicationBinary: return applicationVersion().first;
+ case QtLibraries: return libraryVersion().first;
+ }
+ Q_UNREACHABLE();
+}
+
+QOperatingSystemVersion QMacVersion::currentRuntime()
+{
+ return QOperatingSystemVersion::current();
+}
+
+QMacVersion::VersionTuple QMacVersion::versionsForImage(const mach_header *machHeader)
+{
+ static auto makeVersionTuple = [](uint32_t dt, uint32_t sdk) {
+ return qMakePair(
+ QOperatingSystemVersion(QOperatingSystemVersion::MacOS,
+ dt >> 16 & 0xffff, dt >> 8 & 0xff, dt & 0xff),
+ QOperatingSystemVersion(QOperatingSystemVersion::MacOS,
+ sdk >> 16 & 0xffff, sdk >> 8 & 0xff, sdk & 0xff)
+ );
+ };
+
+ auto commandCursor = uintptr_t(machHeader) + sizeof(mach_header_64);
+ for (uint32_t i = 0; i < machHeader->ncmds; ++i) {
+ load_command *loadCommand = reinterpret_cast<load_command *>(commandCursor);
+ if (loadCommand->cmd == LC_VERSION_MIN_MACOSX) {
+ auto versionCommand = reinterpret_cast<version_min_command *>(loadCommand);
+ return makeVersionTuple(versionCommand->version, versionCommand->sdk);
+#if QT_MACOS_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_13)
+ } else if (loadCommand->cmd == LC_BUILD_VERSION) {
+ auto versionCommand = reinterpret_cast<build_version_command *>(loadCommand);
+ return makeVersionTuple(versionCommand->minos, versionCommand->sdk);
+#endif
+ }
+ commandCursor += loadCommand->cmdsize;
+ }
+ Q_ASSERT_X(false, "QCocoaIntegration", "Could not find any version load command");
+ Q_UNREACHABLE();
+}
+
+QMacVersion::VersionTuple QMacVersion::applicationVersion()
+{
+ static VersionTuple version = []() {
+ const mach_header *executableHeader = nullptr;
+ for (uint32_t i = 0; i < _dyld_image_count(); ++i) {
+ auto header = _dyld_get_image_header(i);
+ if (header->filetype == MH_EXECUTE) {
+ executableHeader = header;
+ break;
+ }
+ }
+ Q_ASSERT_X(executableHeader, "QCocoaIntegration", "Failed to resolve Mach-O header of executable");
+ return versionsForImage(executableHeader);
+ }();
+ return version;
+}
+
+QMacVersion::VersionTuple QMacVersion::libraryVersion()
+{
+ static VersionTuple version = []() {
+ Dl_info cocoaPluginImage;
+ dladdr((const void *)&QMacVersion::libraryVersion, &cocoaPluginImage);
+ Q_ASSERT_X(cocoaPluginImage.dli_fbase, "QCocoaIntegration", "Failed to resolve Mach-O header of Cocoa plugin");
+ return versionsForImage(static_cast<mach_header*>(cocoaPluginImage.dli_fbase));
+ }();
+ return version;
+}
+
QT_END_NAMESPACE
/*! \internal
diff --git a/src/plugins/platforms/cocoa/qcocoaintegration.h b/src/plugins/platforms/cocoa/qcocoaintegration.h
index 7de7e073de..04cb4e1226 100644
--- a/src/plugins/platforms/cocoa/qcocoaintegration.h
+++ b/src/plugins/platforms/cocoa/qcocoaintegration.h
@@ -144,6 +144,7 @@ private:
#endif
QScopedPointer<QPlatformTheme> mPlatformTheme;
QList<QCocoaScreen *> mScreens;
+ QMacScopedObserver m_screensObserver;
#ifndef QT_NO_CLIPBOARD
QCocoaClipboard *mCocoaClipboard;
#endif
diff --git a/src/plugins/platforms/cocoa/qcocoaintegration.mm b/src/plugins/platforms/cocoa/qcocoaintegration.mm
index 612290c9bd..fb3d05d3e4 100644
--- a/src/plugins/platforms/cocoa/qcocoaintegration.mm
+++ b/src/plugins/platforms/cocoa/qcocoaintegration.mm
@@ -63,6 +63,8 @@
#include <QtGui/private/qcoregraphics_p.h>
+#include <QtFontDatabaseSupport/private/qfontengine_coretext_p.h>
+
#ifdef QT_WIDGETS_LIB
#include <QtWidgets/qtwidgetsglobal.h>
#if QT_CONFIG(filedialog)
@@ -79,6 +81,32 @@ static void initResources()
QT_BEGIN_NAMESPACE
+Q_LOGGING_CATEGORY(lcQpa, "qt.qpa", QtWarningMsg);
+
+static void logVersionInformation()
+{
+ if (!lcQpa().isInfoEnabled())
+ return;
+
+ auto osVersion = QMacVersion::currentRuntime();
+ auto qtBuildSDK = QMacVersion::buildSDK(QMacVersion::QtLibraries);
+ auto qtDeploymentTarget = QMacVersion::deploymentTarget(QMacVersion::QtLibraries);
+ auto appBuildSDK = QMacVersion::buildSDK(QMacVersion::ApplicationBinary);
+ auto appDeploymentTarget = QMacVersion::deploymentTarget(QMacVersion::ApplicationBinary);
+
+ qCInfo(lcQpa, "Loading macOS (Cocoa) platform plugin for Qt " QT_VERSION_STR ", running on macOS %d.%d.%d\n\n" \
+ " Component SDK version Deployment target \n" \
+ " ------------- ------------- -------------------\n" \
+ " Qt " QT_VERSION_STR " %d.%d.%d %d.%d.%d\n" \
+ " Application %d.%d.%d %d.%d.%d\n",
+ osVersion.majorVersion(), osVersion.minorVersion(), osVersion.microVersion(),
+ qtBuildSDK.majorVersion(), qtBuildSDK.minorVersion(), qtBuildSDK.microVersion(),
+ qtDeploymentTarget.majorVersion(), qtDeploymentTarget.minorVersion(), qtDeploymentTarget.microVersion(),
+ appBuildSDK.majorVersion(), appBuildSDK.minorVersion(), appBuildSDK.microVersion(),
+ appDeploymentTarget.majorVersion(), appDeploymentTarget.minorVersion(), appDeploymentTarget.microVersion());
+}
+
+
class QCoreTextFontEngine;
class QFontEngineFT;
@@ -112,6 +140,8 @@ QCocoaIntegration::QCocoaIntegration(const QStringList &paramList)
, mServices(new QCocoaServices)
, mKeyboardMapper(new QCocoaKeyMapper)
{
+ logVersionInformation();
+
if (mInstance)
qWarning("Creating multiple Cocoa platform integrations is not supported");
mInstance = this;
@@ -176,6 +206,9 @@ QCocoaIntegration::QCocoaIntegration(const QStringList &paramList)
// by explicitly setting the presentation option to the magic 'default value',
// which will resolve to an actual value and result in screen invalidation.
cocoaApplication.presentationOptions = NSApplicationPresentationDefault;
+
+ m_screensObserver = QMacScopedObserver([NSApplication sharedApplication],
+ NSApplicationDidChangeScreenParametersNotification, [&]() { updateScreens(); });
updateScreens();
QMacInternalPasteboardMime::initializeMimeTypes();
@@ -211,7 +244,7 @@ QCocoaIntegration::~QCocoaIntegration()
// Delete screens in reverse order to avoid crash in case of multiple screens
while (!mScreens.isEmpty()) {
- destroyScreen(mScreens.takeLast());
+ QWindowSystemInterface::handleScreenRemoved(mScreens.takeLast());
}
clearToolbars();
@@ -271,7 +304,7 @@ void QCocoaIntegration::updateScreens()
screen = new QCocoaScreen(i);
mScreens.append(screen);
qCDebug(lcQpaScreen) << "Adding" << screen;
- screenAdded(screen);
+ QWindowSystemInterface::handleScreenAdded(screen);
}
siblings << screen;
}
@@ -288,7 +321,7 @@ void QCocoaIntegration::updateScreens()
// Prevent stale references to NSScreen during destroy
screen->m_screenIndex = -1;
qCDebug(lcQpaScreen) << "Removing" << screen;
- destroyScreen(screen);
+ QWindowSystemInterface::handleScreenRemoved(screen);
}
}
@@ -312,12 +345,17 @@ QCocoaScreen *QCocoaIntegration::screenForNSScreen(NSScreen *nsScreen)
bool QCocoaIntegration::hasCapability(QPlatformIntegration::Capability cap) const
{
switch (cap) {
- case ThreadedPixmaps:
#ifndef QT_NO_OPENGL
- case OpenGL:
case ThreadedOpenGL:
+ // AppKit expects rendering to happen on the main thread, and we can
+ // easily end up in situations where rendering on secondary threads
+ // will result in visual artifacts, bugs, or even deadlocks, when
+ // building with SDK 10.14 or higher which enbles view layer-backing.
+ return QMacVersion::buildSDK() < QOperatingSystemVersion(QOperatingSystemVersion::MacOSMojave);
+ case OpenGL:
case BufferQueueingOpenGL:
#endif
+ case ThreadedPixmaps:
case WindowMasks:
case MultipleWindows:
case ForeignWindows:
@@ -369,7 +407,16 @@ QPlatformOpenGLContext *QCocoaIntegration::createPlatformOpenGLContext(QOpenGLCo
QPlatformBackingStore *QCocoaIntegration::createPlatformBackingStore(QWindow *window) const
{
- return new QCocoaBackingStore(window);
+ QCocoaWindow *platformWindow = static_cast<QCocoaWindow*>(window->handle());
+ if (!platformWindow) {
+ qWarning() << window << "must be created before being used with a backingstore";
+ return nullptr;
+ }
+
+ if (platformWindow->view().layer)
+ return new QCALayerBackingStore(window);
+ else
+ return new QNSWindowBackingStore(window);
}
QAbstractEventDispatcher *QCocoaIntegration::createEventDispatcher() const
@@ -444,7 +491,7 @@ QCocoaServices *QCocoaIntegration::services() const
QVariant QCocoaIntegration::styleHint(StyleHint hint) const
{
if (hint == QPlatformIntegration::FontSmoothingGamma)
- return 2.0;
+ return QCoreTextFontEngine::fontSmoothingGamma();
return QPlatformIntegration::styleHint(hint);
}
diff --git a/src/plugins/platforms/cocoa/qcocoamenu.mm b/src/plugins/platforms/cocoa/qcocoamenu.mm
index 7b96fca3f9..f34988721d 100644
--- a/src/plugins/platforms/cocoa/qcocoamenu.mm
+++ b/src/plugins/platforms/cocoa/qcocoamenu.mm
@@ -250,6 +250,9 @@ void QCocoaMenu::syncMenuItem_helper(QPlatformMenuItem *menuItem, bool menubarUp
if (wasMerged) {
oldItem.enabled = NO;
oldItem.hidden = YES;
+ oldItem.keyEquivalent = @"";
+ oldItem.keyEquivalentModifierMask = NSEventModifierFlagCommand;
+
} else {
[m_nativeMenu removeItem:oldItem];
}
diff --git a/src/plugins/platforms/cocoa/qcocoamenuitem.h b/src/plugins/platforms/cocoa/qcocoamenuitem.h
index 20fc741fb8..c842b08d52 100644
--- a/src/plugins/platforms/cocoa/qcocoamenuitem.h
+++ b/src/plugins/platforms/cocoa/qcocoamenuitem.h
@@ -119,7 +119,6 @@ private:
QString m_text;
QIcon m_icon;
QPointer<QCocoaMenu> m_menu;
- QFont m_font;
MenuRole m_role;
MenuRole m_detectedRole;
#ifndef QT_NO_SHORTCUT
diff --git a/src/plugins/platforms/cocoa/qcocoamenuitem.mm b/src/plugins/platforms/cocoa/qcocoamenuitem.mm
index 21faa6d985..e54b6284e5 100644
--- a/src/plugins/platforms/cocoa/qcocoamenuitem.mm
+++ b/src/plugins/platforms/cocoa/qcocoamenuitem.mm
@@ -173,7 +173,7 @@ void QCocoaMenuItem::setIsSeparator(bool isSeparator)
void QCocoaMenuItem::setFont(const QFont &font)
{
- m_font = font;
+ Q_UNUSED(font)
}
void QCocoaMenuItem::setRole(MenuRole role)
@@ -319,21 +319,7 @@ NSMenuItem *QCocoaMenuItem::sync()
text += QLatin1String(" (") + accel.toString(QKeySequence::NativeText) + QLatin1String(")");
#endif
- QString finalString = QPlatformTheme::removeMnemonics(text);
- bool useAttributedTitle = false;
- // Cocoa Font and title
- if (m_font.resolve()) {
- NSFont *customMenuFont = [NSFont fontWithName:m_font.family().toNSString()
- size:m_font.pointSize()];
- if (customMenuFont) {
- NSAttributedString *str = [[[NSAttributedString alloc] initWithString:finalString.toNSString()
- attributes:@{NSFontAttributeName: customMenuFont}] autorelease];
- m_native.attributedTitle = str;
- useAttributedTitle = true;
- }
- }
- if (!useAttributedTitle)
- m_native.title = finalString.toNSString();
+ m_native.title = QPlatformTheme::removeMnemonics(text).toNSString();
#ifndef QT_NO_SHORTCUT
if (accel.count() == 1) {
diff --git a/src/plugins/platforms/cocoa/qcocoaprintdevice.h b/src/plugins/platforms/cocoa/qcocoaprintdevice.h
index 20b27f3286..d267343b0e 100644
--- a/src/plugins/platforms/cocoa/qcocoaprintdevice.h
+++ b/src/plugins/platforms/cocoa/qcocoaprintdevice.h
@@ -98,7 +98,9 @@ protected:
void loadOutputBins() const override;
void loadDuplexModes() const override;
void loadColorModes() const override;
+#if QT_CONFIG(mimetype)
void loadMimeTypes() const override;
+#endif
private:
QPageSize createPageSize(const PMPaper &paper) const;
diff --git a/src/plugins/platforms/cocoa/qcocoaprintdevice.mm b/src/plugins/platforms/cocoa/qcocoaprintdevice.mm
index 24ec7ca9a4..7605dc9d1a 100644
--- a/src/plugins/platforms/cocoa/qcocoaprintdevice.mm
+++ b/src/plugins/platforms/cocoa/qcocoaprintdevice.mm
@@ -39,7 +39,9 @@
#include "qcocoaprintdevice.h"
+#if QT_CONFIG(mimetype)
#include <QtCore/qmimedatabase.h>
+#endif
#include <qdebug.h>
QT_BEGIN_NAMESPACE
@@ -417,6 +419,7 @@ QPrint::ColorMode QCocoaPrintDevice::defaultColorMode() const
return QPrint::GrayScale;
}
+#if QT_CONFIG(mimetype)
void QCocoaPrintDevice::loadMimeTypes() const
{
// TODO Check how settings affect returned list
@@ -438,6 +441,7 @@ void QCocoaPrintDevice::loadMimeTypes() const
}
m_haveMimeTypes = true;
}
+#endif // mimetype
bool QCocoaPrintDevice::openPpdFile()
{
diff --git a/src/plugins/platforms/cocoa/qcocoascreen.mm b/src/plugins/platforms/cocoa/qcocoascreen.mm
index f82ef202b1..6a5b0e6e3e 100644
--- a/src/plugins/platforms/cocoa/qcocoascreen.mm
+++ b/src/plugins/platforms/cocoa/qcocoascreen.mm
@@ -269,15 +269,14 @@ struct DeferredDebugHelper
void QCocoaScreen::deliverUpdateRequests()
{
- if (!QGuiApplication::instance())
- return;
+ QMacAutoReleasePool pool;
// The CVDisplayLink callback is a notification that it's a good time to produce a new frame.
// Since the callback is delivered on a separate thread we have to marshal it over to the
// main thread, as Qt requires update requests to be delivered there. This needs to happen
// asynchronously, as otherwise we may end up deadlocking if the main thread calls back
// into any of the CVDisplayLink APIs.
- if (QThread::currentThread() != QGuiApplication::instance()->thread()) {
+ if (!NSThread.isMainThread) {
// We're explicitly not using the data of the GCD source to track the pending updates,
// as the data isn't reset to 0 until after the event handler, and also doesn't update
// during the event handler, both of which we need to track late frames.
@@ -330,7 +329,7 @@ void QCocoaScreen::deliverUpdateRequests()
auto windows = QGuiApplication::allWindows();
for (int i = 0; i < windows.size(); ++i) {
QWindow *window = windows.at(i);
- QPlatformWindow *platformWindow = window->handle();
+ auto *platformWindow = static_cast<QCocoaWindow*>(window->handle());
if (!platformWindow)
continue;
@@ -341,7 +340,7 @@ void QCocoaScreen::deliverUpdateRequests()
continue;
// Skip windows that are not doing update requests via display link
- if (!(window->format().swapInterval() > 0))
+ if (!platformWindow->updatesWithDisplayLink())
continue;
platformWindow->deliverUpdateRequest();
@@ -411,8 +410,7 @@ QWindow *QCocoaScreen::topLevelAt(const QPoint &point) const
if (![nsWindow conformsToProtocol:@protocol(QNSWindowProtocol)])
continue;
- id<QNSWindowProtocol> proto = static_cast<id<QNSWindowProtocol> >(nsWindow);
- QCocoaWindow *cocoaWindow = proto.platformWindow;
+ QCocoaWindow *cocoaWindow = qnsview_cast(nsWindow.contentView).platformWindow;
if (!cocoaWindow)
continue;
window = cocoaWindow->window();
@@ -428,65 +426,71 @@ QWindow *QCocoaScreen::topLevelAt(const QPoint &point) const
return window;
}
-QPixmap QCocoaScreen::grabWindow(WId window, int x, int y, int width, int height) const
+QPixmap QCocoaScreen::grabWindow(WId view, 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.
+ // Determine the grab rect. FIXME: The rect should be bounded by the view's
+ // geometry, but note that for the pixeltool use case that window will be the
+ // desktop widgets's view, which currently gets resized to fit one screen
+ // only, since its NSWindow has the NSWindowStyleMaskTitled flag set.
+ Q_UNUSED(view);
+ QRect grabRect = QRect(x, y, width, height);
+ qCDebug(lcQpaScreen) << "input grab rect" << grabRect;
+
+ // Find which displays to grab from, or all of them if the grab size is unspecified
+ const int maxDisplays = 128;
CGDirectDisplayID displays[maxDisplays];
CGDisplayCount displayCount;
- CGRect cgRect;
-
- if (width < 0 || height < 0) {
- // get all displays
- cgRect = CGRectInfinite;
- } else {
- cgRect = CGRectMake(x, y, width, height);
- }
+ CGRect cgRect = (width < 0 || height < 0) ? CGRectInfinite : grabRect.toCGRect();
const CGDisplayErr err = CGGetDisplaysWithRect(cgRect, maxDisplays, displays, &displayCount);
-
- if (err && displayCount == 0)
+ if (err || displayCount == 0)
return QPixmap();
- // calculate pixmap size
- QSize windowSize(width, height);
+ // If the grab size is not specified, set it to be the bounding box of all screens,
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);
+ QRect displayBounds = QRectF::fromCGRect(CGDisplayBounds(displays[i])).toRect();
+ windowRect = windowRect.united(displayBounds);
}
- if (width < 0)
- windowSize.setWidth(windowRect.width());
- if (height < 0)
- windowSize.setHeight(windowRect.height());
+ if (grabRect.width() < 0)
+ grabRect.setWidth(windowRect.width());
+ if (grabRect.height() < 0)
+ grabRect.setHeight(windowRect.height());
}
- const qreal dpr = devicePixelRatio();
- QPixmap windowPixmap(windowSize * dpr);
- windowPixmap.fill(Qt::transparent);
+ qCDebug(lcQpaScreen) << "final grab rect" << grabRect << "from" << displayCount << "displays";
+ // Grab images from each display
+ QVector<QImage> images;
+ QVector<QRect> destinations;
for (uint i = 0; i < displayCount; ++i) {
- const CGRect bounds = CGDisplayBounds(displays[i]);
-
- // Calculate the position and size of the requested area
- QPoint pos(qAbs(bounds.origin.x - x), qAbs(bounds.origin.y - y));
- QSize size(qMin(pos.x() + width, qRound(bounds.size.width)),
- qMin(pos.y() + height, qRound(bounds.size.height)));
- pos *= dpr;
- size *= dpr;
-
- // Take the whole screen and crop it afterwards, because CGDisplayCreateImageForRect
- // has a strange behavior when mixing highDPI and non-highDPI displays
- QCFType<CGImageRef> cgImage = CGDisplayCreateImage(displays[i]);
- const QImage image = qt_mac_toQImage(cgImage);
-
- // Draw into windowPixmap only the requested size
- QPainter painter(&windowPixmap);
- painter.drawImage(windowPixmap.rect(), image, QRect(pos, size));
+ auto display = displays[i];
+ QRect displayBounds = QRectF::fromCGRect(CGDisplayBounds(display)).toRect();
+ QRect grabBounds = displayBounds.intersected(grabRect);
+ QRect displayLocalGrabBounds = QRect(QPoint(grabBounds.topLeft() - displayBounds.topLeft()), grabBounds.size());
+ QImage displayImage = qt_mac_toQImage(QCFType<CGImageRef>(CGDisplayCreateImageForRect(display, displayLocalGrabBounds.toCGRect())));
+ displayImage.setDevicePixelRatio(displayImage.size().width() / displayLocalGrabBounds.size().width());
+ images.append(displayImage);
+ QRect destBounds = QRect(QPoint(grabBounds.topLeft() - grabRect.topLeft()), grabBounds.size());
+ destinations.append(destBounds);
+ qCDebug(lcQpaScreen) << "grab display" << i << "global" << grabBounds << "local" << displayLocalGrabBounds
+ << "grab image size" << displayImage.size() << "devicePixelRatio" << displayImage.devicePixelRatio();
}
+
+ // Determine the highest dpr, which becomes the dpr for the returned pixmap.
+ qreal dpr = 1.0;
+ for (uint i = 0; i < displayCount; ++i)
+ dpr = qMax(dpr, images.at(i).devicePixelRatio());
+
+ // Alocate target pixmap and draw each screen's content
+ qCDebug(lcQpaScreen) << "Create grap pixmap" << grabRect.size() << "at devicePixelRatio" << dpr;
+ QPixmap windowPixmap(grabRect.size() * dpr);
+ windowPixmap.setDevicePixelRatio(dpr);
+ windowPixmap.fill(Qt::transparent);
+ QPainter painter(&windowPixmap);
+ for (uint i = 0; i < displayCount; ++i)
+ painter.drawImage(destinations.at(i), images.at(i));
+
return windowPixmap;
}
diff --git a/src/plugins/platforms/cocoa/qcocoasystemsettings.mm b/src/plugins/platforms/cocoa/qcocoasystemsettings.mm
index c1711e7cd4..9b6dc94d33 100644
--- a/src/plugins/platforms/cocoa/qcocoasystemsettings.mm
+++ b/src/plugins/platforms/cocoa/qcocoasystemsettings.mm
@@ -75,8 +75,9 @@ QPalette * qt_mac_createSystemPalette()
palette->setBrush(QPalette::Disabled, QPalette::Text, dark);
palette->setBrush(QPalette::Disabled, QPalette::ButtonText, dark);
palette->setBrush(QPalette::Disabled, QPalette::Base, backgroundBrush);
- palette->setBrush(QPalette::Active, QPalette::Base, backgroundBrush);
- palette->setBrush(QPalette::Inactive, QPalette::Base, backgroundBrush);
+ QBrush textBackgroundBrush = qt_mac_toQBrush([NSColor textBackgroundColor]);
+ palette->setBrush(QPalette::Active, QPalette::Base, textBackgroundBrush);
+ palette->setBrush(QPalette::Inactive, QPalette::Base, textBackgroundBrush);
palette->setColor(QPalette::Disabled, QPalette::Dark, QColor(191, 191, 191));
palette->setColor(QPalette::Active, QPalette::Dark, QColor(191, 191, 191));
palette->setColor(QPalette::Inactive, QPalette::Dark, QColor(191, 191, 191));
@@ -157,10 +158,13 @@ QHash<QPlatformTheme::Palette, QPalette*> qt_mac_createRolePalettes()
pal.setColor(QPalette::Inactive, QPalette::WindowText, qc);
pal.setColor(QPalette::Active, QPalette::HighlightedText, qc);
pal.setColor(QPalette::Inactive, QPalette::HighlightedText, qc);
+ pal.setColor(QPalette::Active, QPalette::ButtonText, qc);
+ pal.setColor(QPalette::Inactive, QPalette::ButtonText, qc);
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);
+ pal.setColor(QPalette::Disabled, QPalette::ButtonText, qc);
}
if (mac_widget_colors[i].paletteRole == QPlatformTheme::MenuPalette
|| mac_widget_colors[i].paletteRole == QPlatformTheme::MenuBarPalette) {
@@ -202,7 +206,7 @@ QHash<QPlatformTheme::Palette, QPalette*> qt_mac_createRolePalettes()
qt_mac_toQBrush([NSColor unemphasizedSelectedTextColor]));
} else {
baseColors = [NSColor controlAlternatingRowBackgroundColors];
- activeHighlightColor = [NSColor selectedControlColor];
+ activeHighlightColor = [NSColor alternateSelectedControlColor];
pal.setBrush(QPalette::Inactive, QPalette::HighlightedText,
pal.brush(QPalette::Active, QPalette::Text));
}
diff --git a/src/plugins/platforms/cocoa/qcocoasystemtrayicon.mm b/src/plugins/platforms/cocoa/qcocoasystemtrayicon.mm
index 0158895441..4982f5ee05 100644
--- a/src/plugins/platforms/cocoa/qcocoasystemtrayicon.mm
+++ b/src/plugins/platforms/cocoa/qcocoasystemtrayicon.mm
@@ -105,6 +105,8 @@ QT_USE_NAMESPACE
@end
@interface QT_MANGLE_NAMESPACE(QNSImageView) : NSImageView
+@property (nonatomic, assign) BOOL down;
+@property (nonatomic, assign) QT_MANGLE_NAMESPACE(QNSStatusItem) *parent;
@end
QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSStatusItem);
@@ -277,36 +279,32 @@ QT_END_NAMESPACE
@implementation NSStatusItem (Qt)
@end
-@implementation QNSImageView {
- BOOL down;
- QT_MANGLE_NAMESPACE(QNSStatusItem) *parent;
-}
-
+@implementation QNSImageView
- (instancetype)initWithParent:(QNSStatusItem *)myParent {
self = [super init];
- parent = myParent;
- down = NO;
+ self.parent = myParent;
+ self.down = NO;
return self;
}
- (void)menuTrackingDone:(NSNotification *)__unused notification
{
- down = NO;
+ self.down = NO;
[self setNeedsDisplay:YES];
}
- (void)mousePressed:(NSEvent *)mouseEvent
{
- down = YES;
+ self.down = YES;
int clickCount = [mouseEvent clickCount];
[self setNeedsDisplay:YES];
if (clickCount == 2) {
[self menuTrackingDone:nil];
- [parent doubleClickSelector:self];
+ [self.parent doubleClickSelector:self];
} else {
- [parent triggerSelector:self button:cocoaButton2QtButton(mouseEvent)];
+ [self.parent triggerSelector:self button:cocoaButton2QtButton(mouseEvent)];
}
}
@@ -344,7 +342,7 @@ QT_END_NAMESPACE
}
- (void)drawRect:(NSRect)rect {
- [[parent item] drawStatusBarBackgroundInRect:rect withHighlight:down];
+ [[self.parent item] drawStatusBarBackgroundInRect:rect withHighlight:self.down];
[super drawRect:rect];
}
@end
@@ -374,6 +372,7 @@ QT_END_NAMESPACE
- (void)dealloc {
[[NSStatusBar systemStatusBar] removeStatusItem:item];
[[NSNotificationCenter defaultCenter] removeObserver:imageCell];
+ imageCell.parent = nil;
[imageCell release];
[item release];
[super dealloc];
diff --git a/src/plugins/platforms/cocoa/qcocoatheme.mm b/src/plugins/platforms/cocoa/qcocoatheme.mm
index a2229159b5..efe670abed 100644
--- a/src/plugins/platforms/cocoa/qcocoatheme.mm
+++ b/src/plugins/platforms/cocoa/qcocoatheme.mm
@@ -53,11 +53,13 @@
#include "qcocoahelpers.h"
#include <QtCore/qfileinfo.h>
+#include <QtGui/private/qfont_p.h>
#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 <QtFontDatabaseSupport/private/qfontengine_coretext_p.h>
#include <QtThemeSupport/private/qabstractfileiconengine_p.h>
#include <qpa/qplatformdialoghelper.h>
#include <qpa/qplatformintegration.h>
@@ -78,12 +80,7 @@
#include <CoreServices/CoreServices.h>
-#if !QT_MACOS_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_14)
-@interface NSApplication (MojaveForwardDeclarations)
-@property (readonly, strong) NSAppearance *effectiveAppearance NS_AVAILABLE_MAC(10_14);
-@end
-#endif
-
+#if QT_MACOS_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_14)
@interface QT_MANGLE_NAMESPACE(QCocoaThemeAppAppearanceObserver) : NSObject
@property (readonly, nonatomic) QCocoaTheme *theme;
- (instancetype)initWithTheme:(QCocoaTheme *)theme;
@@ -122,6 +119,7 @@ QT_NAMESPACE_ALIAS_OBJC_CLASS(QCocoaThemeAppAppearanceObserver);
self.theme->handleSystemThemeChange();
}
@end
+#endif // QT_MACOS_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_14)
QT_BEGIN_NAMESPACE
@@ -130,8 +128,10 @@ const char *QCocoaTheme::name = "cocoa";
QCocoaTheme::QCocoaTheme()
: m_systemPalette(nullptr), m_appearanceObserver(nil)
{
+#if QT_MACOS_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_14)
if (QOperatingSystemVersion::current() >= QOperatingSystemVersion::MacOSMojave)
m_appearanceObserver = [[QCocoaThemeAppAppearanceObserver alloc] initWithTheme:this];
+#endif
[[NSNotificationCenter defaultCenter] addObserverForName:NSSystemColorsDidChangeNotification
object:nil queue:nil usingBlock:^(NSNotification *) {
@@ -162,6 +162,11 @@ void QCocoaTheme::handleSystemThemeChange()
m_systemPalette = qt_mac_createSystemPalette();
m_palettes = qt_mac_createRolePalettes();
+ if (QCoreTextFontEngine::fontSmoothing() == QCoreTextFontEngine::FontSmoothing::Grayscale) {
+ // Re-populate glyph caches based on the new appearance's assumed text fill color
+ QFontCache::instance()->clear();
+ }
+
QWindowSystemInterface::handleThemeChange(nullptr);
}
@@ -221,16 +226,12 @@ const QPalette *QCocoaTheme::palette(Palette type) const
return nullptr;
}
-QHash<QPlatformTheme::Font, QFont *> qt_mac_createRoleFonts()
-{
- QCoreTextFontDatabase *ctfd = static_cast<QCoreTextFontDatabase *>(QGuiApplicationPrivate::platformIntegration()->fontDatabase());
- return ctfd->themeFonts();
-}
-
const QFont *QCocoaTheme::font(Font type) const
{
if (m_fonts.isEmpty()) {
- m_fonts = qt_mac_createRoleFonts();
+ const auto *platformIntegration = QGuiApplicationPrivate::platformIntegration();
+ const auto *coreTextFontDb = static_cast<QCoreTextFontDatabase *>(platformIntegration->fontDatabase());
+ m_fonts = coreTextFontDb->themeFonts();
}
return m_fonts.value(type, nullptr);
}
diff --git a/src/plugins/platforms/cocoa/qcocoawindow.h b/src/plugins/platforms/cocoa/qcocoawindow.h
index 8f1bdb8af0..fef72bc496 100644
--- a/src/plugins/platforms/cocoa/qcocoawindow.h
+++ b/src/plugins/platforms/cocoa/qcocoawindow.h
@@ -129,6 +129,7 @@ public:
bool isForeignWindow() const override;
void requestUpdate() override;
+ bool updatesWithDisplayLink() const;
void deliverUpdateRequest() override;
void requestActivateWindow() override;
@@ -252,7 +253,6 @@ public: // for QNSView
bool m_needsInvalidateShadow;
- bool m_hasModalSession;
bool m_frameStrutEventsEnabled;
QRect m_exposedRect;
int m_registerTouchCount;
diff --git a/src/plugins/platforms/cocoa/qcocoawindow.mm b/src/plugins/platforms/cocoa/qcocoawindow.mm
index b2d1a80097..298d11fe08 100644
--- a/src/plugins/platforms/cocoa/qcocoawindow.mm
+++ b/src/plugins/platforms/cocoa/qcocoawindow.mm
@@ -153,7 +153,6 @@ QCocoaWindow::QCocoaWindow(QWindow *win, WId nativeHandle)
, m_inSetStyleMask(false)
, m_menubar(nullptr)
, m_needsInvalidateShadow(false)
- , m_hasModalSession(false)
, m_frameStrutEventsEnabled(false)
, m_registerTouchCount(0)
, m_resizableTransientParent(false)
@@ -228,8 +227,9 @@ QSurfaceFormat QCocoaWindow::format() const
// Upgrade the default surface format to include an alpha channel. The default RGB format
// causes Cocoa to spend an unreasonable amount of time converting it to RGBA internally.
- if (format == QSurfaceFormat())
+ if (format.alphaBufferSize() < 0)
format.setAlphaBufferSize(8);
+
return format;
}
@@ -303,13 +303,17 @@ void QCocoaWindow::setVisible(bool visible)
{
qCDebug(lcQpaWindow) << "QCocoaWindow::setVisible" << window() << visible;
- m_inSetVisible = true;
+ QScopedValueRollback<bool> rollback(m_inSetVisible, true);
QMacAutoReleasePool pool;
QCocoaWindow *parentCocoaWindow = nullptr;
if (window()->transientParent())
parentCocoaWindow = static_cast<QCocoaWindow *>(window()->transientParent()->handle());
+ auto eventDispatcher = [] {
+ return static_cast<QCocoaEventDispatcherPrivate *>(QObjectPrivate::get(qApp->eventDispatcher()));
+ };
+
if (visible) {
// We need to recreate if the modality has changed as the style mask will need updating
recreateWindowIfNeeded();
@@ -350,68 +354,46 @@ void QCocoaWindow::setVisible(bool visible)
applyWindowState(window()->windowStates());
if (window()->windowState() != Qt::WindowMinimized) {
- if ((window()->modality() == Qt::WindowModal
- || window()->type() == Qt::Sheet)
- && parentCocoaWindow) {
- // show the window as a sheet
+ if (parentCocoaWindow && (window()->modality() == Qt::WindowModal || window()->type() == Qt::Sheet)) {
+ // Show the window as a sheet
[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());
- Q_ASSERT(cocoaEventDispatcher);
- QCocoaEventDispatcherPrivate *cocoaEventDispatcherPrivate = static_cast<QCocoaEventDispatcherPrivate *>(QObjectPrivate::get(cocoaEventDispatcher));
- cocoaEventDispatcherPrivate->beginModalSession(window());
- m_hasModalSession = true;
- } else if ([m_view.window canBecomeKeyWindow]) {
- QCocoaEventDispatcher *cocoaEventDispatcher = qobject_cast<QCocoaEventDispatcher *>(QGuiApplication::instance()->eventDispatcher());
- QCocoaEventDispatcherPrivate *cocoaEventDispatcherPrivate = nullptr;
- if (cocoaEventDispatcher)
- cocoaEventDispatcherPrivate = static_cast<QCocoaEventDispatcherPrivate *>(QObjectPrivate::get(cocoaEventDispatcher));
-
- if (cocoaEventDispatcherPrivate && cocoaEventDispatcherPrivate->cocoaModalSessionStack.isEmpty())
- [m_view.window makeKeyAndOrderFront:nil];
- else
- [m_view.window orderFront:nil];
+ } else if (window()->modality() == Qt::ApplicationModal) {
+ // Show the window as application modal
+ eventDispatcher()->beginModalSession(window());
+ } else if (m_view.window.canBecomeKeyWindow && !eventDispatcher()->hasModalSession()) {
+ [m_view.window makeKeyAndOrderFront:nil];
} else {
[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_view.window isKindOfClass:[NSPanel class]]) {
- ((NSPanel *)m_view.window).worksWhenModal = YES;
- if (!(parentCocoaWindow && window()->transientParent()->isActive()) && window()->type() == Qt::Popup) {
- removeMonitor();
- NSEventMask eventMask = NSEventMaskLeftMouseDown | NSEventMaskRightMouseDown
- | NSEventMaskOtherMouseDown | NSEventMaskMouseMoved;
- monitor = [NSEvent addGlobalMonitorForEventsMatchingMask:eventMask handler:^(NSEvent *e) {
- const auto button = cocoaButton2QtButton(e);
- const auto buttons = currentlyPressedMouseButtons();
- const auto eventType = cocoaEvent2QtMouseEvent(e);
- const auto globalPoint = QCocoaScreen::mapFromNative(NSEvent.mouseLocation);
- const auto localPoint = window()->mapFromGlobal(globalPoint.toPoint());
- QWindowSystemInterface::handleMouseEvent(window(), localPoint, globalPoint, buttons, button, eventType);
- }];
- }
+ // Close popup when clicking outside it
+ if (window()->type() == Qt::Popup && !(parentCocoaWindow && window()->transientParent()->isActive())) {
+ removeMonitor();
+ NSEventMask eventMask = NSEventMaskLeftMouseDown | NSEventMaskRightMouseDown
+ | NSEventMaskOtherMouseDown | NSEventMaskMouseMoved;
+ monitor = [NSEvent addGlobalMonitorForEventsMatchingMask:eventMask handler:^(NSEvent *e) {
+ const auto button = cocoaButton2QtButton(e);
+ const auto buttons = currentlyPressedMouseButtons();
+ const auto eventType = cocoaEvent2QtMouseEvent(e);
+ const auto globalPoint = QCocoaScreen::mapFromNative(NSEvent.mouseLocation);
+ const auto localPoint = window()->mapFromGlobal(globalPoint.toPoint());
+ QWindowSystemInterface::handleMouseEvent(window(), localPoint, globalPoint, buttons, button, eventType);
+ }];
}
}
}
+
// In some cases, e.g. QDockWidget, the content view is hidden before moving to its own
// Cocoa window, and then shown again. Therefore, we test for the view being hidden even
// if it's attached to an NSWindow.
if ([m_view isHidden])
[m_view setHidden:NO];
+
} else {
- // qDebug() << "close" << this;
- QCocoaEventDispatcher *cocoaEventDispatcher = qobject_cast<QCocoaEventDispatcher *>(QGuiApplication::instance()->eventDispatcher());
- QCocoaEventDispatcherPrivate *cocoaEventDispatcherPrivate = nullptr;
- if (cocoaEventDispatcher)
- cocoaEventDispatcherPrivate = static_cast<QCocoaEventDispatcherPrivate *>(QObjectPrivate::get(cocoaEventDispatcher));
+ // Window not visible, hide it
if (isContentView()) {
- if (m_hasModalSession) {
- if (cocoaEventDispatcherPrivate)
- cocoaEventDispatcherPrivate->endModalSession(window());
- m_hasModalSession = false;
+ if (eventDispatcher()->hasModalSession()) {
+ eventDispatcher()->endModalSession(window());
} else {
if ([m_view.window isSheet]) {
Q_ASSERT_X(parentCocoaWindow, "QCocoaWindow", "Window modal dialog has no transient parent.");
@@ -419,10 +401,14 @@ void QCocoaWindow::setVisible(bool visible)
}
}
+ // Note: We do not guard the order out by checking NSWindow.visible, as AppKit will
+ // in some cases, such as when hiding the application, order out and make a window
+ // invisible, but keep it in a list of "hidden windows", that it then restores again
+ // when the application is unhidden. We need to call orderOut explicitly, to bring
+ // the window out of this "hidden list".
[m_view.window orderOut:nil];
- if (m_view.window == [NSApp keyWindow]
- && !(cocoaEventDispatcherPrivate && cocoaEventDispatcherPrivate->currentModalSession())) {
+ if (m_view.window == [NSApp keyWindow] && !eventDispatcher()->hasModalSession()) {
// 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
// the current NSWindow is still key after being ordered out. Then, after checking we
@@ -434,6 +420,7 @@ void QCocoaWindow::setVisible(bool visible)
} else {
[m_view setHidden:YES];
}
+
removeMonitor();
if (window()->type() == Qt::Popup || window()->type() == Qt::ToolTip)
@@ -447,8 +434,6 @@ void QCocoaWindow::setVisible(bool visible)
nativeParentWindow.styleMask |= NSWindowStyleMaskResizable;
}
}
-
- m_inSetVisible = false;
}
NSInteger QCocoaWindow::windowLevel(Qt::WindowFlags flags)
@@ -542,6 +527,12 @@ void QCocoaWindow::setWindowZoomButton(Qt::WindowFlags flags)
void QCocoaWindow::setWindowFlags(Qt::WindowFlags flags)
{
+ // Updating the window flags may affect the window's theme frame, which
+ // in the process retains and then autoreleases the NSWindow. To make
+ // sure this doesn't leave lingering releases when there is no pool in
+ // place (e.g. during main(), before exec), we add one locally here.
+ QMacAutoReleasePool pool;
+
if (!isContentView())
return;
@@ -628,7 +619,7 @@ void QCocoaWindow::applyWindowState(Qt::WindowStates requestedState)
if (nsWindow.styleMask & NSWindowStyleMaskUtilityWindow
&& newState & (Qt::WindowMinimized | Qt::WindowFullScreen)) {
- qWarning() << window()->type() << "windows can not be made" << newState;
+ qWarning() << window()->type() << "windows cannot be made" << newState;
handleWindowStateChanged(HandleUnconditionally);
return;
}
@@ -892,34 +883,36 @@ void QCocoaWindow::raise()
qCDebug(lcQpaWindow) << "QCocoaWindow::raise" << window();
// ### handle spaces (see Qt 4 raise_sys in qwidget_mac.mm)
- if (!isContentView())
- return;
-
- 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];
+ if (isContentView()) {
+ if (m_view.window.visible) {
+ {
+ // Clean up auto-released 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 cycle), 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];
}
+ } else {
+ [m_view.superview addSubview:m_view positioned:NSWindowAbove relativeTo:nil];
}
}
void QCocoaWindow::lower()
{
qCDebug(lcQpaWindow) << "QCocoaWindow::lower" << window();
- if (!isContentView())
- return;
- if (m_view.window.visible)
- [m_view.window orderBack:m_view.window];
+ if (isContentView()) {
+ if (m_view.window.visible)
+ [m_view.window orderBack:m_view.window];
+ } else {
+ [m_view.superview addSubview:m_view positioned:NSWindowBelow relativeTo:nil];
+ }
}
bool QCocoaWindow::isExposed() const
@@ -1093,6 +1086,9 @@ void QCocoaWindow::setEmbeddedInForeignView()
void QCocoaWindow::viewDidChangeFrame()
{
+ if (isContentView())
+ return; // Handled below
+
handleGeometryChange();
}
@@ -1373,11 +1369,14 @@ void QCocoaWindow::recreateWindowIfNeeded()
if (m_windowModality != window()->modality())
recreateReason |= WindowModalityChanged;
- const bool shouldBeContentView = !parentWindow && !isEmbeddedView;
+ Qt::WindowType type = window()->type();
+
+ const bool shouldBeContentView = !parentWindow
+ && !((type & Qt::SubWindow) == Qt::SubWindow)
+ && !isEmbeddedView;
if (isContentView() != shouldBeContentView)
recreateReason |= ContentViewChanged;
- Qt::WindowType type = window()->type();
const bool isPanel = isContentView() && [m_view.window isKindOfClass:[QNSPanel class]];
const bool shouldBePanel = shouldBeContentView &&
((type & Qt::Popup) == Qt::Popup || (type & Qt::Dialog) == Qt::Dialog);
@@ -1462,11 +1461,10 @@ void QCocoaWindow::recreateWindowIfNeeded()
void QCocoaWindow::requestUpdate()
{
- const int swapInterval = format().swapInterval();
- qCDebug(lcQpaDrawing) << "QCocoaWindow::requestUpdate" << window() << "swapInterval" << swapInterval;
+ qCDebug(lcQpaDrawing) << "QCocoaWindow::requestUpdate" << window()
+ << "using" << (updatesWithDisplayLink() ? "display-link" : "timer");
- if (swapInterval > 0) {
- // Vsync is enabled, deliver via CVDisplayLink
+ if (updatesWithDisplayLink()) {
static_cast<QCocoaScreen *>(screen())->requestUpdate();
} else {
// Fall back to the un-throttled timer-based callback
@@ -1474,17 +1472,34 @@ void QCocoaWindow::requestUpdate()
}
}
+bool QCocoaWindow::updatesWithDisplayLink() const
+{
+ // Update via CVDisplayLink if Vsync is enabled
+ return format().swapInterval() != 0;
+}
+
void QCocoaWindow::deliverUpdateRequest()
{
+ // Don't send update requests for views that need display, as the update
+ // request doesn't carry any information about dirty rects, so the app
+ // may end up painting a smaller region than required. (For some reason
+ // the layer and view's needsDisplay status isn't always in sync, even if
+ // the view is layer-backed, not layer-hosted, so we check both).
+ if (m_view.layer.needsDisplay || m_view.needsDisplay) {
+ qCDebug(lcQpaDrawing) << "View needs display, deferring update request for" << window();
+ requestUpdate();
+ return;
+ }
+
qCDebug(lcQpaDrawing) << "Delivering update request to" << window();
QPlatformWindow::deliverUpdateRequest();
}
void QCocoaWindow::requestActivateWindow()
{
- NSWindow *window = [m_view window];
- [window makeFirstResponder:m_view];
- [window makeKeyWindow];
+ QMacAutoReleasePool pool;
+ [m_view.window makeFirstResponder:m_view];
+ [m_view.window makeKeyWindow];
}
QCocoaNSWindow *QCocoaWindow::createNSWindow(bool shouldBePanel)
@@ -1530,7 +1545,8 @@ QCocoaNSWindow *QCocoaWindow::createNSWindow(bool shouldBePanel)
// 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()];
+ screen:cocoaScreen->nativeScreen()
+ platformWindow:this];
Q_ASSERT_X(nsWindow.screen == cocoaScreen->nativeScreen(), "QCocoaWindow",
"Resulting NSScreen should match the requested NSScreen");
@@ -1540,7 +1556,9 @@ QCocoaNSWindow *QCocoaWindow::createNSWindow(bool shouldBePanel)
QWindowSystemInterface::SynchronousDelivery>(window(), targetScreen);
}
- nsWindow.delegate = [[QNSWindowDelegate alloc] initWithQCocoaWindow:this];
+ static QSharedPointer<QNSWindowDelegate> sharedDelegate([[QNSWindowDelegate alloc] init],
+ [](QNSWindowDelegate *delegate) { [delegate release]; });
+ nsWindow.delegate = sharedDelegate.get();
// Prevent Cocoa from releasing the window on close. Qt
// handles the close event asynchronously and we want to
@@ -1720,6 +1738,14 @@ void QCocoaWindow::applyContentBorderThickness(NSWindow *window)
[window setStyleMask:[window styleMask] | NSWindowStyleMaskTexturedBackground];
window.titlebarAppearsTransparent = YES;
+ // Setting titlebarAppearsTransparent to YES means that the border thickness has to account
+ // for the title bar height as well, otherwise sheets will not be presented at the correct
+ // position, which should be (title bar height + top content border size).
+ const NSRect frameRect = window.frame;
+ const NSRect contentRect = [window contentRectForFrameRect:frameRect];
+ const CGFloat titlebarHeight = frameRect.size.height - contentRect.size.height;
+ effectiveTopContentBorderThickness += titlebarHeight;
+
[window setContentBorderThickness:effectiveTopContentBorderThickness forEdge:NSMaxYEdge];
[window setAutorecalculatesContentBorderThickness:NO forEdge:NSMaxYEdge];
diff --git a/src/plugins/platforms/cocoa/qiosurfacegraphicsbuffer.h b/src/plugins/platforms/cocoa/qiosurfacegraphicsbuffer.h
new file mode 100644
index 0000000000..872773cb7a
--- /dev/null
+++ b/src/plugins/platforms/cocoa/qiosurfacegraphicsbuffer.h
@@ -0,0 +1,77 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 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 QIOSURFACEGRAPHICSBUFFER_H
+#define QIOSURFACEGRAPHICSBUFFER_H
+
+#include <qpa/qplatformgraphicsbuffer.h>
+#include <private/qcore_mac_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class QIOSurfaceGraphicsBuffer : public QPlatformGraphicsBuffer
+{
+public:
+ QIOSurfaceGraphicsBuffer(const QSize &size, const QPixelFormat &format, QCFType<CGColorSpaceRef> colorSpace);
+ ~QIOSurfaceGraphicsBuffer();
+
+ const uchar *data() const override;
+ uchar *data() override;
+ int bytesPerLine() const override;
+
+ IOSurfaceRef surface();
+ bool isInUse() const;
+
+protected:
+ bool doLock(AccessTypes access, const QRect &rect) override;
+ void doUnlock() override;
+
+private:
+ QCFType<IOSurfaceRef> m_surface;
+
+ friend QDebug operator<<(QDebug, const QIOSurfaceGraphicsBuffer *);
+};
+
+#ifndef QT_NO_DEBUG_STREAM
+QDebug operator<<(QDebug, const QIOSurfaceGraphicsBuffer *);
+#endif
+
+QT_END_NAMESPACE
+
+#endif // QIOSURFACEGRAPHICSBUFFER_H
diff --git a/src/plugins/platforms/cocoa/qiosurfacegraphicsbuffer.mm b/src/plugins/platforms/cocoa/qiosurfacegraphicsbuffer.mm
new file mode 100644
index 0000000000..a367487e85
--- /dev/null
+++ b/src/plugins/platforms/cocoa/qiosurfacegraphicsbuffer.mm
@@ -0,0 +1,188 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 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 "qiosurfacegraphicsbuffer.h"
+
+#include <QtCore/qdebug.h>
+#include <QtCore/qloggingcategory.h>
+
+#include <CoreGraphics/CoreGraphics.h>
+#include <IOSurface/IOSurface.h>
+
+// CGColorSpaceCopyPropertyList is available on 10.12 and above,
+// but was only added in the 10.14 SDK, so declare it just in case.
+extern "C" CFPropertyListRef CGColorSpaceCopyPropertyList(CGColorSpaceRef space);
+
+QT_BEGIN_NAMESPACE
+
+Q_LOGGING_CATEGORY(lcQpaIOSurface, "qt.qpa.backingstore.iosurface");
+
+QIOSurfaceGraphicsBuffer::QIOSurfaceGraphicsBuffer(const QSize &size, const QPixelFormat &format, QCFType<CGColorSpaceRef> colorSpace)
+ : QPlatformGraphicsBuffer(size, format)
+{
+ const size_t width = size.width();
+ const size_t height = size.height();
+
+ Q_ASSERT(width <= IOSurfaceGetPropertyMaximum(kIOSurfaceWidth));
+ Q_ASSERT(height <= IOSurfaceGetPropertyMaximum(kIOSurfaceHeight));
+
+ static const char bytesPerElement = 4;
+
+ const size_t bytesPerRow = IOSurfaceAlignProperty(kIOSurfaceBytesPerRow, width * bytesPerElement);
+ const size_t totalBytes = IOSurfaceAlignProperty(kIOSurfaceAllocSize, height * bytesPerRow);
+
+ NSDictionary *options = @{
+ (id)kIOSurfaceWidth: @(width),
+ (id)kIOSurfaceHeight: @(height),
+ (id)kIOSurfacePixelFormat: @(unsigned('BGRA')),
+ (id)kIOSurfaceBytesPerElement: @(bytesPerElement),
+ (id)kIOSurfaceBytesPerRow: @(bytesPerRow),
+ (id)kIOSurfaceAllocSize: @(totalBytes),
+ };
+
+ m_surface = IOSurfaceCreate((CFDictionaryRef)options);
+ Q_ASSERT(m_surface);
+
+ Q_ASSERT(size_t(bytesPerLine()) == bytesPerRow);
+ Q_ASSERT(size_t(byteCount()) == totalBytes);
+
+ if (colorSpace) {
+ IOSurfaceSetValue(m_surface, CFSTR("IOSurfaceColorSpace"),
+ QCFType<CFPropertyListRef>(CGColorSpaceCopyPropertyList(colorSpace)));
+ }
+}
+
+QIOSurfaceGraphicsBuffer::~QIOSurfaceGraphicsBuffer()
+{
+}
+
+const uchar *QIOSurfaceGraphicsBuffer::data() const
+{
+ return (const uchar *)IOSurfaceGetBaseAddress(m_surface);
+}
+
+uchar *QIOSurfaceGraphicsBuffer::data()
+{
+ return (uchar *)IOSurfaceGetBaseAddress(m_surface);
+}
+
+int QIOSurfaceGraphicsBuffer::bytesPerLine() const
+{
+ return IOSurfaceGetBytesPerRow(m_surface);
+}
+
+IOSurfaceRef QIOSurfaceGraphicsBuffer::surface()
+{
+ return m_surface;
+}
+
+bool QIOSurfaceGraphicsBuffer::isInUse() const
+{
+ return IOSurfaceIsInUse(m_surface);
+}
+
+IOSurfaceLockOptions lockOptionsForAccess(QPlatformGraphicsBuffer::AccessTypes access)
+{
+ IOSurfaceLockOptions lockOptions = 0;
+ if (!(access & QPlatformGraphicsBuffer::SWWriteAccess))
+ lockOptions |= kIOSurfaceLockReadOnly;
+ return lockOptions;
+}
+
+bool QIOSurfaceGraphicsBuffer::doLock(AccessTypes access, const QRect &rect)
+{
+ Q_UNUSED(rect);
+ Q_ASSERT(!isLocked());
+
+ qCDebug(lcQpaIOSurface) << "Locking" << this << "for" << access;
+
+ // FIXME: Teach QPlatformBackingStore::composeAndFlush about non-2D texture
+ // targets, so that we can use CGLTexImageIOSurface2D to support TextureAccess.
+ if (access & (TextureAccess | HWCompositor))
+ return false;
+
+ auto lockOptions = lockOptionsForAccess(access);
+
+ // Try without read-back first
+ lockOptions |= kIOSurfaceLockAvoidSync;
+ kern_return_t ret = IOSurfaceLock(m_surface, lockOptions, nullptr);
+ if (ret == kIOSurfaceSuccess)
+ return true;
+
+ if (ret == kIOReturnCannotLock) {
+ qCWarning(lcQpaIOSurface) << "Locking of" << this << "requires read-back";
+ lockOptions ^= kIOSurfaceLockAvoidSync;
+ ret = IOSurfaceLock(m_surface, lockOptions, nullptr);
+ }
+
+ if (ret != kIOSurfaceSuccess) {
+ qCWarning(lcQpaIOSurface) << "Failed to lock" << this << ret;
+ return false;
+ }
+
+ return true;
+}
+
+void QIOSurfaceGraphicsBuffer::doUnlock()
+{
+ qCDebug(lcQpaIOSurface) << "Unlocking" << this << "from" << isLocked();
+
+ auto lockOptions = lockOptionsForAccess(isLocked());
+ bool success = IOSurfaceUnlock(m_surface, lockOptions, nullptr) == kIOSurfaceSuccess;
+ Q_ASSERT_X(success, "QIOSurfaceGraphicsBuffer", "Unlocking surface should succeed");
+}
+
+#ifndef QT_NO_DEBUG_STREAM
+QDebug operator<<(QDebug debug, const QIOSurfaceGraphicsBuffer *graphicsBuffer)
+{
+ QDebugStateSaver saver(debug);
+ debug.nospace();
+ debug << "QIOSurfaceGraphicsBuffer(" << (const void *)graphicsBuffer;
+ if (graphicsBuffer) {
+ debug << ", surface=" << graphicsBuffer->m_surface;
+ debug << ", size=" << graphicsBuffer->size();
+ debug << ", isLocked=" << bool(graphicsBuffer->isLocked());
+ debug << ", isInUse=" << graphicsBuffer->isInUse();
+ }
+ debug << ')';
+ return debug;
+}
+#endif // !QT_NO_DEBUG_STREAM
+
+QT_END_NAMESPACE
diff --git a/src/plugins/platforms/cocoa/qmacclipboard.mm b/src/plugins/platforms/cocoa/qmacclipboard.mm
index 5939003c64..ba6cfca219 100644
--- a/src/plugins/platforms/cocoa/qmacclipboard.mm
+++ b/src/plugins/platforms/cocoa/qmacclipboard.mm
@@ -49,6 +49,7 @@
#include <stdlib.h>
#include <string.h>
#include "qcocoahelpers.h"
+#include <type_traits>
QT_BEGIN_NAMESPACE
@@ -61,6 +62,23 @@ QT_BEGIN_NAMESPACE
QMacPasteboard code
*****************************************************************************/
+namespace
+{
+OSStatus PasteboardGetItemCountSafe(PasteboardRef paste, ItemCount *cnt)
+{
+ Q_ASSERT(paste);
+ Q_ASSERT(cnt);
+ const OSStatus result = PasteboardGetItemCount(paste, cnt);
+ // Despite being declared unsigned, this API can return -1
+ if (std::make_signed<ItemCount>::type(*cnt) < 0)
+ *cnt = 0;
+ return result;
+}
+} // namespace
+
+// Ensure we don't call the broken one later on
+#define PasteboardGetItemCount
+
class QMacMimeData : public QMimeData
{
public:
@@ -210,7 +228,7 @@ QMacPasteboard::hasOSType(int c_flavor) const
sync();
ItemCount cnt = 0;
- if (PasteboardGetItemCount(paste, &cnt) || !cnt)
+ if (PasteboardGetItemCountSafe(paste, &cnt) || !cnt)
return false;
#ifdef DEBUG_PASTEBOARD
@@ -257,7 +275,7 @@ QMacPasteboard::hasFlavor(QString c_flavor) const
sync();
ItemCount cnt = 0;
- if (PasteboardGetItemCount(paste, &cnt) || !cnt)
+ if (PasteboardGetItemCountSafe(paste, &cnt) || !cnt)
return false;
#ifdef DEBUG_PASTEBOARD
@@ -374,7 +392,7 @@ QMacPasteboard::formats() const
QStringList ret;
ItemCount cnt = 0;
- if (PasteboardGetItemCount(paste, &cnt) || !cnt)
+ if (PasteboardGetItemCountSafe(paste, &cnt) || !cnt)
return ret;
#ifdef DEBUG_PASTEBOARD
@@ -417,7 +435,7 @@ QMacPasteboard::hasFormat(const QString &format) const
sync();
ItemCount cnt = 0;
- if (PasteboardGetItemCount(paste, &cnt) || !cnt)
+ if (PasteboardGetItemCountSafe(paste, &cnt) || !cnt)
return false;
#ifdef DEBUG_PASTEBOARD
@@ -460,7 +478,7 @@ QMacPasteboard::retrieveData(const QString &format, QVariant::Type) const
sync();
ItemCount cnt = 0;
- if (PasteboardGetItemCount(paste, &cnt) || !cnt)
+ if (PasteboardGetItemCountSafe(paste, &cnt) || !cnt)
return QByteArray();
#ifdef DEBUG_PASTEBOARD
diff --git a/src/plugins/platforms/cocoa/qnsview.mm b/src/plugins/platforms/cocoa/qnsview.mm
index 9bd53ed334..5309449dce 100644
--- a/src/plugins/platforms/cocoa/qnsview.mm
+++ b/src/plugins/platforms/cocoa/qnsview.mm
@@ -68,10 +68,12 @@
// Private interface
@interface QT_MANGLE_NAMESPACE(QNSView) ()
- (BOOL)isTransparentForUserInput;
+@property (assign) NSView* previousSuperview;
+@property (assign) NSWindow* previousWindow;
@end
@interface QT_MANGLE_NAMESPACE(QNSView) (Drawing) <CALayerDelegate>
-- (BOOL)wantsLayerHelper;
+- (void)initDrawing;
@end
@interface QT_MANGLE_NAMESPACE(QNSViewMouseMoveHelper) : NSObject
@@ -83,6 +85,7 @@
@end
@interface QT_MANGLE_NAMESPACE(QNSView) (Mouse)
+- (void)initMouse;
- (NSPoint)screenMousePoint:(NSEvent *)theEvent;
- (void)mouseMovedImpl:(NSEvent *)theEvent;
- (void)mouseEnteredImpl:(NSEvent *)theEvent;
@@ -112,7 +115,6 @@
@implementation QT_MANGLE_NAMESPACE(QNSView) {
QPointer<QCocoaWindow> m_platformWindow;
- NSTrackingArea *m_trackingArea;
Qt::MouseButtons m_buttons;
Qt::MouseButtons m_acceptedMouseDowns;
Qt::MouseButtons m_frameStrutButtons;
@@ -135,36 +137,19 @@
{
if ((self = [super initWithFrame:NSZeroRect])) {
m_platformWindow = platformWindow;
- m_buttons = Qt::NoButton;
- m_acceptedMouseDowns = Qt::NoButton;
- m_frameStrutButtons = Qt::NoButton;
m_sendKeyEvent = false;
- m_sendUpAsRightButton = false;
m_inputSource = nil;
- m_mouseMoveHelper = [[QT_MANGLE_NAMESPACE(QNSViewMouseMoveHelper) alloc] initWithView:self];
m_resendKeyEvent = false;
- m_scrolling = false;
m_updatingDrag = false;
m_currentlyInterpretedKeyEvent = nil;
- m_dontOverrideCtrlLMB = qt_mac_resolveOption(false, platformWindow->window(),
- "_q_platform_MacDontOverrideCtrlLMB", "QT_MAC_DONT_OVERRIDE_CTRL_LMB");
- m_trackingArea = nil;
self.focusRingType = NSFocusRingTypeNone;
- self.cursor = nil;
- self.wantsLayer = [self wantsLayerHelper];
-
- // 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 application wants to have a "custom" viewport.
- // (like the hellogl example)
- if (m_platformWindow->window()->supportsOpenGL()) {
- self.wantsBestResolutionOpenGLSurface = qt_mac_resolveOption(YES, m_platformWindow->window(),
- "_q_mac_wantsBestResolutionOpenGLSurface", "QT_MAC_WANTS_BEST_RESOLUTION_OPENGL_SURFACE");
- // See also QCocoaGLContext::makeCurrent for software renderer workarounds.
- }
+ self.previousSuperview = nil;
+ self.previousWindow = nil;
+
+ [self initDrawing];
+ [self initMouse];
[self registerDragTypes];
[[NSNotificationCenter defaultCenter] addObserver:self
@@ -177,10 +162,8 @@
- (void)dealloc
{
- if (m_trackingArea) {
- [self removeTrackingArea:m_trackingArea];
- [m_trackingArea release];
- }
+ qCDebug(lcQpaWindow) << "Deallocating" << self;
+
[m_inputSource release];
[[NSNotificationCenter defaultCenter] removeObserver:self];
[m_mouseMoveHelper release];
@@ -204,8 +187,40 @@
return description;
}
+// ----------------------------- Re-parenting ---------------------------------
+
+- (void)removeFromSuperview
+{
+ QMacAutoReleasePool pool;
+ [super removeFromSuperview];
+}
+
+- (void)viewWillMoveToSuperview:(NSView *)newSuperview
+{
+ Q_ASSERT(!self.previousSuperview);
+ self.previousSuperview = self.superview;
+
+ if (newSuperview == self.superview)
+ qCDebug(lcQpaWindow) << "Re-ordering" << self << "inside" << self.superview;
+ else
+ qCDebug(lcQpaWindow) << "Re-parenting" << self << "from" << self.superview << "to" << newSuperview;
+}
+
- (void)viewDidMoveToSuperview
{
+ auto cleanup = qScopeGuard([&] { self.previousSuperview = nil; });
+
+ if (self.superview == self.previousSuperview) {
+ qCDebug(lcQpaWindow) << "Done re-ordering" << self << "new index:"
+ << [self.superview.subviews indexOfObject:self];
+ return;
+ }
+
+ qCDebug(lcQpaWindow) << "Done re-parenting" << self << "into" << self.superview;
+
+ // Note: at this point the view's window property hasn't been updated to match the window
+ // of the new superview. We have to wait for viewDidMoveToWindow for that to be reflected.
+
if (!m_platformWindow)
return;
@@ -219,6 +234,36 @@
}
}
+- (void)viewWillMoveToWindow:(NSWindow *)newWindow
+{
+ Q_ASSERT(!self.previousWindow);
+ self.previousWindow = self.window;
+
+ // This callback is documented to be called also when a view is just moved between
+ // subviews in the same NSWindow, so we're not necessarily moving between NSWindows.
+ if (newWindow == self.window)
+ return;
+
+ qCDebug(lcQpaWindow) << "Moving" << self << "from" << self.window << "to" << newWindow;
+
+ // Note: at this point the superview has already been updated, so we know which view inside
+ // the new window the view will be a child of.
+}
+
+- (void)viewDidMoveToWindow
+{
+ auto cleanup = qScopeGuard([&] { self.previousWindow = nil; });
+
+ // This callback is documented to be called also when a view is just moved between
+ // subviews in the same NSWindow, so we're not necessarily moving between NSWindows.
+ if (self.window == self.previousWindow)
+ return;
+
+ qCDebug(lcQpaWindow) << "Done moving" << self << "to" << self.window;
+}
+
+// ----------------------------------------------------------------------------
+
- (QWindow *)topLevelWindow
{
if (!m_platformWindow)
@@ -248,12 +293,6 @@
// viewDidUnhide so no reason to override it here.
}
-- (void)removeFromSuperview
-{
- QMacAutoReleasePool pool;
- [super removeFromSuperview];
-}
-
- (BOOL)isTransparentForUserInput
{
return m_platformWindow->window() &&
diff --git a/src/plugins/platforms/cocoa/qnsview_complextext.mm b/src/plugins/platforms/cocoa/qnsview_complextext.mm
index d357082d33..6ff9b26ca4 100644
--- a/src/plugins/platforms/cocoa/qnsview_complextext.mm
+++ b/src/plugins/platforms/cocoa/qnsview_complextext.mm
@@ -307,8 +307,8 @@
{
Q_UNUSED(textInputContextKeyboardSelectionDidChangeNotification)
if (([NSApp keyWindow] == self.window) && self.window.firstResponder == self) {
- QCocoaInputContext *ic = qobject_cast<QCocoaInputContext *>(QCocoaIntegration::instance()->inputContext());
- ic->updateLocale();
+ if (QCocoaInputContext *ic = qobject_cast<QCocoaInputContext *>(QCocoaIntegration::instance()->inputContext()))
+ ic->updateLocale();
}
}
diff --git a/src/plugins/platforms/cocoa/qnsview_dragging.mm b/src/plugins/platforms/cocoa/qnsview_dragging.mm
index 1c38c5326c..002cb3279e 100644
--- a/src/plugins/platforms/cocoa/qnsview_dragging.mm
+++ b/src/plugins/platforms/cocoa/qnsview_dragging.mm
@@ -57,9 +57,9 @@
NSFilesPromisePboardType, NSInkTextPboardType,
NSMultipleTextSelectionPboardType, mimeTypeGeneric]];
- // Add custom types supported by the application.
+ // Add custom types supported by the application
for (const QString &customType : qt_mac_enabledDraggedTypes())
- [supportedTypes addObject:customType.toNSString()];
+ [supportedTypes addObject:customType.toNSString()];
[self registerForDraggedTypes:supportedTypes];
}
@@ -79,11 +79,11 @@ static QPoint mapWindowCoordinates(QWindow *source, QWindow *target, QPoint poin
return target->mapFromGlobal(source->mapToGlobal(point));
}
-- (NSDragOperation)draggingSession:(NSDraggingSession *)session
- sourceOperationMaskForDraggingContext:(NSDraggingContext)context
+- (NSDragOperation)draggingSession:(NSDraggingSession *)session sourceOperationMaskForDraggingContext:(NSDraggingContext)context
{
Q_UNUSED(session);
Q_UNUSED(context);
+
QCocoaDrag* nativeDrag = QCocoaIntegration::instance()->drag();
return qt_mac_mapDropActions(nativeDrag->currentDrag()->supportedActions());
}
@@ -134,30 +134,29 @@ static QPoint mapWindowCoordinates(QWindow *source, QWindow *target, QPoint poin
if (pixmapCursor.isNull()) {
switch (response.acceptedAction()) {
- case Qt::CopyAction:
- nativeCursor = [NSCursor dragCopyCursor];
- break;
- case Qt::LinkAction:
- nativeCursor = [NSCursor dragLinkCursor];
- break;
- case Qt::IgnoreAction:
- // Uncomment the next lines if forbiden cursor wanted on non droppable targets.
- /*nativeCursor = [NSCursor operationNotAllowedCursor];
- break;*/
- case Qt::MoveAction:
- default:
- nativeCursor = [NSCursor arrowCursor];
- break;
+ case Qt::CopyAction:
+ nativeCursor = [NSCursor dragCopyCursor];
+ break;
+ case Qt::LinkAction:
+ nativeCursor = [NSCursor dragLinkCursor];
+ break;
+ case Qt::IgnoreAction:
+ // Uncomment the next lines if forbidden cursor is wanted on undroppable targets.
+ /*nativeCursor = [NSCursor operationNotAllowedCursor];
+ break;*/
+ case Qt::MoveAction:
+ default:
+ nativeCursor = [NSCursor arrowCursor];
+ break;
}
- }
- else {
+ } else {
NSImage *nsimage = qt_mac_create_nsimage(pixmapCursor);
nsimage.size = NSSizeFromCGSize((pixmapCursor.size() / pixmapCursor.devicePixelRatioF()).toCGSize());
nativeCursor = [[NSCursor alloc] initWithImage:nsimage hotSpot:NSZeroPoint];
[nsimage release];
}
- // change the cursor
+ // Change the cursor
[nativeCursor set];
// Make sure the cursor is updated correctly if the mouse does not move and window is under cursor
@@ -169,39 +168,33 @@ static QPoint mapWindowCoordinates(QWindow *source, QWindow *target, QPoint poin
if (m_updatingDrag)
return;
- const QPoint mousePos(QCursor::pos());
- CGEventRef moveEvent(CGEventCreateMouseEvent(
- NULL, kCGEventMouseMoved,
- CGPointMake(mousePos.x(), mousePos.y()),
+ QCFType<CGEventRef> moveEvent = CGEventCreateMouseEvent(
+ nullptr, kCGEventMouseMoved, QCursor::pos().toCGPoint(),
kCGMouseButtonLeft // ignored
- ));
+ );
CGEventPost(kCGHIDEventTap, moveEvent);
- CFRelease(moveEvent);
}
-- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
+- (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)sender
{
- return [self handleDrag : sender];
+ return [self handleDrag:(QEvent::DragEnter) sender:sender];
}
-- (NSDragOperation)draggingUpdated:(id <NSDraggingInfo>)sender
+- (NSDragOperation)draggingUpdated:(id<NSDraggingInfo>)sender
{
- m_updatingDrag = true;
- const NSDragOperation ret([self handleDrag : sender]);
- m_updatingDrag = false;
-
- return ret;
+ QScopedValueRollback<bool> rollback(m_updatingDrag, true);
+ return [self handleDrag:(QEvent::DragMove) sender:sender];
}
// Sends drag update to Qt, return the action
-- (NSDragOperation)handleDrag:(id <NSDraggingInfo>)sender
+- (NSDragOperation)handleDrag:(QEvent::Type)dragType sender:(id<NSDraggingInfo>)sender
{
if (!m_platformWindow)
return NSDragOperationNone;
- NSPoint windowPoint = [self convertPoint: [sender draggingLocation] fromView: nil];
- QPoint qt_windowPoint(windowPoint.x, windowPoint.y);
- Qt::DropActions qtAllowed = qt_mac_mapNSDragOperations([sender draggingSourceOperationMask]);
+ QPoint windowPoint = QPointF::fromCGPoint([self convertPoint:sender.draggingLocation fromView:nil]).toPoint();
+
+ Qt::DropActions qtAllowed = qt_mac_mapNSDragOperations(sender.draggingSourceOperationMask);
QWindow *target = findEventTargetWindow(m_platformWindow->window());
if (!target)
@@ -209,7 +202,12 @@ static QPoint mapWindowCoordinates(QWindow *source, QWindow *target, QPoint poin
const auto modifiers = [QNSView convertKeyModifiers:NSApp.currentEvent.modifierFlags];
const auto buttons = currentlyPressedMouseButtons();
- const auto point = mapWindowCoordinates(m_platformWindow->window(), target, qt_windowPoint);
+ const auto point = mapWindowCoordinates(m_platformWindow->window(), target, windowPoint);
+
+ if (dragType == QEvent::DragEnter)
+ qCDebug(lcQpaMouse) << dragType << self << "at" << windowPoint;
+ else
+ qCDebug(lcQpaMouse) << dragType << "at" << windowPoint << "with" << buttons;
QPlatformDragQtResponse response(false, Qt::IgnoreAction, QRect());
QCocoaDrag* nativeDrag = QCocoaIntegration::instance()->drag();
@@ -219,7 +217,7 @@ static QPoint mapWindowCoordinates(QWindow *source, QWindow *target, QPoint poin
point, qtAllowed, buttons, modifiers);
[self updateCursorFromDragResponse:response drag:nativeDrag];
} else {
- QCocoaDropData mimeData([sender draggingPasteboard]);
+ QCocoaDropData mimeData(sender.draggingPasteboard);
response = QWindowSystemInterface::handleDrag(target, &mimeData,
point, qtAllowed, buttons, modifiers);
}
@@ -227,7 +225,7 @@ static QPoint mapWindowCoordinates(QWindow *source, QWindow *target, QPoint poin
return qt_mac_mapDropAction(response.acceptedAction());
}
-- (void)draggingExited:(id <NSDraggingInfo>)sender
+- (void)draggingExited:(id<NSDraggingInfo>)sender
{
if (!m_platformWindow)
return;
@@ -236,17 +234,18 @@ static QPoint mapWindowCoordinates(QWindow *source, QWindow *target, QPoint poin
if (!target)
return;
- NSPoint windowPoint = [self convertPoint: [sender draggingLocation] fromView: nil];
- QPoint qt_windowPoint(windowPoint.x, windowPoint.y);
+ QPoint windowPoint = QPointF::fromCGPoint([self convertPoint:sender.draggingLocation fromView:nil]).toPoint();
+
+ qCDebug(lcQpaMouse) << QEvent::DragLeave << self << "at" << windowPoint;
// Send 0 mime data to indicate drag exit
QWindowSystemInterface::handleDrag(target, nullptr,
- mapWindowCoordinates(m_platformWindow->window(), target, qt_windowPoint),
+ mapWindowCoordinates(m_platformWindow->window(), target, windowPoint),
Qt::IgnoreAction, Qt::NoButton, Qt::NoModifier);
}
-// called on drop, send the drop to Qt and return if it was accepted.
-- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
+// Called on drop, send the drop to Qt and return if it was accepted
+- (BOOL)performDragOperation:(id<NSDraggingInfo>)sender
{
if (!m_platformWindow)
return false;
@@ -255,31 +254,31 @@ static QPoint mapWindowCoordinates(QWindow *source, QWindow *target, QPoint poin
if (!target)
return false;
- NSPoint windowPoint = [self convertPoint: [sender draggingLocation] fromView: nil];
- QPoint qt_windowPoint(windowPoint.x, windowPoint.y);
- Qt::DropActions qtAllowed = qt_mac_mapNSDragOperations([sender draggingSourceOperationMask]);
+ QPoint windowPoint = QPointF::fromCGPoint([self convertPoint:sender.draggingLocation fromView:nil]).toPoint();
+
+ Qt::DropActions qtAllowed = qt_mac_mapNSDragOperations(sender.draggingSourceOperationMask);
QPlatformDropQtResponse response(false, Qt::IgnoreAction);
QCocoaDrag* nativeDrag = QCocoaIntegration::instance()->drag();
const auto modifiers = [QNSView convertKeyModifiers:NSApp.currentEvent.modifierFlags];
const auto buttons = currentlyPressedMouseButtons();
- const auto point = mapWindowCoordinates(m_platformWindow->window(), target, qt_windowPoint);
+ const auto point = mapWindowCoordinates(m_platformWindow->window(), target, windowPoint);
+
+ qCDebug(lcQpaMouse) << QEvent::Drop << "at" << windowPoint << "with" << buttons;
if (nativeDrag->currentDrag()) {
// The drag was started from within the application
response = QWindowSystemInterface::handleDrop(target, nativeDrag->dragMimeData(),
point, qtAllowed, buttons, modifiers);
} else {
- QCocoaDropData mimeData([sender draggingPasteboard]);
+ QCocoaDropData mimeData(sender.draggingPasteboard);
response = QWindowSystemInterface::handleDrop(target, &mimeData,
point, qtAllowed, buttons, modifiers);
}
return response.isAccepted();
}
-- (void)draggingSession:(NSDraggingSession *)session
- endedAtPoint:(NSPoint)screenPoint
- operation:(NSDragOperation)operation
+- (void)draggingSession:(NSDraggingSession *)session endedAtPoint:(NSPoint)screenPoint operation:(NSDragOperation)operation
{
Q_UNUSED(session);
Q_UNUSED(screenPoint);
@@ -295,6 +294,8 @@ static QPoint mapWindowCoordinates(QWindow *source, QWindow *target, QPoint poin
nativeDrag->setAcceptedAction(qt_mac_mapNSDragOperation(operation));
m_buttons = currentlyPressedMouseButtons();
+
+ qCDebug(lcQpaMouse) << "Drag session" << session << "ended, with" << m_buttons;
}
@end
diff --git a/src/plugins/platforms/cocoa/qnsview_drawing.mm b/src/plugins/platforms/cocoa/qnsview_drawing.mm
index 4f9d17504d..cb1799b039 100644
--- a/src/plugins/platforms/cocoa/qnsview_drawing.mm
+++ b/src/plugins/platforms/cocoa/qnsview_drawing.mm
@@ -41,6 +41,24 @@
@implementation QT_MANGLE_NAMESPACE(QNSView) (Drawing)
+- (void)initDrawing
+{
+ self.wantsLayer = [self layerExplicitlyRequested]
+ || [self shouldUseMetalLayer]
+ || [self layerEnabledByMacOS];
+
+ // 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 application wants to have a "custom" viewport.
+ // (like the hellogl example)
+ if (m_platformWindow->window()->supportsOpenGL()) {
+ self.wantsBestResolutionOpenGLSurface = qt_mac_resolveOption(YES, m_platformWindow->window(),
+ "_q_mac_wantsBestResolutionOpenGLSurface", "QT_MAC_WANTS_BEST_RESOLUTION_OPENGL_SURFACE");
+ // See also QCocoaGLContext::makeCurrent for software renderer workarounds.
+ }
+}
+
- (BOOL)isOpaque
{
if (!m_platformWindow)
@@ -71,23 +89,38 @@
m_platformWindow->handleExposeEvent(exposedRegion);
}
-- (BOOL)shouldUseMetalLayer
+- (BOOL)layerEnabledByMacOS
{
- // MetalSurface needs a layer, and so does VulkanSurface (via MoltenVK)
- QSurface::SurfaceType surfaceType = m_platformWindow->window()->surfaceType();
- return surfaceType == QWindow::MetalSurface || surfaceType == QWindow::VulkanSurface;
+ // AppKit has its own logic for this, but if we rely on that, our layers are created
+ // by AppKit at a point where we've already set up other parts of the platform plugin
+ // based on the presence of layers or not. Once we've rewritten these parts to support
+ // dynamically picking up layer enablement we can let AppKit do its thing.
+ return QMacVersion::buildSDK() >= QOperatingSystemVersion::MacOSMojave
+ && QMacVersion::currentRuntime() >= QOperatingSystemVersion::MacOSMojave;
}
-- (BOOL)wantsLayerHelper
+- (BOOL)layerExplicitlyRequested
{
- Q_ASSERT(m_platformWindow);
+ static bool wantsLayer = [&]() {
+ int wantsLayer = qt_mac_resolveOption(-1, m_platformWindow->window(),
+ "_q_mac_wantsLayer", "QT_MAC_WANTS_LAYER");
- bool wantsLayer = qt_mac_resolveOption(true, m_platformWindow->window(),
- "_q_mac_wantsLayer", "QT_MAC_WANTS_LAYER");
+ if (wantsLayer != -1 && [self layerEnabledByMacOS]) {
+ qCWarning(lcQpaDrawing) << "Layer-backing cannot be explicitly controlled on 10.14 when built against the 10.14 SDK";
+ return true;
+ }
- bool layerForSurfaceType = [self shouldUseMetalLayer];
+ return wantsLayer == 1;
+ }();
+
+ return wantsLayer;
+}
- return wantsLayer || layerForSurfaceType;
+- (BOOL)shouldUseMetalLayer
+{
+ // MetalSurface needs a layer, and so does VulkanSurface (via MoltenVK)
+ QSurface::SurfaceType surfaceType = m_platformWindow->window()->surfaceType();
+ return surfaceType == QWindow::MetalSurface || surfaceType == QWindow::VulkanSurface;
}
- (CALayer *)makeBackingLayer
@@ -115,6 +148,14 @@
return [super makeBackingLayer];
}
+- (void)setLayer:(CALayer *)layer
+{
+ qCDebug(lcQpaDrawing) << "Making" << self << "layer-backed with" << layer
+ << "due to being" << ([self layerExplicitlyRequested] ? "explicitly requested"
+ : [self shouldUseMetalLayer] ? "needed by surface type" : "enabled by macOS");
+ [super setLayer:layer];
+}
+
- (NSViewLayerContentsRedrawPolicy)layerContentsRedrawPolicy
{
// We need to set this explicitly since the super implementation
@@ -122,6 +163,15 @@
return NSViewLayerContentsRedrawDuringViewResize;
}
+#if 0 // Disabled until we enable lazy backingstore resizing
+- (NSViewLayerContentsPlacement)layerContentsPlacement
+{
+ // Always place the layer at top left without any automatic scaling,
+ // so that we can re-use larger layers when resizing a window down.
+ return NSViewLayerContentsPlacementTopLeft;
+}
+#endif
+
- (void)updateMetalLayerDrawableSize:(CAMetalLayer *)layer
{
CGSize drawableSize = layer.bounds.size;
diff --git a/src/plugins/platforms/cocoa/qnsview_keys.mm b/src/plugins/platforms/cocoa/qnsview_keys.mm
index 28db532ddc..ad751279bb 100644
--- a/src/plugins/platforms/cocoa/qnsview_keys.mm
+++ b/src/plugins/platforms/cocoa/qnsview_keys.mm
@@ -53,7 +53,7 @@
qtMods |= Qt::AltModifier;
if (modifierFlags & NSEventModifierFlagCommand)
qtMods |= dontSwapCtrlAndMeta ? Qt::MetaModifier : Qt::ControlModifier;
- if (modifierFlags & NSEventModifierFlagCommand)
+ if (modifierFlags & NSEventModifierFlagNumericPad)
qtMods |= Qt::KeypadModifier;
return qtMods;
}
diff --git a/src/plugins/platforms/cocoa/qnsview_mouse.mm b/src/plugins/platforms/cocoa/qnsview_mouse.mm
index 3d6471005d..0ab09b97d3 100644
--- a/src/plugins/platforms/cocoa/qnsview_mouse.mm
+++ b/src/plugins/platforms/cocoa/qnsview_mouse.mm
@@ -39,6 +39,22 @@
// This file is included from qnsview.mm, and only used to organize the code
+/*
+ The reason for using this helper is to ensure that QNSView doesn't implement
+ the NSResponder callbacks for mouseEntered, mouseExited, and mouseMoved.
+
+ If it did, we would get mouse events though the responder chain as well,
+ for example if a subview has a tracking area of its own and calls super
+ in the handler, which results in forwarding the event though the responder
+ chain. The same applies if NSWindow.acceptsMouseMovedEvents is YES.
+
+ By having a helper as the target for our tracking areas, we know for sure
+ that the events we are getting stem from our own tracking areas.
+
+ FIXME: Ideally we wouldn't need this workaround, and would correctly
+ interact with the responder chain by e.g. calling super if Qt does not
+ accept the mouse event
+*/
@implementation QT_MANGLE_NAMESPACE(QNSViewMouseMoveHelper) {
QNSView *view;
}
@@ -77,6 +93,7 @@
- (void)resetMouseButtons
{
+ qCDebug(lcQpaMouse) << "Reseting mouse buttons";
m_buttons = Qt::NoButton;
m_frameStrutButtons = Qt::NoButton;
}
@@ -129,12 +146,50 @@
QPoint qtScreenPoint = QCocoaScreen::mapFromNative(screenPoint).toPoint();
ulong timestamp = [theEvent timestamp] * 1000;
+
+ auto eventType = cocoaEvent2QtMouseEvent(theEvent);
+ qCInfo(lcQpaMouse) << "Frame-strut" << eventType << "at" << qtWindowPoint << "with" << m_frameStrutButtons << "in" << self.window;
QWindowSystemInterface::handleFrameStrutMouseEvent(m_platformWindow->window(), timestamp, qtWindowPoint, qtScreenPoint, m_frameStrutButtons);
}
@end
@implementation QT_MANGLE_NAMESPACE(QNSView) (Mouse)
+- (void)initMouse
+{
+ m_buttons = Qt::NoButton;
+ m_acceptedMouseDowns = Qt::NoButton;
+ m_frameStrutButtons = Qt::NoButton;
+
+ m_scrolling = false;
+ self.cursor = nil;
+
+ m_sendUpAsRightButton = false;
+ m_dontOverrideCtrlLMB = qt_mac_resolveOption(false, m_platformWindow->window(),
+ "_q_platform_MacDontOverrideCtrlLMB", "QT_MAC_DONT_OVERRIDE_CTRL_LMB");
+
+ m_mouseMoveHelper = [[QT_MANGLE_NAMESPACE(QNSViewMouseMoveHelper) alloc] initWithView:self];
+
+ NSUInteger trackingOptions = NSTrackingActiveInActiveApp
+ | NSTrackingMouseEnteredAndExited | NSTrackingCursorUpdate;
+
+ // Ideally, NSTrackingMouseMoved should be turned on only if QWidget::mouseTracking
+ // is enabled, hover is on, or a tool tip is set. Unfortunately, Qt will send "tooltip"
+ // events on mouse moves, so we need to turn it on in ALL case. That means EVERY QWindow
+ // gets to pay the cost of mouse moves delivered to it (Apple recommends keeping it OFF
+ // because there is a performance hit).
+ trackingOptions |= NSTrackingMouseMoved;
+
+ // Using NSTrackingInVisibleRect means AppKit will automatically synchronize the
+ // tracking rect with changes in the view's visible area, so leave it undefined.
+ trackingOptions |= NSTrackingInVisibleRect;
+ static const NSRect trackingRect = NSZeroRect;
+
+ QMacAutoReleasePool pool;
+ [self addTrackingArea:[[[NSTrackingArea alloc] initWithRect:trackingRect
+ options:trackingOptions owner:m_mouseMoveHelper userInfo:nil] autorelease]];
+}
+
- (BOOL)acceptsFirstMouse:(NSEvent *)theEvent
{
Q_UNUSED(theEvent)
@@ -142,6 +197,11 @@
return NO;
if ([self isTransparentForUserInput])
return NO;
+ QPointF windowPoint;
+ QPointF screenPoint;
+ [self convertFromScreen:[NSEvent mouseLocation] toWindowPoint: &windowPoint andScreenPoint: &screenPoint];
+ if (!qt_window_private(m_platformWindow->window())->allowClickThrough(screenPoint.toPoint()))
+ return NO;
return YES;
}
@@ -203,6 +263,11 @@
button = Qt::RightButton;
const auto eventType = cocoaEvent2QtMouseEvent(theEvent);
+ if (eventType == QEvent::MouseMove)
+ qCDebug(lcQpaMouse) << eventType << "at" << qtWindowPoint << "with" << buttons;
+ else
+ qCInfo(lcQpaMouse) << eventType << "of" << button << "at" << qtWindowPoint << "with" << buttons;
+
QWindowSystemInterface::handleMouseEvent(targetView->m_platformWindow->window(),
timestamp, qtWindowPoint, qtScreenPoint,
buttons, button, eventType, modifiers);
@@ -406,44 +471,18 @@
[super otherMouseUp:theEvent];
}
-- (void)updateTrackingAreas
-{
- [super updateTrackingAreas];
-
- QMacAutoReleasePool pool;
-
- // NSTrackingInVisibleRect keeps care of updating once the tracking is set up, so bail out early
- if (m_trackingArea && [[self trackingAreas] containsObject:m_trackingArea])
- return;
-
- // Ideally, we shouldn't have NSTrackingMouseMoved events included below, it should
- // only be turned on if mouseTracking, hover is on or a tool tip is set.
- // Unfortunately, Qt will send "tooltip" events on mouse moves, so we need to
- // turn it on in ALL case. That means EVERY QWindow gets to pay the cost of
- // mouse moves delivered to it (Apple recommends keeping it OFF because there
- // is a performance hit). So it goes.
- NSUInteger trackingOptions = NSTrackingMouseEnteredAndExited | NSTrackingActiveInActiveApp
- | NSTrackingInVisibleRect | NSTrackingMouseMoved | NSTrackingCursorUpdate;
- [m_trackingArea release];
- m_trackingArea = [[NSTrackingArea alloc] initWithRect:[self frame]
- options:trackingOptions
- owner:m_mouseMoveHelper
- userInfo:nil];
- [self addTrackingArea:m_trackingArea];
-}
-
- (void)cursorUpdate:(NSEvent *)theEvent
{
- qCDebug(lcQpaMouse) << "[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)
+ if (self.cursor && self.cursor != NSCursor.currentCursor) {
+ qCInfo(lcQpaMouse) << "Updating cursor for" << self << "to" << self.cursor;
[self.cursor set];
- else
+ } else {
[super cursorUpdate:theEvent];
+ }
}
- (void)mouseMovedImpl:(NSEvent *)theEvent
@@ -498,6 +537,8 @@
QPointF screenPoint;
[self convertFromScreen:[self screenMousePoint:theEvent] toWindowPoint:&windowPoint andScreenPoint:&screenPoint];
m_platformWindow->m_enterLeaveTargetWindow = m_platformWindow->childWindowAt(windowPoint.toPoint());
+
+ qCInfo(lcQpaMouse) << QEvent::Enter << self << "at" << windowPoint << "with" << currentlyPressedMouseButtons();
QWindowSystemInterface::handleEnterEvent(m_platformWindow->m_enterLeaveTargetWindow, windowPoint, screenPoint);
}
@@ -516,6 +557,7 @@
if (!m_platformWindow->isContentView())
return;
+ qCInfo(lcQpaMouse) << QEvent::Leave << self;
QWindowSystemInterface::handleLeaveEvent(m_platformWindow->m_enterLeaveTargetWindow);
m_platformWindow->m_enterLeaveTargetWindow = 0;
}
@@ -614,8 +656,10 @@
// "isInverted": natural OS X scrolling, inverted from the Qt/other platform/Jens perspective.
bool isInverted = [theEvent isDirectionInvertedFromDevice];
- qCDebug(lcQpaMouse) << "scroll wheel @ window pos" << qt_windowPoint << "delta px" << pixelDelta
- << "angle" << angleDelta << "phase" << phase << (isInverted ? "inverted" : "");
+ qCInfo(lcQpaMouse).nospace() << phase << " at " << qt_windowPoint
+ << " pixelDelta=" << pixelDelta << " angleDelta=" << angleDelta
+ << (isInverted ? " inverted=true" : "");
+
QWindowSystemInterface::handleWheelEvent(m_platformWindow->window(), qt_timestamp, qt_windowPoint,
qt_screenPoint, pixelDelta, angleDelta, m_currentWheelModifiers, phase, source, isInverted);
}
diff --git a/src/plugins/platforms/cocoa/qnswindow.h b/src/plugins/platforms/cocoa/qnswindow.h
index 64f1ed0802..5fc48d826f 100644
--- a/src/plugins/platforms/cocoa/qnswindow.h
+++ b/src/plugins/platforms/cocoa/qnswindow.h
@@ -60,14 +60,10 @@ QT_FORWARD_DECLARE_CLASS(QCocoaWindow)
#define QNSWindowProtocol QT_MANGLE_NAMESPACE(QNSWindowProtocol)
@protocol QNSWindowProtocol
-@optional
-- (BOOL)canBecomeKeyWindow;
-- (void)sendEvent:(NSEvent*)theEvent;
+- (instancetype)initWithContentRect:(NSRect)contentRect styleMask:(NSWindowStyleMask)style
+ backing:(NSBackingStoreType)backingStoreType defer:(BOOL)flag screen:(NSScreen *)screen
+ platformWindow:(QCocoaWindow*)window;
- (void)closeAndRelease;
-- (void)dealloc;
-- (BOOL)isOpaque;
-- (NSColor *)backgroundColor;
-- (NSString *)description;
@property (nonatomic, readonly) QCocoaWindow *platformWindow;
@end
diff --git a/src/plugins/platforms/cocoa/qnswindow.mm b/src/plugins/platforms/cocoa/qnswindow.mm
index 1b9dd95cbc..52f765eb31 100644
--- a/src/plugins/platforms/cocoa/qnswindow.mm
+++ b/src/plugins/platforms/cocoa/qnswindow.mm
@@ -37,6 +37,8 @@
**
****************************************************************************/
+#if !defined(QNSWINDOW_PROTOCOL_IMPLMENTATION)
+
#include "qnswindow.h"
#include "qcocoawindow.h"
#include "qcocoahelpers.h"
@@ -89,44 +91,104 @@ static bool isMouseEvent(NSEvent *ev)
}
@end
-#define super USE_qt_objcDynamicSuper_INSTEAD
-
@implementation QNSWindow
+#define QNSWINDOW_PROTOCOL_IMPLMENTATION 1
+#include "qnswindow.mm"
+#undef QNSWINDOW_PROTOCOL_IMPLMENTATION
-+ (void)load
++ (void)applicationActivationChanged:(NSNotification*)notification
{
- const Class windowClass = [self class];
- const Class panelClass = [QNSPanel class];
-
- unsigned int protocolCount;
- Protocol **protocols = class_copyProtocolList(windowClass, &protocolCount);
- for (unsigned int i = 0; i < protocolCount; ++i) {
- Protocol *protocol = protocols[i];
-
- unsigned int methodDescriptionsCount;
- objc_method_description *methods = protocol_copyMethodDescriptionList(
- protocol, NO, YES, &methodDescriptionsCount);
-
- for (unsigned int j = 0; j < methodDescriptionsCount; ++j) {
- objc_method_description method = methods[j];
- class_addMethod(panelClass, method.name,
- class_getMethodImplementation(windowClass, method.name),
- method.types);
+ const id sender = self;
+ NSEnumerator<NSWindow*> *windowEnumerator = nullptr;
+ NSApplication *application = [NSApplication sharedApplication];
+
+ // Unfortunately there's no NSWindowListOrderedBackToFront,
+ // so we have to manually reverse the order using an array.
+ NSMutableArray<NSWindow *> *windows = [NSMutableArray<NSWindow *> new];
+ [application enumerateWindowsWithOptions:NSWindowListOrderedFrontToBack
+ usingBlock:^(NSWindow *window, BOOL *) {
+ // For some reason AppKit will give us nil-windows, skip those
+ if (!window)
+ return;
+
+ [windows addObject:window];
}
- free(methods);
+ ];
+
+ windowEnumerator = windows.reverseObjectEnumerator;
+
+ for (NSWindow *window in windowEnumerator) {
+ // We're meddling with normal and floating windows, so leave others alone
+ if (!(window.level == NSNormalWindowLevel || window.level == NSFloatingWindowLevel))
+ continue;
+
+ // Windows that hide automatically will keep their NSFloatingWindowLevel,
+ // and hence be on top of the window stack. We don't want to affect these
+ // windows, as otherwise we might end up with key windows being ordered
+ // behind these auto-hidden windows when activating the application by
+ // clicking on a new tool window.
+ if (window.hidesOnDeactivate)
+ continue;
+
+ if ([window conformsToProtocol:@protocol(QNSWindowProtocol)]) {
+ QCocoaWindow *cocoaWindow = static_cast<QCocoaNSWindow *>(window).platformWindow;
+ window.level = notification.name == NSApplicationWillResignActiveNotification ?
+ NSNormalWindowLevel : cocoaWindow->windowLevel(cocoaWindow->window()->flags());
+ }
+
+ // The documentation says that "when a window enters a new level, it’s ordered
+ // in front of all its peers in that level", but that doesn't seem to be the
+ // case in practice. To keep the order correct after meddling with the window
+ // levels, we explicitly order each window to the front. Since we are iterating
+ // the windows in back-to-front order, this is okey. The call also triggers AppKit
+ // to re-evaluate the level in relation to windows from other applications,
+ // working around an issue where our tool windows would stay on top of other
+ // application windows if activation was transferred to another application by
+ // clicking on it instead of via the application switcher or Dock. Finally, we
+ // do this re-ordering for all windows (except auto-hiding ones), otherwise we would
+ // end up triggering a bug in AppKit where the tool windows would disappear behind
+ // the application window.
+ [window orderFront:sender];
}
+}
+
+@end
+
+@implementation QNSPanel
+#define QNSWINDOW_PROTOCOL_IMPLMENTATION 1
+#include "qnswindow.mm"
+#undef QNSWINDOW_PROTOCOL_IMPLMENTATION
+@end
+
+#else // QNSWINDOW_PROTOCOL_IMPLMENTATION
- free(protocols);
+// The following content is mixed in to the QNSWindow and QNSPanel classes via includes
+
+{
+ // Member variables
+ QPointer<QCocoaWindow> m_platformWindow;
+}
+
+- (instancetype)initWithContentRect:(NSRect)contentRect styleMask:(NSWindowStyleMask)style
+ backing:(NSBackingStoreType)backingStoreType defer:(BOOL)defer screen:(NSScreen *)screen
+ platformWindow:(QCocoaWindow*)window
+{
+ // Initializing the window will end up in [NSWindow _commonAwake], which calls many
+ // of the getters below. We need to set up the platform window reference first, so
+ // we can properly reflect the window's state during initialization.
+ m_platformWindow = window;
+
+ return [super initWithContentRect:contentRect styleMask:style backing:backingStoreType defer:defer screen:screen];
}
- (QCocoaWindow *)platformWindow
{
- return qnsview_cast(self.contentView).platformWindow;
+ return m_platformWindow;
}
- (NSString *)description
{
- NSMutableString *description = [NSMutableString stringWithString:qt_objcDynamicSuper()];
+ NSMutableString *description = [NSMutableString stringWithString:[super description]];
#ifndef QT_NO_DEBUG_STREAM
QString contentViewDescription;
@@ -142,16 +204,15 @@ static bool isMouseEvent(NSEvent *ev)
- (BOOL)canBecomeKeyWindow
{
- QCocoaWindow *pw = self.platformWindow;
- if (!pw)
+ if (!m_platformWindow)
return NO;
- if (pw->shouldRefuseKeyWindowAndFirstResponder())
+ if (m_platformWindow->shouldRefuseKeyWindowAndFirstResponder())
return NO;
if ([self isKindOfClass:[QNSPanel class]]) {
// Only tool or dialog windows should become key:
- Qt::WindowType type = pw->window()->type();
+ Qt::WindowType type = m_platformWindow->window()->type();
if (type == Qt::Tool || type == Qt::Dialog)
return YES;
@@ -170,17 +231,26 @@ static bool isMouseEvent(NSEvent *ev)
// Windows with a transient parent (such as combobox popup windows)
// cannot become the main window:
- QCocoaWindow *pw = self.platformWindow;
- if (!pw || pw->window()->transientParent())
+ if (!m_platformWindow || m_platformWindow->window()->transientParent())
canBecomeMain = NO;
return canBecomeMain;
}
+- (BOOL)worksWhenModal
+{
+ if (m_platformWindow && [self isKindOfClass:[QNSPanel class]]) {
+ Qt::WindowType type = m_platformWindow->window()->type();
+ if (type == Qt::Popup || type == Qt::Dialog || type == Qt::Tool)
+ return YES;
+ }
+
+ return [super worksWhenModal];
+}
+
- (BOOL)isOpaque
{
- return self.platformWindow ?
- self.platformWindow->isOpaque() : qt_objcDynamicSuper();
+ return m_platformWindow ? m_platformWindow->isOpaque() : [super isOpaque];
}
/*!
@@ -196,7 +266,7 @@ static bool isMouseEvent(NSEvent *ev)
- (NSColor *)backgroundColor
{
return self.styleMask == NSWindowStyleMaskBorderless
- ? [NSColor clearColor] : qt_objcDynamicSuper();
+ ? [NSColor clearColor] : [super backgroundColor];
}
- (void)sendEvent:(NSEvent*)theEvent
@@ -208,7 +278,7 @@ static bool isMouseEvent(NSEvent *ev)
// 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)
+ if (!m_platformWindow)
return;
// Prevent deallocation of this NSWindow during event delivery, as we
@@ -216,106 +286,38 @@ static bool isMouseEvent(NSEvent *ev)
[[self retain] autorelease];
const char *eventType = object_getClassName(theEvent);
- if (QWindowSystemInterface::handleNativeEvent(self.platformWindow->window(),
+ if (QWindowSystemInterface::handleNativeEvent(m_platformWindow->window(),
QByteArray::fromRawData(eventType, qstrlen(eventType)), theEvent, nullptr)) {
return;
}
- qt_objcDynamicSuper(theEvent);
+ [super sendEvent:theEvent];
- if (!self.platformWindow)
+ if (!m_platformWindow)
return; // Platform window went away while processing event
- QCocoaWindow *pw = self.platformWindow;
- if (pw->frameStrutEventsEnabled() && isMouseEvent(theEvent)) {
+ if (m_platformWindow->frameStrutEventsEnabled() && isMouseEvent(theEvent)) {
NSPoint loc = [theEvent locationInWindow];
NSRect windowFrame = [self convertRectFromScreen:self.frame];
NSRect contentFrame = self.contentView.frame;
if (NSMouseInRect(loc, windowFrame, NO) && !NSMouseInRect(loc, contentFrame, NO))
- [qnsview_cast(pw->view()) handleFrameStrutMouseEvent:theEvent];
+ [qnsview_cast(m_platformWindow->view()) handleFrameStrutMouseEvent:theEvent];
}
}
- (void)closeAndRelease
{
- qCDebug(lcQpaWindow) << "closeAndRelease" << self;
-
- [self.delegate release];
- self.delegate = nil;
-
+ qCDebug(lcQpaWindow) << "Closing and releasing" << self;
[self close];
[self release];
}
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wobjc-missing-super-calls"
- (void)dealloc
{
- qCDebug(lcQpaWindow) << "dealloc" << self;
- qt_objcDynamicSuper();
-}
-#pragma clang diagnostic pop
-
-+ (void)applicationActivationChanged:(NSNotification*)notification
-{
- const id sender = self;
- NSEnumerator<NSWindow*> *windowEnumerator = nullptr;
- NSApplication *application = [NSApplication sharedApplication];
-
- // Unfortunately there's no NSWindowListOrderedBackToFront,
- // so we have to manually reverse the order using an array.
- NSMutableArray<NSWindow *> *windows = [NSMutableArray<NSWindow *> new];
- [application enumerateWindowsWithOptions:NSWindowListOrderedFrontToBack
- usingBlock:^(NSWindow *window, BOOL *) {
- // For some reason AppKit will give us nil-windows, skip those
- if (!window)
- return;
-
- [windows addObject:window];
- }
- ];
-
- windowEnumerator = windows.reverseObjectEnumerator;
-
- for (NSWindow *window in windowEnumerator) {
- // We're meddling with normal and floating windows, so leave others alone
- if (!(window.level == NSNormalWindowLevel || window.level == NSFloatingWindowLevel))
- continue;
-
- // Windows that hide automatically will keep their NSFloatingWindowLevel,
- // and hence be on top of the window stack. We don't want to affect these
- // windows, as otherwise we might end up with key windows being ordered
- // behind these auto-hidden windows when activating the application by
- // clicking on a new tool window.
- if (window.hidesOnDeactivate)
- continue;
-
- if ([window conformsToProtocol:@protocol(QNSWindowProtocol)]) {
- QCocoaWindow *cocoaWindow = static_cast<QCocoaNSWindow *>(window).platformWindow;
- window.level = notification.name == NSApplicationWillResignActiveNotification ?
- NSNormalWindowLevel : cocoaWindow->windowLevel(cocoaWindow->window()->flags());
- }
+ qCDebug(lcQpaWindow) << "Deallocating" << self;
+ self.delegate = nil;
- // 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];
- }
+ [super dealloc];
}
-@end
-
-@implementation QNSPanel
-// Implementation shared with QNSWindow, see +[QNSWindow load] above
-@end
-
-#undef super
+#endif
diff --git a/src/plugins/platforms/cocoa/qnswindowdelegate.h b/src/plugins/platforms/cocoa/qnswindowdelegate.h
index e71afcbb2a..be870deb3a 100644
--- a/src/plugins/platforms/cocoa/qnswindowdelegate.h
+++ b/src/plugins/platforms/cocoa/qnswindowdelegate.h
@@ -48,9 +48,6 @@ class QCocoaWindow;
QT_END_NAMESPACE
@interface QT_MANGLE_NAMESPACE(QNSWindowDelegate) : NSObject <NSWindowDelegate>
-
-- (instancetype)initWithQCocoaWindow:(QT_PREPEND_NAMESPACE(QCocoaWindow) *)cocoaWindow;
-
@end
QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSWindowDelegate);
diff --git a/src/plugins/platforms/cocoa/qnswindowdelegate.mm b/src/plugins/platforms/cocoa/qnswindowdelegate.mm
index 97309ea990..087cb3651f 100644
--- a/src/plugins/platforms/cocoa/qnswindowdelegate.mm
+++ b/src/plugins/platforms/cocoa/qnswindowdelegate.mm
@@ -49,23 +49,17 @@
static QRegExp whitespaceRegex = QRegExp(QStringLiteral("\\s*"));
-@implementation QNSWindowDelegate {
- QCocoaWindow *m_cocoaWindow;
-}
-
-- (instancetype)initWithQCocoaWindow:(QCocoaWindow *)cocoaWindow
+static QCocoaWindow *toPlatformWindow(NSWindow *window)
{
- if ((self = [self init]))
- m_cocoaWindow = cocoaWindow;
- return self;
+ return qnsview_cast(window.contentView).platformWindow;
}
-- (BOOL)windowShouldClose:(NSNotification *)notification
+@implementation QNSWindowDelegate
+
+- (BOOL)windowShouldClose:(NSWindow *)window
{
- Q_UNUSED(notification);
- if (m_cocoaWindow) {
- return m_cocoaWindow->windowShouldClose();
- }
+ if (QCocoaWindow *platformWindow = toPlatformWindow(window))
+ return platformWindow->windowShouldClose();
return YES;
}
@@ -79,14 +73,16 @@ static QRegExp whitespaceRegex = QRegExp(QStringLiteral("\\s*"));
- (NSRect)windowWillUseStandardFrame:(NSWindow *)window defaultFrame:(NSRect)proposedFrame
{
Q_UNUSED(proposedFrame);
- Q_ASSERT(window == m_cocoaWindow->nativeWindow());
- const QWindow *w = m_cocoaWindow->window();
+
+ QCocoaWindow *platformWindow = toPlatformWindow(window);
+ Q_ASSERT(platformWindow);
+ const QWindow *w = platformWindow->window();
// maximumSize() refers to the client size, but AppKit expects the full frame size
QSizeF maximumSize = w->maximumSize() + QSize(0, w->frameMargins().top());
// The window should never be larger than the current screen geometry
- const QRectF screenGeometry = m_cocoaWindow->screen()->geometry();
+ const QRectF screenGeometry = platformWindow->screen()->geometry();
maximumSize = maximumSize.boundedTo(screenGeometry.size());
// Use the current frame position for the initial maximized frame,
@@ -106,55 +102,29 @@ static QRegExp whitespaceRegex = QRegExp(QStringLiteral("\\s*"));
return QCocoaScreen::mapToNative(maximizedFrame);
}
-#pragma clang diagnostic push
-// NSDisableScreenUpdates and NSEnableScreenUpdates are deprecated, but the
-// NSAnimationContext API that replaces them doesn't handle the use-case of
-// cross-thread screen update synchronization.
-#pragma clang diagnostic ignored "-Wdeprecated-declarations"
-- (NSSize)windowWillResize:(NSWindow *)window toSize:(NSSize)frameSize
-{
- qCDebug(lcQpaWindow) << window << "will resize to" << QSizeF::fromCGSize(frameSize)
- << "- disabling screen updates temporarily";
-
- // There may be separate threads rendering to CA layers in this window,
- // and if any of them do a swap while the resize is still in progress,
- // the visual bounds of that layer will be updated before the visual
- // bounds of the window frame, resulting in flickering while resizing.
-
- // To prevent this we disable screen updates for the whole process until
- // the resize is complete, which makes the whole thing visually atomic.
- NSDisableScreenUpdates();
-
- return frameSize;
-}
-
-- (void)windowDidResize:(NSNotification *)notification
-{
- NSWindow *window = notification.object;
- qCDebug(lcQpaWindow) << window << "was resized - re-enabling screen updates";
- NSEnableScreenUpdates();
-}
-#pragma clang diagnostic pop
-
- (BOOL)window:(NSWindow *)window shouldPopUpDocumentPathMenu:(NSMenu *)menu
{
- Q_UNUSED(window);
Q_UNUSED(menu);
+ QCocoaWindow *platformWindow = toPlatformWindow(window);
+ Q_ASSERT(platformWindow);
+
// 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());
+ return !whitespaceRegex.exactMatch(platformWindow->window()->filePath());
}
- (BOOL)window:(NSWindow *)window shouldDragDocumentWithEvent:(NSEvent *)event from:(NSPoint)dragImageLocation withPasteboard:(NSPasteboard *)pasteboard
{
- Q_UNUSED(window);
Q_UNUSED(event);
Q_UNUSED(dragImageLocation);
Q_UNUSED(pasteboard);
+ QCocoaWindow *platformWindow = toPlatformWindow(window);
+ Q_ASSERT(platformWindow);
+
// 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());
+ return !whitespaceRegex.exactMatch(platformWindow->window()->filePath());
}
@end
diff --git a/src/plugins/platforms/cocoa/qpaintengine_mac.mm b/src/plugins/platforms/cocoa/qpaintengine_mac.mm
index 96506c67fa..3677877538 100644
--- a/src/plugins/platforms/cocoa/qpaintengine_mac.mm
+++ b/src/plugins/platforms/cocoa/qpaintengine_mac.mm
@@ -47,7 +47,6 @@
#include <private/qpaintengine_raster_p.h>
#include <qprinter.h>
#include <qstack.h>
-#include <qtextcodec.h>
#include <qwidget.h>
#include <qvarlengtharray.h>
#include <qdebug.h>
@@ -314,7 +313,6 @@ static void qt_mac_draw_pattern(void *info, CGContextRef c)
}
pat->image = qt_mac_create_imagemask(pm, pm.rect());
CGImageRelease(swatch);
- CGContextRelease(pm_ctx);
w *= QMACPATTERN_MASK_MULTIPLIER;
h *= QMACPATTERN_MASK_MULTIPLIER;
#endif
@@ -916,7 +914,6 @@ void QCoreGraphicsPaintEngine::drawTextItem(const QPointF &pos, const QTextItem
QFontEngine *fe = ti.fontEngine;
const bool textAA = ((state->renderHints() & QPainter::TextAntialiasing)
- && (fe->fontDef.pointSize > QCoreTextFontEngine::antialiasingThreshold)
&& !(fe->fontDef.styleStrategy & QFont::NoAntialias));
const bool lineAA = state->renderHints() & QPainter::Antialiasing;
if (textAA != lineAA)