summaryrefslogtreecommitdiffstats
path: root/src/gui/kernel/qeventdispatcher_mac.mm
diff options
context:
space:
mode:
authorLars Knoll <lars.knoll@nokia.com>2009-03-23 09:18:55 (GMT)
committerSimon Hausmann <simon.hausmann@nokia.com>2009-03-23 09:18:55 (GMT)
commite5fcad302d86d316390c6b0f62759a067313e8a9 (patch)
treec2afbf6f1066b6ce261f14341cf6d310e5595bc1 /src/gui/kernel/qeventdispatcher_mac.mm
downloadQt-e5fcad302d86d316390c6b0f62759a067313e8a9.zip
Qt-e5fcad302d86d316390c6b0f62759a067313e8a9.tar.gz
Qt-e5fcad302d86d316390c6b0f62759a067313e8a9.tar.bz2
Long live Qt 4.5!
Diffstat (limited to 'src/gui/kernel/qeventdispatcher_mac.mm')
-rw-r--r--src/gui/kernel/qeventdispatcher_mac.mm926
1 files changed, 926 insertions, 0 deletions
diff --git a/src/gui/kernel/qeventdispatcher_mac.mm b/src/gui/kernel/qeventdispatcher_mac.mm
new file mode 100644
index 0000000..e28f170
--- /dev/null
+++ b/src/gui/kernel/qeventdispatcher_mac.mm
@@ -0,0 +1,926 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (qt-info@nokia.com)
+**
+** This file is part of the QtGui module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain
+** additional rights. These rights are described in the Nokia Qt LGPL
+** Exception version 1.0, 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.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+** $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 "qplatformdefs.h"
+#include "private/qt_mac_p.h"
+#include "qeventdispatcher_mac_p.h"
+#include "qapplication.h"
+#include "qevent.h"
+#include "qdialog.h"
+#include "qhash.h"
+#include "qsocketnotifier.h"
+#include "private/qwidget_p.h"
+#include "private/qthread_p.h"
+#include "private/qapplication_p.h"
+
+#include <private/qcocoaapplication_mac_p.h>
+#include "private/qt_cocoa_helpers_mac_p.h"
+
+#ifndef QT_NO_THREAD
+# include "qmutex.h"
+
+QT_BEGIN_NAMESPACE
+
+QT_USE_NAMESPACE
+#endif
+
+/*****************************************************************************
+ Externals
+ *****************************************************************************/
+extern void qt_event_request_timer(MacTimerInfo *); //qapplication_mac.cpp
+extern MacTimerInfo *qt_event_get_timer(EventRef); //qapplication_mac.cpp
+extern void qt_event_request_select(QEventDispatcherMac *); //qapplication_mac.cpp
+extern void qt_event_request_updates(); //qapplication_mac.cpp
+extern OSWindowRef qt_mac_window_for(const QWidget *); //qwidget_mac.cpp
+extern bool qt_is_gui_used; //qapplication.cpp
+extern bool qt_sendSpontaneousEvent(QObject*, QEvent*); // qapplication.cpp
+extern bool qt_mac_is_macsheet(const QWidget *); //qwidget_mac.cpp
+
+static inline CFRunLoopRef mainRunLoop()
+{
+#ifndef QT_MAC_USE_COCOA
+ return reinterpret_cast<CFRunLoopRef>(const_cast<void *>(GetCFRunLoopFromEventLoop(GetMainEventLoop())));
+#else
+ return CFRunLoopGetMain();
+#endif
+}
+
+/*****************************************************************************
+ Timers stuff
+ *****************************************************************************/
+
+/* timer call back */
+void QEventDispatcherMacPrivate::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.
+
+ tmr->pending = true;
+ QTimerEvent e(tmr->id);
+ qt_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 QEventDispatcherMac::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;
+ QEventDispatcherMacPrivate::macTimerHash.insert(timerId, t);
+ CFRunLoopTimerContext info = { 0, (void *)timerId, 0, 0, 0 };
+ t->runLoopTimer = CFRunLoopTimerCreate(0, fireDate, cfinterval, 0, 0,
+ QEventDispatcherMacPrivate::activateTimer, &info);
+ if (t->runLoopTimer == 0) {
+ qFatal("QEventDispatcherMac::registerTimer: Cannot create timer");
+ }
+ CFRunLoopAddTimer(mainRunLoop(), t->runLoopTimer, kCFRunLoopCommonModes);
+}
+
+bool QEventDispatcherMac::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 = QEventDispatcherMacPrivate::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 QEventDispatcherMac::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 = QEventDispatcherMacPrivate::macTimerHash.begin();
+ while (it != QEventDispatcherMacPrivate::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 = QEventDispatcherMacPrivate::macTimerHash.erase(it);
+ }
+ }
+ return true;
+}
+
+QList<QEventDispatcherMac::TimerInfo>
+QEventDispatcherMac::registeredTimers(QObject *object) const
+{
+ if (!object) {
+ qWarning("QEventDispatcherMac:registeredTimers: invalid argument");
+ return QList<TimerInfo>();
+ }
+
+ QList<TimerInfo> list;
+
+ MacTimerHash::const_iterator it = QEventDispatcherMacPrivate::macTimerHash.constBegin();
+ while (it != QEventDispatcherMacPrivate::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) {
+ QEventDispatcherMacPrivate *const eventDispatcher
+ = static_cast<QEventDispatcherMacPrivate *>(info);
+ int nativeSocket = CFSocketGetNative(s);
+ MacSocketInfo *socketInfo = eventDispatcher->macSockets.value(nativeSocket);
+ QEvent notifierEvent(QEvent::SockAct);
+ if (callbackType == kCFSocketReadCallBack) {
+ Q_ASSERT(socketInfo->readNotifier);
+ QApplication::sendEvent(socketInfo->readNotifier, &notifierEvent);
+ } else if (callbackType == kCFSocketWriteCallBack) {
+ // ### Bug in Apple socket notifiers seems to send write even
+ // ### after the notifier has been disabled, need to investigate further.
+ if (socketInfo->writeNotifier)
+ QApplication::sendEvent(socketInfo->writeNotifier, &notifierEvent);
+ }
+}
+
+/*
+ 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 QEventDispatcherMac::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(QEventDispatcherMac);
+
+ 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 QEventDispatcherMac::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(QEventDispatcherMac);
+
+ 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 QEventDispatcherMac::hasPendingEvents()
+{
+ extern uint qGlobalPostedEventsCount();
+ return qGlobalPostedEventsCount() || (qt_is_gui_used && GetNumEventsInQueue(GetMainEventQueue()));
+}
+
+
+static bool qt_mac_send_event(QEventLoop::ProcessEventsFlags, OSEventRef event, OSWindowRef pt)
+{
+#ifndef QT_MAC_USE_COCOA
+ if(pt && SendEventToWindow(event, pt) != eventNotHandledErr)
+ return true;
+ return !SendEventToEventTarget(event, GetEventDispatcherTarget());
+#else // QT_MAC_USE_COCOA
+ if (pt)
+ [pt sendEvent:event];
+ else
+ [NSApp sendEvent:event];
+ return true;
+#endif
+}
+
+#ifdef QT_MAC_USE_COCOA
+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:
+ result = true;
+ break;
+
+ default:
+ break;
+ }
+ return result;
+}
+#endif
+
+bool QEventDispatcherMac::processEvents(QEventLoop::ProcessEventsFlags flags)
+{
+ Q_D(QEventDispatcherMac);
+ d->interrupt = false;
+ // 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();
+
+#ifndef QT_MAC_NO_QUICKDRAW
+ if(!qt_mac_safe_pdev) { //create an empty widget and this can be used for a port anytime
+ QWidget *tlw = new QWidget;
+ tlw->setAttribute(Qt::WA_DeleteOnClose);
+ tlw->setObjectName(QLatin1String("empty_widget"));
+ tlw->hide();
+ qt_mac_safe_pdev = tlw;
+ }
+#endif
+
+ bool retVal = false;
+ forever {
+ if (d->interrupt)
+ break;
+
+#ifdef QT_MAC_USE_COCOA
+ QMacCocoaAutoReleasePool pool;
+ NSEvent* event = 0;
+
+ if (flags & QEventLoop::DialogExec || flags & QEventLoop::EventLoopExec) {
+ // The point of the CocoaRequestModal event is to make sure that a
+ // non-execed app modal window recurses into it's own dialog exec
+ // once cocoa is spinning the event loop for us (e.g on top of [NSApp run]).
+ // We expect only one event to notify us about this, regardless of how many
+ // widgets that are waiting to be modal. So we remove all other pending
+ // events, if any. And since cocoa will now take over event processing for us,
+ // we allow new app modal widgets to recurse on top of us, hence the release of
+ // the block:
+ QBoolBlocker block(d->blockCocoaRequestModal, false);
+ QCoreApplication::removePostedEvents(qApp, QEvent::CocoaRequestModal);
+
+ if (NSModalSession session = d->activeModalSession())
+ while ([NSApp runModalSession:session] == NSRunContinuesResponse) {
+ // runModalSession will not wait for events, so we do it
+ // ourselves (otherwise we would spend 100% CPU inside this loop):
+ event = [NSApp nextEventMatchingMask:NSAnyEventMask
+ untilDate:[NSDate distantFuture] inMode:NSModalPanelRunLoopMode dequeue:YES];
+ if (event)
+ [NSApp postEvent:event atStart:YES];
+ }
+ else
+ [NSApp run];
+
+ d->rebuildModalSessionStack(false);
+ retVal = true;
+ } else do {
+ // Since we now are going to spin the event loop just _one_ round
+ // we need to block all incoming CocoaRequestModal events to ensure
+ // that we don't recurse into a new exec-ing event loop while doing
+ // so (and as such, 'hang' the thread inside the recursion):
+ QBoolBlocker block(d->blockCocoaRequestModal, true);
+ bool mustRelease = false;
+
+ if (!(flags & QEventLoop::ExcludeUserInputEvents) && !d->queuedUserInputEvents.isEmpty()) {
+ // process a pending user input event
+ mustRelease = true;
+ event = static_cast<NSEvent *>(d->queuedUserInputEvents.takeFirst());
+ } else {
+ if (NSModalSession session = d->activeModalSession()) {
+ // There's s a modal widget showing, run it's session:
+ if (flags & QEventLoop::WaitForMoreEvents) {
+ // Wait for at least one event
+ // before spinning the session:
+ event = [NSApp nextEventMatchingMask:NSAnyEventMask
+ untilDate:[NSDate distantFuture] inMode:NSModalPanelRunLoopMode dequeue:YES];
+ if (event)
+ [NSApp postEvent:event atStart:YES];
+ }
+ [NSApp runModalSession:session];
+ retVal = true;
+ break;
+ } else {
+ event = [NSApp nextEventMatchingMask:NSAnyEventMask
+ untilDate:nil
+ inMode:NSDefaultRunLoopMode
+ dequeue: YES];
+
+ if (event != nil) {
+ if (flags & QEventLoop::ExcludeUserInputEvents) {
+ if (IsMouseOrKeyEvent(event)) {
+ // retain event here?
+ [event retain];
+ d->queuedUserInputEvents.append(event);
+ continue;
+ }
+ }
+ }
+ }
+ }
+ if (event) {
+ if (!filterEvent(event) && qt_mac_send_event(flags, event, 0))
+ retVal = true;
+ if (mustRelease)
+ [event release];
+ }
+ } while(!d->interrupt && event != nil);
+
+#else
+ do {
+ EventRef event;
+ if (!(flags & QEventLoop::ExcludeUserInputEvents)
+ && !d->queuedUserInputEvents.isEmpty()) {
+ // process a pending user input event
+ event = static_cast<EventRef>(d->queuedUserInputEvents.takeFirst());
+ } else {
+ OSStatus err = ReceiveNextEvent(0,0, kEventDurationNoWait, true, &event);
+ if(err != noErr)
+ continue;
+ // else
+ if (flags & QEventLoop::ExcludeUserInputEvents) {
+ UInt32 ekind = GetEventKind(event),
+ eclass = GetEventClass(event);
+ switch(eclass) {
+ case kEventClassQt:
+ if(ekind != kEventQtRequestContext)
+ break;
+ // fall through
+ case kEventClassMouse:
+ case kEventClassKeyboard:
+ d->queuedUserInputEvents.append(event);
+ continue;
+ }
+ }
+ }
+
+ if (!filterEvent(&event) && qt_mac_send_event(flags, event, 0))
+ retVal = true;
+ ReleaseEvent(event);
+ } while(!d->interrupt && GetNumEventsInQueue(GetMainEventQueue()) > 0);
+
+#endif
+
+ 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
+ // (WaitForMoreEvents). So we wait on the window server:
+#ifndef QT_MAC_USE_COCOA
+ while(CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0e20, true) == kCFRunLoopRunTimedOut);
+#else
+ QMacCocoaAutoReleasePool pool;
+ NSEvent *manualEvent = [NSApp nextEventMatchingMask:NSAnyEventMask
+ untilDate:[NSDate distantFuture] inMode:NSDefaultRunLoopMode
+ dequeue:YES];
+ if (manualEvent)
+ [NSApp sendEvent:manualEvent];
+#endif
+ flags &= ~QEventLoop::WaitForMoreEvents;
+ } else {
+ // Done with event processing for now. Leave the function:
+ break;
+ }
+ }
+
+ // Because pending deffered-delete events are only sendt after
+ // returning from the loop level they were posted in, we schedule
+ // an extra wakup to force the _current_ run loop to process them (in
+ // case the application stands idle waiting for the delete event):
+ wakeUp();
+
+ if (d->interrupt){
+ // We restart NSApplication by first stopping it, and then call 'run'
+ // again (NSApplication is actually already stopped, hence the need
+ // for a restart, but calling stop again will also make the call
+ // return from the current recursion). When the call returns to
+ // QEventLoop (mind, not from this recursion, but from the one we're
+ // about to stop), it will just call QEventDispatcherMac::processEvents()
+ // again.
+ interrupt();
+ }
+ return retVal;
+}
+
+void QEventDispatcherMac::wakeUp()
+{
+ Q_D(QEventDispatcherMac);
+ d->serialNumber.ref();
+ CFRunLoopSourceSignal(d->postedEventsSource);
+ CFRunLoopWakeUp(mainRunLoop());
+}
+
+void QEventDispatcherMac::flush()
+{
+ if(qApp) {
+ QWidgetList tlws = QApplication::topLevelWidgets();
+ for(int i = 0; i < tlws.size(); i++) {
+ QWidget *tlw = tlws.at(i);
+ if(tlw->isVisible())
+ macWindowFlush(qt_mac_window_for(tlw));
+ }
+ }
+}
+
+/*****************************************************************************
+ QEventDispatcherMac Implementation
+ *****************************************************************************/
+MacTimerHash QEventDispatcherMacPrivate::macTimerHash;
+bool QEventDispatcherMacPrivate::blockSendPostedEvents = false;
+
+#ifdef QT_MAC_USE_COCOA
+QStack<QCocoaModalSessionInfo> QEventDispatcherMacPrivate::cocoaModalSessionStack;
+bool QEventDispatcherMacPrivate::blockCocoaRequestModal = false;
+
+static void qt_mac_setChildDialogsResponsive(QWidget *widget, bool responsive)
+{
+ QList<QDialog *> dialogs = widget->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:responsive];
+ if (responsive && dialogs[i]->isVisible()){
+ [window orderFront:window];
+ }
+ }
+ }
+}
+
+NSModalSession QEventDispatcherMacPrivate::activeModalSession()
+{
+ // Create (if needed) and return the modal session
+ // for the top-most modal dialog, if any:
+ if (cocoaModalSessionStack.isEmpty())
+ return 0;
+ QCocoaModalSessionInfo &info = cocoaModalSessionStack.last();
+ if (!info.widget)
+ return 0;
+ if (info.widget->testAttribute(Qt::WA_DontShowOnScreen)){
+ // INVARIANT: We have a modal widget, but it's not visible on screen.
+ // This will e.g. be true for native dialogs. Make the dialog children
+ // of the previous modal dialog unresponsive, so that the current dialog
+ // (native or not) is the only reponsive dialog on screen:
+ int size = cocoaModalSessionStack.size();
+ if (size > 1){
+ if (QWidget *prevModal = cocoaModalSessionStack[size-2].widget)
+ qt_mac_setChildDialogsResponsive(prevModal, false);
+ }
+ return 0;
+ }
+
+ if (!info.session) {
+ QMacCocoaAutoReleasePool pool;
+ NSWindow *window = qt_mac_window_for(info.widget);
+ if (!window)
+ return 0;
+ // 'beginModalSessionForWindow' will give the event loop a spin, and as
+ // such, deliver Qt events. This might lead to inconsistent behaviour
+ // (especially if CocoaRequestModal is delivered), so we need to block:
+ QBoolBlocker block(blockSendPostedEvents, true);
+ info.session = [NSApp beginModalSessionForWindow:window];
+ // Make the dialog children of the current modal dialog
+ // responsive. And make the dialog children of
+ // the previous modal dialog unresponsive again:
+ qt_mac_setChildDialogsResponsive(info.widget, true);
+ int size = cocoaModalSessionStack.size();
+ if (size > 1){
+ if (QWidget *prevModal = cocoaModalSessionStack[size-2].widget)
+ qt_mac_setChildDialogsResponsive(prevModal, false);
+ }
+ }
+ return info.session;
+}
+
+void QEventDispatcherMacPrivate::rebuildModalSessionStack(bool pop)
+{
+ // Calling [NSApp stopModal], or [NSApp stop], will stop all modal dialogs
+ // in one go. So to to not confuse cocoa, we need to stop all our modal
+ // sessions as well. QMacEventDispatcher will make them modal again
+ // in the correct order as long as they are left on the cocoaModalSessionStack
+ // and a CocoaRequestModal is posted:
+ if (cocoaModalSessionStack.isEmpty())
+ return;
+
+ QMacCocoaAutoReleasePool pool;
+ [NSApp stopModal];
+ [NSApp postEvent:[NSEvent otherEventWithType:NSApplicationDefined location:NSZeroPoint
+ modifierFlags:0 timestamp:0. windowNumber:0 context:0
+ subtype:SHRT_MAX data1:0 data2:0] atStart:NO];
+
+ for (int i=0; i<cocoaModalSessionStack.size(); ++i){
+ QCocoaModalSessionInfo &info = cocoaModalSessionStack[i];
+ if (info.session) {
+ [NSApp endModalSession:info.session];
+ info.session = 0;
+ }
+ }
+
+ if (pop) {
+ QCocoaModalSessionInfo info = cocoaModalSessionStack.pop();
+ if (info.widget)
+ qt_mac_setChildDialogsResponsive(info.widget, false);
+ }
+
+ if (!cocoaModalSessionStack.isEmpty()) {
+ // Since we now have pending modal sessions again, make
+ // sure that we enter modal for the one on the top later:
+ qApp->postEvent(qApp, new QEvent(QEvent::CocoaRequestModal));
+ } else {
+ QCoreApplication::removePostedEvents(qApp, QEvent::CocoaRequestModal);
+ }
+}
+
+#endif
+
+QEventDispatcherMacPrivate::QEventDispatcherMacPrivate()
+ : interrupt(false)
+{
+}
+
+QEventDispatcherMac::QEventDispatcherMac(QObject *parent)
+ : QAbstractEventDispatcher(*new QEventDispatcherMacPrivate, parent)
+{
+ Q_D(QEventDispatcherMac);
+ CFRunLoopSourceContext context;
+ bzero(&context, sizeof(CFRunLoopSourceContext));
+ context.info = d;
+ context.equal = QEventDispatcherMacPrivate::postedEventSourceEqualCallback;
+ context.perform = QEventDispatcherMacPrivate::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,
+ QEventDispatcherMacPrivate::waitingObserverCallback,
+ &observerContext);
+ CFRunLoopAddObserver(mainRunLoop(), d->waitingObserver, kCFRunLoopCommonModes);
+}
+
+void QEventDispatcherMacPrivate::waitingObserverCallback(CFRunLoopObserverRef,
+ CFRunLoopActivity activity, void *info)
+{
+ if (activity == kCFRunLoopBeforeWaiting)
+ emit static_cast<QEventDispatcherMac*>(info)->aboutToBlock();
+ else
+ emit static_cast<QEventDispatcherMac*>(info)->awake();
+}
+
+Boolean QEventDispatcherMacPrivate::postedEventSourceEqualCallback(const void *info1, const void *info2)
+{
+ return info1 == info2;
+}
+
+void QEventDispatcherMacPrivate::postedEventsSourcePerformCallback(void *info)
+{
+ QEventDispatcherMacPrivate *d = static_cast<QEventDispatcherMacPrivate *>(info);
+ if (blockSendPostedEvents) {
+ CFRunLoopSourceSignal(d->postedEventsSource);
+ } else {
+ if (!d->threadData->canWait || (d->serialNumber != d->lastSerial)) {
+ d->lastSerial = d->serialNumber;
+ QApplicationPrivate::sendPostedEvents(0, 0, d->threadData);
+ }
+ }
+}
+
+#ifdef QT_MAC_USE_COCOA
+static void stopNSApp()
+{
+ QMacCocoaAutoReleasePool pool;
+ static const short NSAppShouldStopForQt = SHRT_MAX;
+ [NSApp stop:NSApp];
+ [NSApp postEvent:[NSEvent otherEventWithType:NSApplicationDefined location:NSZeroPoint
+ modifierFlags:0 timestamp:0. windowNumber:0 context:0
+ subtype:NSAppShouldStopForQt data1:0 data2:0] atStart:NO];
+}
+#endif
+
+void QEventDispatcherMac::interrupt()
+{
+ Q_D(QEventDispatcherMac);
+ d->interrupt = true;
+ wakeUp();
+#ifndef QT_MAC_USE_COCOA
+ CFRunLoopStop(mainRunLoop());
+#else
+ stopNSApp();
+#endif
+}
+
+QEventDispatcherMac::~QEventDispatcherMac()
+{
+ Q_D(QEventDispatcherMac);
+ //timer cleanup
+ MacTimerHash::iterator it = QEventDispatcherMacPrivate::macTimerHash.begin();
+ while (it != QEventDispatcherMacPrivate::macTimerHash.end()) {
+ MacTimerInfo *t = it.value();
+ if (t->runLoopTimer) {
+ CFRunLoopTimerInvalidate(t->runLoopTimer);
+ CFRelease(t->runLoopTimer);
+ }
+ delete t;
+ ++it;
+ }
+ QEventDispatcherMacPrivate::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);
+}
+
+
+QT_END_NAMESPACE