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
parent
6273b484b3
commit
7319c342c3
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue