diff options
Diffstat (limited to 'src/plugins/platforms/cocoa')
32 files changed, 2935 insertions, 262 deletions
diff --git a/src/plugins/platforms/cocoa/cocoa.pro b/src/plugins/platforms/cocoa/cocoa.pro index bfa147f948..afc094c4cb 100644 --- a/src/plugins/platforms/cocoa/cocoa.pro +++ b/src/plugins/platforms/cocoa/cocoa.pro @@ -2,28 +2,39 @@ TARGET = qcocoa load(qt_plugin) DESTDIR = $$QT.gui.plugins/platforms -OBJECTIVE_SOURCES = main.mm \ +OBJECTIVE_SOURCES += main.mm \ qcocoaintegration.mm \ - qcocoawindowsurface.mm \ + qcocoabackingstore.mm \ qcocoawindow.mm \ qnsview.mm \ - qcocoaeventloopintegration.mm \ qcocoaautoreleasepool.mm \ - qnswindowdelegate.mm + qnswindowdelegate.mm \ + qcocoaglcontext.mm \ + qcocoanativeinterface.mm \ + qcocoaeventdispatcher.mm \ + qcocoamenuloader.mm \ + qcocoahelpers.mm \ -OBJECTIVE_HEADERS = qcocoaintegration.h \ - qcocoawindowsurface.h \ +HEADERS += qcocoaintegration.h \ + qcocoabackingstore.h \ qcocoawindow.h \ qnsview.h \ - qcocoaeventloopintegration.h \ qcocoaautoreleasepool.h \ - qnswindowdelegate.h + qnswindowdelegate.h \ + qcocoaglcontext.h \ + qcocoanativeinterface.h \ + qcocoaeventdispatcher.h \ + qcocoamenuloader.h \ + qcocoahelpers.h \ + + +RESOURCES += qcocoaresources.qrc #add libz for freetype. -LIBS += -lz -LIBS += -framework cocoa +LIBS += -lz -framework Cocoa -include(../fontdatabases/coretext/coretext.pri) +QT += core-private gui-private platformsupport-private + +CONFIG += qpa/basicunixfontdatabase target.path += $$[QT_INSTALL_PLUGINS]/platforms INSTALLS += target - diff --git a/src/plugins/platforms/cocoa/images/copyarrowcursor.png b/src/plugins/platforms/cocoa/images/copyarrowcursor.png Binary files differnew file mode 100644 index 0000000000..13dfca95bc --- /dev/null +++ b/src/plugins/platforms/cocoa/images/copyarrowcursor.png diff --git a/src/plugins/platforms/cocoa/images/forbiddencursor.png b/src/plugins/platforms/cocoa/images/forbiddencursor.png Binary files differnew file mode 100644 index 0000000000..a9f21b4a5e --- /dev/null +++ b/src/plugins/platforms/cocoa/images/forbiddencursor.png diff --git a/src/plugins/platforms/cocoa/images/leopard-unified-toolbar-on.png b/src/plugins/platforms/cocoa/images/leopard-unified-toolbar-on.png Binary files differnew file mode 100644 index 0000000000..6716597046 --- /dev/null +++ b/src/plugins/platforms/cocoa/images/leopard-unified-toolbar-on.png diff --git a/src/plugins/platforms/cocoa/images/pluscursor.png b/src/plugins/platforms/cocoa/images/pluscursor.png Binary files differnew file mode 100644 index 0000000000..c583c088c9 --- /dev/null +++ b/src/plugins/platforms/cocoa/images/pluscursor.png diff --git a/src/plugins/platforms/cocoa/images/spincursor.png b/src/plugins/platforms/cocoa/images/spincursor.png Binary files differnew file mode 100644 index 0000000000..ca44ab50fd --- /dev/null +++ b/src/plugins/platforms/cocoa/images/spincursor.png diff --git a/src/plugins/platforms/cocoa/images/waitcursor.png b/src/plugins/platforms/cocoa/images/waitcursor.png Binary files differnew file mode 100644 index 0000000000..a9abe61320 --- /dev/null +++ b/src/plugins/platforms/cocoa/images/waitcursor.png diff --git a/src/plugins/platforms/cocoa/qcocoawindowsurface.h b/src/plugins/platforms/cocoa/qcocoabackingstore.h index 95eea2b7ea..938e27347c 100644 --- a/src/plugins/platforms/cocoa/qcocoawindowsurface.h +++ b/src/plugins/platforms/cocoa/qcocoabackingstore.h @@ -39,33 +39,31 @@ ** ****************************************************************************/ -#ifndef QWINDOWSURFACE_COCOA_H -#define QWINDOWSURFACE_COCOA_H +#ifndef QBACKINGSTORE_COCOA_H +#define QBACKINGSTORE_COCOA_H #include <Cocoa/Cocoa.h> #include "qcocoawindow.h" #include "qnsview.h" -#include <QtGui/private/qwindowsurface_p.h> +#include <QPlatformBackingStore> QT_BEGIN_NAMESPACE -class QCocoaWindowSurface : public QWindowSurface +class QCocoaBackingStore : public QPlatformBackingStore { public: - QCocoaWindowSurface(QWidget *window, WId wid); - ~QCocoaWindowSurface(); + QCocoaBackingStore(QWindow *window); + ~QCocoaBackingStore(); QPaintDevice *paintDevice(); - void flush(QWidget *widget, const QRegion ®ion, const QPoint &offset); - void resize (const QSize &size); + void flush(QWindow *widget, const QRegion ®ion, const QPoint &offset); + void resize (const QSize &size, const QRegion &); private: - QCocoaWindow *m_cocoaWindow; QImage *m_image; - QNSView *m_contentView; }; QT_END_NAMESPACE diff --git a/src/plugins/platforms/cocoa/qcocoawindowsurface.mm b/src/plugins/platforms/cocoa/qcocoabackingstore.mm index 16bb327196..1a25608a7a 100644 --- a/src/plugins/platforms/cocoa/qcocoawindowsurface.mm +++ b/src/plugins/platforms/cocoa/qcocoabackingstore.mm @@ -39,10 +39,9 @@ ** ****************************************************************************/ -#include "qcocoawindowsurface.h" +#include "qcocoabackingstore.h" #include <QtCore/qdebug.h> - #include <QtGui/QPainter> QT_BEGIN_NAMESPACE @@ -56,30 +55,28 @@ QRect flipedRect(const QRect &sourceRect,int height) return flippedRect; } -QCocoaWindowSurface::QCocoaWindowSurface(QWidget *window, WId wId) - : QWindowSurface(window) +QCocoaBackingStore::QCocoaBackingStore(QWindow *window) + : QPlatformBackingStore(window) { - m_cocoaWindow = static_cast<QCocoaWindow *>(window->platformWindow()); + m_cocoaWindow = static_cast<QCocoaWindow *>(window->handle()); const QRect geo = window->geometry(); NSRect rect = NSMakeRect(geo.x(),geo.y(),geo.width(),geo.height()); - m_contentView = [[QNSView alloc] initWithWidget:window]; - m_cocoaWindow->setContentView(m_contentView); - m_image = new QImage(window->size(),QImage::Format_ARGB32); + m_image = new QImage(window->geometry().size(),QImage::Format_ARGB32); } -QCocoaWindowSurface::~QCocoaWindowSurface() +QCocoaBackingStore::~QCocoaBackingStore() { delete m_image; } -QPaintDevice *QCocoaWindowSurface::paintDevice() +QPaintDevice *QCocoaBackingStore::paintDevice() { return m_image; } -void QCocoaWindowSurface::flush(QWidget *widget, const QRegion ®ion, const QPoint &offset) +void QCocoaBackingStore::flush(QWindow *widget, const QRegion ®ion, const QPoint &offset) { Q_UNUSED(widget); Q_UNUSED(offset); @@ -87,17 +84,15 @@ void QCocoaWindowSurface::flush(QWidget *widget, const QRegion ®ion, const QP QRect geo = region.boundingRect(); NSRect rect = NSMakeRect(geo.x(), geo.y(), geo.width(), geo.height()); - [m_contentView displayRect:rect]; + [m_cocoaWindow->m_windowSurfaceView displayRect:rect]; } -void QCocoaWindowSurface::resize(const QSize &size) +void QCocoaBackingStore::resize(const QSize &size, const QRegion &) { - QWindowSurface::resize(size); delete m_image; m_image = new QImage(size,QImage::Format_ARGB32_Premultiplied); NSSize newSize = NSMakeSize(size.width(),size.height()); - [m_contentView setImage:m_image]; - + [static_cast<QNSView *>(m_cocoaWindow->m_windowSurfaceView) setImage:m_image]; } QT_END_NAMESPACE diff --git a/src/plugins/platforms/cocoa/qcocoaeventdispatcher.h b/src/plugins/platforms/cocoa/qcocoaeventdispatcher.h new file mode 100644 index 0000000000..2085a437ac --- /dev/null +++ b/src/plugins/platforms/cocoa/qcocoaeventdispatcher.h @@ -0,0 +1,231 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia 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. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/**************************************************************************** +** +** Copyright (c) 2007-2008, Apple, Inc. +** +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are met: +** +** * Redistributions of source code must retain the above copyright notice, +** this list of conditions and the following disclaimer. +** +** * Redistributions in binary form must reproduce the above copyright notice, +** this list of conditions and the following disclaimer in the documentation +** and/or other materials provided with the distribution. +** +** * Neither the name of Apple, Inc. nor the names of its contributors +** may be used to endorse or promote products derived from this software +** without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +** CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +** EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +** PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +** +****************************************************************************/ + +#ifndef QEVENTDISPATCHER_MAC_P_H +#define QEVENTDISPATCHER_MAC_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtCore/qhash.h> +#include <QtCore/qstack.h> +#include <QtGui/qwindowdefs.h> +#include <QtCore/private/qeventdispatcher_unix_p.h> + +#include <CoreFoundation/CoreFoundation.h> + +QT_BEGIN_NAMESPACE + +typedef struct _NSModalSession *NSModalSession; +typedef struct _QCocoaModalSessionInfo { + QPointer<QWindow> window; + NSModalSession session; + void *nswindow; +} QCocoaModalSessionInfo; + +class Q_GUI_EXPORT QMacCocoaAutoReleasePool +{ +private: + void *pool; +public: + QMacCocoaAutoReleasePool(); + ~QMacCocoaAutoReleasePool(); + + inline void *handle() const { return pool; } +}; + +class QCocoaEventDispatcherPrivate; +class QCocoaEventDispatcher : public QEventDispatcherUNIX +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QCocoaEventDispatcher) + +public: + QCocoaEventDispatcher(QAbstractEventDispatcherPrivate &priv, QObject *parent = 0); + explicit QCocoaEventDispatcher(QObject *parent = 0); + ~QCocoaEventDispatcher(); + + + bool processEvents(QEventLoop::ProcessEventsFlags flags); + bool hasPendingEvents(); + + void registerSocketNotifier(QSocketNotifier *notifier); + void unregisterSocketNotifier(QSocketNotifier *notifier); + + void registerTimer(int timerId, int interval, QObject *object); + bool unregisterTimer(int timerId); + bool unregisterTimers(QObject *object); + QList<TimerInfo> registeredTimers(QObject *object) const; + + void wakeUp(); + void interrupt(); + +private: + //friend void qt_mac_select_timer_callbk(__EventLoopTimer*, void*); + friend class QApplicationPrivate; +}; + +struct MacTimerInfo { + int id; + int interval; + QObject *obj; + bool pending; + CFRunLoopTimerRef runLoopTimer; + bool operator==(const MacTimerInfo &other) + { + return (id == other.id); + } +}; +typedef QHash<int, MacTimerInfo *> MacTimerHash; + +struct MacSocketInfo { + MacSocketInfo() : socket(0), runloop(0), readNotifier(0), writeNotifier(0) {} + CFSocketRef socket; + CFRunLoopSourceRef runloop; + QObject *readNotifier; + QObject *writeNotifier; +}; +typedef QHash<int, MacSocketInfo *> MacSocketHash; + +class QCocoaEventDispatcherPrivate : public QEventDispatcherUNIXPrivate +{ + Q_DECLARE_PUBLIC(QCocoaEventDispatcher) + +public: + QCocoaEventDispatcherPrivate(); + + static MacTimerHash macTimerHash; + // Set 'blockSendPostedEvents' to true if you _really_ need + // to make sure that qt events are not posted while calling + // low-level cocoa functions (like beginModalForWindow). And + // use a QBoolBlocker to be safe: + static bool blockSendPostedEvents; + // The following variables help organizing modal sessions: + static QStack<QCocoaModalSessionInfo> cocoaModalSessionStack; + static bool currentExecIsNSAppRun; + static bool nsAppRunCalledByQt; + static bool cleanupModalSessionsNeeded; + static NSModalSession currentModalSessionCached; + static NSModalSession currentModalSession(); + static void updateChildrenWorksWhenModal(); + static void temporarilyStopAllModalSessions(); + static void beginModalSession(QWindow *widget); + static void endModalSession(QWindow *widget); + static void cancelWaitForMoreEvents(); + static void cleanupModalSessions(); + static void ensureNSAppInitialized(); + + MacSocketHash macSockets; + QList<void *> queuedUserInputEvents; // NSEvent * + CFRunLoopSourceRef postedEventsSource; + CFRunLoopObserverRef waitingObserver; + CFRunLoopObserverRef firstTimeObserver; + QAtomicInt serialNumber; + int lastSerial; + static bool interrupt; +private: + static Boolean postedEventSourceEqualCallback(const void *info1, const void *info2); + static void postedEventsSourcePerformCallback(void *info); + static void activateTimer(CFRunLoopTimerRef, void *info); + static void waitingObserverCallback(CFRunLoopObserverRef observer, + CFRunLoopActivity activity, void *info); + static void firstLoopEntry(CFRunLoopObserverRef ref, CFRunLoopActivity activity, void *info); + friend void processPostedEvents(QCocoaEventDispatcherPrivate *const d, const bool blockSendPostedEvents); +}; + +class QtCocoaInterruptDispatcher : public QObject +{ + static QtCocoaInterruptDispatcher *instance; + bool cancelled; + + QtCocoaInterruptDispatcher(); + ~QtCocoaInterruptDispatcher(); + + public: + static void interruptLater(); + static void cancelInterruptLater(); +}; + +QT_END_NAMESPACE + +#endif // QEVENTDISPATCHER_MAC_P_H diff --git a/src/plugins/platforms/cocoa/qcocoaeventdispatcher.mm b/src/plugins/platforms/cocoa/qcocoaeventdispatcher.mm new file mode 100644 index 0000000000..22fe8b2e33 --- /dev/null +++ b/src/plugins/platforms/cocoa/qcocoaeventdispatcher.mm @@ -0,0 +1,1134 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia 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. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/**************************************************************************** +** +** Copyright (c) 2007-2008, Apple, Inc. +** +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are met: +** +** * Redistributions of source code must retain the above copyright notice, +** this list of conditions and the following disclaimer. +** +** * Redistributions in binary form must reproduce the above copyright notice, +** this list of conditions and the following disclaimer in the documentation +** and/or other materials provided with the distribution. +** +** * Neither the name of Apple, Inc. nor the names of its contributors +** may be used to endorse or promote products derived from this software +** without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +** CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +** EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +** PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +** +****************************************************************************/ + +#include "qcocoaeventdispatcher.h" +#include "qguiapplication.h" +#include "qevent.h" +#include "qhash.h" +#include "qmutex.h" +#include "qsocketnotifier.h" +#include <qplatformwindow_qpa.h> +#include "private/qthread_p.h" +#include "private/qguiapplication_p.h" +#include <qdebug.h> + +//#include <private/qcocoaapplication_mac_p.h> +//#include "private/qt_cocoa_helpers_mac_p.h" +#undef slots +#include <Cocoa/Cocoa.h> +#include <Carbon/Carbon.h> + +QT_BEGIN_NAMESPACE + +QT_USE_NAMESPACE + +enum { + QtCocoaEventSubTypeWakeup = SHRT_MAX, + QtCocoaEventSubTypePostMessage = SHRT_MAX-1 +}; + +static inline CFRunLoopRef mainRunLoop() +{ + return CFRunLoopGetMain(); +} + +QMacCocoaAutoReleasePool::QMacCocoaAutoReleasePool() +{ + pool = (void*)[[NSAutoreleasePool alloc] init]; +} + +QMacCocoaAutoReleasePool::~QMacCocoaAutoReleasePool() +{ + [(NSAutoreleasePool*)pool release]; +} + + +/***************************************************************************** + Timers stuff + *****************************************************************************/ + +/* timer call back */ +void QCocoaEventDispatcherPrivate::activateTimer(CFRunLoopTimerRef, void *info) +{ + int timerID = +#ifdef Q_OS_MAC64 + qint64(info); +#else + int(info); +#endif + + MacTimerInfo *tmr; + tmr = macTimerHash.value(timerID); + if (tmr == 0 || tmr->pending == true) + return; // Can't send another timer event if it's pending. + + + if (blockSendPostedEvents) { + QCoreApplication::postEvent(tmr->obj, new QTimerEvent(tmr->id)); + } else { + tmr->pending = true; + QTimerEvent e(tmr->id); + + QCoreApplication::sendSpontaneousEvent(tmr->obj, &e); + // Get the value again in case the timer gets unregistered during the sendEvent. + tmr = macTimerHash.value(timerID); + if (tmr != 0) + tmr->pending = false; + } + +} + +void QCocoaEventDispatcher::registerTimer(int timerId, int interval, QObject *obj) +{ +#ifndef QT_NO_DEBUG + if (timerId < 1 || interval < 0 || !obj) { + qWarning("QEventDispatcherMac::registerTimer: invalid arguments"); + return; + } else if (obj->thread() != thread() || thread() != QThread::currentThread()) { + qWarning("QObject::startTimer: timers cannot be started from another thread"); + return; + } +#endif + + MacTimerInfo *t = new MacTimerInfo(); + t->id = timerId; + t->interval = interval; + t->obj = obj; + t->runLoopTimer = 0; + t->pending = false; + + CFAbsoluteTime fireDate = CFAbsoluteTimeGetCurrent(); + CFTimeInterval cfinterval = qMax(CFTimeInterval(interval) / 1000, 0.0000001); + fireDate += cfinterval; + QCocoaEventDispatcherPrivate::macTimerHash.insert(timerId, t); + CFRunLoopTimerContext info = { 0, (void *)timerId, 0, 0, 0 }; + t->runLoopTimer = CFRunLoopTimerCreate(0, fireDate, cfinterval, 0, 0, + QCocoaEventDispatcherPrivate::activateTimer, &info); + if (t->runLoopTimer == 0) { + qFatal("QEventDispatcherMac::registerTimer: Cannot create timer"); + } + CFRunLoopAddTimer(mainRunLoop(), t->runLoopTimer, kCFRunLoopCommonModes); +} + +bool QCocoaEventDispatcher::unregisterTimer(int identifier) +{ +#ifndef QT_NO_DEBUG + if (identifier < 1) { + qWarning("QEventDispatcherMac::unregisterTimer: invalid argument"); + return false; + } else if (thread() != QThread::currentThread()) { + qWarning("QObject::killTimer: timers cannot be stopped from another thread"); + return false; + } +#endif + if (identifier <= 0) + return false; // not init'd or invalid timer + + MacTimerInfo *timerInfo = QCocoaEventDispatcherPrivate::macTimerHash.take(identifier); + if (timerInfo == 0) + return false; + + if (!QObjectPrivate::get(timerInfo->obj)->inThreadChangeEvent) + QAbstractEventDispatcherPrivate::releaseTimerId(identifier); + CFRunLoopTimerInvalidate(timerInfo->runLoopTimer); + CFRelease(timerInfo->runLoopTimer); + delete timerInfo; + + return true; +} + +bool QCocoaEventDispatcher::unregisterTimers(QObject *obj) +{ +#ifndef QT_NO_DEBUG + if (!obj) { + qWarning("QEventDispatcherMac::unregisterTimers: invalid argument"); + return false; + } else if (obj->thread() != thread() || thread() != QThread::currentThread()) { + qWarning("QObject::killTimers: timers cannot be stopped from another thread"); + return false; + } +#endif + + MacTimerHash::iterator it = QCocoaEventDispatcherPrivate::macTimerHash.begin(); + while (it != QCocoaEventDispatcherPrivate::macTimerHash.end()) { + MacTimerInfo *timerInfo = it.value(); + if (timerInfo->obj != obj) { + ++it; + } else { + if (!QObjectPrivate::get(timerInfo->obj)->inThreadChangeEvent) + QAbstractEventDispatcherPrivate::releaseTimerId(timerInfo->id); + CFRunLoopTimerInvalidate(timerInfo->runLoopTimer); + CFRelease(timerInfo->runLoopTimer); + delete timerInfo; + it = QCocoaEventDispatcherPrivate::macTimerHash.erase(it); + } + } + return true; +} + +QList<QCocoaEventDispatcher::TimerInfo> +QCocoaEventDispatcher::registeredTimers(QObject *object) const +{ + if (!object) { + qWarning("QEventDispatcherMac:registeredTimers: invalid argument"); + return QList<TimerInfo>(); + } + + QList<TimerInfo> list; + + MacTimerHash::const_iterator it = QCocoaEventDispatcherPrivate::macTimerHash.constBegin(); + while (it != QCocoaEventDispatcherPrivate::macTimerHash.constEnd()) { + MacTimerInfo *t = it.value(); + if (t->obj == object) + list << TimerInfo(t->id, t->interval); + ++it; + } + return list; +} + +/************************************************************************** + Socket Notifiers + *************************************************************************/ +void qt_mac_socket_callback(CFSocketRef s, CFSocketCallBackType callbackType, CFDataRef, + const void *, void *info) { + QCocoaEventDispatcherPrivate *const eventDispatcher + = static_cast<QCocoaEventDispatcherPrivate *>(info); + int nativeSocket = CFSocketGetNative(s); + MacSocketInfo *socketInfo = eventDispatcher->macSockets.value(nativeSocket); + QEvent notifierEvent(QEvent::SockAct); + + // There is a race condition that happen where we disable the notifier and + // the kernel still has a notification to pass on. We then get this + // notification after we've successfully disabled the CFSocket, but our Qt + // notifier is now gone. The upshot is we have to check the notifier + // everytime. + if (callbackType == kCFSocketReadCallBack) { + if (socketInfo->readNotifier) + QGuiApplication::sendEvent(socketInfo->readNotifier, ¬ifierEvent); + } else if (callbackType == kCFSocketWriteCallBack) { + if (socketInfo->writeNotifier) + QGuiApplication::sendEvent(socketInfo->writeNotifier, ¬ifierEvent); + } +} + +/* + Adds a loop source for the given socket to the current run loop. +*/ +CFRunLoopSourceRef qt_mac_add_socket_to_runloop(const CFSocketRef socket) +{ + CFRunLoopSourceRef loopSource = CFSocketCreateRunLoopSource(kCFAllocatorDefault, socket, 0); + if (!loopSource) + return 0; + + CFRunLoopAddSource(mainRunLoop(), loopSource, kCFRunLoopCommonModes); + return loopSource; +} + +/* + Removes the loop source for the given socket from the current run loop. +*/ +void qt_mac_remove_socket_from_runloop(const CFSocketRef socket, CFRunLoopSourceRef runloop) +{ + Q_ASSERT(runloop); + CFRunLoopRemoveSource(mainRunLoop(), runloop, kCFRunLoopCommonModes); + CFSocketDisableCallBacks(socket, kCFSocketReadCallBack); + CFSocketDisableCallBacks(socket, kCFSocketWriteCallBack); + CFRunLoopSourceInvalidate(runloop); +} + +/* + Register a QSocketNotifier with the mac event system by creating a CFSocket with + with a read/write callback. + + Qt has separate socket notifiers for reading and writing, but on the mac there is + a limitation of one CFSocket object for each native socket. +*/ +void QCocoaEventDispatcher::registerSocketNotifier(QSocketNotifier *notifier) +{ + Q_ASSERT(notifier); + int nativeSocket = notifier->socket(); + int type = notifier->type(); +#ifndef QT_NO_DEBUG + if (nativeSocket < 0 || nativeSocket > FD_SETSIZE) { + qWarning("QSocketNotifier: Internal error"); + return; + } else if (notifier->thread() != thread() + || thread() != QThread::currentThread()) { + qWarning("QSocketNotifier: socket notifiers cannot be enabled from another thread"); + return; + } +#endif + + Q_D(QCocoaEventDispatcher); + + if (type == QSocketNotifier::Exception) { + qWarning("QSocketNotifier::Exception is not supported on Mac OS X"); + return; + } + + // Check if we have a CFSocket for the native socket, create one if not. + MacSocketInfo *socketInfo = d->macSockets.value(nativeSocket); + if (!socketInfo) { + socketInfo = new MacSocketInfo(); + + // Create CFSocket, specify that we want both read and write callbacks (the callbacks + // are enabled/disabled later on). + const int callbackTypes = kCFSocketReadCallBack | kCFSocketWriteCallBack; + CFSocketContext context = {0, d, 0, 0, 0}; + socketInfo->socket = CFSocketCreateWithNative(kCFAllocatorDefault, nativeSocket, callbackTypes, qt_mac_socket_callback, &context); + if (CFSocketIsValid(socketInfo->socket) == false) { + qWarning("QEventDispatcherMac::registerSocketNotifier: Failed to create CFSocket"); + return; + } + + CFOptionFlags flags = CFSocketGetSocketFlags(socketInfo->socket); + flags |= kCFSocketAutomaticallyReenableWriteCallBack; //QSocketNotifier stays enabled after a write + flags &= ~kCFSocketCloseOnInvalidate; //QSocketNotifier doesn't close the socket upon destruction/invalidation + CFSocketSetSocketFlags(socketInfo->socket, flags); + + // Add CFSocket to runloop. + if(!(socketInfo->runloop = qt_mac_add_socket_to_runloop(socketInfo->socket))) { + qWarning("QEventDispatcherMac::registerSocketNotifier: Failed to add CFSocket to runloop"); + CFSocketInvalidate(socketInfo->socket); + CFRelease(socketInfo->socket); + return; + } + + // Disable both callback types by default. This must be done after + // we add the CFSocket to the runloop, or else these calls will have + // no effect. + CFSocketDisableCallBacks(socketInfo->socket, kCFSocketReadCallBack); + CFSocketDisableCallBacks(socketInfo->socket, kCFSocketWriteCallBack); + + d->macSockets.insert(nativeSocket, socketInfo); + } + + // Increment read/write counters and select enable callbacks if necessary. + if (type == QSocketNotifier::Read) { + Q_ASSERT(socketInfo->readNotifier == 0); + socketInfo->readNotifier = notifier; + CFSocketEnableCallBacks(socketInfo->socket, kCFSocketReadCallBack); + } else if (type == QSocketNotifier::Write) { + Q_ASSERT(socketInfo->writeNotifier == 0); + socketInfo->writeNotifier = notifier; + CFSocketEnableCallBacks(socketInfo->socket, kCFSocketWriteCallBack); + } +} + +/* + Unregister QSocketNotifer. The CFSocket correspoding to this notifier is + removed from the runloop of this is the last notifier that users + that CFSocket. +*/ +void QCocoaEventDispatcher::unregisterSocketNotifier(QSocketNotifier *notifier) +{ + Q_ASSERT(notifier); + int nativeSocket = notifier->socket(); + int type = notifier->type(); +#ifndef QT_NO_DEBUG + if (nativeSocket < 0 || nativeSocket > FD_SETSIZE) { + qWarning("QSocketNotifier: Internal error"); + return; + } else if (notifier->thread() != thread() || thread() != QThread::currentThread()) { + qWarning("QSocketNotifier: socket notifiers cannot be disabled from another thread"); + return; + } +#endif + + Q_D(QCocoaEventDispatcher); + + if (type == QSocketNotifier::Exception) { + qWarning("QSocketNotifier::Exception is not supported on Mac OS X"); + return; + } + MacSocketInfo *socketInfo = d->macSockets.value(nativeSocket); + if (!socketInfo) { + qWarning("QEventDispatcherMac::unregisterSocketNotifier: Tried to unregister a not registered notifier"); + return; + } + + // Decrement read/write counters and disable callbacks if necessary. + if (type == QSocketNotifier::Read) { + Q_ASSERT(notifier == socketInfo->readNotifier); + socketInfo->readNotifier = 0; + CFSocketDisableCallBacks(socketInfo->socket, kCFSocketReadCallBack); + } else if (type == QSocketNotifier::Write) { + Q_ASSERT(notifier == socketInfo->writeNotifier); + socketInfo->writeNotifier = 0; + CFSocketDisableCallBacks(socketInfo->socket, kCFSocketWriteCallBack); + } + + // Remove CFSocket from runloop if this was the last QSocketNotifier. + if (socketInfo->readNotifier == 0 && socketInfo->writeNotifier == 0) { + if (CFSocketIsValid(socketInfo->socket)) + qt_mac_remove_socket_from_runloop(socketInfo->socket, socketInfo->runloop); + CFRunLoopSourceInvalidate(socketInfo->runloop); + CFRelease(socketInfo->runloop); + CFSocketInvalidate(socketInfo->socket); + CFRelease(socketInfo->socket); + delete socketInfo; + d->macSockets.remove(nativeSocket); + } +} + +bool QCocoaEventDispatcher::hasPendingEvents() +{ + extern uint qGlobalPostedEventsCount(); + extern bool qt_is_gui_used; //qapplication.cpp + return qGlobalPostedEventsCount() || (qt_is_gui_used && GetNumEventsInQueue(GetMainEventQueue())); +} + +static bool IsMouseOrKeyEvent( NSEvent* event ) +{ + bool result = false; + + switch( [event type] ) + { + case NSLeftMouseDown: + case NSLeftMouseUp: + case NSRightMouseDown: + case NSRightMouseUp: + case NSMouseMoved: // ?? + case NSLeftMouseDragged: + case NSRightMouseDragged: + case NSMouseEntered: + case NSMouseExited: + case NSKeyDown: + case NSKeyUp: + case NSFlagsChanged: // key modifiers changed? + case NSCursorUpdate: // ?? + case NSScrollWheel: + case NSTabletPoint: + case NSTabletProximity: + case NSOtherMouseDown: + case NSOtherMouseUp: + case NSOtherMouseDragged: +#ifndef QT_NO_GESTURES +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6 + case NSEventTypeGesture: // touch events + case NSEventTypeMagnify: + case NSEventTypeSwipe: + case NSEventTypeRotate: + case NSEventTypeBeginGesture: + case NSEventTypeEndGesture: +#endif +#endif // QT_NO_GESTURES + result = true; + break; + + default: + break; + } + return result; +} + +static inline void qt_mac_waitForMoreEvents() +{ + // If no event exist in the cocoa event que, wait + // (and free up cpu time) until at least one event occur. + // This implementation is a bit on the edge, but seems to + // work fine: + NSEvent* event = [NSApp nextEventMatchingMask:NSAnyEventMask + untilDate:[NSDate distantFuture] + inMode:NSDefaultRunLoopMode + dequeue:YES]; + if (event) + [NSApp postEvent:event atStart:YES]; +} + +static inline void qt_mac_waitForMoreModalSessionEvents() +{ + // If no event exist in the cocoa event que, wait + // (and free up cpu time) until at least one event occur. + // This implementation is a bit on the edge, but seems to + // work fine: + NSEvent* event = [NSApp nextEventMatchingMask:NSAnyEventMask + untilDate:[NSDate distantFuture] + inMode:NSModalPanelRunLoopMode + dequeue:YES]; + if (event) + [NSApp postEvent:event atStart:YES]; +} + +bool QCocoaEventDispatcher::processEvents(QEventLoop::ProcessEventsFlags flags) +{ + Q_D(QCocoaEventDispatcher); + d->interrupt = false; + + bool interruptLater = false; + QtCocoaInterruptDispatcher::cancelInterruptLater(); + + // In case we end up recursing while we now process events, make sure + // that we send remaining posted Qt events before this call returns: + wakeUp(); + emit awake(); + + bool excludeUserEvents = flags & QEventLoop::ExcludeUserInputEvents; + bool retVal = false; + forever { + if (d->interrupt) + break; + + QMacCocoaAutoReleasePool pool; + NSEvent* event = 0; + + // First, send all previously excluded input events, if any: + if (!excludeUserEvents) { + while (!d->queuedUserInputEvents.isEmpty()) { + event = static_cast<NSEvent *>(d->queuedUserInputEvents.takeFirst()); + if (!filterEvent(event)) { + [NSApp sendEvent:event]; + retVal = true; + } + [event release]; + } + } + + // If Qt is used as a plugin, or as an extension in a native cocoa + // application, we should not run or stop NSApplication; This will be + // done from the application itself. And if processEvents is called + // manually (rather than from a QEventLoop), we cannot enter a tight + // loop and block this call, but instead we need to return after one flush. + // Finally, if we are to exclude user input events, we cannot call [NSApp run] + // as we then loose control over which events gets dispatched: + const bool canExec_3rdParty = d->nsAppRunCalledByQt || ![NSApp isRunning]; + const bool canExec_Qt = !excludeUserEvents && + (flags & QEventLoop::DialogExec || flags & QEventLoop::EventLoopExec) ; + + if (canExec_Qt && canExec_3rdParty) { + // We can use exec-mode, meaning that we can stay in a tight loop until + // interrupted. This is mostly an optimization, but it allow us to use + // [NSApp run], which is the normal code path for cocoa applications. + if (NSModalSession session = d->currentModalSession()) { + QBoolBlocker execGuard(d->currentExecIsNSAppRun, false); + while ([NSApp runModalSession:session] == NSRunContinuesResponse && !d->interrupt) + qt_mac_waitForMoreModalSessionEvents(); + + if (!d->interrupt && session == d->currentModalSessionCached) { + // Someone called [NSApp stopModal:] from outside the event + // dispatcher (e.g to stop a native dialog). But that call wrongly stopped + // 'session' as well. As a result, we need to restart all internal sessions: + d->temporarilyStopAllModalSessions(); + } + } else { + d->nsAppRunCalledByQt = true; + QBoolBlocker execGuard(d->currentExecIsNSAppRun, true); + [NSApp run]; + } + retVal = true; + } else { + // We cannot block the thread (and run in a tight loop). + // Instead we will process all current pending events and return. + d->ensureNSAppInitialized(); + if (NSModalSession session = d->currentModalSession()) { + // INVARIANT: a modal window is executing. + if (!excludeUserEvents) { + // Since we can dispatch all kinds of events, we choose + // to use cocoa's native way of running modal sessions: + if (flags & QEventLoop::WaitForMoreEvents) + qt_mac_waitForMoreModalSessionEvents(); + NSInteger status = [NSApp runModalSession:session]; + if (status != NSRunContinuesResponse && session == d->currentModalSessionCached) { + // INVARIANT: Someone called [NSApp stopModal:] from outside the event + // dispatcher (e.g to stop a native dialog). But that call wrongly stopped + // 'session' as well. As a result, we need to restart all internal sessions: + d->temporarilyStopAllModalSessions(); + } + retVal = true; + } else do { + // Dispatch all non-user events (but que non-user events up for later). In + // this case, we need more control over which events gets dispatched, and + // cannot use [NSApp runModalSession:session]: + event = [NSApp nextEventMatchingMask:NSAnyEventMask + untilDate:nil + inMode:NSModalPanelRunLoopMode + dequeue: YES]; + + if (event) { + if (IsMouseOrKeyEvent(event)) { + [event retain]; + d->queuedUserInputEvents.append(event); + continue; + } + if (!filterEvent(event)) { + [NSApp sendEvent:event]; + retVal = true; + } + } + } while (!d->interrupt && event != nil); + } else do { + // INVARIANT: No modal window is executing. + event = [NSApp nextEventMatchingMask:NSAnyEventMask + untilDate:nil + inMode:NSDefaultRunLoopMode + dequeue: YES]; + + if (event) { + if (flags & QEventLoop::ExcludeUserInputEvents) { + if (IsMouseOrKeyEvent(event)) { + [event retain]; + d->queuedUserInputEvents.append(event); + continue; + } + } + if (!filterEvent(event)) { + [NSApp sendEvent:event]; + retVal = true; + } + } + } while (!d->interrupt && event != nil); + + // Be sure to flush the Qt posted events when not using exec mode + // (exec mode will always do this call from the event loop source): + if (!d->interrupt) + QCoreApplicationPrivate::sendPostedEvents(0, 0, d->threadData); + + // Since the window that holds modality might have changed while processing + // events, we we need to interrupt when we return back the previous process + // event recursion to ensure that we spin the correct modal session. + // We do the interruptLater at the end of the function to ensure that we don't + // disturb the 'wait for more events' below (as deleteLater will post an event): + interruptLater = true; + } + bool canWait = (d->threadData->canWait + && !retVal + && !d->interrupt + && (flags & QEventLoop::WaitForMoreEvents)); + if (canWait) { + // INVARIANT: We haven't processed any events yet. And we're told + // to stay inside this function until at least one event is processed. + qt_mac_waitForMoreEvents(); + flags &= ~QEventLoop::WaitForMoreEvents; + } else { + // Done with event processing for now. + // Leave the function: + break; + } + } + + // If we're interrupted, we need to interrupt the _current_ + // recursion as well to check if it is still supposed to be + // executing. This way we wind down the stack until we land + // on a recursion that again calls processEvents (typically + // from QEventLoop), and set interrupt to false: + if (d->interrupt) + interrupt(); + + if (interruptLater) + QtCocoaInterruptDispatcher::interruptLater(); + + return retVal; +} + +void QCocoaEventDispatcher::wakeUp() +{ + Q_D(QCocoaEventDispatcher); + d->serialNumber.ref(); + CFRunLoopSourceSignal(d->postedEventsSource); + CFRunLoopWakeUp(mainRunLoop()); +} + +/***************************************************************************** + QEventDispatcherMac Implementation + *****************************************************************************/ +MacTimerHash QCocoaEventDispatcherPrivate::macTimerHash; +bool QCocoaEventDispatcherPrivate::blockSendPostedEvents = false; +bool QCocoaEventDispatcherPrivate::interrupt = false; + + +QStack<QCocoaModalSessionInfo> QCocoaEventDispatcherPrivate::cocoaModalSessionStack; +bool QCocoaEventDispatcherPrivate::currentExecIsNSAppRun = false; +bool QCocoaEventDispatcherPrivate::nsAppRunCalledByQt = false; +bool QCocoaEventDispatcherPrivate::cleanupModalSessionsNeeded = false; +NSModalSession QCocoaEventDispatcherPrivate::currentModalSessionCached = 0; + +void QCocoaEventDispatcherPrivate::ensureNSAppInitialized() +{ + // Some elements in Cocoa require NSApplication to be running before + // they get fully initialized, in particular the menu bar. This + // function is intended for cases where a dialog is told to execute before + // QGuiApplication::exec is called, or the application spins the events loop + // manually rather than calling QGuiApplication:exec. + // The function makes sure that NSApplication starts running, but stops + // it again as soon as the send posted events callback is called. That way + // we let Cocoa finish the initialization it seems to need. We'll only + // apply this trick at most once for any application, and we avoid doing it + // for the common case where main just starts QGuiApplication::exec. + if (nsAppRunCalledByQt || [NSApp isRunning]) + return; + nsAppRunCalledByQt = true; + QBoolBlocker block1(interrupt, true); + QBoolBlocker block2(currentExecIsNSAppRun, true); + [NSApp run]; +} + +void QCocoaEventDispatcherPrivate::temporarilyStopAllModalSessions() +{ + // Flush, and Stop, all created modal session, and as + // such, make them pending again. The next call to + // currentModalSession will recreate them again. The + // reason to stop all session like this is that otherwise + // a call [NSApp stop] would not stop NSApp, but rather + // the current modal session. So if we need to stop NSApp + // we need to stop all the modal session first. To avoid changing + // the stacking order of the windows while doing so, we put + // up a block that is used in QCocoaWindow and QCocoaPanel: + int stackSize = cocoaModalSessionStack.size(); + for (int i=0; i<stackSize; ++i) { + QCocoaModalSessionInfo &info = cocoaModalSessionStack[i]; + if (info.session) { + [NSApp endModalSession:info.session]; + info.session = 0; + } + } + currentModalSessionCached = 0; +} + +NSModalSession QCocoaEventDispatcherPrivate::currentModalSession() +{ + // If we have one or more modal windows, this function will create + // a session for each of those, and return the one for the top. + if (currentModalSessionCached) + return currentModalSessionCached; + + if (cocoaModalSessionStack.isEmpty()) + return 0; + + int sessionCount = cocoaModalSessionStack.size(); + for (int i=0; i<sessionCount; ++i) { + QCocoaModalSessionInfo &info = cocoaModalSessionStack[i]; + if (!info.window) + continue; +// ### port +// if (info.window->testAttribute(Qt::WA_DontShowOnScreen)) +// continue; + + if (!info.session) { + QMacCocoaAutoReleasePool pool; + NSWindow *window = reinterpret_cast<NSWindow *>(info.window->handle()->winId()); + if (!window) + continue; + + ensureNSAppInitialized(); + QBoolBlocker block1(blockSendPostedEvents, true); + info.nswindow = window; + [(NSWindow*) info.nswindow retain]; + int levelBeforeEnterModal = [window level]; + info.session = [NSApp beginModalSessionForWindow:window]; + // Make sure we don't stack the window lower that it was before + // entering modal, in case it e.g. had the stays-on-top flag set: + if (levelBeforeEnterModal > [window level]) + [window setLevel:levelBeforeEnterModal]; + } + currentModalSessionCached = info.session; + cleanupModalSessionsNeeded = false; + } + return currentModalSessionCached; +} + +static void setChildrenWorksWhenModal(QWindow *window, bool worksWhenModal) +{ + // For NSPanels (but not NSWindows, sadly), we can set the flag + // worksWhenModal, so that they are active even when they are not modal. +/* + ### not ported + QList<QDialog *> dialogs = window->findChildren<QDialog *>(); + for (int i=0; i<dialogs.size(); ++i){ + NSWindow *window = qt_mac_window_for(dialogs[i]); + if (window && [window isKindOfClass:[NSPanel class]]) { + [static_cast<NSPanel *>(window) setWorksWhenModal:worksWhenModal]; + if (worksWhenModal && [window isVisible]){ + [window orderFront:window]; + } + } + } +*/ +} + +void QCocoaEventDispatcherPrivate::updateChildrenWorksWhenModal() +{ + // Make the dialog children of the window + // active. And make the dialog children of + // the previous modal dialog unactive again: + QMacCocoaAutoReleasePool pool; + int size = cocoaModalSessionStack.size(); + if (size > 0){ + if (QWindow *prevModal = cocoaModalSessionStack[size-1].window) + setChildrenWorksWhenModal(prevModal, true); + if (size > 1){ + if (QWindow *prevModal = cocoaModalSessionStack[size-2].window) + setChildrenWorksWhenModal(prevModal, false); + } + } +} + +void QCocoaEventDispatcherPrivate::cleanupModalSessions() +{ + // Go through the list of modal sessions, and end those + // that no longer has a window assosiated; no window means + // the the session has logically ended. The reason we wait like + // this to actually end the sessions for real (rather than at the + // point they were marked as stopped), is that ending a session + // when no other session runs below it on the stack will make cocoa + // drop some events on the floor. + QMacCocoaAutoReleasePool pool; + int stackSize = cocoaModalSessionStack.size(); + + for (int i=stackSize-1; i>=0; --i) { + QCocoaModalSessionInfo &info = cocoaModalSessionStack[i]; + if (info.window) { + // This session has a window, and is therefore not marked + // as stopped. So just make it current. There might still be other + // stopped sessions on the stack, but those will be stopped on + // a later "cleanup" call. + currentModalSessionCached = info.session; + break; + } + cocoaModalSessionStack.remove(i); + currentModalSessionCached = 0; + if (info.session) { + [NSApp endModalSession:info.session]; + [(NSWindow *)info.nswindow release]; + } + } + + updateChildrenWorksWhenModal(); + cleanupModalSessionsNeeded = false; +} + +void QCocoaEventDispatcherPrivate::beginModalSession(QWindow *window) +{ + // Add a new, empty (null), NSModalSession to the stack. + // It will become active the next time QEventDispatcher::processEvents is called. + // A QCocoaModalSessionInfo is considered pending to become active if the window pointer + // is non-zero, and the session pointer is zero (it will become active upon a call to + // currentModalSession). A QCocoaModalSessionInfo is considered pending to be stopped if + // the window pointer is zero, and the session pointer is non-zero (it will be fully + // stopped in cleanupModalSessions()). + QCocoaModalSessionInfo info = {window, 0, 0}; + cocoaModalSessionStack.push(info); + updateChildrenWorksWhenModal(); + currentModalSessionCached = 0; +} + +void QCocoaEventDispatcherPrivate::endModalSession(QWindow *window) +{ + // Mark all sessions attached to window as pending to be stopped. We do this + // by setting the window pointer to zero, but leave the session pointer. + // We don't tell cocoa to stop any sessions just yet, because cocoa only understands + // when we stop the _current_ modal session (which is the session on top of + // the stack, and might not belong to 'window'). + int stackSize = cocoaModalSessionStack.size(); + for (int i=stackSize-1; i>=0; --i) { + QCocoaModalSessionInfo &info = cocoaModalSessionStack[i]; + if (info.window == window) { + info.window = 0; + if (i == stackSize-1) { + // The top sessions ended. Interrupt the event dispatcher + // to start spinning the correct session immidiatly: + currentModalSessionCached = 0; + cleanupModalSessionsNeeded = true; + QCocoaEventDispatcher::instance()->interrupt(); + } + } + } +} + +QCocoaEventDispatcherPrivate::QCocoaEventDispatcherPrivate() +{ +} + +QCocoaEventDispatcher::QCocoaEventDispatcher(QObject *parent) + : QEventDispatcherUNIX(*new QCocoaEventDispatcherPrivate, parent) +{ + Q_D(QCocoaEventDispatcher); + CFRunLoopSourceContext context; + bzero(&context, sizeof(CFRunLoopSourceContext)); + context.info = d; + context.equal = QCocoaEventDispatcherPrivate::postedEventSourceEqualCallback; + context.perform = QCocoaEventDispatcherPrivate::postedEventsSourcePerformCallback; + d->postedEventsSource = CFRunLoopSourceCreate(0, 0, &context); + Q_ASSERT(d->postedEventsSource); + CFRunLoopAddSource(mainRunLoop(), d->postedEventsSource, kCFRunLoopCommonModes); + + CFRunLoopObserverContext observerContext; + bzero(&observerContext, sizeof(CFRunLoopObserverContext)); + observerContext.info = this; + d->waitingObserver = CFRunLoopObserverCreate(kCFAllocatorDefault, + kCFRunLoopBeforeWaiting | kCFRunLoopAfterWaiting, + true, 0, + QCocoaEventDispatcherPrivate::waitingObserverCallback, + &observerContext); + CFRunLoopAddObserver(mainRunLoop(), d->waitingObserver, kCFRunLoopCommonModes); + + /* The first cycle in the loop adds the source and the events of the source + are not processed. + We use an observer to process the posted events for the first + execution of the loop. */ + CFRunLoopObserverContext firstTimeObserverContext; + bzero(&firstTimeObserverContext, sizeof(CFRunLoopObserverContext)); + firstTimeObserverContext.info = d; + d->firstTimeObserver = CFRunLoopObserverCreate(kCFAllocatorDefault, + kCFRunLoopEntry, + /* repeats = */ false, + 0, + QCocoaEventDispatcherPrivate::firstLoopEntry, + &firstTimeObserverContext); + CFRunLoopAddObserver(mainRunLoop(), d->firstTimeObserver, kCFRunLoopCommonModes); +} + +void QCocoaEventDispatcherPrivate::waitingObserverCallback(CFRunLoopObserverRef, + CFRunLoopActivity activity, void *info) +{ + if (activity == kCFRunLoopBeforeWaiting) + emit static_cast<QCocoaEventDispatcher*>(info)->aboutToBlock(); + else + emit static_cast<QCocoaEventDispatcher*>(info)->awake(); +} + +Boolean QCocoaEventDispatcherPrivate::postedEventSourceEqualCallback(const void *info1, const void *info2) +{ + return info1 == info2; +} + +void processPostedEvents(QCocoaEventDispatcherPrivate *const d, const bool blockSendPostedEvents) +{ + if (blockSendPostedEvents) { + // We're told to not send posted events (because the event dispatcher + // is currently working on setting up the correct session to run). But + // we still need to make sure that we don't fall asleep until pending events + // are sendt, so we just signal this need, and return: + CFRunLoopSourceSignal(d->postedEventsSource); + return; + } + + if (d->cleanupModalSessionsNeeded) + d->cleanupModalSessions(); + + if (d->interrupt) { + if (d->currentExecIsNSAppRun) { + // The event dispatcher has been interrupted. But since + // [NSApplication run] is running the event loop, we + // delayed stopping it until now (to let cocoa process + // pending cocoa events first). + if (d->currentModalSessionCached) + d->temporarilyStopAllModalSessions(); + [NSApp stop:NSApp]; + d->cancelWaitForMoreEvents(); + } + return; + } + + if (!d->threadData->canWait || (d->serialNumber != d->lastSerial)) { + d->lastSerial = d->serialNumber; + QWindowSystemInterface::sendWindowSystemEvents(d->q_func(), QEventLoop::AllEvents); + } +} + +void QCocoaEventDispatcherPrivate::firstLoopEntry(CFRunLoopObserverRef ref, + CFRunLoopActivity activity, + void *info) +{ + Q_UNUSED(ref); + Q_UNUSED(activity); +/* + // This function is called when NSApplication has finished initialization, + // which appears to be just after [NSApplication run] has started to execute. + // By setting up our apple events handlers this late, we override the ones + // set up by NSApplication. + + // If Qt is used as a plugin, we let the 3rd party application handle events + // like quit and open file events. Otherwise, if we install our own handlers, we + // easily end up breaking functionallity the 3rd party application depend on: + if (QGuiApplication::testAttribute(Qt::AA_MacPluginApplication)) + return; + + QT_MANGLE_NAMESPACE(QCocoaApplicationDelegate) *newDelegate = [QT_MANGLE_NAMESPACE(QCocoaApplicationDelegate) sharedDelegate]; + NSAppleEventManager *eventManager = [NSAppleEventManager sharedAppleEventManager]; + [eventManager setEventHandler:newDelegate andSelector:@selector(appleEventQuit:withReplyEvent:) + forEventClass:kCoreEventClass andEventID:kAEQuitApplication]; + [eventManager setEventHandler:newDelegate andSelector:@selector(getUrl:withReplyEvent:) + forEventClass:kInternetEventClass andEventID:kAEGetURL]; +*/ + + processPostedEvents(static_cast<QCocoaEventDispatcherPrivate *>(info), blockSendPostedEvents); +} + +void QCocoaEventDispatcherPrivate::postedEventsSourcePerformCallback(void *info) +{ + processPostedEvents(static_cast<QCocoaEventDispatcherPrivate *>(info), blockSendPostedEvents); +} + +void QCocoaEventDispatcherPrivate::cancelWaitForMoreEvents() +{ + // In case the event dispatcher is waiting for more + // events somewhere, we post a dummy event to wake it up: + QMacCocoaAutoReleasePool pool; + [NSApp postEvent:[NSEvent otherEventWithType:NSApplicationDefined location:NSZeroPoint + modifierFlags:0 timestamp:0. windowNumber:0 context:0 + subtype:QtCocoaEventSubTypeWakeup data1:0 data2:0] atStart:NO]; +} + +void QCocoaEventDispatcher::interrupt() +{ + Q_D(QCocoaEventDispatcher); + d->interrupt = true; + wakeUp(); + + // We do nothing more here than setting d->interrupt = true, and + // poke the event loop if it is sleeping. Actually stopping + // NSApp, or the current modal session, is done inside the send + // posted events callback. We do this to ensure that all current pending + // cocoa events gets delivered before we stop. Otherwise, if we now stop + // the last event loop recursion, cocoa will just drop pending posted + // events on the floor before we get a chance to reestablish a new session. + d->cancelWaitForMoreEvents(); +} + +QCocoaEventDispatcher::~QCocoaEventDispatcher() +{ + Q_D(QCocoaEventDispatcher); + //timer cleanup + MacTimerHash::iterator it = QCocoaEventDispatcherPrivate::macTimerHash.begin(); + while (it != QCocoaEventDispatcherPrivate::macTimerHash.end()) { + MacTimerInfo *t = it.value(); + if (t->runLoopTimer) { + CFRunLoopTimerInvalidate(t->runLoopTimer); + CFRelease(t->runLoopTimer); + } + delete t; + ++it; + } + QCocoaEventDispatcherPrivate::macTimerHash.clear(); + + // Remove CFSockets from the runloop. + for (MacSocketHash::ConstIterator it = d->macSockets.constBegin(); it != d->macSockets.constEnd(); ++it) { + MacSocketInfo *socketInfo = (*it); + if (CFSocketIsValid(socketInfo->socket)) { + qt_mac_remove_socket_from_runloop(socketInfo->socket, socketInfo->runloop); + CFRunLoopSourceInvalidate(socketInfo->runloop); + CFRelease(socketInfo->runloop); + CFSocketInvalidate(socketInfo->socket); + CFRelease(socketInfo->socket); + } + } + CFRunLoopRemoveSource(mainRunLoop(), d->postedEventsSource, kCFRunLoopCommonModes); + CFRelease(d->postedEventsSource); + + CFRunLoopObserverInvalidate(d->waitingObserver); + CFRelease(d->waitingObserver); + + CFRunLoopObserverInvalidate(d->firstTimeObserver); + CFRelease(d->firstTimeObserver); +} + +QtCocoaInterruptDispatcher* QtCocoaInterruptDispatcher::instance = 0; + +QtCocoaInterruptDispatcher::QtCocoaInterruptDispatcher() : cancelled(false) +{ + // The whole point of this class is that we enable a way to interrupt + // the event dispatcher when returning back to a lower recursion level + // than where interruptLater was called. This is needed to detect if + // [NSApp run] should still be running at the recursion level it is at. + // Since the interrupt is canceled if processEvents is called before + // this object gets deleted, we also avoid interrupting unnecessary. + deleteLater(); +} + +QtCocoaInterruptDispatcher::~QtCocoaInterruptDispatcher() +{ + if (cancelled) + return; + instance = 0; + QCocoaEventDispatcher::instance()->interrupt(); +} + +void QtCocoaInterruptDispatcher::cancelInterruptLater() +{ + if (!instance) + return; + instance->cancelled = true; + delete instance; + instance = 0; +} + +void QtCocoaInterruptDispatcher::interruptLater() +{ + cancelInterruptLater(); + instance = new QtCocoaInterruptDispatcher; +} + +QT_END_NAMESPACE + diff --git a/src/plugins/platforms/cocoa/qcocoaeventloopintegration.mm b/src/plugins/platforms/cocoa/qcocoaeventloopintegration.mm deleted file mode 100644 index ac0b75e9ea..0000000000 --- a/src/plugins/platforms/cocoa/qcocoaeventloopintegration.mm +++ /dev/null @@ -1,112 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). -** All rights reserved. -** Contact: Nokia Corporation (qt-info@nokia.com) -** -** This file is part of the plugins of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** 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. -** -** In addition, as a special exception, Nokia gives you certain additional -** rights. These rights are described in the Nokia 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. -** -** -** -** -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#include "qcocoaeventloopintegration.h" - -#import <Cocoa/Cocoa.h> - -#include "qcocoaautoreleasepool.h" - -#include <QtCore/QElapsedTimer> - -#include <QDebug> -#include <QApplication> - -void wakeupCallback ( void * ) { - QPlatformEventLoopIntegration::processEvents(); -} - -void timerCallback( CFRunLoopTimerRef timer, void *info) -{ - QPlatformEventLoopIntegration::processEvents(); - QCocoaEventLoopIntegration *eventLoopIntegration = - static_cast<QCocoaEventLoopIntegration *>(info); - qint64 nextTime = eventLoopIntegration->nextTimerEvent(); - CFAbsoluteTime nexttime = CFAbsoluteTimeGetCurrent(); - nexttime = nexttime + (double(nextTime)/1000); - CFRunLoopTimerSetNextFireDate(timer,nexttime); -} - -QCocoaEventLoopIntegration::QCocoaEventLoopIntegration() : - QPlatformEventLoopIntegration() -{ - [NSApplication sharedApplication]; - m_sourceContext.version = 0; - m_sourceContext.info = this; - m_sourceContext.retain = 0; - m_sourceContext.release = 0; - m_sourceContext.copyDescription = 0; - m_sourceContext.equal = 0; - m_sourceContext.hash = 0; - m_sourceContext.schedule = 0; - m_sourceContext.cancel = 0; - m_sourceContext.perform = wakeupCallback; - - m_source = CFRunLoopSourceCreate(0,0,&m_sourceContext); - CFRunLoopAddSource(CFRunLoopGetMain(),m_source,kCFRunLoopCommonModes); - - m_timerContext.version = 0; - m_timerContext.info = this; - m_timerContext.retain = 0; - m_timerContext.release = 0; - m_timerContext.copyDescription = 0; - CFAbsoluteTime fireDate = CFAbsoluteTimeGetCurrent (); - CFTimeInterval interval = 30; - - CFRunLoopTimerRef m_timerSource = CFRunLoopTimerCreate(0,fireDate,interval,0,0,timerCallback,&m_timerContext); - CFRunLoopAddTimer(CFRunLoopGetMain(),m_timerSource,kCFRunLoopCommonModes); -} - -void QCocoaEventLoopIntegration::startEventLoop() -{ - [[NSApplication sharedApplication] run]; -} - -void QCocoaEventLoopIntegration::quitEventLoop() -{ - [[NSApplication sharedApplication] terminate:nil]; -} - -void QCocoaEventLoopIntegration::qtNeedsToProcessEvents() -{ - CFRunLoopSourceSignal(m_source); -} - diff --git a/src/plugins/platforms/cocoa/qcocoaglcontext.h b/src/plugins/platforms/cocoa/qcocoaglcontext.h new file mode 100644 index 0000000000..1b84e7b305 --- /dev/null +++ b/src/plugins/platforms/cocoa/qcocoaglcontext.h @@ -0,0 +1,43 @@ +#ifndef QCOCOAGLCONTEXT_H +#define QCOCOAGLCONTEXT_H + +#include <QtCore/QWeakPointer> +#include <QtGui/QPlatformOpenGLContext> +#include <QtGui/QOpenGLContext> +#include <QtGui/QWindow> + +#undef slots +#include <Cocoa/Cocoa.h> + +QT_BEGIN_NAMESPACE + +class QCocoaGLContext : public QPlatformOpenGLContext +{ +public: + QCocoaGLContext(const QSurfaceFormat &format, QPlatformOpenGLContext *share); + + QSurfaceFormat format() const; + + void swapBuffers(QPlatformSurface *surface); + + bool makeCurrent(QPlatformSurface *surface); + void doneCurrent(); + + void (*getProcAddress(const QByteArray &procName)) (); + + void update(); + + static NSOpenGLPixelFormat *createNSOpenGLPixelFormat(); + NSOpenGLContext *nsOpenGLContext() const; + +private: + void setActiveWindow(QWindow *window); + + NSOpenGLContext *m_context; + QSurfaceFormat m_format; + QWeakPointer<QWindow> m_currentWindow; +}; + +QT_END_NAMESPACE + +#endif // QCOCOAGLCONTEXT_H diff --git a/src/plugins/platforms/cocoa/qcocoaglcontext.mm b/src/plugins/platforms/cocoa/qcocoaglcontext.mm new file mode 100644 index 0000000000..fabed4e207 --- /dev/null +++ b/src/plugins/platforms/cocoa/qcocoaglcontext.mm @@ -0,0 +1,97 @@ +#include "qcocoaglcontext.h" +#include "qcocoawindow.h" +#include "qcocoaautoreleasepool.h" +#include <qdebug.h> +#include <QtCore/private/qcore_mac_p.h> +#include <QtPlatformSupport/private/cglconvenience_p.h> + +#import <Cocoa/Cocoa.h> + +QCocoaGLContext::QCocoaGLContext(const QSurfaceFormat &format, QPlatformOpenGLContext *share) + : m_format(format) +{ + QCocoaAutoReleasePool pool; // For the SG Canvas render thread. + + NSOpenGLPixelFormat *pixelFormat = static_cast <NSOpenGLPixelFormat *>(qcgl_createNSOpenGLPixelFormat()); + NSOpenGLContext *actualShare = share ? static_cast<QCocoaGLContext *>(share)->m_context : 0; + + m_context = [NSOpenGLContext alloc]; + [m_context initWithFormat:pixelFormat shareContext:actualShare]; + + const GLint interval = 1; + [m_context setValues:&interval forParameter:NSOpenGLCPSwapInterval]; + +} + +// Match up with createNSOpenGLPixelFormat! +QSurfaceFormat QCocoaGLContext::format() const +{ + return m_format; +} + +void QCocoaGLContext::swapBuffers(QPlatformSurface *surface) +{ + QWindow *window = static_cast<QCocoaWindow *>(surface)->window(); + setActiveWindow(window); + + [m_context flushBuffer]; +} + +bool QCocoaGLContext::makeCurrent(QPlatformSurface *surface) +{ + QWindow *window = static_cast<QCocoaWindow *>(surface)->window(); + setActiveWindow(window); + + [m_context makeCurrentContext]; + return true; +} + +void QCocoaGLContext::setActiveWindow(QWindow *window) +{ + if (window == m_currentWindow.data()) + return; + + if (m_currentWindow) + static_cast<QCocoaWindow *>(m_currentWindow.data()->handle())->setCurrentContext(0); + + Q_ASSERT(window->handle()); + + m_currentWindow = window; + + QCocoaWindow *cocoaWindow = static_cast<QCocoaWindow *>(window->handle()); + cocoaWindow->setCurrentContext(this); + + NSView *view = cocoaWindow->windowSurfaceView(); + [m_context setView:view]; +} + +void QCocoaGLContext::doneCurrent() +{ + if (m_currentWindow) + static_cast<QCocoaWindow *>(m_currentWindow.data()->handle())->setCurrentContext(0); + + m_currentWindow.clear(); + + [NSOpenGLContext clearCurrentContext]; +} + +void (*QCocoaGLContext::getProcAddress(const QByteArray &procName))() +{ + return qcgl_getProcAddress(procName); +} + +void QCocoaGLContext::update() +{ + [m_context update]; +} + +NSOpenGLPixelFormat *QCocoaGLContext::createNSOpenGLPixelFormat() +{ + return static_cast<NSOpenGLPixelFormat *>(qcgl_createNSOpenGLPixelFormat()); +} + +NSOpenGLContext *QCocoaGLContext::nsOpenGLContext() const +{ + return m_context; +} + diff --git a/src/plugins/platforms/cocoa/qcocoahelpers.h b/src/plugins/platforms/cocoa/qcocoahelpers.h new file mode 100644 index 0000000000..3e978cf858 --- /dev/null +++ b/src/plugins/platforms/cocoa/qcocoahelpers.h @@ -0,0 +1,81 @@ +/**************************************************************************** + ** + ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). + ** All rights reserved. + ** Contact: Nokia Corporation (qt-info@nokia.com) + ** + ** This file is part of the plugins of the Qt Toolkit. + ** + ** $QT_BEGIN_LICENSE:LGPL$ + ** 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. + ** + ** In addition, as a special exception, Nokia gives you certain additional + ** rights. These rights are described in the Nokia 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. + ** + ** + ** + ** + ** + ** $QT_END_LICENSE$ + ** + ****************************************************************************/ + +#ifndef QCOCOAHELPERS_H +#define QCOCOAHELPERS_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It provides helper functions +// for the Cocoa lighthouse plugin. This header file may +// change from version to version without notice, or even be removed. +// +// We mean it. +// + +#include <QtCore/private/qcore_mac_p.h> +#include <Cocoa/Cocoa.h> + +class QString; + +// Conversion functions +QStringList qt_mac_NSArrayToQStringList(void *nsarray); +void *qt_mac_QStringListToNSMutableArrayVoid(const QStringList &list); + +inline NSMutableArray *qt_mac_QStringListToNSMutableArray(const QStringList &qstrlist) +{ return reinterpret_cast<NSMutableArray *>(qt_mac_QStringListToNSMutableArrayVoid(qstrlist)); } + +inline QString qt_mac_NSStringToQString(const NSString *nsstr) +{ return QCFString::toQString(reinterpret_cast<const CFStringRef>(nsstr)); } + +inline NSString *qt_mac_QStringToNSString(const QString &qstr) +{ return [reinterpret_cast<const NSString *>(QCFString::toCFStringRef(qstr)) autorelease]; } + +// Misc + +void qt_mac_transformProccessToForegroundApplication(); + + + +#endif //QCOCOAHELPERS_H + diff --git a/src/plugins/platforms/cocoa/qcocoahelpers.mm b/src/plugins/platforms/cocoa/qcocoahelpers.mm new file mode 100644 index 0000000000..335da680ac --- /dev/null +++ b/src/plugins/platforms/cocoa/qcocoahelpers.mm @@ -0,0 +1,120 @@ +/**************************************************************************** + ** + ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). + ** All rights reserved. + ** Contact: Nokia Corporation (qt-info@nokia.com) + ** + ** This file is part of the plugins of the Qt Toolkit. + ** + ** $QT_BEGIN_LICENSE:LGPL$ + ** 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. + ** + ** In addition, as a special exception, Nokia gives you certain additional + ** rights. These rights are described in the Nokia 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. + ** + ** + ** + ** + ** + ** $QT_END_LICENSE$ + ** + ****************************************************************************/ + +#include "qcocoahelpers.h" + +#include <QtCore> +#include <QtGui> + +// +// Conversion Functions +// + +QStringList qt_mac_NSArrayToQStringList(void *nsarray) +{ + QStringList result; + NSArray *array = static_cast<NSArray *>(nsarray); + for (NSUInteger i=0; i<[array count]; ++i) + result << qt_mac_NSStringToQString([array objectAtIndex:i]); + return result; +} + +void *qt_mac_QStringListToNSMutableArrayVoid(const QStringList &list) +{ + NSMutableArray *result = [NSMutableArray arrayWithCapacity:list.size()]; + for (int i=0; i<list.size(); ++i){ + [result addObject:reinterpret_cast<const NSString *>(QCFString::toCFStringRef(list[i]))]; + } + return result; +} + + +// +// Misc +// + +// Changes the process type for this process to kProcessTransformToForegroundApplication, +// unless either LSUIElement or LSBackgroundOnly is set in the Info.plist. +void qt_mac_transformProccessToForegroundApplication() +{ + ProcessSerialNumber psn; + if (GetCurrentProcess(&psn) == noErr) { + bool forceTransform = true; + CFTypeRef value = CFBundleGetValueForInfoDictionaryKey(CFBundleGetMainBundle(), + CFSTR("LSUIElement")); + if (value) { + CFTypeID valueType = CFGetTypeID(value); + // Officially it's supposed to be a string, a boolean makes sense, so we'll check. + // A number less so, but OK. + if (valueType == CFStringGetTypeID()) + forceTransform = !(QCFString::toQString(static_cast<CFStringRef>(value)).toInt()); + else if (valueType == CFBooleanGetTypeID()) + forceTransform = !CFBooleanGetValue(static_cast<CFBooleanRef>(value)); + else if (valueType == CFNumberGetTypeID()) { + int valueAsInt; + CFNumberGetValue(static_cast<CFNumberRef>(value), kCFNumberIntType, &valueAsInt); + forceTransform = !valueAsInt; + } + } + + if (forceTransform) { + value = CFBundleGetValueForInfoDictionaryKey(CFBundleGetMainBundle(), + CFSTR("LSBackgroundOnly")); + if (value) { + CFTypeID valueType = CFGetTypeID(value); + if (valueType == CFBooleanGetTypeID()) + forceTransform = !CFBooleanGetValue(static_cast<CFBooleanRef>(value)); + else if (valueType == CFStringGetTypeID()) + forceTransform = !(QCFString::toQString(static_cast<CFStringRef>(value)).toInt()); + else if (valueType == CFNumberGetTypeID()) { + int valueAsInt; + CFNumberGetValue(static_cast<CFNumberRef>(value), kCFNumberIntType, &valueAsInt); + forceTransform = !valueAsInt; + } + } + } + + if (forceTransform) { + TransformProcessType(&psn, kProcessTransformToForegroundApplication); + } + } +} + diff --git a/src/plugins/platforms/cocoa/qcocoaintegration.h b/src/plugins/platforms/cocoa/qcocoaintegration.h index 120bee46b7..0c1b350cef 100644 --- a/src/plugins/platforms/cocoa/qcocoaintegration.h +++ b/src/plugins/platforms/cocoa/qcocoaintegration.h @@ -76,19 +76,18 @@ public: ~QCocoaIntegration(); bool hasCapability(QPlatformIntegration::Capability cap) const; - QPixmapData *createPixmapData(QPixmapData::PixelType type) const; - QPlatformWindow *createPlatformWindow(QWidget *widget, WId winId = 0) const; - QWindowSurface *createWindowSurface(QWidget *widget, WId winId) const; + QPlatformWindow *createPlatformWindow(QWindow *window) const; + QPlatformOpenGLContext *createPlatformOpenGLContext(QOpenGLContext *context) const; + QPlatformBackingStore *createPlatformBackingStore(QWindow *widget) const; - QList<QPlatformScreen *> screens() const { return mScreens; } + QAbstractEventDispatcher *guiThreadEventDispatcher() const; QPlatformFontDatabase *fontDatabase() const; - QPlatformEventLoopIntegration *createEventLoopIntegration() const; - + QPlatformNativeInterface *nativeInterface() const; private: - QList<QPlatformScreen *> mScreens; QPlatformFontDatabase *mFontDb; + QAbstractEventDispatcher *mEventDispatcher; QCocoaAutoReleasePool *mPool; }; diff --git a/src/plugins/platforms/cocoa/qcocoaintegration.mm b/src/plugins/platforms/cocoa/qcocoaintegration.mm index 086f7b62e9..1d06c182e6 100644 --- a/src/plugins/platforms/cocoa/qcocoaintegration.mm +++ b/src/plugins/platforms/cocoa/qcocoaintegration.mm @@ -42,14 +42,13 @@ #include "qcocoaintegration.h" #include "qcocoawindow.h" -#include "qcocoawindowsurface.h" -#include "qcocoaeventloopintegration.h" +#include "qcocoabackingstore.h" +#include "qcocoanativeinterface.h" +#include "qcocoamenuloader.h" +#include "qcocoaeventdispatcher.h" +#include "qcocoahelpers.h" -#include "qcoretextfontdatabase.h" - -#include <QtGui/QApplication> - -#include <private/qpixmap_raster_p.h> +#include <QtPlatformSupport/private/qbasicunixfontdatabase_p.h> QT_BEGIN_NAMESPACE @@ -74,7 +73,8 @@ QCocoaScreen::~QCocoaScreen() } QCocoaIntegration::QCocoaIntegration() - : mFontDb(new QCoreTextFontDatabase()) + : mFontDb(new QBasicUnixFontDatabase()) + , mEventDispatcher(new QCocoaEventDispatcher()) { mPool = new QCocoaAutoReleasePool; @@ -82,10 +82,28 @@ QCocoaIntegration::QCocoaIntegration() [NSApplication sharedApplication]; // [[OurApplication alloc] init]; + // Applications launched from plain executables (without an app + // bundle) are "background" applications that does not take keybaord + // focus or have a dock icon or task switcher entry. Qt Gui apps generally + // wants to be foreground applications so change the process type. (But + // see the function implementation for exceptions.) + qt_mac_transformProccessToForegroundApplication(); + + // Move the application window to front to avoid launching behind the terminal. + // Ignoring other apps is neccessary (we must ignore the terminal), but makes + // Qt apps play slightly less nice with other apps when lanching from Finder + // (See the activateIgnoringOtherApps docs.) + [[NSApplication sharedApplication] activateIgnoringOtherApps : YES]; + + // Load the application menu. This menu contains Preferences, Hide, Quit. + QT_MANGLE_NAMESPACE(QCocoaMenuLoader) *qtMenuLoader = [[QT_MANGLE_NAMESPACE(QCocoaMenuLoader) alloc] init]; + qt_mac_loadMenuNib(qtMenuLoader); + [[NSApplication sharedApplication] setMenu:[qtMenuLoader menu]]; + NSArray *screens = [NSScreen screens]; for (uint i = 0; i < [screens count]; i++) { QCocoaScreen *screen = new QCocoaScreen(i); - mScreens.append(screen); + screenAdded(screen); } } @@ -98,26 +116,32 @@ bool QCocoaIntegration::hasCapability(QPlatformIntegration::Capability cap) cons { switch (cap) { case ThreadedPixmaps: return true; + case OpenGL : return true; + case ThreadedOpenGL : return true; default: return QPlatformIntegration::hasCapability(cap); } } -QPixmapData *QCocoaIntegration::createPixmapData(QPixmapData::PixelType type) const +QPlatformWindow *QCocoaIntegration::createPlatformWindow(QWindow *window) const { - return new QRasterPixmapData(type); + return new QCocoaWindow(window); } -QPlatformWindow *QCocoaIntegration::createPlatformWindow(QWidget *widget, WId winId) const +QPlatformOpenGLContext *QCocoaIntegration::createPlatformOpenGLContext(QOpenGLContext *context) const { - Q_UNUSED(winId); - return new QCocoaWindow(widget); + return new QCocoaGLContext(context->format(), context->shareHandle()); } -QWindowSurface *QCocoaIntegration::createWindowSurface(QWidget *widget, WId winId) const +QPlatformBackingStore *QCocoaIntegration::createPlatformBackingStore(QWindow *window) const { - return new QCocoaWindowSurface(widget,winId); + return new QCocoaBackingStore(window); +} + +QAbstractEventDispatcher *QCocoaIntegration::guiThreadEventDispatcher() const +{ + return mEventDispatcher; } QPlatformFontDatabase *QCocoaIntegration::fontDatabase() const @@ -125,8 +149,9 @@ QPlatformFontDatabase *QCocoaIntegration::fontDatabase() const return mFontDb; } -QPlatformEventLoopIntegration *QCocoaIntegration::createEventLoopIntegration() const +QPlatformNativeInterface *QCocoaIntegration::nativeInterface() const { - return new QCocoaEventLoopIntegration(); + return new QCocoaNativeInterface(); } + QT_END_NAMESPACE diff --git a/src/plugins/platforms/cocoa/qcocoamenuloader.h b/src/plugins/platforms/cocoa/qcocoamenuloader.h new file mode 100644 index 0000000000..9f3a31e3bc --- /dev/null +++ b/src/plugins/platforms/cocoa/qcocoamenuloader.h @@ -0,0 +1,95 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia 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. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QCOCOAMENULOADER_P_H +#define QCOCOAMENULOADER_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of qapplication_*.cpp, qwidget*.cpp and qfiledialog.cpp. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#include <QtCore/private/qcore_mac_p.h> +#import <Cocoa/Cocoa.h> + +@interface QT_MANGLE_NAMESPACE(QCocoaMenuLoader) : NSResponder +{ + IBOutlet NSMenu *theMenu; + IBOutlet NSMenu *appMenu; + IBOutlet NSMenuItem *quitItem; + IBOutlet NSMenuItem *preferencesItem; + IBOutlet NSMenuItem *aboutItem; + IBOutlet NSMenuItem *aboutQtItem; + IBOutlet NSMenuItem *hideItem; + NSMenuItem *lastAppSpecificItem; + NSMenuItem *servicesItem; + NSMenuItem *hideAllOthersItem; + NSMenuItem *showAllItem; +} +- (void)ensureAppMenuInMenu:(NSMenu *)menu; +- (void)removeActionsFromAppMenu; +- (NSMenu *)applicationMenu; +- (NSMenu *)menu; +- (NSMenuItem *)quitMenuItem; +- (NSMenuItem *)preferencesMenuItem; +- (NSMenuItem *)aboutMenuItem; +- (NSMenuItem *)aboutQtMenuItem; +- (NSMenuItem *)hideMenuItem; +- (NSMenuItem *)appSpecificMenuItem; +- (IBAction)terminate:(id)sender; +- (IBAction)orderFrontStandardAboutPanel:(id)sender; +- (IBAction)hideOtherApplications:(id)sender; +- (IBAction)unhideAllApplications:(id)sender; +- (IBAction)hide:(id)sender; +- (IBAction)qtDispatcherToQAction:(id)sender; +- (void)qtUpdateMenubar; +- (void)orderFrontCharacterPalette:(id)sender; +@end + +void qt_mac_loadMenuNib(QT_MANGLE_NAMESPACE(QCocoaMenuLoader) *qtMenuLoader); + +#endif // QCOCOAMENULOADER_P_H diff --git a/src/plugins/platforms/cocoa/qcocoamenuloader.mm b/src/plugins/platforms/cocoa/qcocoamenuloader.mm new file mode 100644 index 0000000000..d47c4675ca --- /dev/null +++ b/src/plugins/platforms/cocoa/qcocoamenuloader.mm @@ -0,0 +1,310 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia 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. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qcocoamenuloader.h" + +#include <QtCore/private/qcore_mac_p.h> +#include <QtCore/qcoreapplication.h> +#include <QtCore/qdir.h> +#include <QtCore/qstring.h> + +QT_FORWARD_DECLARE_CLASS(QCFString) +QT_FORWARD_DECLARE_CLASS(QString) + +#ifndef QT_NO_TRANSLATION + QT_BEGIN_NAMESPACE + extern QString qt_mac_applicationmenu_string(int type); + QT_END_NAMESPACE +#endif + +QT_USE_NAMESPACE + +/* + Loads and instantiates the main app menu from the menu nib file(s). + + The main app menu contains the Quit, Hide About, Preferences entries, and + The reason for having the nib file is that those can not be created + programmatically. To ease deployment the nib files are stored in Qt resources + and written to QDir::temp() before loading. (Earlier Qt versions used + to require having the nib file in the QtGui framework.) +*/ +void qt_mac_loadMenuNib(QT_MANGLE_NAMESPACE(QCocoaMenuLoader) *qtMenuLoader) +{ + // Create qt_menu.nib dir in temp. + QDir temp = QDir::temp(); + temp.mkdir("qt_menu.nib"); + QString nibDir = temp.canonicalPath() + QLatin1String("/") + QLatin1String("qt_menu.nib/"); + if (!QDir(nibDir).exists()) { + qWarning("qt_mac_loadMenuNib: could not create nib directory in temp"); + return; + } + + // Copy nib files from resources to temp. + QDir nibResource(":/trolltech/mac/qt_menu.nib/"); + if (!nibResource.exists()) { + qWarning("qt_mac_loadMenuNib: could not load nib from resources"); + return; + } + foreach (const QFileInfo &file, nibResource.entryInfoList()) { + QFile::copy(file.absoluteFilePath(), nibDir + QLatin1String("/") + file.fileName()); + } + + // Load and instantiate nib file from temp + NSURL *nibUrl = [NSURL fileURLWithPath : reinterpret_cast<const NSString *>(QCFString::toCFStringRef(nibDir))]; + [nibUrl autorelease]; + NSNib *nib = [[NSNib alloc] initWithContentsOfURL : nibUrl]; + [nib autorelease]; + if(!nib) { + qWarning("qt_mac_loadMenuNib: could not load nib from temp"); + return; + } + bool ok = [nib instantiateNibWithOwner : qtMenuLoader topLevelObjects : nil]; + if (!ok) { + qWarning("qt_mac_loadMenuNib: could not instantiate nib"); + } +} + + + +@implementation QT_MANGLE_NAMESPACE(QCocoaMenuLoader) + +- (void)awakeFromNib +{ + servicesItem = [[appMenu itemWithTitle:@"Services"] retain]; + hideAllOthersItem = [[appMenu itemWithTitle:@"Hide Others"] retain]; + showAllItem = [[appMenu itemWithTitle:@"Show All"] retain]; + + // Get the names in the nib to match the app name set by Qt. + const NSString *appName = reinterpret_cast<const NSString*>(QCFString::toCFStringRef(qAppName())); + [quitItem setTitle:[[quitItem title] stringByReplacingOccurrencesOfString:@"NewApplication" + withString:const_cast<NSString *>(appName)]]; + [hideItem setTitle:[[hideItem title] stringByReplacingOccurrencesOfString:@"NewApplication" + withString:const_cast<NSString *>(appName)]]; + [aboutItem setTitle:[[aboutItem title] stringByReplacingOccurrencesOfString:@"NewApplication" + withString:const_cast<NSString *>(appName)]]; + [appName release]; + // Disable the items that don't do anything. If someone associates a QAction with them + // They should get synced back in. + [preferencesItem setEnabled:NO]; + [preferencesItem setHidden:YES]; + [aboutItem setEnabled:NO]; + [aboutItem setHidden:YES]; +} + +- (void)ensureAppMenuInMenu:(NSMenu *)menu +{ + // The application menu is the menu in the menu bar that contains the + // 'Quit' item. When changing menu bar (e.g when switching between + // windows with different menu bars), we never recreate this menu, but + // instead pull it out the current menu bar and place into the new one: + NSMenu *mainMenu = [NSApp mainMenu]; + if ([NSApp mainMenu] == menu) + return; // nothing to do (menu is the current menu bar)! + +#ifndef QT_NAMESPACE + Q_ASSERT(mainMenu); +#endif + // Grab the app menu out of the current menu. + int numItems = [mainMenu numberOfItems]; + NSMenuItem *oldAppMenuItem = 0; + for (int i = 0; i < numItems; ++i) { + NSMenuItem *item = [mainMenu itemAtIndex:i]; + if ([item submenu] == appMenu) { + oldAppMenuItem = item; + [oldAppMenuItem retain]; + [mainMenu removeItemAtIndex:i]; + break; + } + } + + if (oldAppMenuItem) { + [oldAppMenuItem setSubmenu:nil]; + [oldAppMenuItem release]; + NSMenuItem *appMenuItem = [[NSMenuItem alloc] initWithTitle:@"Apple" + action:nil keyEquivalent:@""]; + [appMenuItem setSubmenu:appMenu]; + [menu insertItem:appMenuItem atIndex:0]; + } +} + +- (void)removeActionsFromAppMenu +{ + for (NSMenuItem *item in [appMenu itemArray]) + [item setTag:nil]; +} + +- (void)dealloc +{ + [servicesItem release]; + [hideAllOthersItem release]; + [showAllItem release]; + + [lastAppSpecificItem release]; + [theMenu release]; + [appMenu release]; + [super dealloc]; +} + +- (NSMenu *)menu +{ + return [[theMenu retain] autorelease]; +} + +- (NSMenu *)applicationMenu +{ + return [[appMenu retain] autorelease]; +} + +- (NSMenuItem *)quitMenuItem +{ + return [[quitItem retain] autorelease]; +} + +- (NSMenuItem *)preferencesMenuItem +{ + return [[preferencesItem retain] autorelease]; +} + +- (NSMenuItem *)aboutMenuItem +{ + return [[aboutItem retain] autorelease]; +} + +- (NSMenuItem *)aboutQtMenuItem +{ + return [[aboutQtItem retain] autorelease]; +} + +- (NSMenuItem *)hideMenuItem +{ + return [[hideItem retain] autorelease]; +} + +- (NSMenuItem *)appSpecificMenuItem +{ + // Create an App-Specific menu item, insert it into the menu and return + // it as an autorelease item. + NSMenuItem *item = [[NSMenuItem alloc] init]; + + NSInteger location; + if (lastAppSpecificItem == nil) { + location = [appMenu indexOfItem:aboutQtItem]; + } else { + location = [appMenu indexOfItem:lastAppSpecificItem]; + [lastAppSpecificItem release]; + } + lastAppSpecificItem = item; // Keep track of this for later (i.e., don't release it) + [appMenu insertItem:item atIndex:location + 1]; + + return [[item retain] autorelease]; +} + +- (BOOL) acceptsFirstResponder +{ + return YES; +} + +- (void)terminate:(id)sender +{ + [NSApp terminate:sender]; +} + +- (void)orderFrontStandardAboutPanel:(id)sender +{ + [NSApp orderFrontStandardAboutPanel:sender]; +} + +- (void)hideOtherApplications:(id)sender +{ + [NSApp hideOtherApplications:sender]; +} + +- (void)unhideAllApplications:(id)sender +{ + [NSApp unhideAllApplications:sender]; +} + +- (void)hide:(id)sender +{ + [NSApp hide:sender]; +} + +- (void)qtUpdateMenubar +{ + // QMenuBarPrivate::macUpdateMenuBarImmediatly(); +} + +- (void)qtTranslateApplicationMenu +{ +/* +#ifndef QT_NO_TRANSLATION + [servicesItem setTitle: qt_mac_QStringToNSString(qt_mac_applicationmenu_string(0))]; + [hideItem setTitle: qt_mac_QStringToNSString(qt_mac_applicationmenu_string(1).arg(qAppName()))]; + [hideAllOthersItem setTitle: qt_mac_QStringToNSString(qt_mac_applicationmenu_string(2))]; + [showAllItem setTitle: qt_mac_QStringToNSString(qt_mac_applicationmenu_string(3))]; + [preferencesItem setTitle: qt_mac_QStringToNSString(qt_mac_applicationmenu_string(4))]; + [quitItem setTitle: qt_mac_QStringToNSString(qt_mac_applicationmenu_string(5).arg(qAppName()))]; + [aboutItem setTitle: qt_mac_QStringToNSString(qt_mac_applicationmenu_string(6).arg(qAppName()))]; +#endif +*/ +} + +- (IBAction)qtDispatcherToQAction:(id)sender +{ + /* + QScopedLoopLevelCounter loopLevelCounter(QApplicationPrivate::instance()->threadData); + NSMenuItem *item = static_cast<NSMenuItem *>(sender); + if (QAction *action = reinterpret_cast<QAction *>([item tag])) { + action->trigger(); + } else if (item == quitItem) { + // We got here because someone was once the quitItem, but it has been + // abandoned (e.g., the menubar was deleted). In the meantime, just do + // normal QApplication::quit(). + qApp->quit(); + } +*/ +} + + - (void)orderFrontCharacterPalette:(id)sender + { + [NSApp orderFrontCharacterPalette:sender]; + } +@end diff --git a/src/plugins/platforms/cocoa/qcocoaeventloopintegration.h b/src/plugins/platforms/cocoa/qcocoanativeinterface.h index 5765483fc7..f8216d8e61 100644 --- a/src/plugins/platforms/cocoa/qcocoaeventloopintegration.h +++ b/src/plugins/platforms/cocoa/qcocoanativeinterface.h @@ -39,27 +39,17 @@ ** ****************************************************************************/ -#ifndef QCOCAEVENTLOOPINTEGRATION_H -#define QCOCAEVENTLOOPINTEGRATION_H +#ifndef QCOCOANATIVEINTERFACE_H +#define QCOCOANATIVEINTERFACE_H -#include <Cocoa/Cocoa.h> +#include <QtGui/QPlatformNativeInterface> -#include <QPlatformEventLoopIntegration> +class QWidget; - -class QCocoaEventLoopIntegration : public QPlatformEventLoopIntegration +class QCocoaNativeInterface : public QPlatformNativeInterface { public: - QCocoaEventLoopIntegration(); - void startEventLoop(); - void quitEventLoop(); - void qtNeedsToProcessEvents(); - -private: - CFRunLoopSourceContext m_sourceContext; - CFRunLoopTimerContext m_timerContext; - CFRunLoopSourceRef m_source; + void *nativeResourceForWindow(const QByteArray &resourceString, QWindow *window); }; -#endif // QCOCAEVENTLOOPINTEGRATION_H - +#endif // QCOCOANATIVEINTERFACE_H diff --git a/src/plugins/platforms/cocoa/qcocoanativeinterface.mm b/src/plugins/platforms/cocoa/qcocoanativeinterface.mm new file mode 100644 index 0000000000..c6aa0d39e6 --- /dev/null +++ b/src/plugins/platforms/cocoa/qcocoanativeinterface.mm @@ -0,0 +1,59 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia 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. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qcocoanativeinterface.h" +#include "qcocoaglcontext.h" +#include "qcocoawindow.h" +#include <qbytearray.h> +#include <qwindow.h> +#include "qplatformwindow_qpa.h" +#include "qsurfaceformat.h" +#include "qplatformopenglcontext_qpa.h" +#include "qopenglcontext.h" +#include <qdebug.h> + +void *QCocoaNativeInterface::nativeResourceForWindow(const QByteArray &resourceString, QWindow *window) +{ + if (resourceString == "nsopenglcontext") { + return static_cast<QCocoaWindow *>(window->handle())->currentContext()->nsOpenGLContext(); + } + return 0; +} diff --git a/src/plugins/platforms/cocoa/qcocoaresources.qrc b/src/plugins/platforms/cocoa/qcocoaresources.qrc new file mode 100644 index 0000000000..de50d397c6 --- /dev/null +++ b/src/plugins/platforms/cocoa/qcocoaresources.qrc @@ -0,0 +1,17 @@ +<!DOCTYPE RCC><RCC version="1.0"> +<qresource prefix="/trolltech/mac/cursors"> +<file>images/copyarrowcursor.png</file> +<file>images/forbiddencursor.png</file> +<file>images/spincursor.png</file> +<file>images/waitcursor.png</file> +<file>images/pluscursor.png</file> +</qresource> +<qresource prefix="/trolltech/mac/style"> +<file>images/leopard-unified-toolbar-on.png</file> +</qresource> +<qresource prefix="/trolltech/mac/"> +<file>qt_menu.nib/classes.nib</file> +<file>qt_menu.nib/info.nib</file> +<file>qt_menu.nib/keyedobjects.nib</file> +</qresource> +</RCC> diff --git a/src/plugins/platforms/cocoa/qcocoawindow.h b/src/plugins/platforms/cocoa/qcocoawindow.h index 9e7e68b7d2..035a09b607 100644 --- a/src/plugins/platforms/cocoa/qcocoawindow.h +++ b/src/plugins/platforms/cocoa/qcocoawindow.h @@ -45,28 +45,57 @@ #include <Cocoa/Cocoa.h> #include <QPlatformWindow> +#include <QRect> + +#include "qcocoaglcontext.h" +#include "qnsview.h" QT_BEGIN_NAMESPACE +@interface QNSWindow : NSWindow { + +} + +@end + class QCocoaWindow : public QPlatformWindow { public: - QCocoaWindow(QWidget *tlw); + QCocoaWindow(QWindow *tlw); ~QCocoaWindow(); void setGeometry(const QRect &rect); - void setVisible(bool visible); + void setWindowTitle(const QString &title); + void raise(); + void lower(); WId winId() const; - NSView *contentView() const; - void setContentView(NSView *contentView); + NSView *windowSurfaceView() const; + void windowDidMove(); void windowDidResize(); + void windowWillClose(); + + void setCurrentContext(QCocoaGLContext *context); + QCocoaGLContext *currentContext() const; + +protected: + void determineWindowClass(); + QNSWindow *createWindow(); + NSRect globalGeometry(const QRect localWindowGeometry) const; + QRect windowGeometry() const; + QCocoaWindow *parentCocoaWindow() const; private: - NSWindow *m_nsWindow; + friend class QCocoaBackingStore; + QNSWindow *m_nsWindow; + QNSView *m_contentView; + NSView *m_windowSurfaceView; + quint32 m_windowAttributes; + quint32 m_windowClass; + QCocoaGLContext *m_glContext; }; QT_END_NAMESPACE diff --git a/src/plugins/platforms/cocoa/qcocoawindow.mm b/src/plugins/platforms/cocoa/qcocoawindow.mm index a2fdce3520..b7d7bd83bc 100644 --- a/src/plugins/platforms/cocoa/qcocoawindow.mm +++ b/src/plugins/platforms/cocoa/qcocoawindow.mm @@ -41,54 +41,124 @@ #include "qcocoawindow.h" #include "qnswindowdelegate.h" #include "qcocoaautoreleasepool.h" +#include "qcocoaglcontext.h" +#include "qnsview.h" +#include <QtCore/private/qcore_mac_p.h> +#include <qwindow.h> +#include <QWindowSystemInterface> +#include <QPlatformScreen> -#include <QWidget> +#include <Cocoa/Cocoa.h> +#include <Carbon/Carbon.h> -#include <QtGui/QApplication> +#include <QDebug> -#include <QWindowSystemInterface> +@implementation QNSWindow -#include <QDebug> +- (BOOL)canBecomeKeyWindow +{ + return YES; +} -QCocoaWindow::QCocoaWindow(QWidget *tlw) +- (BOOL)canBecomeMainWindow +{ + return YES; +} + +@end + +QCocoaWindow::QCocoaWindow(QWindow *tlw) : QPlatformWindow(tlw) + , m_windowAttributes(0) + , m_windowClass(0) + , m_glContext(0) { QCocoaAutoReleasePool pool; - const QRect geo = tlw->geometry(); - NSRect frame = NSMakeRect(geo.x(), geo.y(), geo.width(), geo.height()); - m_nsWindow = [[NSWindow alloc] initWithContentRect:frame - styleMask:NSTitledWindowMask|NSClosableWindowMask|NSMiniaturizableWindowMask|NSResizableWindowMask - backing:NSBackingStoreBuffered - defer:YES]; + determineWindowClass(); + m_nsWindow = createWindow(); QNSWindowDelegate *delegate = [[QNSWindowDelegate alloc] initWithQCocoaWindow:this]; [m_nsWindow setDelegate:delegate]; - - [m_nsWindow makeKeyAndOrderFront:nil]; [m_nsWindow setAcceptsMouseMovedEvents:YES]; + + // 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]; + + m_contentView = [[QNSView alloc] initWithQWindow:tlw]; + + if (tlw->surfaceType() == QWindow::OpenGLSurface) { + NSRect glFrame = globalGeometry(window()->geometry()); + m_windowSurfaceView = [[NSOpenGLView alloc] initWithFrame : glFrame pixelFormat : QCocoaGLContext::createNSOpenGLPixelFormat() ]; + [m_contentView setAutoresizesSubviews : YES]; + [m_windowSurfaceView setAutoresizingMask : (NSViewWidthSizable | NSViewHeightSizable)]; + [m_contentView addSubview : m_windowSurfaceView]; + } else { + m_windowSurfaceView = m_contentView; + } + + setGeometry(tlw->geometry()); + + [m_nsWindow setContentView:m_contentView]; } QCocoaWindow::~QCocoaWindow() { + [m_nsWindow release]; } void QCocoaWindow::setGeometry(const QRect &rect) { QPlatformWindow::setGeometry(rect); - NSRect bounds = NSMakeRect(rect.x(), rect.y(), rect.width(), rect.height()); + 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::setVisible(bool visible) { - Q_UNUSED(visible); + 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()) + setGeometry(window()->geometry()); + + [m_nsWindow makeKeyAndOrderFront:nil]; + QWindowSystemInterface::handleExposeEvent(window(), QRect(QPoint(), geometry().size())); + } else { + [m_nsWindow orderOut:nil]; + } +} + +void QCocoaWindow::setWindowTitle(const QString &title) +{ + CFStringRef windowTitle = QCFString::toCFStringRef(title); + [m_nsWindow setTitle: reinterpret_cast<const NSString *>(windowTitle)]; + CFRelease(windowTitle); +} + +void QCocoaWindow::raise() +{ + // ### handle spaces (see Qt 4 raise_sys in qwidget_mac.mm) + [m_nsWindow orderFront: m_nsWindow]; +} + +void QCocoaWindow::lower() +{ + [m_nsWindow orderFront: m_nsWindow]; } WId QCocoaWindow::winId() const { - return WId([m_nsWindow windowNumber]); + return WId(m_nsWindow); } NSView *QCocoaWindow::contentView() const @@ -96,15 +166,235 @@ NSView *QCocoaWindow::contentView() const return [m_nsWindow contentView]; } -void QCocoaWindow::setContentView(NSView *contentView) +NSView *QCocoaWindow::windowSurfaceView() const +{ + return m_windowSurfaceView; +} + +void QCocoaWindow::windowDidMove() { - [m_nsWindow setContentView:contentView]; + if (m_glContext) + m_glContext->update(); } void QCocoaWindow::windowDidResize() { - //jlind: XXX This isn't ideal. Eventdispatcher does not run when resizing... + if (m_glContext) + m_glContext->update(); + NSRect rect = [[m_nsWindow contentView]frame]; QRect geo(rect.origin.x,rect.origin.y,rect.size.width,rect.size.height); - QWindowSystemInterface::handleGeometryChange(widget(),geo); + QWindowSystemInterface::handleSynchronousGeometryChange(window(), geo); +} + + +void QCocoaWindow::windowWillClose() +{ + QWindowSystemInterface::handleCloseEvent(window()); } + +void QCocoaWindow::setCurrentContext(QCocoaGLContext *context) +{ + m_glContext = context; +} + +QCocoaGLContext *QCocoaWindow::currentContext() const +{ + 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() +{ + Qt::WindowType type = window()->windowType(); + Qt::WindowFlags flags = window()->windowFlags(); + + const bool popup = (type == Qt::Popup); + + if (type == Qt::ToolTip || type == Qt::SplashScreen || popup) + flags |= Qt::FramelessWindowHint; + + m_windowClass = kSheetWindowClass; + + 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; + } + + // Only add extra decorations (well, buttons) for widgets that can have them + // and have an actual border we can put them on. + + 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); + } + + } + + if((popup || type == Qt::Tool) && !window()->isModal()) + m_windowAttributes |= kWindowHideOnSuspendAttribute; + m_windowAttributes |= kWindowLiveResizeAttribute; +} + +/* + +*/ +QNSWindow * 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 resizeable, 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; + QNSWindow *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; + } + + panel = [[NSPanel alloc] initWithContentRect:frame + styleMask:m_windowAttributes + backing:NSBackingStoreBuffered + defer:YES]; +// ### crashes +// [panel setFloatingPanel:needFloating]; +// [panel setWorksWhenModal:worksWhenModal]; + window = panel; + break; + } + default: + window = [[QNSWindow alloc] initWithContentRect:frame + styleMask:(NSResizableWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask | NSTitledWindowMask) + backing:NSBackingStoreBuffered + defer:YES]; + break; + } + + //qt_syncCocoaTitleBarButtons(window, widget); + return window; +} + +// 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 +{ + QRect finalGeometry = localGeometry; + + if (QCocoaWindow *parent = parentCocoaWindow()) { + QRect parentGeometry = parent->windowGeometry(); + finalGeometry.adjust(parentGeometry.x(), parentGeometry.y(), parentGeometry.x(), parentGeometry.y()); + + // 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 contet area, but is still included + // in the NSWindow height. Move the child window down to acccount + // 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()); +} + +// Returns the current global screen geometry for the nswindow accociated with this window. +QRect QCocoaWindow::windowGeometry() const +{ + 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. + QRect qRect = QRect(rect.origin.x, flippedY, rect.size.width, rect.size.height); + return qRect; +} + +// Returns a pointer to the parent QCocoaWindow for this window, or 0 if there is none. +QCocoaWindow *QCocoaWindow::parentCocoaWindow() const +{ + if (window() && window()->transientParent()) { + return static_cast<QCocoaWindow*>(window()->transientParent()->handle()); + } + return 0; +} + diff --git a/src/plugins/platforms/cocoa/qnsview.h b/src/plugins/platforms/cocoa/qnsview.h index 69a11134bd..0b96928d5b 100644 --- a/src/plugins/platforms/cocoa/qnsview.h +++ b/src/plugins/platforms/cocoa/qnsview.h @@ -48,17 +48,18 @@ @interface QNSView : NSView { CGImageRef m_cgImage; - QWidget *m_widget; + QWindow *m_window; Qt::MouseButtons m_buttons; } - (id)init; -- (id)initWithWidget:(QWidget *)widget; +- (id)initWithQWindow:(QWindow *)window; - (void)setImage:(QImage *)image; - (void)drawRect:(NSRect)dirtyRect; - (BOOL)isFlipped; +- (BOOL)acceptsFirstResponder; - (void)handleMouseEvent:(NSEvent *)theEvent; - (void)mouseDown:(NSEvent *)theEvent; @@ -74,6 +75,12 @@ - (void)otherMouseDragged:(NSEvent *)theEvent; - (void)otherMouseUp:(NSEvent *)theEvent; +- (int) convertKeyCode : (QChar)keyCode; +- (Qt::KeyboardModifiers) convertKeyModifiers : (ulong)modifierFlags; +- (void)handleKeyEvent:(NSEvent *)theEvent eventType:(int)eventType; +- (void)keyDown:(NSEvent *)theEvent; +- (void)keyUp:(NSEvent *)theEvent; + @end #endif //QNSVIEW_H diff --git a/src/plugins/platforms/cocoa/qnsview.mm b/src/plugins/platforms/cocoa/qnsview.mm index 037cbdb5d6..b2a3d73d48 100644 --- a/src/plugins/platforms/cocoa/qnsview.mm +++ b/src/plugins/platforms/cocoa/qnsview.mm @@ -39,12 +39,18 @@ ** ****************************************************************************/ -#include "qnsview.h" +#include <Carbon/Carbon.h> +#include "qnsview.h" #include <QtGui/QWindowSystemInterface> - #include <QtCore/QDebug> +@interface NSEvent (Qt_Compile_Leopard_DeviceDelta) + - (CGFloat)deviceDeltaX; + - (CGFloat)deviceDeltaY; + - (CGFloat)deviceDeltaZ; +@end + @implementation QNSView - (id) init @@ -52,16 +58,16 @@ self = [super init]; if (self) { m_cgImage = 0; - m_widget = 0; + m_window = 0; m_buttons = Qt::NoButton; } return self; } -- (id)initWithWidget:(QWidget *)widget { +- (id)initWithQWindow:(QWindow *)widget { self = [self init]; if (self) { - m_widget = widget; + m_window = widget; } return self; } @@ -91,7 +97,7 @@ bitDepth, bytesPrLine, cgColourSpaceRef, - kCGImageAlphaNone, + kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipFirst, cgDataProviderRef, NULL, false, @@ -130,82 +136,222 @@ return YES; } +- (BOOL)acceptsFirstResponder +{ + return YES; +} + - (void)handleMouseEvent:(NSEvent *)theEvent; { - NSPoint point = [self convertPoint: [theEvent locationInWindow] fromView: nil]; - QPoint qt_localPoint(point.x,point.y); + NSPoint windowPoint = [self convertPoint: [theEvent locationInWindow] fromView: nil]; + QPoint qt_windowPoint(windowPoint.x, windowPoint.y); NSTimeInterval timestamp = [theEvent timestamp]; ulong qt_timestamp = timestamp * 1000; - QWindowSystemInterface::handleMouseEvent(m_widget,qt_timestamp,qt_localPoint,QPoint(),m_buttons); + // ### Should the points be windowPoint and screenPoint? + QWindowSystemInterface::handleMouseEvent(m_window, qt_timestamp, qt_windowPoint, qt_windowPoint, m_buttons); +} +- (void)mouseDown:(NSEvent *)theEvent +{ + m_buttons |= Qt::LeftButton; + [self handleMouseEvent:theEvent]; +} + +- (void)mouseDragged:(NSEvent *)theEvent +{ + if (!(m_buttons & Qt::LeftButton)) + qWarning("Internal Mousebutton tracking invalid(missing Qt::LeftButton"); + [self handleMouseEvent:theEvent]; +} + +- (void)mouseUp:(NSEvent *)theEvent +{ + m_buttons &= QFlag(~int(Qt::LeftButton)); + [self handleMouseEvent:theEvent]; } - - (void)mouseDown:(NSEvent *)theEvent - { - m_buttons |= Qt::LeftButton; - [self handleMouseEvent:theEvent]; - } - - (void)mouseDragged:(NSEvent *)theEvent - { - if (!(m_buttons & Qt::LeftButton)) - qWarning("Internal Mousebutton tracking invalid(missing Qt::LeftButton"); - [self handleMouseEvent:theEvent]; - } - - (void)mouseUp:(NSEvent *)theEvent - { - m_buttons &= QFlag(~int(Qt::LeftButton)); - [self handleMouseEvent:theEvent]; - } - (void)mouseMoved:(NSEvent *)theEvent { - qDebug() << "mouseMove"; [self handleMouseEvent:theEvent]; } + - (void)mouseEntered:(NSEvent *)theEvent { - Q_UNUSED(theEvent); - QWindowSystemInterface::handleEnterEvent(m_widget); + Q_UNUSED(theEvent); + QWindowSystemInterface::handleEnterEvent(m_window); } + - (void)mouseExited:(NSEvent *)theEvent { - Q_UNUSED(theEvent); - QWindowSystemInterface::handleLeaveEvent(m_widget); + Q_UNUSED(theEvent); + QWindowSystemInterface::handleLeaveEvent(m_window); } + - (void)rightMouseDown:(NSEvent *)theEvent { - m_buttons |= Qt::RightButton; + m_buttons |= Qt::RightButton; [self handleMouseEvent:theEvent]; } + - (void)rightMouseDragged:(NSEvent *)theEvent { - if (!(m_buttons & Qt::LeftButton)) - qWarning("Internal Mousebutton tracking invalid(missing Qt::LeftButton"); - [self handleMouseEvent:theEvent]; + if (!(m_buttons & Qt::LeftButton)) + qWarning("Internal Mousebutton tracking invalid(missing Qt::LeftButton"); + [self handleMouseEvent:theEvent]; } + - (void)rightMouseUp:(NSEvent *)theEvent { - m_buttons &= QFlag(~int(Qt::RightButton)); - [self handleMouseEvent:theEvent]; + m_buttons &= QFlag(~int(Qt::RightButton)); + [self handleMouseEvent:theEvent]; } + - (void)otherMouseDown:(NSEvent *)theEvent { - m_buttons |= Qt::RightButton; + m_buttons |= Qt::RightButton; [self handleMouseEvent:theEvent]; } + - (void)otherMouseDragged:(NSEvent *)theEvent { - if (!(m_buttons & Qt::LeftButton)) - qWarning("Internal Mousebutton tracking invalid(missing Qt::LeftButton"); - [self handleMouseEvent:theEvent]; + if (!(m_buttons & Qt::LeftButton)) + qWarning("Internal Mousebutton tracking invalid(missing Qt::LeftButton"); + [self handleMouseEvent:theEvent]; } + - (void)otherMouseUp:(NSEvent *)theEvent { - m_buttons &= QFlag(~int(Qt::MiddleButton)); - [self handleMouseEvent:theEvent]; + m_buttons &= QFlag(~int(Qt::MiddleButton)); + [self handleMouseEvent:theEvent]; } +#ifndef QT_NO_WHEELEVENT +- (void)scrollWheel:(NSEvent *)theEvent +{ + int deltaX = 0; + int deltaY = 0; + int deltaZ = 0; + + const EventRef carbonEvent = (EventRef)[theEvent eventRef]; + const UInt32 carbonEventKind = carbonEvent ? ::GetEventKind(carbonEvent) : 0; + const bool scrollEvent = carbonEventKind == kEventMouseScroll; + + if (scrollEvent) { + // The mouse device containts pixel scroll wheel support (Mighty Mouse, Trackpad). + // Since deviceDelta is delivered as pixels rather than degrees, we need to + // convert from pixels to degrees in a sensible manner. + // It looks like 1/4 degrees per pixel behaves most native. + // (NB: Qt expects the unit for delta to be 8 per degree): + const int pixelsToDegrees = 2; // 8 * 1/4 + deltaX = [theEvent deviceDeltaX] * pixelsToDegrees; + deltaY = [theEvent deviceDeltaY] * pixelsToDegrees; + deltaZ = [theEvent deviceDeltaZ] * pixelsToDegrees; + } else { + // carbonEventKind == kEventMouseWheelMoved + // Remove acceleration, and use either -120 or 120 as delta: + deltaX = qBound(-120, int([theEvent deltaX] * 10000), 120); + deltaY = qBound(-120, int([theEvent deltaY] * 10000), 120); + deltaZ = qBound(-120, int([theEvent deltaZ] * 10000), 120); + } + + NSPoint windowPoint = [self convertPoint: [theEvent locationInWindow] fromView: nil]; + QPoint qt_windowPoint(windowPoint.x, windowPoint.y); + NSTimeInterval timestamp = [theEvent timestamp]; + ulong qt_timestamp = timestamp * 1000; + + if (deltaX != 0) + QWindowSystemInterface::handleWheelEvent(m_window, qt_timestamp, qt_windowPoint, qt_windowPoint, deltaX, Qt::Horizontal); + + if (deltaY != 0) + QWindowSystemInterface::handleWheelEvent(m_window, qt_timestamp, qt_windowPoint, qt_windowPoint, deltaY, Qt::Vertical); + + if (deltaZ != 0) + // Qt doesn't explicitly support wheels with a Z component. In a misguided attempt to + // try to be ahead of the pack, I'm adding this extra value. + QWindowSystemInterface::handleWheelEvent(m_window, qt_timestamp, qt_windowPoint, qt_windowPoint, deltaY, (Qt::Orientation)3); +} +#endif //QT_NO_WHEELEVENT + +- (int) convertKeyCode : (QChar)keyChar +{ + if (keyChar.isLower()) + keyChar = keyChar.toUpper(); + int keyCode = keyChar.unicode(); + + int qtKeyCode = Qt::Key(keyCode); // default case, overrides below + switch (keyCode) { + case NSEnterCharacter: qtKeyCode = Qt::Key_Enter; break; + case NSBackspaceCharacter: qtKeyCode = Qt::Key_Backspace; break; + case NSTabCharacter: qtKeyCode = Qt::Key_Tab; break; + case NSNewlineCharacter: qtKeyCode = Qt::Key_Return; break; + case NSCarriageReturnCharacter: qtKeyCode = Qt::Key_Return; break; + case NSBackTabCharacter: qtKeyCode = Qt::Key_Backtab; break; + case 27 : qtKeyCode = Qt::Key_Escape; break; + case NSDeleteCharacter : qtKeyCode = Qt::Key_Backspace; break; // Cocoa sends us delete when pressing backspace. + case NSUpArrowFunctionKey: qtKeyCode = Qt::Key_Up; break; + case NSDownArrowFunctionKey: qtKeyCode = Qt::Key_Down; break; + case NSLeftArrowFunctionKey: qtKeyCode = Qt::Key_Left; break; + case NSRightArrowFunctionKey: qtKeyCode = Qt::Key_Right; break; + case NSInsertFunctionKey: qtKeyCode = Qt::Key_Insert; break; + case NSDeleteFunctionKey: qtKeyCode = Qt::Key_Delete; break; + case NSHomeFunctionKey: qtKeyCode = Qt::Key_Home; break; + case NSEndFunctionKey: qtKeyCode = Qt::Key_End; break; + case NSPageUpFunctionKey: qtKeyCode = Qt::Key_PageUp; break; + case NSPageDownFunctionKey: qtKeyCode = Qt::Key_PageDown; break; + case NSPrintScreenFunctionKey: qtKeyCode = Qt::Key_Print; break; + case NSScrollLockFunctionKey: qtKeyCode = Qt::Key_ScrollLock; break; + case NSPauseFunctionKey: qtKeyCode = Qt::Key_Pause; break; + case NSSysReqFunctionKey: qtKeyCode = Qt::Key_SysReq; break; + case NSMenuFunctionKey: qtKeyCode = Qt::Key_Menu; break; + case NSHelpFunctionKey: qtKeyCode = Qt::Key_Help; break; + default : break; + } + + // handle all function keys (F1-F35) + if (keyCode >= NSF1FunctionKey && keyCode <= NSF35FunctionKey) + qtKeyCode = Qt::Key_F1 + (keyCode - NSF1FunctionKey); + return qtKeyCode; +} + +- (Qt::KeyboardModifiers) convertKeyModifiers : (ulong)modifierFlags +{ + Qt::KeyboardModifiers qtMods =Qt::NoModifier; + if (modifierFlags & NSShiftKeyMask) + qtMods |= Qt::ShiftModifier; + if (modifierFlags & NSControlKeyMask) + qtMods |= Qt::MetaModifier; + if (modifierFlags & NSAlternateKeyMask) + qtMods |= Qt::AltModifier; + if (modifierFlags & NSCommandKeyMask) + qtMods |= Qt::ControlModifier; + if (modifierFlags & NSNumericPadKeyMask) + qtMods |= Qt::KeypadModifier; + return qtMods; +} + +- (void)handleKeyEvent:(NSEvent *)theEvent eventType:(int)eventType +{ + NSTimeInterval timestamp = [theEvent timestamp]; + ulong qt_timestamp = timestamp * 1000; + QString characters = QString::fromUtf8([[theEvent characters] UTF8String]); + Qt::KeyboardModifiers modifiers = [self convertKeyModifiers : [theEvent modifierFlags]]; + QChar ch([[theEvent charactersIgnoringModifiers] characterAtIndex:0]); + int keyCode = [self convertKeyCode : ch]; + + QWindowSystemInterface::handleKeyEvent(m_window, qt_timestamp, QEvent::Type(eventType), keyCode, modifiers, characters); +} + +- (void)keyDown:(NSEvent *)theEvent +{ + [self handleKeyEvent : theEvent eventType :int(QEvent::KeyPress)]; +} + +- (void)keyUp:(NSEvent *)theEvent +{ + [self handleKeyEvent : theEvent eventType :int(QEvent::KeyRelease)]; +} @end diff --git a/src/plugins/platforms/cocoa/qnswindowdelegate.h b/src/plugins/platforms/cocoa/qnswindowdelegate.h index cf296c4a8b..5cd226a71d 100644 --- a/src/plugins/platforms/cocoa/qnswindowdelegate.h +++ b/src/plugins/platforms/cocoa/qnswindowdelegate.h @@ -46,6 +46,26 @@ #include "qcocoawindow.h" +#if MAC_OS_X_VERSION_MAX_ALLOWED <= MAC_OS_X_VERSION_10_5 +@protocol NSWindowDelegate <NSObject> +//- (NSSize)windowWillResize:(NSWindow *)window toSize:(NSSize)proposedFrameSize; +//- (void)windowDidMiniaturize:(NSNotification*)notification; +- (void)windowDidResize:(NSNotification *)notification; +- (void)windowWillClose:(NSNotification *)notification; +//- (NSRect)windowWillUseStandardFrame:(NSWindow *)window defaultFrame:(NSRect)defaultFrame; +- (void)windowDidMove:(NSNotification *)notification; +//- (BOOL)windowShouldClose:(id)window; +//- (void)windowDidDeminiaturize:(NSNotification *)notification; +//- (void)windowDidBecomeMain:(NSNotification*)notification; +//- (void)windowDidResignMain:(NSNotification*)notification; +//- (void)windowDidBecomeKey:(NSNotification*)notification; +//- (void)windowDidResignKey:(NSNotification*)notification; +//- (BOOL)window:(NSWindow *)window shouldPopUpDocumentPathMenu:(NSMenu *)menu; +//- (BOOL)window:(NSWindow *)window shouldDragDocumentWithEvent:(NSEvent *)event from:(NSPoint)dragImageLocation withPasteboard:(NSPasteboard *)pasteboard; +//- (BOOL)windowShouldZoom:(NSWindow *)window toFrame:(NSRect)newFrame; +@end +#endif + @interface QNSWindowDelegate : NSObject <NSWindowDelegate> { QCocoaWindow *m_cocoaWindow; @@ -54,6 +74,7 @@ - (id)initWithQCocoaWindow: (QCocoaWindow *) cocoaWindow; - (void)windowDidResize:(NSNotification *)notification; +- (void)windowDidMove:(NSNotification *)notification; - (void)windowWillClose:(NSNotification *)notification; @end diff --git a/src/plugins/platforms/cocoa/qnswindowdelegate.mm b/src/plugins/platforms/cocoa/qnswindowdelegate.mm index 887b08f6d2..869ef7840b 100644 --- a/src/plugins/platforms/cocoa/qnswindowdelegate.mm +++ b/src/plugins/platforms/cocoa/qnswindowdelegate.mm @@ -64,10 +64,20 @@ } } +- (void)windowDidMove:(NSNotification *)notification +{ + Q_UNUSED(notification); + if (m_cocoaWindow) { + m_cocoaWindow->windowDidMove(); + } +} + - (void)windowWillClose:(NSNotification *)notification { Q_UNUSED(notification); - QWindowSystemInterface::handleCloseEvent(m_cocoaWindow->widget()); + if (m_cocoaWindow) { + m_cocoaWindow->windowWillClose(); + } } @end diff --git a/src/plugins/platforms/cocoa/qt_menu.nib/classes.nib b/src/plugins/platforms/cocoa/qt_menu.nib/classes.nib new file mode 100644 index 0000000000..0031e0e4e5 --- /dev/null +++ b/src/plugins/platforms/cocoa/qt_menu.nib/classes.nib @@ -0,0 +1,59 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>IBClasses</key> + <array> + <dict> + <key>ACTIONS</key> + <dict> + <key>hide</key> + <string>id</string> + <key>hideOtherApplications</key> + <string>id</string> + <key>orderFrontStandardAboutPanel</key> + <string>id</string> + <key>qtDispatcherToQAction</key> + <string>id</string> + <key>terminate</key> + <string>id</string> + <key>unhideAllApplications</key> + <string>id</string> + </dict> + <key>CLASS</key> + <string>QCocoaMenuLoader</string> + <key>LANGUAGE</key> + <string>ObjC</string> + <key>OUTLETS</key> + <dict> + <key>aboutItem</key> + <string>NSMenuItem</string> + <key>aboutQtItem</key> + <string>NSMenuItem</string> + <key>appMenu</key> + <string>NSMenu</string> + <key>hideItem</key> + <string>NSMenuItem</string> + <key>preferencesItem</key> + <string>NSMenuItem</string> + <key>quitItem</key> + <string>NSMenuItem</string> + <key>theMenu</key> + <string>NSMenu</string> + </dict> + <key>SUPERCLASS</key> + <string>NSResponder</string> + </dict> + <dict> + <key>CLASS</key> + <string>FirstResponder</string> + <key>LANGUAGE</key> + <string>ObjC</string> + <key>SUPERCLASS</key> + <string>NSObject</string> + </dict> + </array> + <key>IBVersion</key> + <string>1</string> +</dict> +</plist> diff --git a/src/plugins/platforms/cocoa/qt_menu.nib/info.nib b/src/plugins/platforms/cocoa/qt_menu.nib/info.nib new file mode 100644 index 0000000000..02e5cca562 --- /dev/null +++ b/src/plugins/platforms/cocoa/qt_menu.nib/info.nib @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>IBFramework Version</key> + <string>672</string> + <key>IBOldestOS</key> + <integer>5</integer> + <key>IBOpenObjects</key> + <array> + <integer>57</integer> + </array> + <key>IBSystem Version</key> + <string>9L31a</string> + <key>targetFramework</key> + <string>IBCocoaFramework</string> +</dict> +</plist> diff --git a/src/plugins/platforms/cocoa/qt_menu.nib/keyedobjects.nib b/src/plugins/platforms/cocoa/qt_menu.nib/keyedobjects.nib Binary files differnew file mode 100644 index 0000000000..3edb0ed2eb --- /dev/null +++ b/src/plugins/platforms/cocoa/qt_menu.nib/keyedobjects.nib |