Cocoa: Use helper class for event handling in native windows

QNSWindow and QNSPanel duplicate some code when it comes to event
handling, which can be refactored. Also, it's currently not
possible to keep an NSWindow derived instance temporarily alive
as QCocoaWindow is not designed to keep track of more than one
NSWindow. Finally, we can reduce the size of (and eventually remove)
the QCocoaWindowCategory which polutes the NSWindow namespace.

We move QNSWindow and QNSPanel specific API into QNSWindowProtocol,
and define QCocoaNSWindow as NSWindow extended by that protocol.
This gives us a type we can refer to any of the native windows
QCocoaWindow instanciates.

We introduce QNSWindowHelper which gathers the common code between
QNSWindow and QNSPanel. This is a one-to-one mapping that keeps a
weak (non-retaining) reference to the NSWindow and a weak reference
to the QCocoaWindow. It has the same life span as its associated
NSWindow.

Task-number: QTBUG-33082
Reviewed-by: Morten Johan Sørvig <morten.sorvig@digia.com>
Change-Id: I38d001bf13f64a1ba4f1439291c5103c3f755183
Reviewed-by: Liang Qi <liang.qi@digia.com>
This commit is contained in:
Gabriel de Dietrich 2014-02-25 20:33:39 +01:00 committed by The Qt Project
parent ce909a138a
commit 018d1ca5f3
2 changed files with 178 additions and 115 deletions

View File

@ -52,27 +52,57 @@
QT_FORWARD_DECLARE_CLASS(QCocoaWindow) QT_FORWARD_DECLARE_CLASS(QCocoaWindow)
@interface QNSWindow : NSWindow @class QNSWindowHelper;
{
@public QCocoaWindow *m_cocoaPlatformWindow; @protocol QNSWindowProtocol
}
- (id)initWithContentRect:(NSRect)contentRect @property (nonatomic, readonly) QNSWindowHelper *helper;
styleMask:(NSUInteger)windowStyle
qPlatformWindow:(QCocoaWindow *)qpw; - (void)superSendEvent:(NSEvent *)theEvent;
- (void)closeAndRelease;
- (void)clearPlatformWindow;
@end @end
@interface QNSPanel : NSPanel typedef NSWindow<QNSWindowProtocol> QCocoaNSWindow;
@interface QNSWindowHelper : NSObject
{ {
@public QCocoaWindow *m_cocoaPlatformWindow; QCocoaNSWindow *_window;
QCocoaWindow *_platformWindow;
} }
@property (nonatomic, readonly) QCocoaNSWindow *window;
@property (nonatomic, readonly) QCocoaWindow *platformWindow;
- (id)initWithNSWindow:(QCocoaNSWindow *)window platformWindow:(QCocoaWindow *)platformWindow;
- (void)handleWindowEvent:(NSEvent *)theEvent;
@end
@interface QNSWindow : NSWindow<QNSWindowProtocol>
{
QNSWindowHelper *_helper;
}
@property (nonatomic, readonly) QNSWindowHelper *helper;
- (id)initWithContentRect:(NSRect)contentRect
styleMask:(NSUInteger)windowStyle
qPlatformWindow:(QCocoaWindow *)qpw;
@end
@interface QNSPanel : NSPanel<QNSWindowProtocol>
{
QNSWindowHelper *_helper;
}
@property (nonatomic, readonly) QNSWindowHelper *helper;
- (id)initWithContentRect:(NSRect)contentRect - (id)initWithContentRect:(NSRect)contentRect
styleMask:(NSUInteger)windowStyle styleMask:(NSUInteger)windowStyle
qPlatformWindow:(QCocoaWindow *)qpw; qPlatformWindow:(QCocoaWindow *)qpw;
- (void)clearPlatformWindow;
@end @end
@class QNSWindowDelegate; @class QNSWindowDelegate;
@ -183,9 +213,8 @@ public:
QWindow *childWindowAt(QPoint windowPoint); QWindow *childWindowAt(QPoint windowPoint);
protected: protected:
void recreateWindow(const QPlatformWindow *parentWindow); void recreateWindow(const QPlatformWindow *parentWindow);
NSWindow *createNSWindow(); QCocoaNSWindow *createNSWindow();
void setNSWindow(NSWindow *window); void setNSWindow(QCocoaNSWindow *window);
void clearNSWindow(NSWindow *window);
bool shouldUseNSPanel(); bool shouldUseNSPanel();
@ -202,7 +231,7 @@ public: // for QNSView
NSView *m_contentView; NSView *m_contentView;
QNSView *m_qtView; QNSView *m_qtView;
NSWindow *m_nsWindow; QCocoaNSWindow *m_nsWindow;
QCocoaWindow *m_forwardWindow; QCocoaWindow *m_forwardWindow;
// TODO merge to one variable if possible // TODO merge to one variable if possible
@ -213,7 +242,6 @@ public: // for QNSView
bool m_isNSWindowChild; // this window is a non-top level QWindow with a NSWindow. bool m_isNSWindowChild; // this window is a non-top level QWindow with a NSWindow.
QList<QCocoaWindow *> m_childWindows; QList<QCocoaWindow *> m_childWindows;
QNSWindowDelegate *m_nsWindowDelegate;
Qt::WindowFlags m_windowFlags; Qt::WindowFlags m_windowFlags;
Qt::WindowState m_synchedWindowState; Qt::WindowState m_synchedWindowState;
Qt::WindowModality m_windowModality; Qt::WindowModality m_windowModality;

