/****************************************************************************
**
-** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
-** All rights reserved.
-** Contact: Nokia Corporation (qt-info@nokia.com)
+** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
**
** This file is part of the plugins of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
** GNU Lesser General Public License Usage
-** This file may be used under the terms of the GNU Lesser General Public
-** License version 2.1 as published by the Free Software Foundation and
-** appearing in the file LICENSE.LGPL included in the packaging of this
-** file. Please review the following information to ensure the GNU Lesser
-** General Public License version 2.1 requirements will be met:
-** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
-** In addition, as a special exception, Nokia gives you certain additional
-** rights. These rights are described in the Nokia Qt LGPL Exception
+** In addition, as a special exception, Digia gives you certain additional
+** rights. These rights are described in the Digia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU General
-** Public License version 3.0 as published by the Free Software Foundation
-** and appearing in the file LICENSE.GPL included in the packaging of this
-** file. Please review the following information to ensure the GNU General
-** Public License version 3.0 requirements will be met:
-** http://www.gnu.org/copyleft/gpl.html.
-**
-** Other Usage
-** Alternatively, this file may be used in accordance with the terms and
-** conditions contained in a signed written agreement between you and Nokia.
-**
-**
-**
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
**
**
** $QT_END_LICENSE$
#include "qcocoawindow.h"
#include "qnswindowdelegate.h"
#include "qcocoaautoreleasepool.h"
+#include "qcocoaeventdispatcher.h"
#include "qcocoaglcontext.h"
#include "qcocoahelpers.h"
#include "qnsview.h"
+#include <QtCore/qfileinfo.h>
#include <QtCore/private/qcore_mac_p.h>
#include <qwindow.h>
-#include <QWindowSystemInterface>
-#include <QPlatformScreen>
+#include <qpa/qwindowsysteminterface.h>
+#include <qpa/qplatformscreen.h>
#include <Cocoa/Cocoa.h>
#include <Carbon/Carbon.h>
#include <QDebug>
-QCocoaWindow::QCocoaWindow(QWindow *tlw)
- : QPlatformWindow(tlw)
- , m_windowAttributes(0)
- , m_windowClass(0)
- , m_glContext(0)
+static bool isMouseEvent(NSEvent *ev)
{
- QCocoaAutoReleasePool pool;
+ switch ([ev type]) {
+ case NSLeftMouseDown:
+ case NSLeftMouseUp:
+ case NSRightMouseDown:
+ case NSRightMouseUp:
+ case NSMouseMoved:
+ case NSLeftMouseDragged:
+ case NSRightMouseDragged:
+ return true;
+ default:
+ return false;
+ }
+}
- determineWindowClass();
- m_nsWindow = createWindow();
+@interface NSWindow (CocoaWindowCategory)
+- (void) clearPlatformWindow;
+- (NSRect) legacyConvertRectFromScreen:(NSRect) rect;
+@end
- QNSWindowDelegate *delegate = [[QNSWindowDelegate alloc] initWithQCocoaWindow:this];
- [m_nsWindow setDelegate:delegate];
- [m_nsWindow setAcceptsMouseMovedEvents:YES];
+@implementation NSWindow (CocoaWindowCategory)
+- (void) clearPlatformWindow
+{
+}
- // 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.
- [m_nsWindow setReleasedWhenClosed : NO];
+- (NSRect) legacyConvertRectFromScreen:(NSRect) rect
+{
+#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_7
+ if (QSysInfo::QSysInfo::MacintoshVersion >= QSysInfo::MV_10_7) {
+ return [self convertRectFromScreen: rect];
+ }
+#endif
+ NSRect r = rect;
+ r.origin = [self convertScreenToBase:rect.origin];
+ return r;
+}
+@end
+
+@implementation QNSWindow
+
+- (BOOL)canBecomeKeyWindow
+{
+ // 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
- m_contentView = [[QNSView alloc] initWithQWindow:tlw];
+ // Windows with a transient parent (such as combobox popup windows)
+ // cannot become the main window:
+ if (m_cocoaPlatformWindow->window()->transientParent())
+ canBecomeMain = NO;
- // ### Accept touch events by default.
- // Beware that enabling touch events has a negative impact on the overall performance.
- // We probably need a QWindowSystemInterface API to enable/disable touch events.
- [m_contentView setAcceptsTouchEvents:YES];
+ return canBecomeMain;
+}
+
+- (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
+
+@implementation QNSPanel
+
+- (BOOL)canBecomeKeyWindow
+{
+ // Most panels can be come the key window. Exceptions are:
+ if (m_cocoaPlatformWindow->window()->type() == Qt::ToolTip)
+ return NO;
+ if (m_cocoaPlatformWindow->window()->type() == Qt::SplashScreen)
+ return NO;
+ return YES;
+}
+- (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
+
+QCocoaWindow::QCocoaWindow(QWindow *tlw)
+ : QPlatformWindow(tlw)
+ , m_nsWindow(0)
+ , m_nsWindowDelegate(0)
+ , m_synchedWindowState(Qt::WindowActive)
+ , m_windowModality(Qt::NonModal)
+ , m_inConstructor(true)
+ , m_glContext(0)
+ , m_menubar(0)
+ , m_hasModalSession(false)
+ , m_frameStrutEventsEnabled(false)
+{
+#ifdef QT_COCOA_ENABLE_WINDOW_DEBUG
+ qDebug() << "QCocoaWindow::QCocoaWindow" << this;
+#endif
+ QCocoaAutoReleasePool pool;
+
+ m_contentView = [[QNSView alloc] initWithQWindow:tlw platformWindow:this];
setGeometry(tlw->geometry());
- [m_nsWindow setContentView:m_contentView];
+ recreateWindow(parent());
+
+ m_inConstructor = false;
}
QCocoaWindow::~QCocoaWindow()
{
+#ifdef QT_COCOA_ENABLE_WINDOW_DEBUG
+ qDebug() << "QCocoaWindow::~QCocoaWindow" << this;
+#endif
+
+ QCocoaAutoReleasePool pool;
+ clearNSWindow(m_nsWindow);
+ [m_contentView release];
[m_nsWindow release];
+ [m_nsWindowDelegate release];
}
void QCocoaWindow::setGeometry(const QRect &rect)
{
+ if (geometry() == rect)
+ return;
+#ifdef QT_COCOA_ENABLE_WINDOW_DEBUG
+ qDebug() << "QCocoaWindow::setGeometry" << this << rect;
+#endif
QPlatformWindow::setGeometry(rect);
+ setCocoaGeometry(rect);
+}
- NSRect bounds = globalGeometry(rect);
- [[m_nsWindow contentView]setFrameSize:bounds.size];
- [m_nsWindow setContentSize : bounds.size];
- [m_nsWindow setFrameOrigin : bounds.origin];
-
- if (m_glContext)
- m_glContext->update();
+void QCocoaWindow::setCocoaGeometry(const QRect &rect)
+{
+ QCocoaAutoReleasePool pool;
+ if (m_nsWindow) {
+ NSRect bounds = qt_mac_flipRect(rect, window());
+ [m_nsWindow setContentSize : bounds.size];
+ [m_nsWindow setFrameOrigin : bounds.origin];
+ } else {
+ [m_contentView setFrame : NSMakeRect(rect.x(), rect.y(), rect.width(), rect.height())];
+ }
}
void QCocoaWindow::setVisible(bool visible)
{
+ QCocoaAutoReleasePool pool;
+#ifdef QT_COCOA_ENABLE_WINDOW_DEBUG
+ qDebug() << "QCocoaWindow::setVisible" << window() << visible;
+#endif
if (visible) {
- // The parent window might have moved while this window was hidden,
- // update the window geometry if there is a parent.
- if (window()->transientParent())
+ // We need to recreate if the modality has changed as the style mask will need updating
+ if (m_windowModality != window()->modality())
+ recreateWindow(parent());
+ QCocoaWindow *parentCocoaWindow = 0;
+ if (window()->transientParent()) {
+ parentCocoaWindow = static_cast<QCocoaWindow *>(window()->transientParent()->handle());
+
+ // The parent window might have moved while this window was hidden,
+ // update the window geometry if there is a parent.
setGeometry(window()->geometry());
+ // Register popup windows so that the parent window can
+ // close them when needed.
+ if (window()->type() == Qt::Popup) {
+ // qDebug() << "transientParent and popup" << window()->type() << Qt::Popup << (window()->type() & Qt::Popup);
+ parentCocoaWindow->m_activePopupWindow = window();
+ }
+
+ }
+
// Make sure the QWindow has a frame ready before we show the NSWindow.
- QWindowSystemInterface::handleSynchronousExposeEvent(window(), QRect(QPoint(), geometry().size()));
+ QWindowSystemInterface::handleExposeEvent(window(), QRect(QPoint(), geometry().size()));
+ QWindowSystemInterface::flushWindowSystemEvents();
+
+ if (m_nsWindow) {
+ // setWindowState might have been called while the window was hidden and
+ // will not change the NSWindow state in that case. Sync up here:
+ syncWindowState(window()->windowState());
+
+ if (window()->windowState() != Qt::WindowMinimized) {
+ if ((window()->modality() == Qt::WindowModal
+ || window()->type() == Qt::Sheet)
+ && parentCocoaWindow) {
+ // show the window as a sheet
+ [NSApp beginSheet:m_nsWindow modalForWindow:parentCocoaWindow->m_nsWindow modalDelegate:nil didEndSelector:nil contextInfo:nil];
+ } else if (window()->modality() != Qt::NonModal) {
+ // show the window as application modal
+ QCocoaEventDispatcher *cocoaEventDispatcher = qobject_cast<QCocoaEventDispatcher *>(QGuiApplication::instance()->eventDispatcher());
+ Q_ASSERT(cocoaEventDispatcher != 0);
+ QCocoaEventDispatcherPrivate *cocoaEventDispatcherPrivate = static_cast<QCocoaEventDispatcherPrivate *>(QObjectPrivate::get(cocoaEventDispatcher));
+ cocoaEventDispatcherPrivate->beginModalSession(window());
+ m_hasModalSession = true;
+ } else if ([m_nsWindow canBecomeKeyWindow]) {
+ [m_nsWindow makeKeyAndOrderFront:nil];
+ } else {
+ [m_nsWindow orderFront: nil];
+ }
+
+ // We want the events to properly reach the popup
+ if (window()->type() == Qt::Popup)
+ [(NSPanel *)m_nsWindow setWorksWhenModal:YES];
+ }
+ }
+ } else {
+ // qDebug() << "close" << this;
+ if (m_nsWindow) {
+ if (m_hasModalSession) {
+ QCocoaEventDispatcher *cocoaEventDispatcher = qobject_cast<QCocoaEventDispatcher *>(QGuiApplication::instance()->eventDispatcher());
+ Q_ASSERT(cocoaEventDispatcher != 0);
+ QCocoaEventDispatcherPrivate *cocoaEventDispatcherPrivate = static_cast<QCocoaEventDispatcherPrivate *>(QObjectPrivate::get(cocoaEventDispatcher));
+ cocoaEventDispatcherPrivate->endModalSession(window());
+ m_hasModalSession = false;
+ } else {
+ if ([m_nsWindow isSheet])
+ [NSApp endSheet:m_nsWindow];
+ }
+ [m_nsWindow orderOut:m_nsWindow];
+ }
+ if (!QCoreApplication::closingDown())
+ QWindowSystemInterface::handleExposeEvent(window(), QRegion());
+ }
+}
+
+NSInteger QCocoaWindow::windowLevel(Qt::WindowFlags flags)
+{
+ Qt::WindowType type = static_cast<Qt::WindowType>(int(flags & Qt::WindowType_Mask));
+
+ NSInteger windowLevel = NSNormalWindowLevel;
+
+ if (type == Qt::Tool)
+ windowLevel = NSFloatingWindowLevel;
+ else if ((type & Qt::Popup) == Qt::Popup)
+ windowLevel = NSPopUpMenuWindowLevel;
- [m_nsWindow makeKeyAndOrderFront:nil];
+ // StayOnTop window should appear above Tool windows.
+ if (flags & Qt::WindowStaysOnTopHint)
+ windowLevel = NSPopUpMenuWindowLevel;
+ // Tooltips should appear above StayOnTop windows.
+ if (type == Qt::ToolTip)
+ windowLevel = NSScreenSaverWindowLevel;
+
+ // A window should be in at least the same level as its parent.
+ const QWindow * const transientParent = window()->transientParent();
+ const QCocoaWindow * const transientParentWindow = transientParent ? static_cast<QCocoaWindow *>(transientParent->handle()) : 0;
+ if (transientParentWindow)
+ windowLevel = qMax([transientParentWindow->m_nsWindow level], windowLevel);
+
+ return windowLevel;
+}
+
+NSUInteger QCocoaWindow::windowStyleMask(Qt::WindowFlags flags)
+{
+ Qt::WindowType type = static_cast<Qt::WindowType>(int(flags & Qt::WindowType_Mask));
+ NSInteger styleMask = NSBorderlessWindowMask;
+
+ if ((type & Qt::Popup) == Qt::Popup) {
+ if (!windowIsPopupType(type))
+ styleMask = (NSUtilityWindowMask | NSResizableWindowMask | NSClosableWindowMask |
+ NSMiniaturizableWindowMask | NSTitledWindowMask);
} else {
- [m_nsWindow orderOut:nil];
+ // Filter flags for supported properties
+ flags &= Qt::WindowType_Mask | Qt::FramelessWindowHint | Qt::WindowTitleHint |
+ Qt::WindowMinMaxButtonsHint | Qt::WindowCloseButtonHint;
+ if (flags == Qt::Window) {
+ styleMask = (NSResizableWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask | NSTitledWindowMask);
+ } else if ((flags & Qt::Dialog) && (window()->modality() != Qt::NonModal)) {
+ styleMask = NSTitledWindowMask;
+ } else if (!(flags & Qt::FramelessWindowHint)) {
+ if (flags & Qt::WindowMaximizeButtonHint)
+ styleMask |= NSResizableWindowMask;
+ if (flags & Qt::WindowTitleHint)
+ styleMask |= NSTitledWindowMask;
+ if (flags & Qt::WindowCloseButtonHint)
+ styleMask |= NSClosableWindowMask;
+ if (flags & Qt::WindowMinimizeButtonHint)
+ styleMask |= NSMiniaturizableWindowMask;
+ }
+ }
+
+ return styleMask;
+}
+
+void QCocoaWindow::setWindowShadow(Qt::WindowFlags flags)
+{
+ bool keepShadow = !(flags & Qt::NoDropShadowWindowHint);
+ [m_nsWindow setHasShadow:(keepShadow ? YES : NO)];
+}
+
+void QCocoaWindow::setWindowFlags(Qt::WindowFlags flags)
+{
+ if (m_nsWindow) {
+ NSUInteger styleMask = windowStyleMask(flags);
+ NSInteger level = this->windowLevel(flags);
+ [m_nsWindow setStyleMask:styleMask];
+ [m_nsWindow setLevel:level];
+ setWindowShadow(flags);
}
+
+ m_windowFlags = flags;
+}
+
+void QCocoaWindow::setWindowState(Qt::WindowState state)
+{
+ if ([m_nsWindow isVisible])
+ syncWindowState(state); // Window state set for hidden windows take effect when show() is called.
}
void QCocoaWindow::setWindowTitle(const QString &title)
{
+ QCocoaAutoReleasePool pool;
+ if (!m_nsWindow)
+ return;
+
CFStringRef windowTitle = QCFString::toCFStringRef(title);
[m_nsWindow setTitle: const_cast<NSString *>(reinterpret_cast<const NSString *>(windowTitle))];
CFRelease(windowTitle);
}
+void QCocoaWindow::setWindowFilePath(const QString &filePath)
+{
+ QCocoaAutoReleasePool pool;
+ if (!m_nsWindow)
+ return;
+
+ QFileInfo fi(filePath);
+ [m_nsWindow setRepresentedFilename: fi.exists() ? QCFString::toNSString(filePath) : @""];
+}
+
+void QCocoaWindow::setWindowIcon(const QIcon &icon)
+{
+ QCocoaAutoReleasePool pool;
+
+ NSButton *iconButton = [m_nsWindow standardWindowButton:NSWindowDocumentIconButton];
+ if (iconButton == nil) {
+ NSString *title = QCFString::toNSString(window()->title());
+ [m_nsWindow setRepresentedURL:[NSURL fileURLWithPath:title]];
+ iconButton = [m_nsWindow standardWindowButton:NSWindowDocumentIconButton];
+ }
+ if (icon.isNull()) {
+ [iconButton setImage:nil];
+ } else {
+ QPixmap pixmap = icon.pixmap(QSize(22, 22));
+ NSImage *image = static_cast<NSImage *>(qt_mac_create_nsimage(pixmap));
+ [iconButton setImage:image];
+ [image release];
+ }
+}
+
void QCocoaWindow::raise()
{
+ //qDebug() << "raise" << this;
// ### handle spaces (see Qt 4 raise_sys in qwidget_mac.mm)
- [m_nsWindow orderFront: m_nsWindow];
+ if (!m_nsWindow)
+ return;
+ if ([m_nsWindow isVisible])
+ [m_nsWindow orderFront: m_nsWindow];
}
void QCocoaWindow::lower()
{
- [m_nsWindow orderFront: m_nsWindow];
+ if (!m_nsWindow)
+ return;
+ if ([m_nsWindow isVisible])
+ [m_nsWindow orderBack: m_nsWindow];
}
void QCocoaWindow::propagateSizeHints()
{
- [m_nsWindow setMinSize : qt_mac_toNSSize(window()->minimumSize())];
- [m_nsWindow setMaxSize : qt_mac_toNSSize(window()->maximumSize())];
-
- if (!window()->sizeIncrement().isNull())
+ QCocoaAutoReleasePool pool;
+ if (!m_nsWindow)
+ return;
+
+#ifdef QT_COCOA_ENABLE_WINDOW_DEBUG
+ qDebug() << "QCocoaWindow::propagateSizeHints" << this;
+ qDebug() << " min/max " << window()->minimumSize() << window()->maximumSize();
+ qDebug() << "size increment" << window()->sizeIncrement();
+ qDebug() << " basesize" << window()->baseSize();
+ qDebug() << " geometry" << geometry();
+#endif
+
+ // Set the minimum content size.
+ const QSize minimumSize = window()->minimumSize();
+ if (!minimumSize.isValid()) // minimumSize is (-1, -1) when not set. Make that (0, 0) for Cocoa.
+ [m_nsWindow setContentMinSize : NSMakeSize(0.0, 0.0)];
+ [m_nsWindow setContentMinSize : NSMakeSize(minimumSize.width(), minimumSize.height())];
+
+ // Set the maximum content size.
+ const QSize maximumSize = window()->maximumSize();
+ [m_nsWindow setContentMaxSize : NSMakeSize(maximumSize.width(), maximumSize.height())];
+
+ // sizeIncrement is observed to take values of (-1, -1) and (0, 0) for windows that should be
+ // resizable and that have no specific size increment set. Cocoa expects (1.0, 1.0) in this case.
+ if (!window()->sizeIncrement().isEmpty())
[m_nsWindow setResizeIncrements : qt_mac_toNSSize(window()->sizeIncrement())];
+ else
+ [m_nsWindow setResizeIncrements : NSMakeSize(1.0, 1.0)];
- // We must set the window frame after setting the minimum size to prevent the window
- // from being resized to the minimum size. Use QWindow::baseSize if set, otherwise
- // use the current size.
- QSize baseSize = window()->baseSize();
QRect rect = geometry();
- if (!baseSize.isNull()) {
- [m_nsWindow setFrame : NSMakeRect(rect.x(), rect.y(), baseSize.width(), baseSize.height()) display : YES];
- } else {
- [m_nsWindow setFrame : NSMakeRect(rect.x(), rect.y(), rect.width(), rect.height()) display : YES];
+ QSize baseSize = window()->baseSize();
+ if (!baseSize.isNull() && baseSize.isValid()) {
+ [m_nsWindow setFrame:NSMakeRect(rect.x(), rect.y(), baseSize.width(), baseSize.height()) display:YES];
}
}
+void QCocoaWindow::setOpacity(qreal level)
+{
+ if (m_nsWindow)
+ [m_nsWindow setAlphaValue:level];
+}
+
+void QCocoaWindow::setMask(const QRegion ®ion)
+{
+ if (m_nsWindow) {
+ [m_nsWindow setOpaque:NO];
+ [m_nsWindow setBackgroundColor:[NSColor clearColor]];
+ }
+
+ [m_contentView setMaskRegion:®ion];
+}
+
+bool QCocoaWindow::setKeyboardGrabEnabled(bool grab)
+{
+ if (!m_nsWindow)
+ return false;
+
+ if (grab && ![m_nsWindow isKeyWindow])
+ [m_nsWindow makeKeyWindow];
+ else if (!grab && [m_nsWindow isKeyWindow])
+ [m_nsWindow resignKeyWindow];
+ return true;
+}
+
+bool QCocoaWindow::setMouseGrabEnabled(bool grab)
+{
+ if (!m_nsWindow)
+ return false;
+
+ if (grab && ![m_nsWindow isKeyWindow])
+ [m_nsWindow makeKeyWindow];
+ else if (!grab && [m_nsWindow isKeyWindow])
+ [m_nsWindow resignKeyWindow];
+ return true;
+}
+
WId QCocoaWindow::winId() const
{
- return WId(m_nsWindow);
+ return WId(m_contentView);
}
-NSView *QCocoaWindow::contentView() const
+void QCocoaWindow::setParent(const QPlatformWindow *parentWindow)
{
- return [m_nsWindow contentView];
+ // recreate the window for compatibility
+ recreateWindow(parentWindow);
+ setCocoaGeometry(geometry());
}
-void QCocoaWindow::windowDidMove()
+NSView *QCocoaWindow::contentView() const
{
- if (m_glContext)
- m_glContext->update();
+ return m_contentView;
+}
- NSRect rect = [[m_nsWindow contentView]frame];
- NSRect windowRect = [m_nsWindow frame];
+void QCocoaWindow::windowWillMove()
+{
+ // Close any open popups on window move
+ if (m_activePopupWindow) {
+ QWindowSystemInterface::handleCloseEvent(m_activePopupWindow);
+ QWindowSystemInterface::flushWindowSystemEvents();
+ m_activePopupWindow = 0;
+ }
+}
- QRect geo(windowRect.origin.x, qt_mac_flipYCoordinate(windowRect.origin.y + rect.size.height), rect.size.width, rect.size.height);
- setGeometry(geo);
- QWindowSystemInterface::handleSynchronousGeometryChange(window(), geo);
+void QCocoaWindow::windowDidMove()
+{
+ [m_contentView updateGeometry];
}
void QCocoaWindow::windowDidResize()
{
- if (m_glContext)
- m_glContext->update();
+ if (!m_nsWindow)
+ return;
NSRect rect = [[m_nsWindow contentView]frame];
- NSRect windowRect = [m_nsWindow frame];
-
- QRect geo(windowRect.origin.x, qt_mac_flipYCoordinate(windowRect.origin.y + rect.size.height), rect.size.width, rect.size.height);
- setGeometry(geo);
- QWindowSystemInterface::handleSynchronousGeometryChange(window(), geo);
+ // Call setFrameSize which will trigger a frameDidChangeNotification on QNSView.
+ [[m_nsWindow contentView] setFrameSize:rect.size];
}
-
void QCocoaWindow::windowWillClose()
{
QWindowSystemInterface::handleCloseEvent(window());
+ QWindowSystemInterface::flushWindowSystemEvents();
+}
+
+bool QCocoaWindow::windowIsPopupType(Qt::WindowType type) const
+{
+ if (type == Qt::Widget)
+ type = window()->type();
+ if (type == Qt::Tool)
+ return false; // Qt::Tool has the Popup bit set but isn't, at least on Mac.
+
+ return ((type & Qt::Popup) == Qt::Popup);
}
void QCocoaWindow::setCurrentContext(QCocoaGLContext *context)
return m_glContext;
}
-/*
- Determine the window class based on the window type and
- window flags, and widget attr Sets m_windowAttributes
- and m_windowClass.
-*/
-void QCocoaWindow::determineWindowClass()
+void QCocoaWindow::recreateWindow(const QPlatformWindow *parentWindow)
{
- Qt::WindowType type = window()->windowType();
- Qt::WindowFlags flags = window()->windowFlags();
+ // Remove current window (if any)
+ if (m_nsWindow) {
+ clearNSWindow(m_nsWindow);
+ [m_nsWindow close];
+ [m_nsWindow release];
+ m_nsWindow = 0;
+ [m_nsWindowDelegate release];
+ m_nsWindowDelegate = 0;
+ }
- const bool popup = (type == Qt::Popup);
+ if (!parentWindow) {
+ // Create a new NSWindow if this is a top-level window.
+ m_nsWindow = createNSWindow();
+ setNSWindow(m_nsWindow);
- if (type == Qt::ToolTip || type == Qt::SplashScreen || popup)
- flags |= Qt::FramelessWindowHint;
+ // QPlatformWindow subclasses must sync up with QWindow on creation:
+ propagateSizeHints();
+ setWindowFlags(window()->flags());
+ setWindowTitle(window()->title());
+ setWindowState(window()->windowState());
+ } else {
+ // Child windows have no NSWindow, link the NSViews instead.
+ const QCocoaWindow *parentCococaWindow = static_cast<const QCocoaWindow *>(parentWindow);
+ [parentCococaWindow->m_contentView addSubview : m_contentView];
+ }
+}
- m_windowClass = kSheetWindowClass;
+NSWindow * QCocoaWindow::createNSWindow()
+{
+ QCocoaAutoReleasePool pool;
- if (popup || type == Qt::SplashScreen)
- m_windowClass = kModalWindowClass;
- else if (type == Qt::ToolTip)
- m_windowClass = kHelpWindowClass;
- else if (type == Qt::Tool)
- m_windowClass = kFloatingWindowClass;
- else
- m_windowClass = kDocumentWindowClass;
-
- m_windowAttributes = (kWindowCompositingAttribute | kWindowStandardHandlerAttribute);
-
-// if(qt_mac_is_macsheet(window())) {
-// m_windowClass = kSheetWindowClass;
-// } else
-
- {
- // Shift things around a bit to get the correct window class based on the presence
- // (or lack) of the border.
-
- bool customize = flags & Qt::CustomizeWindowHint;
- bool framelessWindow = (flags & Qt::FramelessWindowHint || (customize && !(flags & Qt::WindowTitleHint)));
- if (framelessWindow) {
- if (m_windowClass == kDocumentWindowClass) {
- m_windowAttributes |= kWindowNoTitleBarAttribute;
- } else if (m_windowClass == kFloatingWindowClass) {
- m_windowAttributes |= kWindowNoTitleBarAttribute;
- } else if (m_windowClass == kMovableModalWindowClass) {
- m_windowClass = kModalWindowClass;
- }
- } else {
- m_windowAttributes |= NSTitledWindowMask;
- if (m_windowClass != kModalWindowClass)
- m_windowAttributes |= NSResizableWindowMask;
- }
+ NSRect frame = qt_mac_flipRect(window()->geometry(), window());
- // Only add extra decorations (well, buttons) for widgets that can have them
- // and have an actual border we can put them on.
+ Qt::WindowType type = window()->type();
+ Qt::WindowFlags flags = window()->flags();
- if(m_windowClass != kModalWindowClass && m_windowClass != kMovableModalWindowClass
- && m_windowClass != kSheetWindowClass && m_windowClass != kPlainWindowClass
- && !framelessWindow && m_windowClass != kDrawerWindowClass
- && m_windowClass != kHelpWindowClass) {
- if (flags & Qt::WindowMinimizeButtonHint)
- m_windowAttributes |= NSMiniaturizableWindowMask;
- if (flags & Qt::WindowSystemMenuHint || flags & Qt::WindowCloseButtonHint)
- m_windowAttributes |= NSClosableWindowMask;
- } else {
- // Clear these hints so that we aren't call them on invalid windows
- flags &= ~(Qt::WindowMaximizeButtonHint | Qt::WindowMinimizeButtonHint
- | Qt::WindowCloseButtonHint | Qt::WindowSystemMenuHint);
- }
+ NSUInteger styleMask = windowStyleMask(flags);
+ NSWindow *createdWindow = 0;
- }
+ // Use NSPanel for popup-type windows. (Popup, Tool, ToolTip, SplashScreen)
+ if ((type & Qt::Popup) == Qt::Popup) {
+ QNSPanel *window;
+ window = [[QNSPanel alloc] initWithContentRect:frame
+ styleMask: styleMask
+ 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.).
+ [window setHasShadow:YES];
- if((popup || type == Qt::Tool) && !window()->isModal())
- m_windowAttributes |= kWindowHideOnSuspendAttribute;
- m_windowAttributes |= kWindowLiveResizeAttribute;
-}
-
-/*
-
-*/
-NSWindow * QCocoaWindow::createWindow()
-{
- // Determine if we need to add in our "custom window" attribute. Cocoa is rather clever
- // in deciding if we need the maximize button or not (i.e., it's resizable, so you
- // must need a maximize button). So, the only buttons we have control over are the
- // close and minimize buttons. If someone wants to customize and NOT have the maximize
- // button, then we have to do our hack. We only do it for these cases because otherwise
- // the window looks different when activated. This "QtMacCustomizeWindow" attribute is
- // intruding on a public space and WILL BREAK in the future.
- // One can hope that there is a more public API available by that time.
-/*
- Qt::WindowFlags flags = widget ? widget->windowFlags() : Qt::WindowFlags(0);
- if ((flags & Qt::CustomizeWindowHint)) {
- if ((flags & (Qt::WindowCloseButtonHint | Qt::WindowSystemMenuHint
- | Qt::WindowMinimizeButtonHint | Qt::WindowTitleHint))
- && !(flags & Qt::WindowMaximizeButtonHint))
- wattr |= QtMacCustomizeWindow;
- }
-*/
- NSRect frame = globalGeometry(window()->geometry());
- QCocoaAutoReleasePool pool;
- NSWindow *window;
-
- switch (m_windowClass) {
- case kMovableModalWindowClass:
- case kModalWindowClass:
- case kSheetWindowClass:
- case kFloatingWindowClass:
- case kOverlayWindowClass:
- case kHelpWindowClass: {
- NSPanel *panel;
-
- BOOL needFloating = NO;
- //BOOL worksWhenModal = (this->window()->windowType() == Qt::Popup);
-
- // Add in the extra flags if necessary.
- switch (m_windowClass) {
- case kSheetWindowClass:
- m_windowAttributes |= NSDocModalWindowMask;
- break;
- case kFloatingWindowClass:
- case kHelpWindowClass:
- needFloating = YES;
- m_windowAttributes |= NSUtilityWindowMask;
- break;
- default:
- break;
+#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_7
+ 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];
}
-
- panel = [[NSPanel alloc] initWithContentRect:frame
- styleMask:m_windowAttributes
- backing:NSBackingStoreBuffered
- defer:NO]; // see window case below
-// ### crashes
-// [panel setFloatingPanel:needFloating];
-// [panel setWorksWhenModal:worksWhenModal];
- window = static_cast<NSWindow *>(panel);
- break;
+#endif
+ window->m_cocoaPlatformWindow = this;
+ createdWindow = window;
+ } else {
+ QNSWindow *window;
+ window = [[QNSWindow alloc] initWithContentRect:frame
+ styleMask: styleMask
+ 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.).
+ window->m_cocoaPlatformWindow = this;
+ setWindowShadow(flags);
+
+#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_7
+ if (QSysInfo::QSysInfo::MacintoshVersion >= QSysInfo::MV_10_7) {
+ // All windows with the WindowMaximizeButtonHint set also get a full-screen button.
+ if (flags & Qt::WindowMaximizeButtonHint)
+ [window setCollectionBehavior:NSWindowCollectionBehaviorFullScreenPrimary];
}
- default:
- window = [[NSWindow alloc] initWithContentRect:frame
- styleMask:(NSResizableWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask | NSTitledWindowMask)
- 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.).
- break;
+#endif
+
+ createdWindow = window;
}
- //qt_syncCocoaTitleBarButtons(window, widget);
- return window;
+ NSInteger level = windowLevel(flags);
+ [createdWindow setLevel:level];
+ m_windowModality = window()->modality();
+ return createdWindow;
}
-// Calculate the global screen geometry for the given local geometry, which
-// might be in the parent window coordinate system.
-NSRect QCocoaWindow::globalGeometry(const QRect localGeometry) const
+void QCocoaWindow::setNSWindow(NSWindow *window)
{
- QRect finalGeometry = localGeometry;
+ m_nsWindowDelegate = [[QNSWindowDelegate alloc] initWithQCocoaWindow:this];
+ [window setDelegate:m_nsWindowDelegate];
+ [window setAcceptsMouseMovedEvents:YES];
- if (QCocoaWindow *parent = parentCocoaWindow()) {
- QRect parentGeometry = parent->windowGeometry();
- finalGeometry.adjust(parentGeometry.x(), parentGeometry.y(), parentGeometry.x(), parentGeometry.y());
+ // 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];
- // Qt child window geometry assumes that the origin is at the
- // top-left of the content area of the parent window. The title
- // bar is not a part of this content area, but is still included
- // in the NSWindow height. Move the child window down to account
- // for this if the parent window has a title bar.
- const int titlebarHeight = 22;
- if (!(window()->windowFlags() & Qt::FramelessWindowHint))
- finalGeometry.adjust(0, titlebarHeight, 0, titlebarHeight);
- }
- // The final "y invert" to get OS X global geometry:
- QPlatformScreen *onScreen = QPlatformScreen::platformScreenForWindow(window());
- int flippedY = onScreen->geometry().height() - finalGeometry.y() - finalGeometry.height();
- return NSMakeRect(finalGeometry.x(), flippedY, finalGeometry.width(), finalGeometry.height());
+ [[NSNotificationCenter defaultCenter] addObserver:m_contentView
+ selector:@selector(windowNotification:)
+ name:nil // Get all notifications
+ object:m_nsWindow];
+
+ [window setContentView:m_contentView];
+}
+
+void QCocoaWindow::clearNSWindow(NSWindow *window)
+{
+ [window setContentView:nil];
+ [window setDelegate:nil];
+ [window clearPlatformWindow];
+ [[NSNotificationCenter defaultCenter] removeObserver:m_contentView];
}
// Returns the current global screen geometry for the nswindow associated with this window.
QRect QCocoaWindow::windowGeometry() const
{
+ if (!m_nsWindow)
+ return geometry();
+
NSRect rect = [m_nsWindow frame];
QPlatformScreen *onScreen = QPlatformScreen::platformScreenForWindow(window());
int flippedY = onScreen->geometry().height() - rect.origin.y - rect.size.height; // account for nswindow inverted y.
return 0;
}
+// Syncs the NSWindow minimize/maximize/fullscreen state with the current QWindow state
+void QCocoaWindow::syncWindowState(Qt::WindowState newState)
+{
+ if (!m_nsWindow)
+ return;
+
+ // if content view width or height is 0 then the window animations will crash so
+ // do nothing except set the new state
+ NSRect contentRect = [contentView() frame];
+ if (contentRect.size.width <= 0 || contentRect.size.height <= 0) {
+ qWarning() << Q_FUNC_INFO << "invalid window content view size, check your window geometry";
+ m_synchedWindowState = newState;
+ return;
+ }
+
+ if ((m_synchedWindowState & Qt::WindowMaximized) != (newState & Qt::WindowMaximized)) {
+ [m_nsWindow performZoom : m_nsWindow]; // toggles
+ }
+
+ if ((m_synchedWindowState & Qt::WindowMinimized) != (newState & Qt::WindowMinimized)) {
+ if (newState & Qt::WindowMinimized) {
+ [m_nsWindow performMiniaturize : m_nsWindow];
+ } else {
+ [m_nsWindow deminiaturize : m_nsWindow];
+ }
+ }
+
+ if ((m_synchedWindowState & Qt::WindowFullScreen) != (newState & Qt::WindowFullScreen)) {
+#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_7
+ if (QSysInfo::QSysInfo::MacintoshVersion >= QSysInfo::MV_10_7) {
+ [m_nsWindow toggleFullScreen : m_nsWindow];
+ } else {
+ // TODO: "normal" fullscreen
+ }
+#endif
+ }
+
+ // New state is now the current synched state
+ m_synchedWindowState = newState;
+}
+
+bool QCocoaWindow::setWindowModified(bool modified)
+{
+ if (!m_nsWindow)
+ return false;
+ [m_nsWindow setDocumentEdited:(modified?YES:NO)];
+ return true;
+}
+
+void QCocoaWindow::setMenubar(QCocoaMenuBar *mb)
+{
+ m_menubar = mb;
+}
+
+QCocoaMenuBar *QCocoaWindow::menubar() const
+{
+ return m_menubar;
+}
+
+QMargins QCocoaWindow::frameMargins() const
+{
+ NSRect frameW = [m_nsWindow frame];
+ NSRect frameC = [m_nsWindow contentRectForFrameRect:frameW];
+
+ return QMargins(frameW.origin.x - frameC.origin.x,
+ (frameW.origin.y + frameW.size.height) - (frameC.origin.y + frameC.size.height),
+ (frameW.origin.x + frameW.size.width) - (frameC.origin.x + frameC.size.width),
+ frameC.origin.y - frameW.origin.y);
+}
+
+void QCocoaWindow::setFrameStrutEventsEnabled(bool enabled)
+{
+ m_frameStrutEventsEnabled = enabled;
+}