summaryrefslogtreecommitdiffstats
path: root/src/plugins/platforms
diff options
context:
space:
mode:
authorTor Arne Vestbø <tor.arne.vestbo@qt.io>2017-08-17 19:01:40 +0200
committerTor Arne Vestbø <tor.arne.vestbo@qt.io>2017-08-24 23:54:43 +0000
commita980250a666bc87e5db006b8668c6af9340915f2 (patch)
treed9c3f6eca8a9941cf734dca4deea07f272515835 /src/plugins/platforms
parentf537dc0da288949c4df903c1f2b21156e62fbae5 (diff)
macOS: Deduplicate QNSWindow/QNSPanel code
By sharing the implementations of the methods between QNSWindow and QNSPanel we don't need a helper, and can remove duplicated code. This duplication would expand in the future, as for each method added to the QNSWindowProtocol, we would have to add forwarding functions in both QNSWindow and QNSPanel, forwarding to QNSWindowHelper, and then two more functions in QNSWindow and QNSPanel in case we wanted to call super from the helper, similar to [QNSWindow superSendEvent]. The only snag is that calls to super are hard-coded to a specific superclass during complication, so we provide our wrapper for objc_msgSendSuper that resolves the superclass at runtime. The helper class QSendSuperHelper provides compile time implicit instantiation of the right template without having to provide the return type as a template argument, via operator T and a fallback for the case of no return type via the destructor. Change-Id: Iaf13f27675d90f884470f5005270ea0d9d0316f3 Reviewed-by: Simon Hausmann <simon.hausmann@qt.io>
Diffstat (limited to 'src/plugins/platforms')
-rw-r--r--src/plugins/platforms/cocoa/qcocoahelpers.h129
-rw-r--r--src/plugins/platforms/cocoa/qcocoaintegration.mm2
-rw-r--r--src/plugins/platforms/cocoa/qcocoawindow.mm27
-rw-r--r--src/plugins/platforms/cocoa/qnswindow.h50
-rw-r--r--src/plugins/platforms/cocoa/qnswindow.mm237
5 files changed, 232 insertions, 213 deletions
diff --git a/src/plugins/platforms/cocoa/qcocoahelpers.h b/src/plugins/platforms/cocoa/qcocoahelpers.h
index 4478895538..1f4f9cd276 100644
--- a/src/plugins/platforms/cocoa/qcocoahelpers.h
+++ b/src/plugins/platforms/cocoa/qcocoahelpers.h
@@ -55,6 +55,9 @@
#include <QtGui/qpalette.h>
#include <QtGui/qscreen.h>
+#include <objc/runtime.h>
+#include <objc/message.h>
+
Q_FORWARD_DECLARE_OBJC_CLASS(QT_MANGLE_NAMESPACE(QNSView));
QT_BEGIN_NAMESPACE
@@ -188,5 +191,131 @@ QT_END_NAMESPACE
QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSPanelContentsWrapper);
+// -------------------------------------------------------------------------
+
+// Depending on the ABI of the platform, we may need to use objc_msgSendSuper_stret:
+// - http://www.sealiesoftware.com/blog/archive/2008/10/30/objc_explain_objc_msgSend_stret.html
+// - https://lists.apple.com/archives/cocoa-dev/2008/Feb/msg02338.html
+template <typename T>
+struct objc_msgsend_requires_stret
+{ static const bool value =
+#if defined(Q_PROCESSOR_X86)
+ // Any return value larger than two registers on i386/x86_64
+ sizeof(T) > sizeof(void*) * 2;
+#elif defined(Q_PROCESSOR_ARM_32)
+ // Any return value larger than a single register on arm
+ sizeof(T) > sizeof(void*);
+#elif defined(Q_PROCESSOR_ARM_64)
+ // Stret not used on arm64
+ false;
+#endif
+};
+
+template <>
+struct objc_msgsend_requires_stret<void>
+{ static const bool value = false; };
+
+template <typename ReturnType, typename... Args>
+ReturnType qt_msgSendSuper(id receiver, SEL selector, Args... args)
+{
+ static_assert(!objc_msgsend_requires_stret<ReturnType>::value,
+ "The given return type requires stret on this platform");
+
+ typedef ReturnType (*SuperFn)(objc_super *, SEL, Args...);
+ SuperFn superFn = reinterpret_cast<SuperFn>(objc_msgSendSuper);
+ objc_super sup = { receiver, class_getSuperclass(object_getClass(receiver)) };
+ return superFn(&sup, selector, args...);
+}
+
+template <typename ReturnType, typename... Args>
+ReturnType qt_msgSendSuper_stret(id receiver, SEL selector, Args... args)
+{
+ static_assert(objc_msgsend_requires_stret<ReturnType>::value,
+ "The given return type does not use stret on this platform");
+
+ typedef void (*SuperStretFn)(ReturnType *, objc_super *, SEL, Args...);
+ SuperStretFn superStretFn = reinterpret_cast<SuperStretFn>(objc_msgSendSuper_stret);
+
+ objc_super sup = { receiver, class_getSuperclass(object_getClass(receiver)) };
+ ReturnType ret;
+ superStretFn(&ret, &sup, selector, args...);
+ return ret;
+}
+
+template<typename... Args>
+class QSendSuperHelper {
+public:
+ QSendSuperHelper(id receiver, SEL sel, Args... args)
+ : m_receiver(receiver), m_selector(sel), m_args(std::make_tuple(args...)), m_sent(false)
+ {
+ }
+
+ ~QSendSuperHelper()
+ {
+ if (!m_sent)
+ msgSendSuper<void>(m_args);
+ }
+
+ template <typename ReturnType>
+ operator ReturnType()
+ {
+#if defined(QT_DEBUG)
+ Method method = class_getInstanceMethod(object_getClass(m_receiver), m_selector);
+ char returnTypeEncoding[256];
+ method_getReturnType(method, returnTypeEncoding, sizeof(returnTypeEncoding));
+ NSUInteger alignedReturnTypeSize = 0;
+ NSGetSizeAndAlignment(returnTypeEncoding, nullptr, &alignedReturnTypeSize);
+ Q_ASSERT(alignedReturnTypeSize == sizeof(ReturnType));
+#endif
+ m_sent = true;
+ return msgSendSuper<ReturnType>(m_args);
+ }
+
+private:
+ template <std::size_t... Ts>
+ struct index {};
+
+ template <std::size_t N, std::size_t... Ts>
+ struct gen_seq : gen_seq<N - 1, N - 1, Ts...> {};
+
+ template <std::size_t... Ts>
+ struct gen_seq<0, Ts...> : index<Ts...> {};
+
+ template <typename ReturnType, bool V>
+ using if_requires_stret = typename std::enable_if<objc_msgsend_requires_stret<ReturnType>::value == V, ReturnType>::type;
+
+ template <typename ReturnType, std::size_t... Is>
+ if_requires_stret<ReturnType, false> msgSendSuper(std::tuple<Args...>& args, index<Is...>)
+ {
+ return qt_msgSendSuper<ReturnType>(m_receiver, m_selector, std::get<Is>(args)...);
+ }
+
+ template <typename ReturnType, std::size_t... Is>
+ if_requires_stret<ReturnType, true> msgSendSuper(std::tuple<Args...>& args, index<Is...>)
+ {
+ return qt_msgSendSuper_stret<ReturnType>(m_receiver, m_selector, std::get<Is>(args)...);
+ }
+
+ template <typename ReturnType>
+ ReturnType msgSendSuper(std::tuple<Args...>& args)
+ {
+ return msgSendSuper<ReturnType>(args, gen_seq<sizeof...(Args)>{});
+ }
+
+ id m_receiver;
+ SEL m_selector;
+ std::tuple<Args...> m_args;
+ bool m_sent;
+};
+
+template<typename... Args>
+QSendSuperHelper<Args...> qt_objcDynamicSuperHelper(id receiver, SEL selector, Args... args)
+{
+ return QSendSuperHelper<Args...>(receiver, selector, args...);
+}
+
+// Same as calling super, but the super_class field resolved at runtime instead of compile time
+#define qt_objcDynamicSuper(...) qt_objcDynamicSuperHelper(self, _cmd, ##__VA_ARGS__)
+
#endif //QCOCOAHELPERS_H
diff --git a/src/plugins/platforms/cocoa/qcocoaintegration.mm b/src/plugins/platforms/cocoa/qcocoaintegration.mm
index 1df74c986a..7b1e689388 100644
--- a/src/plugins/platforms/cocoa/qcocoaintegration.mm
+++ b/src/plugins/platforms/cocoa/qcocoaintegration.mm
@@ -218,7 +218,7 @@ QWindow *QCocoaScreen::topLevelAt(const QPoint &point) const
continue;
id<QNSWindowProtocol> proto = static_cast<id<QNSWindowProtocol> >(nsWindow);
- QCocoaWindow *cocoaWindow = proto.helper.platformWindow;
+ QCocoaWindow *cocoaWindow = proto.platformWindow;
if (!cocoaWindow)
continue;
window = cocoaWindow->window();
diff --git a/src/plugins/platforms/cocoa/qcocoawindow.mm b/src/plugins/platforms/cocoa/qcocoawindow.mm
index d3f26df6c5..e906f0fd1c 100644
--- a/src/plugins/platforms/cocoa/qcocoawindow.mm
+++ b/src/plugins/platforms/cocoa/qcocoawindow.mm
@@ -210,7 +210,6 @@ QCocoaWindow::~QCocoaWindow()
QMacAutoReleasePool pool;
[m_nsWindow makeFirstResponder:nil];
[m_nsWindow setContentView:nil];
- [m_nsWindow.helper detachFromPlatformWindow];
if ([m_view superview])
[m_view removeFromSuperview];
@@ -1279,10 +1278,34 @@ QCocoaNSWindow *QCocoaWindow::createNSWindow(bool shouldBePanel)
// Create NSWindow
Class windowClass = shouldBePanel ? [QNSPanel class] : [QNSWindow class];
QCocoaNSWindow *nsWindow = [[windowClass alloc] initWithContentRect:frame
- screen:cocoaScreen->nativeScreen() styleMask:windowStyleMask(flags) qPlatformWindow:this];
+ styleMask:windowStyleMask(flags)
+ // Deferring window creation breaks OpenGL (the GL context is
+ // set up before the window is shown and needs a proper window)
+ backing:NSBackingStoreBuffered defer:NO
+ screen:cocoaScreen->nativeScreen()];
+
Q_ASSERT_X(nsWindow.screen == cocoaScreen->nativeScreen(), "QCocoaWindow",
"Resulting NSScreen should match the requested NSScreen");
+ nsWindow.delegate = [[QNSWindowDelegate alloc] initWithQCocoaWindow:this];
+
+ // Prevent Cocoa from releasing the window on close. Qt
+ // handles the close event asynchronously and we want to
+ // make sure that NSWindow stays valid until the
+ // QCocoaWindow is deleted by Qt.
+ [nsWindow setReleasedWhenClosed:NO];
+
+ if (alwaysShowToolWindow()) {
+ static dispatch_once_t onceToken;
+ dispatch_once(&onceToken, ^{
+ NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
+ [center addObserver:[QNSWindow class] selector:@selector(applicationActivationChanged:)
+ name:NSApplicationWillResignActiveNotification object:nil];
+ [center addObserver:[QNSWindow class] selector:@selector(applicationActivationChanged:)
+ name:NSApplicationWillBecomeActiveNotification object:nil];
+ });
+ }
+
if (targetScreen != window()->screen())
QWindowSystemInterface::handleWindowScreenChanged(window(), targetScreen);
diff --git a/src/plugins/platforms/cocoa/qnswindow.h b/src/plugins/platforms/cocoa/qnswindow.h
index b13b6d42a9..ac9cbb978f 100644
--- a/src/plugins/platforms/cocoa/qnswindow.h
+++ b/src/plugins/platforms/cocoa/qnswindow.h
@@ -47,62 +47,26 @@
#include <AppKit/AppKit.h>
QT_FORWARD_DECLARE_CLASS(QCocoaWindow)
-Q_FORWARD_DECLARE_OBJC_CLASS(QT_MANGLE_NAMESPACE(QNSWindowHelper));
-
-// -------------------------------------------------------------------------
@interface NSWindow (FullScreenProperty)
@property(readonly) BOOL qt_fullScreen;
@end
-// -------------------------------------------------------------------------
-
@protocol QNSWindowProtocol
-
-@property (nonatomic, readonly) QT_MANGLE_NAMESPACE(QNSWindowHelper) *helper;
-
-- (id)initWithContentRect:(NSRect)contentRect screen:(NSScreen*)screen
- styleMask:(NSUInteger)windowStyle qPlatformWindow:(QCocoaWindow *)qpw;
-
-- (void)superSendEvent:(NSEvent *)theEvent;
+@optional
+- (BOOL)canBecomeKeyWindow;
+- (void)sendEvent:(NSEvent*)theEvent;
- (void)closeAndRelease;
-
-@end
-
-typedef NSWindow<QNSWindowProtocol> QCocoaNSWindow;
-
-// -------------------------------------------------------------------------
-
-@interface QT_MANGLE_NAMESPACE(QNSWindowHelper) : NSObject
-{
- QPointer<QCocoaWindow> _platformWindow;
-}
-
-@property (nonatomic, readonly) QCocoaNSWindow *window;
+- (void)dealloc;
@property (nonatomic, readonly) QCocoaWindow *platformWindow;
-
-- (id)initWithNSWindow:(QCocoaNSWindow *)window platformWindow:(QCocoaWindow *)platformWindow;
-- (void)handleWindowEvent:(NSEvent *)theEvent;
-- (void)detachFromPlatformWindow;
-
@end
-QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSWindowHelper);
-
-// -------------------------------------------------------------------------
-
-@interface QT_MANGLE_NAMESPACE(QNSWindow) : NSWindow<QNSWindowProtocol>
-@end
+typedef NSWindow<QNSWindowProtocol> QCocoaNSWindow;
+@interface QT_MANGLE_NAMESPACE(QNSWindow) : NSWindow<QNSWindowProtocol> @end
QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSWindow);
-// -------------------------------------------------------------------------
-
-@interface QT_MANGLE_NAMESPACE(QNSPanel) : NSPanel<QNSWindowProtocol>
-@end
-
+@interface QT_MANGLE_NAMESPACE(QNSPanel) : NSPanel<QNSWindowProtocol> @end
QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSPanel);
-// -------------------------------------------------------------------------
-
#endif // QNSWINDOW_H
diff --git a/src/plugins/platforms/cocoa/qnswindow.mm b/src/plugins/platforms/cocoa/qnswindow.mm
index e44db3ff3b..e5ddd3ca0f 100644
--- a/src/plugins/platforms/cocoa/qnswindow.mm
+++ b/src/plugins/platforms/cocoa/qnswindow.mm
@@ -88,32 +88,72 @@ static bool isMouseEvent(NSEvent *ev)
}
@end
-@implementation QNSWindowHelper
+#define super USE_qt_objcDynamicSuper_INSTEAD
+
+@implementation QNSWindow
+
++ (void)load
+{
+ const Class windowClass = [self class];
+ const Class panelClass = [QNSPanel class];
+
+ unsigned int methodDescriptionsCount;
+ objc_method_description *methods = protocol_copyMethodDescriptionList(
+ objc_getProtocol("QNSWindowProtocol"), NO, YES, &methodDescriptionsCount);
+
+ for (unsigned int i = 0; i < methodDescriptionsCount; ++i) {
+ objc_method_description method = methods[i];
+ class_addMethod(panelClass, method.name,
+ class_getMethodImplementation(windowClass, method.name),
+ method.types);
+ }
+
+ free(methods);
+}
- (QCocoaWindow *)platformWindow
{
- return _platformWindow.data();
+ return qnsview_cast(self.contentView).platformWindow;
}
-- (id)initWithNSWindow:(QCocoaNSWindow *)window platformWindow:(QCocoaWindow *)platformWindow
+- (BOOL)canBecomeKeyWindow
{
- if (self = [super init]) {
- _window = window;
- _platformWindow = platformWindow;
+ QCocoaWindow *pw = self.platformWindow;
+ if (!pw)
+ return NO;
+
+ if (pw->shouldRefuseKeyWindowAndFirstResponder())
+ return NO;
- _window.delegate = [[QNSWindowDelegate alloc] initWithQCocoaWindow:_platformWindow];
+ if ([self isKindOfClass:[QNSPanel class]]) {
+ // Only tool or dialog windows should become key:
+ Qt::WindowType type = pw->window()->type();
+ if (type == Qt::Tool || type == Qt::Dialog)
+ return YES;
- // Prevent Cocoa from releasing the window on close. Qt
- // handles the close event asynchronously and we want to
- // make sure that NSWindow stays valid until the
- // QCocoaWindow is deleted by Qt.
- [_window setReleasedWhenClosed:NO];
+ return NO;
+ } else {
+ // The default implementation returns NO for title-bar less windows,
+ // override and return yes here to make sure popup windows such as
+ // the combobox popup can become the key window.
+ return YES;
}
+}
+
+- (BOOL)canBecomeMainWindow
+{
+ BOOL canBecomeMain = YES; // By default, windows can become the main window
- return self;
+ // Windows with a transient parent (such as combobox popup windows)
+ // cannot become the main window:
+ QCocoaWindow *pw = self.platformWindow;
+ if (!pw || pw->window()->transientParent())
+ canBecomeMain = NO;
+
+ return canBecomeMain;
}
-- (void)handleWindowEvent:(NSEvent *)theEvent
+- (void)sendEvent:(NSEvent*)theEvent
{
// We might get events for a NSWindow after the corresponding platform
// window has been deleted, as the NSWindow can outlive the QCocoaWindow
@@ -129,7 +169,7 @@ static bool isMouseEvent(NSEvent *ev)
return;
}
- [self.window superSendEvent:theEvent];
+ qt_objcDynamicSuper(theEvent);
if (!self.platformWindow)
return; // Platform window went away while processing event
@@ -137,108 +177,31 @@ static bool isMouseEvent(NSEvent *ev)
QCocoaWindow *pw = self.platformWindow;
if (pw->frameStrutEventsEnabled() && isMouseEvent(theEvent)) {
NSPoint loc = [theEvent locationInWindow];
- NSRect windowFrame = [self.window convertRectFromScreen:[self.window frame]];
- NSRect contentFrame = [[self.window contentView] frame];
+ NSRect windowFrame = [self convertRectFromScreen:self.frame];
+ NSRect contentFrame = self.contentView.frame;
if (NSMouseInRect(loc, windowFrame, NO) && !NSMouseInRect(loc, contentFrame, NO))
[qnsview_cast(pw->view()) handleFrameStrutMouseEvent:theEvent];
}
}
-- (void)detachFromPlatformWindow
-{
- _platformWindow.clear();
- [self.window.delegate release];
- self.window.delegate = nil;
-}
-
-- (void)dealloc
-{
- _window = nil;
- _platformWindow.clear();
- [super dealloc];
-}
-
-@end
-
-// Deferring window creation breaks OpenGL (the GL context is
-// set up before the window is shown and needs a proper window)
-static const bool kNoDefer = NO;
-
-@implementation QNSWindow
-
-@synthesize helper = _helper;
-
-- (id)initWithContentRect:(NSRect)contentRect
- screen:(NSScreen*)screen
- styleMask:(NSUInteger)windowStyle
- qPlatformWindow:(QCocoaWindow *)qpw
-{
- if (self = [super initWithContentRect:contentRect styleMask:windowStyle
- backing:NSBackingStoreBuffered defer:kNoDefer screen:screen]) {
- _helper = [[QNSWindowHelper alloc] initWithNSWindow:self platformWindow:qpw];
- }
- return self;
-}
-
-- (BOOL)canBecomeKeyWindow
-{
- QCocoaWindow *pw = self.helper.platformWindow;
- if (!pw)
- return NO;
-
- if (pw->shouldRefuseKeyWindowAndFirstResponder())
- return NO;
-
- // The default implementation returns NO for title-bar less windows,
- // override and return yes here to make sure popup windows such as
- // the combobox popup can become the key window.
- return YES;
-}
-
-- (BOOL)canBecomeMainWindow
-{
- BOOL canBecomeMain = YES; // By default, windows can become the main window
-
- // Windows with a transient parent (such as combobox popup windows)
- // cannot become the main window:
- QCocoaWindow *pw = self.helper.platformWindow;
- if (!pw || pw->window()->transientParent())
- canBecomeMain = NO;
-
- return canBecomeMain;
-}
-
-- (void)sendEvent:(NSEvent*)theEvent
-{
- [self.helper handleWindowEvent:theEvent];
-}
-
-- (void)superSendEvent:(NSEvent *)theEvent
-{
- [super sendEvent:theEvent];
-}
-
- (void)closeAndRelease
{
qCDebug(lcQpaCocoaWindow) << "closeAndRelease" << self;
- [self.helper detachFromPlatformWindow];
+ [self.delegate release];
+ self.delegate = nil;
+
[self close];
[self release];
}
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wobjc-missing-super-calls"
- (void)dealloc
{
- [_helper release];
- _helper = nil;
- [super dealloc];
+ qt_objcDynamicSuper();
}
-
-@end
-
-@implementation QNSPanel
-
-@synthesize helper = _helper;
+#pragma clang diagnostic pop
+ (void)applicationActivationChanged:(NSNotification*)notification
{
@@ -284,7 +247,7 @@ static const bool kNoDefer = NO;
continue;
if ([window conformsToProtocol:@protocol(QNSWindowProtocol)]) {
- QCocoaWindow *cocoaWindow = static_cast<id<QNSWindowProtocol>>(window).helper.platformWindow;
+ QCocoaWindow *cocoaWindow = static_cast<QCocoaNSWindow *>(window).platformWindow;
window.level = notification.name == NSApplicationWillResignActiveNotification ?
NSNormalWindowLevel : cocoaWindow->windowLevel(cocoaWindow->window()->flags());
}
@@ -305,70 +268,10 @@ static const bool kNoDefer = NO;
}
}
-- (id)initWithContentRect:(NSRect)contentRect
- screen:(NSScreen*)screen
- styleMask:(NSUInteger)windowStyle
- qPlatformWindow:(QCocoaWindow *)qpw
-{
- if (self = [super initWithContentRect:contentRect styleMask:windowStyle
- backing:NSBackingStoreBuffered defer:kNoDefer screen:screen]) {
- _helper = [[QNSWindowHelper alloc] initWithNSWindow:self platformWindow:qpw];
-
- if (qpw->alwaysShowToolWindow()) {
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
- [center addObserver:[self class] selector:@selector(applicationActivationChanged:)
- name:NSApplicationWillResignActiveNotification object:nil];
- [center addObserver:[self class] selector:@selector(applicationActivationChanged:)
- name:NSApplicationWillBecomeActiveNotification object:nil];
- });
- }
- }
- return self;
-}
-
-- (BOOL)canBecomeKeyWindow
-{
- QCocoaWindow *pw = self.helper.platformWindow;
- if (!pw)
- return NO;
-
- if (pw->shouldRefuseKeyWindowAndFirstResponder())
- return NO;
-
- // Only tool or dialog windows should become key:
- Qt::WindowType type = pw->window()->type();
- if (type == Qt::Tool || type == Qt::Dialog)
- return YES;
-
- return NO;
-}
-
-- (void)sendEvent:(NSEvent*)theEvent
-{
- [self.helper handleWindowEvent:theEvent];
-}
-
-- (void)superSendEvent:(NSEvent *)theEvent
-{
- [super sendEvent:theEvent];
-}
-
-- (void)closeAndRelease
-{
- qCDebug(lcQpaCocoaWindow) << "closeAndRelease" << self;
-
- [self.helper detachFromPlatformWindow];
- [self close];
- [self release];
-}
-
-- (void)dealloc
-{
- [_helper release];
- _helper = nil;
- [super dealloc];
-}
+@end
+@implementation QNSPanel
+// Implementation shared with QNSWindow, see +[QNSWindow load] above
@end
+
+#undef super