Cocoa: Bring back two-class native window system

While inheriting from NSPanel proved to be robust enough, it is
not really future proof as we're at the mercy of Apple changing
NSPanel's behavior. On the other hand, we can't inherit exclu-
sively from NSWindow as the tool window case, where the QWindow
should look like an NSPanel, can't be emulated perfectly without
using private APIs.

This reverts commits 79fb39a87c and df86721bb4.

Change-Id: I9021193e3614633a943578df9e2794b00094a1f7
Reviewed-by: Morten Johan Sørvig <morten.sorvig@digia.com>
This commit is contained in:
Gabriel de Dietrich 2014-02-12 11:51:13 +01:00 committed by The Qt Project
parent ff23fb6cf7
commit 4beafcd944
3 changed files with 157 additions and 76 deletions

View File

@ -52,32 +52,42 @@
QT_FORWARD_DECLARE_CLASS(QCocoaWindow)
@class QNSWindowDelegate;
@interface QNSWindow : NSPanel {
@public
QCocoaWindow *m_cocoaPlatformWindow;
@interface QNSWindow : NSWindow
{
@public QCocoaWindow *m_cocoaPlatformWindow;
}
- (id)initWithContentRect:(NSRect)contentRect
styleMask:(NSUInteger)windowStyle
qPlatformWindow:(QCocoaWindow *)qpw;
- (void)clearPlatformWindow;
@end
QT_BEGIN_NAMESPACE
@interface QNSPanel : NSPanel
{
@public QCocoaWindow *m_cocoaPlatformWindow;
}
- (id)initWithContentRect:(NSRect)contentRect
styleMask:(NSUInteger)windowStyle
qPlatformWindow:(QCocoaWindow *)qpw;
- (void)clearPlatformWindow;
@end
@class QNSWindowDelegate;
QT_BEGIN_NAMESPACE
// QCocoaWindow
//
// A QCocoaWindow is backed by a NSView and optionally a NSWindow.
// QCocoaWindow is an NSView (not an NSWindow!) in the sense
// that it relies on a NSView for all event handling and
// graphics output and does not require a NSWindow, except for
// for the window-related functions like setWindowTitle.
//
// The NSView is used for most event handling and graphics output.
//
// Top-level QWindows are always backed by a NSWindow in addition to
// the NSView. Child QWindows can also be backed by NSWindows, which
// enables proper stacking of GL Widgets and threaded GL rendering
// to multiple contexts.
//
// It is possible to embed the QCocoaWindow in an NSView hierarchy
// by getting a pointer to the backing NSView and not calling
// QCocoaWindow::show():
// As a consequence of this it is possible to embed the QCocoaWindow
// in an NSView hierarchy by getting a pointer to the "backing"
// NSView and not calling QCocoaWindow::show():
//
// QWindow *qtWindow = new MyWindow();
// qtWindow->create();
@ -139,7 +149,6 @@ public:
void windowDidResize();
bool windowShouldClose();
bool windowIsPopupType(Qt::WindowType type = Qt::Widget) const;
bool windowShouldBehaveAsPanel() const;
void setSynchedWindowStateFromWindow();
@ -174,9 +183,11 @@ public:
QWindow *childWindowAt(QPoint windowPoint);
protected:
void recreateWindow(const QPlatformWindow *parentWindow);
QNSWindow *createNSWindow();
void setNSWindow(QNSWindow *window);
void clearNSWindow(QNSWindow *window);
NSWindow *createNSWindow();
void setNSWindow(NSWindow *window);
void clearNSWindow(NSWindow *window);
bool shouldUseNSPanel();
QRect windowGeometry() const;
QCocoaWindow *parentCocoaWindow() const;
@ -191,7 +202,7 @@ public: // for QNSView
NSView *m_contentView;
QNSView *m_qtView;
QNSWindow *m_nsWindow;
NSWindow *m_nsWindow;
QCocoaWindow *m_forwardWindow;
// TODO merge to one variable if possible

View File

@ -81,10 +81,15 @@ static bool isMouseEvent(NSEvent *ev)
}
@interface NSWindow (CocoaWindowCategory)
- (void) clearPlatformWindow;
- (NSRect) legacyConvertRectFromScreen:(NSRect) rect;
@end
@implementation NSWindow (CocoaWindowCategory)
- (void) clearPlatformWindow
{
}
- (NSRect) legacyConvertRectFromScreen:(NSRect) rect
{
#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_7
@ -123,17 +128,9 @@ static bool isMouseEvent(NSEvent *ev)
if (!m_cocoaPlatformWindow || m_cocoaPlatformWindow->m_isNSWindowChild)
return NO;
// Only tool or dialog windows should become key:
if (m_cocoaPlatformWindow && m_cocoaPlatformWindow->windowShouldBehaveAsPanel()) {
Qt::WindowType type = m_cocoaPlatformWindow->window()->type();
if (type == Qt::Tool || type == Qt::Dialog)
return YES;
return NO;
}
// All other windows can become the key window. This includes
// popup windows such as the combobox popup, which is a title-bar
// less window that by default can't become key.
// 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;
}
@ -147,9 +144,6 @@ static bool isMouseEvent(NSEvent *ev)
|| m_cocoaPlatformWindow->window()->transientParent())
canBecomeMain = NO;
if (m_cocoaPlatformWindow && m_cocoaPlatformWindow->windowShouldBehaveAsPanel())
canBecomeMain = NO;
return canBecomeMain;
}
@ -198,6 +192,64 @@ static bool isMouseEvent(NSEvent *ev)
@end
@implementation QNSPanel
- (id)initWithContentRect:(NSRect)contentRect
styleMask:(NSUInteger)windowStyle
qPlatformWindow:(QCocoaWindow *)qpw
{
self = [super initWithContentRect:contentRect
styleMask:windowStyle
backing:NSBackingStoreBuffered
defer:NO]; // Deferring window creation breaks OpenGL (the GL context is
// set up before the window is shown and needs a proper window)
if (self) {
m_cocoaPlatformWindow = qpw;
}
return self;
}
- (BOOL)canBecomeKeyWindow
{
if (!m_cocoaPlatformWindow)
return NO;
// Only tool or dialog windows should become key:
if (m_cocoaPlatformWindow
&& (m_cocoaPlatformWindow->window()->type() == Qt::Tool ||
m_cocoaPlatformWindow->window()->type() == Qt::Dialog))
return YES;
return NO;
}
- (void) sendEvent: (NSEvent*) theEvent
{
[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
{
m_cocoaPlatformWindow = 0;
}
@end
const int QCocoaWindow::NoAlertRequest = -1;
QCocoaWindow::QCocoaWindow(QWindow *tlw)
@ -675,21 +727,15 @@ void QCocoaWindow::setWindowFlags(Qt::WindowFlags flags)
#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_7
if (QSysInfo::QSysInfo::MacintoshVersion >= QSysInfo::MV_10_7) {
NSWindowCollectionBehavior behavior = [m_nsWindow collectionBehavior];
if (windowShouldBehaveAsPanel()) {
behavior &= ~NSWindowCollectionBehaviorFullScreenPrimary;
behavior |= NSWindowCollectionBehaviorFullScreenAuxiliary;
} else {
Qt::WindowType type = window()->type();
if ((type & Qt::Popup) != Qt::Popup && (type & Qt::Dialog) != Qt::Dialog) {
NSWindowCollectionBehavior behavior = [m_nsWindow collectionBehavior];
if (flags & Qt::WindowFullscreenButtonHint)
behavior |= NSWindowCollectionBehaviorFullScreenPrimary;
else
behavior &= ~NSWindowCollectionBehaviorFullScreenPrimary;
[m_nsWindow setCollectionBehavior:behavior];
}
[m_nsWindow setCollectionBehavior:behavior];
[m_nsWindow setAnimationBehavior:(flags & Qt::Popup) == Qt::Popup
? NSWindowAnimationBehaviorUtilityWindow
: NSWindowAnimationBehaviorDefault];
}
#endif
}
@ -1013,14 +1059,6 @@ bool QCocoaWindow::windowIsPopupType(Qt::WindowType type) const
return ((type & Qt::Popup) == Qt::Popup);
}
bool QCocoaWindow::windowShouldBehaveAsPanel() const
{
// Before merging QNSPanel and QNSWindow, we used NSPanel for popup-type
// windows (Popup, Tool, ToolTip, SplashScreen) and dialogs
Qt::WindowType type = window()->type();
return (type & Qt::Popup) == Qt::Popup || (type & Qt::Dialog) == Qt::Dialog;
}
void QCocoaWindow::setCurrentContext(QCocoaGLContext *context)
{
m_glContext = context;
@ -1034,17 +1072,21 @@ QCocoaGLContext *QCocoaWindow::currentContext() const
void QCocoaWindow::recreateWindow(const QPlatformWindow *parentWindow)
{
bool wasNSWindowChild = m_isNSWindowChild;
// TODO Set value for m_isNSWindowChild here
bool needsNSWindow = m_isNSWindowChild || !parentWindow;
QCocoaWindow *oldParentCocoaWindow = m_parentCocoaWindow;
m_parentCocoaWindow = const_cast<QCocoaWindow *>(static_cast<const QCocoaWindow *>(parentWindow));
bool usesNSPanel = [m_nsWindow isKindOfClass:[QNSPanel class]];
// No child QNSWindow should notify its QNSView
if (m_nsWindow && m_qtView && m_parentCocoaWindow && !oldParentCocoaWindow)
[[NSNotificationCenter defaultCenter] removeObserver:m_qtView
name:nil object:m_nsWindow];
// Remove current window (if any)
if (m_nsWindow && !needsNSWindow) {
if ((m_nsWindow && !needsNSWindow) || (usesNSPanel != shouldUseNSPanel())) {
clearNSWindow(m_nsWindow);
[m_nsWindow close];
[m_nsWindow release];
@ -1075,7 +1117,6 @@ void QCocoaWindow::recreateWindow(const QPlatformWindow *parentWindow)
setNSWindow(m_nsWindow);
}
if (m_contentViewIsToBeEmbedded) {
// An embedded window doesn't have its own NSWindow.
} else if (!parentWindow) {
@ -1147,13 +1188,22 @@ void QCocoaWindow::requestActivateWindow()
[ window makeKeyWindow ];
}
QNSWindow * QCocoaWindow::createNSWindow()
bool QCocoaWindow::shouldUseNSPanel()
{
Qt::WindowType type = window()->type();
return !m_isNSWindowChild &&
((type & Qt::Popup) == Qt::Popup || (type & Qt::Dialog) == Qt::Dialog);
}
NSWindow * QCocoaWindow::createNSWindow()
{
QCocoaAutoReleasePool pool;
QRect rect = initialGeometry(window(), window()->geometry(), defaultWindowWidth, defaultWindowHeight);
NSRect frame = qt_mac_flipRect(rect, window());
Qt::WindowType type = window()->type();
Qt::WindowFlags flags = window()->flags();
NSUInteger styleMask;
@ -1162,17 +1212,44 @@ QNSWindow * QCocoaWindow::createNSWindow()
} else {
styleMask = windowStyleMask(flags);
}
NSWindow *createdWindow = 0;
QNSWindow *createdWindow = [[QNSWindow alloc] initWithContentRect:frame styleMask:styleMask qPlatformWindow:this];
Qt::WindowFlags type = window()->type();
createdWindow.hidesOnDeactivate = type == Qt::Tool || type == Qt::ToolTip;
// Use NSPanel for popup-type windows. (Popup, Tool, ToolTip, SplashScreen)
// and dialogs
if (shouldUseNSPanel()) {
QNSPanel *window;
window = [[QNSPanel alloc] initWithContentRect:frame
styleMask: styleMask
qPlatformWindow:this];
if ((type & Qt::Popup) == Qt::Popup)
[window setHasShadow:YES];
[window setHidesOnDeactivate: NO];
#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_7
if (QSysInfo::QSysInfo::MacintoshVersion >= QSysInfo::MV_10_7) {
[createdWindow setRestorable: NO];
}
if (QSysInfo::QSysInfo::MacintoshVersion >= QSysInfo::MV_10_7) {
// Make popup winows show on the same desktop as the parent full-screen window.
[window setCollectionBehavior:NSWindowCollectionBehaviorFullScreenAuxiliary];
if ((type & Qt::Popup) == Qt::Popup)
[window setAnimationBehavior:NSWindowAnimationBehaviorUtilityWindow];
}
#endif
createdWindow = window;
} else {
QNSWindow *window;
window = [[QNSWindow alloc] initWithContentRect:frame
styleMask: styleMask
qPlatformWindow:this];
createdWindow = window;
}
#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_7
if ([createdWindow respondsToSelector:@selector(setRestorable:)])
[createdWindow setRestorable: NO];
#endif
NSInteger level = windowLevel(flags);
[createdWindow setLevel:level];
if (window()->format().alphaBufferSize() > 0) {
[createdWindow setBackgroundColor:[NSColor clearColor]];
@ -1186,12 +1263,10 @@ QNSWindow * QCocoaWindow::createNSWindow()
return createdWindow;
}
void QCocoaWindow::setNSWindow(QNSWindow *window)
void QCocoaWindow::setNSWindow(NSWindow *window)
{
if (!m_nsWindowDelegate) {
m_nsWindowDelegate = [[QNSWindowDelegate alloc] initWithQCocoaWindow:this];
[window setDelegate:m_nsWindowDelegate];
}
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
@ -1206,11 +1281,12 @@ void QCocoaWindow::setNSWindow(QNSWindow *window)
}
}
void QCocoaWindow::clearNSWindow(QNSWindow *window)
void QCocoaWindow::clearNSWindow(NSWindow *window)
{
[window setContentView:nil];
[window setDelegate:nil];
[window clearPlatformWindow];
if (m_isNSWindowChild) {
m_parentCocoaWindow->removeChildWindow(this);
}

View File

@ -1383,12 +1383,6 @@ static QTabletEvent::TabletDevice wacomTabletDevice(NSEvent *theEvent)
[self tryToPerform:aSelector with:self];
}
- (void)cancelOperation:(id)sender
{
if (!m_platformWindow || m_platformWindow->windowShouldBehaveAsPanel())
[super cancelOperation:sender];
}
- (void) insertText:(id)aString replacementRange:(NSRange)replacementRange
{
Q_UNUSED(replacementRange)