macOS: Implement QNSWindow/QNSPanel mixin using preprocessor includes

We want to share the implementation between the two classes, but
Objective-C doesn't natively have a mixin-feature. Instead of using
dynamic super-calls at runtime (which worked fine, but added
complexity), we now do the mixin at compile time using the
preprocessor.

The dynamic-super feature is left in, in case we need it in other
areas in the future.

Change-Id: I95dfa7f18cba86cc518e963dd018944ef113ac06
Reviewed-by: Timur Pocheptsov <timur.pocheptsov@qt.io>
bb10
Tor Arne Vestbø 2019-02-13 12:52:33 +01:00
parent 6273b484b3
commit 7319c342c3
2 changed files with 158 additions and 186 deletions

View File

@ -60,15 +60,7 @@ QT_FORWARD_DECLARE_CLASS(QCocoaWindow)
#define QNSWindowProtocol QT_MANGLE_NAMESPACE(QNSWindowProtocol)
@protocol QNSWindowProtocol
@optional
- (BOOL)canBecomeKeyWindow;
- (BOOL)worksWhenModal;
- (void)sendEvent:(NSEvent*)theEvent;
- (void)closeAndRelease;
- (void)dealloc;
- (BOOL)isOpaque;
- (NSColor *)backgroundColor;
- (NSString *)description;
@property (nonatomic, readonly) QCocoaWindow *platformWindow;
@end

View File