View File

@ -81,15 +81,10 @@ static bool isMouseEvent(NSEvent *ev)
} }
@interface NSWindow (CocoaWindowCategory) @interface NSWindow (CocoaWindowCategory)
- (void) clearPlatformWindow;
- (NSRect) legacyConvertRectFromScreen:(NSRect) rect; - (NSRect) legacyConvertRectFromScreen:(NSRect) rect;
@end @end
@implementation NSWindow (CocoaWindowCategory) @implementation NSWindow (CocoaWindowCategory)
- (void) clearPlatformWindow
{
}
- (NSRect) legacyConvertRectFromScreen:(NSRect) rect - (NSRect) legacyConvertRectFromScreen:(NSRect) rect
{ {
#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_7 #if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_7
@ -103,8 +98,87 @@ static bool isMouseEvent(NSEvent *ev)
} }
@end @end
@implementation QNSWindowHelper
@synthesize window = _window;
@synthesize platformWindow = _platformWindow;
- (id)initWithNSWindow:(QCocoaNSWindow *)window platformWindow:(QCocoaWindow *)platformWindow
{
self = [super init];
if (self) {
_window = window;
_platformWindow = platformWindow;
_window.delegate = [[QNSWindowDelegate alloc] initWithQCocoaWindow:_platformWindow];
// Prevent Cocoa from releasing the window on close. Qt
// handles the close event asynchronously and we want to
// make sure that m_nsWindow stays valid until the
// QCocoaWindow is deleted by Qt.
[_window setReleasedWhenClosed:NO];
}
return self;
}
- (void)handleWindowEvent:(NSEvent *)theEvent
{
QCocoaWindow *pw = self.platformWindow;
if (pw && pw->m_forwardWindow) {
if (theEvent.type == NSLeftMouseUp || theEvent.type == NSLeftMouseDragged) {
QNSView *forwardView = pw->m_qtView;
if (theEvent.type == NSLeftMouseUp) {
[forwardView mouseUp:theEvent];
pw->m_forwardWindow = 0;
} else {
[forwardView mouseDragged:theEvent];
}
}
if (!pw->m_isNSWindowChild && theEvent.type == NSLeftMouseDown) {
pw->m_forwardWindow = 0;
}
}
[self.window superSendEvent:theEvent];
if (!self.window.delegate)
return; // Already detached, pending NSAppKitDefined event
if (pw && pw->frameStrutEventsEnabled() && isMouseEvent(theEvent)) {
NSPoint loc = [theEvent locationInWindow];
NSRect windowFrame = [self.window legacyConvertRectFromScreen:[self.window frame]];
NSRect contentFrame = [[self.window contentView] frame];
if (NSMouseInRect(loc, windowFrame, NO) &&
!NSMouseInRect(loc, contentFrame, NO))
{
QNSView *contentView = (QNSView *)pw->contentView();
[contentView handleFrameStrutMouseEvent: theEvent];
}
}
}
- (void)detachFromPlatformWindow
{
[self.window.delegate release];
self.window.delegate = nil;
}
- (void)dealloc
{
_window = nil;
_platformWindow = 0;
[super dealloc];
}
@end
@implementation QNSWindow @implementation QNSWindow
@synthesize helper = _helper;
- (id)initWithContentRect:(NSRect)contentRect - (id)initWithContentRect:(NSRect)contentRect
styleMask:(NSUInteger)windowStyle styleMask:(NSUInteger)windowStyle
qPlatformWindow:(QCocoaWindow *)qpw qPlatformWindow:(QCocoaWindow *)qpw
@ -116,7 +190,7 @@ static bool isMouseEvent(NSEvent *ev)
// set up before the window is shown and needs a proper window) // set up before the window is shown and needs a proper window)
if (self) { if (self) {
m_cocoaPlatformWindow = qpw; _helper = [[QNSWindowHelper alloc] initWithNSWindow:self platformWindow:qpw];
} }
return self; return self;
} }
@ -125,7 +199,8 @@ static bool isMouseEvent(NSEvent *ev)
{ {
// Prevent child NSWindows from becoming the key window in // Prevent child NSWindows from becoming the key window in
// order keep the active apperance of the top-level window. // order keep the active apperance of the top-level window.
if (!m_cocoaPlatformWindow || m_cocoaPlatformWindow->m_isNSWindowChild) QCocoaWindow *pw = self.helper.platformWindow;
if (!pw || pw->m_isNSWindowChild)
return NO; return NO;
// The default implementation returns NO for title-bar less windows, // The default implementation returns NO for title-bar less windows,
@ -140,8 +215,8 @@ static bool isMouseEvent(NSEvent *ev)
// Windows with a transient parent (such as combobox popup windows) // Windows with a transient parent (such as combobox popup windows)
// cannot become the main window: // cannot become the main window:
if (!m_cocoaPlatformWindow || m_cocoaPlatformWindow->m_isNSWindowChild QCocoaWindow *pw = self.helper.platformWindow;
|| m_cocoaPlatformWindow->window()->transientParent()) if (!pw || pw->m_isNSWindowChild || pw->window()->transientParent())
canBecomeMain = NO; canBecomeMain = NO;
return canBecomeMain; return canBecomeMain;
@ -149,51 +224,34 @@ static bool isMouseEvent(NSEvent *ev)
- (void) sendEvent: (NSEvent*) theEvent - (void) sendEvent: (NSEvent*) theEvent
{ {
if (m_cocoaPlatformWindow && m_cocoaPlatformWindow->m_forwardWindow) { [self.helper handleWindowEvent:theEvent];
if (theEvent.type == NSLeftMouseUp || theEvent.type == NSLeftMouseDragged) {
QNSView *forwardView = m_cocoaPlatformWindow->m_qtView;
if (theEvent.type == NSLeftMouseUp) {
[forwardView mouseUp:theEvent];
m_cocoaPlatformWindow->m_forwardWindow = 0;
} else {
[forwardView mouseDragged:theEvent];
}
return;
}
if (theEvent.type == NSLeftMouseDown) {
m_cocoaPlatformWindow->m_forwardWindow = 0;
}
}
[super sendEvent: theEvent];
if (!m_cocoaPlatformWindow)
return;
if (m_cocoaPlatformWindow->frameStrutEventsEnabled() && isMouseEvent(theEvent)) {
NSPoint loc = [theEvent locationInWindow];
NSRect windowFrame = [self legacyConvertRectFromScreen:[self frame]];
NSRect contentFrame = [[self contentView] frame];
if (NSMouseInRect(loc, windowFrame, NO) &&
!NSMouseInRect(loc, contentFrame, NO))
{
QNSView *contentView = (QNSView *) m_cocoaPlatformWindow->contentView();
[contentView handleFrameStrutMouseEvent: theEvent];
}
}
} }
- (void)clearPlatformWindow - (void)superSendEvent:(NSEvent *)theEvent
{ {
m_cocoaPlatformWindow = 0; [super sendEvent:theEvent];
}
- (void)closeAndRelease
{
[self.helper detachFromPlatformWindow];
[self close];
[self release];
}
- (void)dealloc
{
[_helper release];
_helper = nil;
[super dealloc];
} }
@end @end
@implementation QNSPanel @implementation QNSPanel
@synthesize helper = _helper;
- (id)initWithContentRect:(NSRect)contentRect - (id)initWithContentRect:(NSRect)contentRect
styleMask:(NSUInteger)windowStyle styleMask:(NSUInteger)windowStyle
qPlatformWindow:(QCocoaWindow *)qpw qPlatformWindow:(QCocoaWindow *)qpw
@ -205,47 +263,47 @@ static bool isMouseEvent(NSEvent *ev)
// set up before the window is shown and needs a proper window) // set up before the window is shown and needs a proper window)
if (self) { if (self) {
m_cocoaPlatformWindow = qpw; _helper = [[QNSWindowHelper alloc] initWithNSWindow:self platformWindow:qpw];
} }
return self; return self;
} }
- (BOOL)canBecomeKeyWindow - (BOOL)canBecomeKeyWindow
{ {
if (!m_cocoaPlatformWindow) QCocoaWindow *pw = self.helper.platformWindow;
if (!pw)
return NO; return NO;
// Only tool or dialog windows should become key: // Only tool or dialog windows should become key:
if (m_cocoaPlatformWindow Qt::WindowType type = pw->window()->type();
&& (m_cocoaPlatformWindow->window()->type() == Qt::Tool || if (type == Qt::Tool || type == Qt::Dialog)
m_cocoaPlatformWindow->window()->type() == Qt::Dialog))
return YES; return YES;
return NO; return NO;
} }
- (void) sendEvent: (NSEvent*) theEvent - (void) sendEvent: (NSEvent*) theEvent
{ {
[super sendEvent: theEvent]; [self.helper handleWindowEvent:theEvent];
if (!m_cocoaPlatformWindow)
return;
if (m_cocoaPlatformWindow->frameStrutEventsEnabled() && isMouseEvent(theEvent)) {
NSPoint loc = [theEvent locationInWindow];
NSRect windowFrame = [self legacyConvertRectFromScreen:[self frame]];
NSRect contentFrame = [[self contentView] frame];
if (NSMouseInRect(loc, windowFrame, NO) &&
!NSMouseInRect(loc, contentFrame, NO))
{
QNSView *contentView = (QNSView *) m_cocoaPlatformWindow->contentView();
[contentView handleFrameStrutMouseEvent: theEvent];
}
}
} }
- (void)clearPlatformWindow - (void)superSendEvent:(NSEvent *)theEvent
{ {
m_cocoaPlatformWindow = 0; [super sendEvent:theEvent];
}
- (void)closeAndRelease
{
[self.helper detachFromPlatformWindow];
[self close];
[self release];
}
- (void)dealloc
{
[_helper release];
_helper = nil;
[super dealloc];
} }
@end @end
@ -262,7 +320,6 @@ QCocoaWindow::QCocoaWindow(QWindow *tlw)
, m_contentViewIsToBeEmbedded(false) , m_contentViewIsToBeEmbedded(false)
, m_parentCocoaWindow(0) , m_parentCocoaWindow(0)
, m_isNSWindowChild(false) , m_isNSWindowChild(false)
, m_nsWindowDelegate(0)
, m_synchedWindowState(Qt::WindowActive) , m_synchedWindowState(Qt::WindowActive)
, m_windowModality(Qt::NonModal) , m_windowModality(Qt::NonModal)
, m_windowUnderMouse(false) , m_windowUnderMouse(false)
@ -328,7 +385,8 @@ QCocoaWindow::~QCocoaWindow()
#endif #endif
QCocoaAutoReleasePool pool; QCocoaAutoReleasePool pool;
clearNSWindow(m_nsWindow); [m_nsWindow setContentView:nil];
[m_nsWindow.helper detachFromPlatformWindow];
if (m_isNSWindowChild) { if (m_isNSWindowChild) {
if (m_parentCocoaWindow) if (m_parentCocoaWindow)
m_parentCocoaWindow->removeChildWindow(this); m_parentCocoaWindow->removeChildWindow(this);
@ -346,7 +404,6 @@ QCocoaWindow::~QCocoaWindow()
[m_contentView release]; [m_contentView release];
[m_nsWindow release]; [m_nsWindow release];
[m_nsWindowDelegate release];
[m_windowCursor release]; [m_windowCursor release];
} }
@ -1090,12 +1147,10 @@ void QCocoaWindow::recreateWindow(const QPlatformWindow *parentWindow)
// Remove current window (if any) // Remove current window (if any)
if ((m_nsWindow && !needsNSWindow) || (usesNSPanel != shouldUseNSPanel())) { if ((m_nsWindow && !needsNSWindow) || (usesNSPanel != shouldUseNSPanel())) {
clearNSWindow(m_nsWindow); [m_nsWindow closeAndRelease];
[m_nsWindow close]; if (wasNSWindowChild && oldParentCocoaWindow)
[m_nsWindow release]; oldParentCocoaWindow->removeChildWindow(this);
m_nsWindow = 0; m_nsWindow = 0;
[m_nsWindowDelegate release];
m_nsWindowDelegate = 0;
} }
if (needsNSWindow) { if (needsNSWindow) {
@ -1203,7 +1258,7 @@ bool QCocoaWindow::shouldUseNSPanel()
((type & Qt::Popup) == Qt::Popup || (type & Qt::Dialog) == Qt::Dialog); ((type & Qt::Popup) == Qt::Popup || (type & Qt::Dialog) == Qt::Dialog);
} }
NSWindow * QCocoaWindow::createNSWindow() QCocoaNSWindow * QCocoaWindow::createNSWindow()
{ {
QCocoaAutoReleasePool pool; QCocoaAutoReleasePool pool;
@ -1219,7 +1274,7 @@ NSWindow * QCocoaWindow::createNSWindow()
} else { } else {
styleMask = windowStyleMask(flags); styleMask = windowStyleMask(flags);
} }
NSWindow *createdWindow = 0; QCocoaNSWindow *createdWindow = 0;
// Use NSPanel for popup-type windows. (Popup, Tool, ToolTip, SplashScreen) // Use NSPanel for popup-type windows. (Popup, Tool, ToolTip, SplashScreen)
// and dialogs // and dialogs
@ -1270,17 +1325,8 @@ NSWindow * QCocoaWindow::createNSWindow()
return createdWindow; return createdWindow;
} }
void QCocoaWindow::setNSWindow(NSWindow *window) void QCocoaWindow::setNSWindow(QCocoaNSWindow *window)
{ {
m_nsWindowDelegate = [[QNSWindowDelegate alloc] initWithQCocoaWindow:this];
[window setDelegate:m_nsWindowDelegate];
// Prevent Cocoa from releasing the window on close. Qt
// handles the close event asynchronously and we want to
// make sure that m_nsWindow stays valid until the
// QCocoaWindow is deleted by Qt.
[window setReleasedWhenClosed : NO];
if (window.contentView != m_contentView) { if (window.contentView != m_contentView) {
[m_contentView setPostsFrameChangedNotifications: NO]; [m_contentView setPostsFrameChangedNotifications: NO];
[window setContentView:m_contentView]; [window setContentView:m_contentView];
@ -1288,17 +1334,6 @@ void QCocoaWindow::setNSWindow(NSWindow *window)
} }
} }
void QCocoaWindow::clearNSWindow(NSWindow *window)
{
[window setContentView:nil];
[window setDelegate:nil];
[window clearPlatformWindow];
if (m_isNSWindowChild) {
m_parentCocoaWindow->removeChildWindow(this);
}
}
void QCocoaWindow::removeChildWindow(QCocoaWindow *child) void QCocoaWindow::removeChildWindow(QCocoaWindow *child)
{ {
m_childWindows.removeOne(child); m_childWindows.removeOne(child);