Ignore:
Timestamp:
Feb 11, 2010, 11:19:06 PM (15 years ago)
Author:
Dmitry A. Kuminov
Message:

trunk: Merged in qt 4.6.1 sources.

Location:
trunk
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • trunk

  • trunk/src/gui/kernel/qeventdispatcher_mac.mm

    r2 r561  
    22**
    33** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
    4 ** Contact: Qt Software Information (qt-info@nokia.com)
     4** All rights reserved.
     5** Contact: Nokia Corporation (qt-info@nokia.com)
    56**
    67** This file is part of the QtGui module of the Qt Toolkit.
     
    2122** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
    2223**
    23 ** In addition, as a special exception, Nokia gives you certain
    24 ** additional rights. These rights are described in the Nokia Qt LGPL
    25 ** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this
    26 ** package.
     24** In addition, as a special exception, Nokia gives you certain additional
     25** rights.  These rights are described in the Nokia Qt LGPL Exception
     26** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
    2727**
    2828** GNU General Public License Usage
     
    3434** met: http://www.gnu.org/copyleft/gpl.html.
    3535**
    36 ** If you are unsure which license is appropriate for your use, please
    37 ** contact the sales department at qt-sales@nokia.com.
     36** If you have questions regarding the use of this file, please contact
     37** Nokia at qt-info@nokia.com.
    3838** $QT_END_LICENSE$
    3939**
     
    137137        return; // Can't send another timer event if it's pending.
    138138
    139     tmr->pending = true;
    140     QTimerEvent e(tmr->id);
    141     qt_sendSpontaneousEvent(tmr->obj, &e);
    142 
    143     // Get the value again in case the timer gets unregistered during the sendEvent.
    144     tmr = macTimerHash.value(timerID);
    145     if (tmr != 0)
    146         tmr->pending = false;
     139
     140    if (blockSendPostedEvents) {
     141        QCoreApplication::postEvent(tmr->obj, new QTimerEvent(tmr->id));
     142    } else {
     143        tmr->pending = true;
     144        QTimerEvent e(tmr->id);
     145        qt_sendSpontaneousEvent(tmr->obj, &e);
     146        // Get the value again in case the timer gets unregistered during the sendEvent.
     147        tmr = macTimerHash.value(timerID);
     148        if (tmr != 0)
     149            tmr->pending = false;
     150    }
     151
    147152}
    148153
     
    265270    MacSocketInfo *socketInfo = eventDispatcher->macSockets.value(nativeSocket);
    266271    QEvent notifierEvent(QEvent::SockAct);
     272
     273    // There is a race condition that happen where we disable the notifier and
     274    // the kernel still has a notification to pass on. We then get this
     275    // notification after we've successfully disabled the CFSocket, but our Qt
     276    // notifier is now gone. The upshot is we have to check the notifier
     277    // everytime.
    267278    if (callbackType == kCFSocketReadCallBack) {
    268         Q_ASSERT(socketInfo->readNotifier);
    269         QApplication::sendEvent(socketInfo->readNotifier, &notifierEvent);
     279        if (socketInfo->readNotifier)
     280            QApplication::sendEvent(socketInfo->readNotifier, &notifierEvent);
    270281    } else if (callbackType == kCFSocketWriteCallBack) {
    271         // ### Bug in Apple socket notifiers seems to send write even
    272         // ### after the notifier has been disabled, need to investigate further.
    273282        if (socketInfo->writeNotifier)
    274283            QApplication::sendEvent(socketInfo->writeNotifier, &notifierEvent);
     
    494503#endif
    495504
     505static inline void qt_mac_waitForMoreEvents()
     506{
     507#ifndef QT_MAC_USE_COCOA
     508    while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0e20, true) == kCFRunLoopRunTimedOut) ;
     509#else
     510    // If no event exist in the cocoa event que, wait
     511    // (and free up cpu time) until at least one event occur.
     512    // This implementation is a bit on the edge, but seems to
     513    // work fine:
     514    NSEvent* event = [NSApp nextEventMatchingMask:NSAnyEventMask
     515        untilDate:[NSDate distantFuture]
     516        inMode:NSDefaultRunLoopMode
     517        dequeue:YES];
     518    if (event)
     519        [NSApp postEvent:event atStart:YES];
     520#endif
     521}
     522
     523#ifdef QT_MAC_USE_COCOA
     524static inline void qt_mac_waitForMoreModalSessionEvents()
     525{
     526    // If no event exist in the cocoa event que, wait
     527    // (and free up cpu time) until at least one event occur.
     528    // This implementation is a bit on the edge, but seems to
     529    // work fine:
     530    NSEvent* event = [NSApp nextEventMatchingMask:NSAnyEventMask
     531        untilDate:[NSDate distantFuture]
     532        inMode:NSModalPanelRunLoopMode
     533        dequeue:YES];
     534    if (event)
     535        [NSApp postEvent:event atStart:YES];
     536}
     537#endif
     538
    496539bool QEventDispatcherMac::processEvents(QEventLoop::ProcessEventsFlags flags)
    497540{
     
    503546    emit awake();
    504547
    505 #ifndef QT_MAC_NO_QUICKDRAW
    506     if(!qt_mac_safe_pdev) { //create an empty widget and this can be used for a port anytime
    507         QWidget *tlw = new QWidget;
    508         tlw->setAttribute(Qt::WA_DeleteOnClose);
    509         tlw->setObjectName(QLatin1String("empty_widget"));
    510         tlw->hide();
    511         qt_mac_safe_pdev = tlw;
    512     }
    513 #endif
    514 
    515548    bool retVal = false;
    516549    forever {
     
    522555        NSEvent* event = 0;
    523556
    524         if (flags & QEventLoop::DialogExec || flags & QEventLoop::EventLoopExec) {
    525             // The point of the CocoaRequestModal event is to make sure that a
    526             // non-execed app modal window recurses into it's own dialog exec
    527             // once cocoa is spinning the event loop for us (e.g on top of [NSApp run]).
    528             // We expect only one event to notify us about this, regardless of how many
    529             // widgets that are waiting to be modal. So we remove all other pending
    530             // events, if any. And since cocoa will now take over event processing for us,
    531             // we allow new app modal widgets to recurse on top of us, hence the release of
    532             // the block:
    533             QBoolBlocker block(d->blockCocoaRequestModal, false);
    534             QCoreApplication::removePostedEvents(qApp, QEvent::CocoaRequestModal);
    535 
    536             if (NSModalSession session = d->activeModalSession())
    537                 while ([NSApp runModalSession:session] == NSRunContinuesResponse) {
    538                     // runModalSession will not wait for events, so we do it
    539                     // ourselves (otherwise we would spend 100% CPU inside this loop):
    540                     event = [NSApp nextEventMatchingMask:NSAnyEventMask
    541                         untilDate:[NSDate distantFuture] inMode:NSModalPanelRunLoopMode dequeue:YES];
    542                     if (event)
    543                         [NSApp postEvent:event atStart:YES];
     557        // If Qt is used as a plugin, or just added into a native cocoa
     558        // application, we should not run or stop NSApplication;
     559        // This will be done from outside Qt.
     560        // And if processEvents is called manually (rather than from QEventLoop), we
     561        // cannot enter a tight loop and block the call, but instead return after one flush:
     562        bool canExec_3rdParty = d->nsAppRunCalledByQt || ![NSApp isRunning];
     563        bool canExec_Qt = flags & QEventLoop::DialogExec || flags & QEventLoop::EventLoopExec;
     564
     565        if (canExec_Qt && canExec_3rdParty) {
     566            // We can use exec-mode, meaning that we can stay in a tight loop until
     567            // interrupted. This is mostly an optimization, but it also allow us
     568            // to use [NSApp run], which is the recommended way of running applications
     569            // in cocoa. [NSApp run] should be called at least once for any cocoa app.
     570            if (NSModalSession session = d->currentModalSession()) {
     571                QBoolBlocker execGuard(d->currentExecIsNSAppRun, false);
     572                while (!d->interrupt && [NSApp runModalSession:session] == NSRunContinuesResponse)
     573                    qt_mac_waitForMoreModalSessionEvents();
     574                if (!d->interrupt && session == d->currentModalSessionCached) {
     575                    // INVARIANT: Someone called e.g. [NSApp stopModal:] from outside the event
     576                    // dispatcher (e.g to stop a native dialog). But that call wrongly stopped
     577                    // 'session' as well. As a result, we need to restart all internal sessions:
     578                    d->temporarilyStopAllModalSessions();
    544579                }
    545             else
     580            } else {
     581                d->nsAppRunCalledByQt = true;
     582                QBoolBlocker execGuard(d->currentExecIsNSAppRun, true);
    546583                [NSApp run];
    547 
    548             d->rebuildModalSessionStack(false);
     584            }
    549585            retVal = true;
    550586        } else do {
    551             // Since we now are going to spin the event loop just _one_ round
    552             // we need to block all incoming CocoaRequestModal events to ensure
    553             // that we don't recurse into a new exec-ing event loop while doing
    554             // so (and as such, 'hang' the thread inside the recursion):
    555             QBoolBlocker block(d->blockCocoaRequestModal, true);
     587            // INVARIANT: We cannot block the thread (and run in a tight loop).
     588            // Instead we will process all current pending events and return.
    556589            bool mustRelease = false;
    557590
    558591            if (!(flags & QEventLoop::ExcludeUserInputEvents) && !d->queuedUserInputEvents.isEmpty()) {
    559                 // process a pending user input event
     592                // Process a pending user input event
    560593                mustRelease = true;
    561594                event = static_cast<NSEvent *>(d->queuedUserInputEvents.takeFirst());
    562595            } else {
    563                 if (NSModalSession session = d->activeModalSession()) {
    564                     // There's s a modal widget showing, run it's session:
    565                     if (flags & QEventLoop::WaitForMoreEvents) {
    566                         // Wait for at least one event
    567                         // before spinning the session:
    568                         event = [NSApp nextEventMatchingMask:NSAnyEventMask
    569                             untilDate:[NSDate distantFuture] inMode:NSModalPanelRunLoopMode dequeue:YES];
    570                         if (event)
    571                             [NSApp postEvent:event atStart:YES];
     596                if (NSModalSession session = d->currentModalSession()) {
     597                    if (flags & QEventLoop::WaitForMoreEvents)
     598                        qt_mac_waitForMoreModalSessionEvents();
     599                    NSInteger status = [NSApp runModalSession:session];
     600                    if (status != NSRunContinuesResponse && session == d->currentModalSessionCached) {
     601                        // INVARIANT: Someone called e.g. [NSApp stopModal:] from outside the event
     602                        // dispatcher (e.g to stop a native dialog). But that call wrongly stopped
     603                        // 'session' as well. As a result, we need to restart all internal sessions:
     604                        d->temporarilyStopAllModalSessions();
    572605                    }
    573                     [NSApp runModalSession:session];
    574606                    retVal = true;
    575607                    break;
     
    641673        if (canWait) {
    642674            // INVARIANT: We haven't processed any events yet. And we're told
    643             // to stay inside this function until at least one event is processed
    644             // (WaitForMoreEvents). So we wait on the window server:
    645 #ifndef QT_MAC_USE_COCOA
    646             while(CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0e20, true) == kCFRunLoopRunTimedOut);
    647 #else
    648             QMacCocoaAutoReleasePool pool;
    649             NSEvent *manualEvent = [NSApp nextEventMatchingMask:NSAnyEventMask
    650                 untilDate:[NSDate distantFuture] inMode:NSDefaultRunLoopMode
    651                 dequeue:YES];
    652             if (manualEvent)
    653                 [NSApp sendEvent:manualEvent];
    654 #endif
     675            // to stay inside this function until at least one event is processed.
     676            qt_mac_waitForMoreEvents();
    655677            flags &= ~QEventLoop::WaitForMoreEvents;
    656678        } else {
    657             // Done with event processing for now. Leave the function:
     679            // Done with event processing for now.
     680            // Leave the function:
    658681            break;
    659682        }
    660683    }
    661684
    662     // Because pending deffered-delete events are only sendt after
    663     // returning from the loop level they were posted in, we schedule
    664     // an extra wakup to force the _current_ run loop to process them (in
    665     // case the application stands idle waiting for the delete event):
    666     wakeUp();
    667 
    668     if (d->interrupt){
    669         // We restart NSApplication by first stopping it, and then call 'run'
    670         // again (NSApplication is actually already stopped, hence the need
    671         // for a restart, but calling stop again will also make the call
    672         // return from the current recursion). When the call returns to
    673         // QEventLoop (mind, not from this recursion, but from the one we're
    674         // about to stop), it will just call QEventDispatcherMac::processEvents()
    675         // again.
     685#ifdef QT_MAC_USE_COCOA
     686    // In case we _now_ process events using [NSApp run], we need to stop it to
     687    // ensure that:
     688    //    1. the QEventLoop that called us is still executing, or
     689    //    2. we have a modal session that needs to be spun instead.
     690    // In case this is a plain call to processEvents (perhaps from a loop)
     691    // from the application (rather than from a QEventLoop), we delay the
     692    // interrupting until we/ actually enter a lower loop level (hence the
     693    // deffered delete of the object below):
     694    QtMacInterruptDispatcherHelp::interruptLater();
     695#endif
     696
     697    if (d->interrupt) {
     698        // We should continue to leave all recursion to processEvents until
     699        // processEvents is called again (e.g. from a QEventLoop that
     700        // was not yet told to quit:
    676701        interrupt();
    677702    }
     703
    678704    return retVal;
    679705}
     
    707733#ifdef QT_MAC_USE_COCOA
    708734QStack<QCocoaModalSessionInfo> QEventDispatcherMacPrivate::cocoaModalSessionStack;
    709 bool QEventDispatcherMacPrivate::blockCocoaRequestModal = false;
    710 
    711 static void qt_mac_setChildDialogsResponsive(QWidget *widget, bool responsive)
    712 {
     735bool QEventDispatcherMacPrivate::currentExecIsNSAppRun = false;
     736bool QEventDispatcherMacPrivate::nsAppRunCalledByQt = false;
     737NSModalSession QEventDispatcherMacPrivate::currentModalSessionCached = 0;
     738
     739int QEventDispatcherMacPrivate::activeModalSessionCount()
     740{
     741    // Returns the number of modal sessions created
     742    // (and not just pushed onto the stack, pending to be created)
     743    int count = 0;
     744    for (int i=cocoaModalSessionStack.size()-1; i>=0; --i) {
     745        QCocoaModalSessionInfo &info = cocoaModalSessionStack[i];
     746        if (info.session)
     747            ++count;
     748    }
     749    return count;
     750}
     751
     752void QEventDispatcherMacPrivate::temporarilyStopAllModalSessions()
     753{
     754    // Stop all created modal session, and as such, make then
     755    // pending again. The next call to currentModalSession will
     756    // recreate the session on top again:
     757    int stackSize = cocoaModalSessionStack.size();
     758    for (int i=stackSize-1; i>=0; --i) {
     759        QCocoaModalSessionInfo &info = cocoaModalSessionStack[i];
     760        if (info.session) {
     761            [NSApp endModalSession:info.session];
     762            info.session = 0;
     763        }
     764    }
     765    currentModalSessionCached = 0;
     766}
     767
     768NSModalSession QEventDispatcherMacPrivate::currentModalSession()
     769{
     770    // If we have one or more modal windows, this function will create
     771    // a session for each of those, and return the one for the top.
     772    if (currentModalSessionCached)
     773        return currentModalSessionCached;
     774
     775    if (cocoaModalSessionStack.isEmpty())
     776        return 0;
     777
     778    // Since this code will end up calling our Qt event handler
     779    // (also from beginModalSessionForWindow), we need to block
     780    // that to avoid side effects of events beeing delivered:
     781    QBoolBlocker block(blockSendPostedEvents, true);
     782
     783    if (![NSApp isRunning]) {
     784        // Sadly, we need to introduce this little event flush
     785        // to stop dialogs from blinking/poping in front if a
     786        // modal session restart was needed:
     787        while (NSEvent *event = [NSApp nextEventMatchingMask:0
     788                untilDate:nil
     789                inMode:NSDefaultRunLoopMode
     790                dequeue: YES]) {
     791            qt_mac_send_event(0, event, 0);
     792        }
     793    }
     794
     795    int sessionCount = cocoaModalSessionStack.size();
     796    for (int i=0; i<sessionCount; ++i) {
     797        QCocoaModalSessionInfo &info = cocoaModalSessionStack[i];
     798        if (!info.widget)
     799            continue;
     800        if (info.widget->testAttribute(Qt::WA_DontShowOnScreen))
     801            continue;
     802        if (!info.session) {
     803            QMacCocoaAutoReleasePool pool;
     804            NSWindow *window = qt_mac_window_for(info.widget);
     805            if (!window)
     806                continue;
     807            info.session = [NSApp beginModalSessionForWindow:window];
     808        }
     809        currentModalSessionCached = info.session;
     810    }
     811
     812    return currentModalSessionCached;
     813}
     814
     815static void setChildrenWorksWhenModal(QWidget *widget, bool worksWhenModal)
     816{
     817    // For NSPanels (but not NSWindows, sadly), we can set the flag
     818    // worksWhenModal, so that they are active even when they are not modal.
    713819    QList<QDialog *> dialogs = widget->findChildren<QDialog *>();
    714820    for (int i=0; i<dialogs.size(); ++i){
    715821        NSWindow *window = qt_mac_window_for(dialogs[i]);
    716822        if (window && [window isKindOfClass:[NSPanel class]]) {
    717             [static_cast<NSPanel *>(window) setWorksWhenModal:responsive];
    718             if (responsive && dialogs[i]->isVisible()){
     823            [static_cast<NSPanel *>(window) setWorksWhenModal:worksWhenModal];
     824            if (worksWhenModal && dialogs[i]->isVisible()){
    719825                [window orderFront:window];
    720826            }
     
    723829}
    724830
    725 NSModalSession QEventDispatcherMacPrivate::activeModalSession()
    726 {
    727     // Create (if needed) and return the modal session
    728     // for the  top-most modal dialog, if any:
    729     if (cocoaModalSessionStack.isEmpty())
    730         return 0;
    731     QCocoaModalSessionInfo &info = cocoaModalSessionStack.last();
    732     if (!info.widget)
    733         return 0;
    734     if (info.widget->testAttribute(Qt::WA_DontShowOnScreen)){
    735         // INVARIANT: We have a modal widget, but it's not visible on screen.
    736         // This will e.g. be true for native dialogs. Make the dialog children
    737         // of the previous modal dialog unresponsive, so that the current dialog
    738         // (native or not) is the only reponsive dialog on screen:
    739         int size = cocoaModalSessionStack.size();
     831void QEventDispatcherMacPrivate::updateChildrenWorksWhenModal()
     832{
     833    // Make the dialog children of the widget
     834    // active. And make the dialog children of
     835    // the previous modal dialog unactive again:
     836    int size = cocoaModalSessionStack.size();
     837    if (size > 0){
     838        if (QWidget *prevModal = cocoaModalSessionStack[size-1].widget)
     839            setChildrenWorksWhenModal(prevModal, true);
    740840        if (size > 1){
    741841            if (QWidget *prevModal = cocoaModalSessionStack[size-2].widget)
    742                 qt_mac_setChildDialogsResponsive(prevModal, false);
    743         }
    744         return 0;
    745     }
    746 
    747     if (!info.session) {
    748         QMacCocoaAutoReleasePool pool;
    749         NSWindow *window = qt_mac_window_for(info.widget);
    750         if (!window)
    751             return 0;
    752         // 'beginModalSessionForWindow' will give the event loop a spin, and as
    753         // such, deliver Qt events. This might lead to inconsistent behaviour
    754         // (especially if CocoaRequestModal is delivered), so we need to block:
    755         QBoolBlocker block(blockSendPostedEvents, true);
    756         info.session = [NSApp beginModalSessionForWindow:window];
    757         // Make the dialog children of the current modal dialog
    758         // responsive. And make the dialog children of
    759         // the previous modal dialog unresponsive again:
    760         qt_mac_setChildDialogsResponsive(info.widget, true);
    761         int size = cocoaModalSessionStack.size();
    762         if (size > 1){
    763             if (QWidget *prevModal = cocoaModalSessionStack[size-2].widget)
    764                 qt_mac_setChildDialogsResponsive(prevModal, false);
    765         }
    766     }
    767     return info.session;
    768 }
    769 
    770 void QEventDispatcherMacPrivate::rebuildModalSessionStack(bool pop)
    771 {
    772     // Calling [NSApp stopModal], or [NSApp stop], will stop all modal dialogs
    773     // in one go. So to to not confuse cocoa, we need to stop all our modal
    774     // sessions as well. QMacEventDispatcher will make them modal again
    775     // in the correct order as long as they are left on the cocoaModalSessionStack
    776     // and a CocoaRequestModal is posted:
    777     if (cocoaModalSessionStack.isEmpty())
    778         return;
    779 
    780     QMacCocoaAutoReleasePool pool;
    781     [NSApp stopModal];
    782     [NSApp postEvent:[NSEvent otherEventWithType:NSApplicationDefined location:NSZeroPoint
    783         modifierFlags:0 timestamp:0. windowNumber:0 context:0
    784         subtype:SHRT_MAX data1:0 data2:0] atStart:NO];
    785 
    786     for (int i=0; i<cocoaModalSessionStack.size(); ++i){
     842                setChildrenWorksWhenModal(prevModal, false);
     843        }
     844    }
     845}
     846
     847void QEventDispatcherMacPrivate::beginModalSession(QWidget *widget)
     848{
     849    // Add a new, empty (null), NSModalSession to the stack.
     850    // It will become active the next time QEventDispatcher::processEvents is called.
     851    // A QCocoaModalSessionInfo is considered pending to become active if the widget pointer
     852    // is non-zero, and the session pointer is zero (it will become active upon a call to
     853    // currentModalSession). A QCocoaModalSessionInfo is considered pending to be stopped if
     854    // the widget pointer is zero, and the session pointer is non-zero (it will be fully
     855    // stopped in endModalSession().
     856    QCocoaModalSessionInfo info = {widget, 0};
     857    cocoaModalSessionStack.push(info);
     858    updateChildrenWorksWhenModal();
     859    currentModalSessionCached = 0;
     860}
     861
     862void QEventDispatcherMacPrivate::endModalSession(QWidget *widget)
     863{
     864    // Mark all sessions attached to widget as pending to be stopped. We do this
     865    // by setting the widget pointer to zero, but leave the session pointer.
     866    // We don't tell cocoa to stop any sessions just yet, because cocoa only understands
     867    // when we stop the _current_ modal session (which is the session on top of
     868    // the stack, and might not belong to 'widget').
     869    int stackSize = cocoaModalSessionStack.size();
     870    for (int i=stackSize-1; i>=0; --i) {
    787871        QCocoaModalSessionInfo &info = cocoaModalSessionStack[i];
     872        if (info.widget == widget)
     873            info.widget = 0;
     874    }
     875
     876    // Now we stop, and remove, all sessions marked as pending
     877    // to be stopped on _top_ of the stack, if any:
     878    bool needToInterruptEventDispatcher = false;
     879    bool needToUpdateChildrenWorksWhenModal = false;
     880
     881    for (int i=stackSize-1; i>=0; --i) {
     882        QCocoaModalSessionInfo &info = cocoaModalSessionStack[i];
     883        if (info.widget)
     884            break;
     885        cocoaModalSessionStack.remove(i);
     886        needToUpdateChildrenWorksWhenModal = true;
     887        currentModalSessionCached = 0;
    788888        if (info.session) {
    789889            [NSApp endModalSession:info.session];
    790             info.session = 0;
    791         }
    792     }
    793 
    794     if (pop) {
    795         QCocoaModalSessionInfo info = cocoaModalSessionStack.pop();
    796         if (info.widget)
    797             qt_mac_setChildDialogsResponsive(info.widget, false);
    798     }
    799 
    800     if (!cocoaModalSessionStack.isEmpty()) {
    801         // Since we now have pending modal sessions again, make
    802         // sure that we enter modal for the one on the top later:
    803         qApp->postEvent(qApp, new QEvent(QEvent::CocoaRequestModal));
    804     } else {
    805         QCoreApplication::removePostedEvents(qApp, QEvent::CocoaRequestModal);
    806     }
     890            needToInterruptEventDispatcher = true;
     891        }
     892    }
     893
     894    if (needToUpdateChildrenWorksWhenModal)
     895        updateChildrenWorksWhenModal();
     896    if (needToInterruptEventDispatcher)
     897        QEventDispatcherMac::instance()->interrupt();
    807898}
    808899
     
    836927                                                 &observerContext);
    837928    CFRunLoopAddObserver(mainRunLoop(), d->waitingObserver, kCFRunLoopCommonModes);
     929
     930    /* The first cycle in the loop adds the source and the events of the source
     931       are not processed.
     932       We use an observer to process the posted events for the first
     933       execution of the loop. */
     934    CFRunLoopObserverContext firstTimeObserverContext;
     935    bzero(&firstTimeObserverContext, sizeof(CFRunLoopObserverContext));
     936    firstTimeObserverContext.info = d;
     937    d->firstTimeObserver = CFRunLoopObserverCreate(kCFAllocatorDefault,
     938                                                   kCFRunLoopEntry,
     939                                                   /* repeats = */ false,
     940                                                   0,
     941                                                   QEventDispatcherMacPrivate::firstLoopEntry,
     942                                                   &firstTimeObserverContext);
     943    CFRunLoopAddObserver(mainRunLoop(), d->firstTimeObserver, kCFRunLoopCommonModes);
    838944}
    839945
     
    852958}
    853959
    854 void QEventDispatcherMacPrivate::postedEventsSourcePerformCallback(void *info)
    855 {
    856     QEventDispatcherMacPrivate *d = static_cast<QEventDispatcherMacPrivate *>(info);
    857     if (blockSendPostedEvents) {
     960inline static void processPostedEvents(QEventDispatcherMacPrivate *const d, const bool blockSendPostedEvents)
     961{
     962    if (blockSendPostedEvents || d->interrupt) {
    858963        CFRunLoopSourceSignal(d->postedEventsSource);
    859964    } else {
     
    865970}
    866971
    867 #ifdef QT_MAC_USE_COCOA
    868 static void stopNSApp()
    869 {
    870     QMacCocoaAutoReleasePool pool;
    871     static const short NSAppShouldStopForQt = SHRT_MAX;
    872     [NSApp stop:NSApp];
    873     [NSApp postEvent:[NSEvent otherEventWithType:NSApplicationDefined location:NSZeroPoint
    874                                    modifierFlags:0 timestamp:0. windowNumber:0 context:0
    875                                          subtype:NSAppShouldStopForQt data1:0 data2:0] atStart:NO];
    876 }
    877 #endif
     972void QEventDispatcherMacPrivate::firstLoopEntry(CFRunLoopObserverRef ref,
     973                                                CFRunLoopActivity activity,
     974                                                void *info)
     975{
     976    Q_UNUSED(ref);
     977    Q_UNUSED(activity);
     978    processPostedEvents(static_cast<QEventDispatcherMacPrivate *>(info), blockSendPostedEvents);
     979}
     980
     981void QEventDispatcherMacPrivate::postedEventsSourcePerformCallback(void *info)
     982{
     983    processPostedEvents(static_cast<QEventDispatcherMacPrivate *>(info), blockSendPostedEvents);
     984}
    878985
    879986void QEventDispatcherMac::interrupt()
     
    882989    d->interrupt = true;
    883990    wakeUp();
     991
    884992#ifndef QT_MAC_USE_COCOA
    885993    CFRunLoopStop(mainRunLoop());
    886994#else
    887     stopNSApp();
     995    QMacCocoaAutoReleasePool pool;
     996    // In case we wait for more events inside
     997    // processEvents (or NSApp run), post a dummy to wake it up:
     998    static const short NSAppShouldStopForQt = SHRT_MAX;
     999    [NSApp postEvent:[NSEvent otherEventWithType:NSApplicationDefined location:NSZeroPoint
     1000        modifierFlags:0 timestamp:0. windowNumber:0 context:0
     1001        subtype:NSAppShouldStopForQt data1:0 data2:0] atStart:NO];
     1002
     1003    if (d->activeModalSessionCount() == 0) {
     1004        // We should only stop NSApp if we actually started it (and
     1005        // not some 3rd party application, e.g. if we are a plugin).
     1006        if (d->nsAppRunCalledByQt)
     1007            [NSApp stop:NSApp];
     1008    }
    8881009#endif
    8891010}
     
    9211042    CFRunLoopObserverInvalidate(d->waitingObserver);
    9221043    CFRelease(d->waitingObserver);
    923 }
    924 
     1044
     1045    CFRunLoopObserverInvalidate(d->firstTimeObserver);
     1046    CFRelease(d->firstTimeObserver);
     1047}
     1048
     1049/////////////////////////////////////////////////////////////////////////////
     1050
     1051#ifdef QT_MAC_USE_COCOA
     1052
     1053QtMacInterruptDispatcherHelp* QtMacInterruptDispatcherHelp::instance = 0;
     1054
     1055QtMacInterruptDispatcherHelp::QtMacInterruptDispatcherHelp() : cancelled(false)
     1056{
     1057    // This is the whole point of encapsulation this code
     1058    // inside a class; we can make the code (inside destructor)
     1059    // execute on lower loop level:
     1060    deleteLater();
     1061}
     1062
     1063QtMacInterruptDispatcherHelp::~QtMacInterruptDispatcherHelp()
     1064{
     1065    if (cancelled)
     1066        return;
     1067
     1068    instance = 0;
     1069
     1070    if (QEventDispatcherMacPrivate::currentExecIsNSAppRun) {
     1071        int activeCount = QEventDispatcherMacPrivate::activeModalSessionCount();
     1072        if (activeCount > 0) {
     1073            // The problem we now have hit: [NSApp stop] will not stop NSApp
     1074            // if a session is active; it will stop the session instead.
     1075            // So to stop NSApp, we need to temporarily stop all the
     1076            // sessions, then stop NSApp, then restart the session on top again.
     1077            // We need to do this to ensure that we're not stuck inside
     1078            // [NSApp run] when we really should be running a modal session:
     1079            QEventDispatcherMacPrivate::temporarilyStopAllModalSessions();
     1080        }
     1081    }
     1082    // Always interrupt once more in case the modal session stack changed
     1083    // while processEvents was called manually from within the application:
     1084    QEventDispatcherMac::instance()->interrupt();
     1085}
     1086
     1087void QtMacInterruptDispatcherHelp::interruptLater() {
     1088    if (instance) {
     1089        instance->cancelled = true;
     1090        delete instance;
     1091    }
     1092    instance = new QtMacInterruptDispatcherHelp;
     1093}
     1094
     1095#endif
    9251096
    9261097QT_END_NAMESPACE
Note: See TracChangeset for help on using the changeset viewer.