@ -37,6 +37,8 @@
**
****************************************************************************/
#if !defined(QNSWINDOW_PROTOCOL_IMPLMENTATION)
#include "qnswindow.h"
#include "qcocoawindow.h"
#include "qcocoahelpers.h"
@ -89,183 +91,10 @@ static bool isMouseEvent(NSEvent *ev)
}
@end
#define super USE_qt_objcDynamicSuper_INSTEAD
@implementation QNSWindow
+ (void)load
{
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);
}
free(methods);
}
free(protocols);
}
- (QCocoaWindow *)platformWindow
{
return qnsview_cast(self.contentView).platformWindow;
}
- (NSString *)description
{
NSMutableString *description = [NSMutableString stringWithString:qt_objcDynamicSuper()];
#ifndef QT_NO_DEBUG_STREAM
QString contentViewDescription;
QDebug debug(&contentViewDescription);
debug.nospace() << "; contentView=" << qnsview_cast(self.contentView) << ">";
NSRange lastCharacter = [description rangeOfComposedCharacterSequenceAtIndex:description.length - 1];
[description replaceCharactersInRange:lastCharacter withString:contentViewDescription.toNSString()];
#endif
return description;
}
- (BOOL)canBecomeKeyWindow
{
QCocoaWindow *pw = self.platformWindow;
if (!pw)
return NO;
if (pw->shouldRefuseKeyWindowAndFirstResponder())
return NO;
if ([self isKindOfClass:[QNSPanel class]]) {
// Only tool or dialog windows should become key:
Qt::WindowType type = pw->window()->type();
if (type == Qt::Tool || type == Qt::Dialog)
return YES;
return NO;
} else {
// The default implementation returns NO for title-bar less windows,
// override and return yes here to make sure popup windows such as
// the combobox popup can become the key window.
return YES;
}
}
- (BOOL)canBecomeMainWindow
{
BOOL canBecomeMain = YES; // By default, windows can become the main window
// Windows with a transient parent (such as combobox popup windows)
// cannot become the main window:
QCocoaWindow *pw = self.platformWindow;
if (!pw || pw->window()->transientParent())
canBecomeMain = NO;
return canBecomeMain;
}
- (BOOL)worksWhenModal
{
if ([self isKindOfClass:[QNSPanel class]]) {
if (QCocoaWindow *pw = self.platformWindow) {
Qt::WindowType type = pw->window()->type();
if (type == Qt::Popup || type == Qt::Dialog || type == Qt::Tool)
return YES;
}
}
return qt_objcDynamicSuper();
}
- (BOOL)isOpaque
{
return self.platformWindow ?
self.platformWindow->isOpaque() : qt_objcDynamicSuper();
}
/*!
Borderless windows need a transparent background
Technically windows with NSWindowStyleMaskTexturedBackground
(such as windows with unified toolbars) need to draw the textured
background of the NSWindow, and can't have a transparent
background, but as NSWindowStyleMaskBorderless is 0, you can't
have a window with NSWindowStyleMaskTexturedBackground that is
also borderless.
*/
- (NSColor *)backgroundColor
{
return self.styleMask == NSWindowStyleMaskBorderless
? [NSColor clearColor] : qt_objcDynamicSuper();
}
- (void)sendEvent:(NSEvent*)theEvent
{
qCDebug(lcQpaEvents) << "Sending" << theEvent << "to" << self;
// We might get events for a NSWindow after the corresponding platform
// window has been deleted, as the NSWindow can outlive the QCocoaWindow
// e.g. if being retained by other parts of AppKit, or in an auto-release
// pool. We guard against this in QNSView as well, as not all callbacks
// come via events, but if they do there's no point in propagating them.
if (!self.platformWindow)
return;
// Prevent deallocation of this NSWindow during event delivery, as we
// have logic further below that depends on the window being alive.
[[self retain] autorelease];
const char *eventType = object_getClassName(theEvent);
if (QWindowSystemInterface::handleNativeEvent(self.platformWindow->window(),
QByteArray::fromRawData(eventType, qstrlen(eventType)), theEvent, nullptr)) {
return;
}
qt_objcDynamicSuper(theEvent);
if (!self.platformWindow)
return; // Platform window went away while processing event
QCocoaWindow *pw = self.platformWindow;
if (pw->frameStrutEventsEnabled() && isMouseEvent(theEvent)) {
NSPoint loc = [theEvent locationInWindow];
NSRect windowFrame = [self convertRectFromScreen:self.frame];
NSRect contentFrame = self.contentView.frame;
if (NSMouseInRect(loc, windowFrame, NO) && !NSMouseInRect(loc, contentFrame, NO))
[qnsview_cast(pw->view()) handleFrameStrutMouseEvent:theEvent];
}
}
- (void)closeAndRelease
{
qCDebug(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) << "Deallocating" << self;
self.delegate = nil;
qt_objcDynamicSuper();
}
#pragma clang diagnostic pop
#define QNSWINDOW_PROTOCOL_IMPLMENTATION 1
#include "qnswindow.mm"
#undef QNSWINDOW_PROTOCOL_IMPLMENTATION
+ (void)applicationActivationChanged:(NSNotification*)notification
{
@ -326,7 +155,158 @@ static bool isMouseEvent(NSEvent *ev)
@end
@implementation QNSPanel
// Implementation shared with QNSWindow, see +[QNSWindow load] above
#define QNSWINDOW_PROTOCOL_IMPLMENTATION 1
#include "qnswindow.mm"
#undef QNSWINDOW_PROTOCOL_IMPLMENTATION
@end
#undef super
#else // QNSWINDOW_PROTOCOL_IMPLMENTATION
// The following methods are mixed in to the QNSWindow and QNSPanel classes via includes
- (QCocoaWindow *)platformWindow
{
return qnsview_cast(self.contentView).platformWindow;
}
- (NSString *)description
{
NSMutableString *description = [NSMutableString stringWithString:[super description]];
#ifndef QT_NO_DEBUG_STREAM
QString contentViewDescription;
QDebug debug(&contentViewDescription);
debug.nospace() << "; contentView=" << qnsview_cast(self.contentView) << ">";
NSRange lastCharacter = [description rangeOfComposedCharacterSequenceAtIndex:description.length - 1];
[description replaceCharactersInRange:lastCharacter withString:contentViewDescription.toNSString()];
#endif
return description;
}
- (BOOL)canBecomeKeyWindow
{
QCocoaWindow *pw = self.platformWindow;
if (!pw)
return NO;
if (pw->shouldRefuseKeyWindowAndFirstResponder())
return NO;
if ([self isKindOfClass:[QNSPanel class]]) {
// Only tool or dialog windows should become key:
Qt::WindowType type = pw->window()->type();
if (type == Qt::Tool || type == Qt::Dialog)
return YES;
return NO;
} else {
// The default implementation returns NO for title-bar less windows,
// override and return yes here to make sure popup windows such as
// the combobox popup can become the key window.
return YES;
}
}
- (BOOL)canBecomeMainWindow
{
BOOL canBecomeMain = YES; // By default, windows can become the main window
// Windows with a transient parent (such as combobox popup windows)
// cannot become the main window:
QCocoaWindow *pw = self.platformWindow;
if (!pw || pw->window()->transientParent())
canBecomeMain = NO;
return canBecomeMain;
}
- (BOOL)worksWhenModal
{
if ([self isKindOfClass:[QNSPanel class]]) {
if (QCocoaWindow *pw = self.platformWindow) {
Qt::WindowType type = pw->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() : [super isOpaque];
}
/*!
Borderless windows need a transparent background
Technically windows with NSWindowStyleMaskTexturedBackground
(such as windows with unified toolbars) need to draw the textured
background of the NSWindow, and can't have a transparent
background, but as NSWindowStyleMaskBorderless is 0, you can't
have a window with NSWindowStyleMaskTexturedBackground that is
also borderless.
*/
- (NSColor *)backgroundColor
{
return self.styleMask == NSWindowStyleMaskBorderless
? [NSColor clearColor] : [super backgroundColor];
}
- (void)sendEvent:(NSEvent*)theEvent
{
qCDebug(lcQpaEvents) << "Sending" << theEvent << "to" << self;
// We might get events for a NSWindow after the corresponding platform
// window has been deleted, as the NSWindow can outlive the QCocoaWindow
// e.g. if being retained by other parts of AppKit, or in an auto-release
// pool. We guard against this in QNSView as well, as not all callbacks
// come via events, but if they do there's no point in propagating them.
if (!self.platformWindow)
return;
// Prevent deallocation of this NSWindow during event delivery, as we
// have logic further below that depends on the window being alive.
[[self retain] autorelease];
const char *eventType = object_getClassName(theEvent);
if (QWindowSystemInterface::handleNativeEvent(self.platformWindow->window(),
QByteArray::fromRawData(eventType, qstrlen(eventType)), theEvent, nullptr)) {
return;
}
[super sendEvent:theEvent];
if (!self.platformWindow)
return; // Platform window went away while processing event
QCocoaWindow *pw = self.platformWindow;
if (pw->frameStrutEventsEnabled() && isMouseEvent(theEvent)) {
NSPoint loc = [theEvent locationInWindow];
NSRect windowFrame = [self convertRectFromScreen:self.frame];
NSRect contentFrame = self.contentView.frame;
if (NSMouseInRect(loc, windowFrame, NO) && !NSMouseInRect(loc, contentFrame, NO))
[qnsview_cast(pw->view()) handleFrameStrutMouseEvent:theEvent];
}
}
- (void)closeAndRelease
{
qCDebug(lcQpaWindow) << "Closing and releasing" << self;
[self close];
[self release];
}
- (void)dealloc
{
qCDebug(lcQpaWindow) << "Deallocating" << self;
self.delegate = nil;
[super dealloc];
}
#endif