diff options
Diffstat (limited to 'src/gui/platforms/mac/qeventdispatcher_mac.mm')
-rw-r--r-- | src/gui/platforms/mac/qeventdispatcher_mac.mm | 1200 |
1 files changed, 0 insertions, 1200 deletions
diff --git a/src/gui/platforms/mac/qeventdispatcher_mac.mm b/src/gui/platforms/mac/qeventdispatcher_mac.mm deleted file mode 100644 index 677a7368b4..0000000000 --- a/src/gui/platforms/mac/qeventdispatcher_mac.mm +++ /dev/null @@ -1,1200 +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 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 Technology Preview License Agreement accompanying -** this package. -** -** 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.1, included in the file LGPL_EXCEPTION.txt in this package. -** -** If you have questions regarding the use of this file, please contact -** Nokia at qt-info@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" -#endif - -QT_BEGIN_NAMESPACE - -QT_USE_NAMESPACE - -/***************************************************************************** - 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. - - - if (blockSendPostedEvents) { - QCoreApplication::postEvent(tmr->obj, new QTimerEvent(tmr->id)); - } else { - 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); - - // 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) - QApplication::sendEvent(socketInfo->readNotifier, ¬ifierEvent); - } else if (callbackType == kCFSocketWriteCallBack) { - if (socketInfo->writeNotifier) - QApplication::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 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: -#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; -} -#endif - -static inline void qt_mac_waitForMoreEvents() -{ -#ifndef QT_MAC_USE_COCOA - while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0e20, true) == kCFRunLoopRunTimedOut) ; -#else - // 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]; -#endif -} - -#ifdef QT_MAC_USE_COCOA -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]; -} -#endif - -bool QEventDispatcherMac::processEvents(QEventLoop::ProcessEventsFlags flags) -{ - Q_D(QEventDispatcherMac); - d->interrupt = false; - -#ifdef QT_MAC_USE_COCOA - bool interruptLater = false; - QtMacInterruptDispatcherHelp::cancelInterruptLater(); -#endif - - // 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; - -#ifdef QT_MAC_USE_COCOA - 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)) { - qt_mac_send_event(flags, event, 0); - 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) && qt_mac_send_event(flags, event, 0)) - 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) && qt_mac_send_event(flags, event, 0)) - 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; - } -#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. - 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(); - -#ifdef QT_MAC_USE_COCOA - if (interruptLater) - QtMacInterruptDispatcherHelp::interruptLater(); -#endif - - 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; -bool QEventDispatcherMacPrivate::interrupt = false; - -#ifdef QT_MAC_USE_COCOA -QStack<QCocoaModalSessionInfo> QEventDispatcherMacPrivate::cocoaModalSessionStack; -bool QEventDispatcherMacPrivate::currentExecIsNSAppRun = false; -bool QEventDispatcherMacPrivate::nsAppRunCalledByQt = false; -bool QEventDispatcherMacPrivate::cleanupModalSessionsNeeded = false; -NSModalSession QEventDispatcherMacPrivate::currentModalSessionCached = 0; - -void QEventDispatcherMacPrivate::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 - // QApplication::exec is called, or the application spins the events loop - // manually rather than calling QApplication: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 QApplication::exec. - if (nsAppRunCalledByQt || [NSApp isRunning]) - return; - nsAppRunCalledByQt = true; - QBoolBlocker block1(interrupt, true); - QBoolBlocker block2(currentExecIsNSAppRun, true); - [NSApp run]; -} - -void QEventDispatcherMacPrivate::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 QEventDispatcherMacPrivate::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.widget) - continue; - if (info.widget->testAttribute(Qt::WA_DontShowOnScreen)) - continue; - if (!info.session) { - QMacCocoaAutoReleasePool pool; - NSWindow *window = qt_mac_window_for(info.widget); - 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(QWidget *widget, 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. - 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:worksWhenModal]; - if (worksWhenModal && [window isVisible]){ - [window orderFront:window]; - } - } - } -} - -void QEventDispatcherMacPrivate::updateChildrenWorksWhenModal() -{ - // Make the dialog children of the widget - // active. And make the dialog children of - // the previous modal dialog unactive again: - QMacCocoaAutoReleasePool pool; - int size = cocoaModalSessionStack.size(); - if (size > 0){ - if (QWidget *prevModal = cocoaModalSessionStack[size-1].widget) - setChildrenWorksWhenModal(prevModal, true); - if (size > 1){ - if (QWidget *prevModal = cocoaModalSessionStack[size-2].widget) - setChildrenWorksWhenModal(prevModal, false); - } - } -} - -void QEventDispatcherMacPrivate::cleanupModalSessions() -{ - // Go through the list of modal sessions, and end those - // that no longer has a widget assosiated; no widget 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.widget) { - // This session has a widget, 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 QEventDispatcherMacPrivate::beginModalSession(QWidget *widget) -{ - // 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 widget 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 widget pointer is zero, and the session pointer is non-zero (it will be fully - // stopped in cleanupModalSessions()). - QCocoaModalSessionInfo info = {widget, 0, 0}; - cocoaModalSessionStack.push(info); - updateChildrenWorksWhenModal(); - currentModalSessionCached = 0; -} - -void QEventDispatcherMacPrivate::endModalSession(QWidget *widget) -{ - // Mark all sessions attached to widget as pending to be stopped. We do this - // by setting the widget 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 'widget'). - int stackSize = cocoaModalSessionStack.size(); - for (int i=stackSize-1; i>=0; --i) { - QCocoaModalSessionInfo &info = cocoaModalSessionStack[i]; - if (info.widget == widget) { - info.widget = 0; - if (i == stackSize-1) { - // The top sessions ended. Interrupt the event dispatcher - // to start spinning the correct session immidiatly: - currentModalSessionCached = 0; - cleanupModalSessionsNeeded = true; - QEventDispatcherMac::instance()->interrupt(); - } - } - } -} - -#endif - -QEventDispatcherMacPrivate::QEventDispatcherMacPrivate() -{ -} - -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); - - /* 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, - QEventDispatcherMacPrivate::firstLoopEntry, - &firstTimeObserverContext); - CFRunLoopAddObserver(mainRunLoop(), d->firstTimeObserver, 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; -} - -inline static void processPostedEvents(QEventDispatcherMacPrivate *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; - } - -#ifdef QT_MAC_USE_COCOA - if (d->cleanupModalSessionsNeeded) - d->cleanupModalSessions(); -#endif - - if (d->interrupt) { -#ifdef QT_MAC_USE_COCOA - 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(); - } -#endif - return; - } - - if (!d->threadData->canWait || (d->serialNumber != d->lastSerial)) { - d->lastSerial = d->serialNumber; - QApplicationPrivate::sendPostedEvents(0, 0, d->threadData); - } -} - -void QEventDispatcherMacPrivate::firstLoopEntry(CFRunLoopObserverRef ref, - CFRunLoopActivity activity, - void *info) -{ - Q_UNUSED(ref); - Q_UNUSED(activity); -#ifdef QT_MAC_USE_COCOA - QApplicationPrivate::qt_initAfterNSAppStarted(); -#endif - processPostedEvents(static_cast<QEventDispatcherMacPrivate *>(info), blockSendPostedEvents); -} - -void QEventDispatcherMacPrivate::postedEventsSourcePerformCallback(void *info) -{ - processPostedEvents(static_cast<QEventDispatcherMacPrivate *>(info), blockSendPostedEvents); -} - -#ifdef QT_MAC_USE_COCOA -void QEventDispatcherMacPrivate::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]; -} -#endif - -void QEventDispatcherMac::interrupt() -{ - Q_D(QEventDispatcherMac); - d->interrupt = true; - wakeUp(); - -#ifndef QT_MAC_USE_COCOA - CFRunLoopStop(mainRunLoop()); -#else - // 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(); -#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); - - CFRunLoopObserverInvalidate(d->firstTimeObserver); - CFRelease(d->firstTimeObserver); -} - -#ifdef QT_MAC_USE_COCOA - -QtMacInterruptDispatcherHelp* QtMacInterruptDispatcherHelp::instance = 0; - -QtMacInterruptDispatcherHelp::QtMacInterruptDispatcherHelp() : 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(); -} - -QtMacInterruptDispatcherHelp::~QtMacInterruptDispatcherHelp() -{ - if (cancelled) - return; - instance = 0; - QEventDispatcherMac::instance()->interrupt(); -} - -void QtMacInterruptDispatcherHelp::cancelInterruptLater() -{ - if (!instance) - return; - instance->cancelled = true; - delete instance; - instance = 0; -} - -void QtMacInterruptDispatcherHelp::interruptLater() -{ - cancelInterruptLater(); - instance = new QtMacInterruptDispatcherHelp; -} - -#endif - -QT_END_NAMESPACE - |