source: trunk/src/gui/kernel/qeventdispatcher_mac.mm@ 187

Last change on this file since 187 was 2, checked in by Dmitry A. Kuminov, 16 years ago

Initially imported qt-all-opensource-src-4.5.1 from Trolltech.

File size: 35.5 KB
Line 
1/****************************************************************************
2**
3** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
4** Contact: Qt Software Information ([email protected])
5**
6** This file is part of the QtGui module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial Usage
10** Licensees holding valid Qt Commercial licenses may use this file in
11** accordance with the Qt Commercial License Agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and Nokia.
14**
15** GNU Lesser General Public License Usage
16** Alternatively, this file may be used under the terms of the GNU Lesser
17** General Public License version 2.1 as published by the Free Software
18** Foundation and appearing in the file LICENSE.LGPL included in the
19** packaging of this file. Please review the following information to
20** ensure the GNU Lesser General Public License version 2.1 requirements
21** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
22**
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.
27**
28** GNU General Public License Usage
29** Alternatively, this file may be used under the terms of the GNU
30** General Public License version 3.0 as published by the Free Software
31** Foundation and appearing in the file LICENSE.GPL included in the
32** packaging of this file. Please review the following information to
33** ensure the GNU General Public License version 3.0 requirements will be
34** met: http://www.gnu.org/copyleft/gpl.html.
35**
36** If you are unsure which license is appropriate for your use, please
37** contact the sales department at [email protected].
38** $QT_END_LICENSE$
39**
40****************************************************************************/
41
42/****************************************************************************
43**
44** Copyright (c) 2007-2008, Apple, Inc.
45**
46** All rights reserved.
47**
48** Redistribution and use in source and binary forms, with or without
49** modification, are permitted provided that the following conditions are met:
50**
51** * Redistributions of source code must retain the above copyright notice,
52** this list of conditions and the following disclaimer.
53**
54** * Redistributions in binary form must reproduce the above copyright notice,
55** this list of conditions and the following disclaimer in the documentation
56** and/or other materials provided with the distribution.
57**
58** * Neither the name of Apple, Inc. nor the names of its contributors
59** may be used to endorse or promote products derived from this software
60** without specific prior written permission.
61**
62** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
63** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
64** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
65** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
66** CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
67** EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
68** PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
69** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
70** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
71** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
72** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
73**
74****************************************************************************/
75
76#include "qplatformdefs.h"
77#include "private/qt_mac_p.h"
78#include "qeventdispatcher_mac_p.h"
79#include "qapplication.h"
80#include "qevent.h"
81#include "qdialog.h"
82#include "qhash.h"
83#include "qsocketnotifier.h"
84#include "private/qwidget_p.h"
85#include "private/qthread_p.h"
86#include "private/qapplication_p.h"
87
88#include <private/qcocoaapplication_mac_p.h>
89#include "private/qt_cocoa_helpers_mac_p.h"
90
91#ifndef QT_NO_THREAD
92# include "qmutex.h"
93
94QT_BEGIN_NAMESPACE
95
96QT_USE_NAMESPACE
97#endif
98
99/*****************************************************************************
100 Externals
101 *****************************************************************************/
102extern void qt_event_request_timer(MacTimerInfo *); //qapplication_mac.cpp
103extern MacTimerInfo *qt_event_get_timer(EventRef); //qapplication_mac.cpp
104extern void qt_event_request_select(QEventDispatcherMac *); //qapplication_mac.cpp
105extern void qt_event_request_updates(); //qapplication_mac.cpp
106extern OSWindowRef qt_mac_window_for(const QWidget *); //qwidget_mac.cpp
107extern bool qt_is_gui_used; //qapplication.cpp
108extern bool qt_sendSpontaneousEvent(QObject*, QEvent*); // qapplication.cpp
109extern bool qt_mac_is_macsheet(const QWidget *); //qwidget_mac.cpp
110
111static inline CFRunLoopRef mainRunLoop()
112{
113#ifndef QT_MAC_USE_COCOA
114 return reinterpret_cast<CFRunLoopRef>(const_cast<void *>(GetCFRunLoopFromEventLoop(GetMainEventLoop())));
115#else
116 return CFRunLoopGetMain();
117#endif
118}
119
120/*****************************************************************************
121 Timers stuff
122 *****************************************************************************/
123
124/* timer call back */
125void QEventDispatcherMacPrivate::activateTimer(CFRunLoopTimerRef, void *info)
126{
127 int timerID =
128#ifdef Q_OS_MAC64
129 qint64(info);
130#else
131 int(info);
132#endif
133
134 MacTimerInfo *tmr;
135 tmr = macTimerHash.value(timerID);
136 if (tmr == 0 || tmr->pending == true)
137 return; // Can't send another timer event if it's pending.
138
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;
147}
148
149void QEventDispatcherMac::registerTimer(int timerId, int interval, QObject *obj)
150{
151#ifndef QT_NO_DEBUG
152 if (timerId < 1 || interval < 0 || !obj) {
153 qWarning("QEventDispatcherMac::registerTimer: invalid arguments");
154 return;
155 } else if (obj->thread() != thread() || thread() != QThread::currentThread()) {
156 qWarning("QObject::startTimer: timers cannot be started from another thread");
157 return;
158 }
159#endif
160
161 MacTimerInfo *t = new MacTimerInfo();
162 t->id = timerId;
163 t->interval = interval;
164 t->obj = obj;
165 t->runLoopTimer = 0;
166 t->pending = false;
167
168 CFAbsoluteTime fireDate = CFAbsoluteTimeGetCurrent();
169 CFTimeInterval cfinterval = qMax(CFTimeInterval(interval) / 1000, 0.0000001);
170 fireDate += cfinterval;
171 QEventDispatcherMacPrivate::macTimerHash.insert(timerId, t);
172 CFRunLoopTimerContext info = { 0, (void *)timerId, 0, 0, 0 };
173 t->runLoopTimer = CFRunLoopTimerCreate(0, fireDate, cfinterval, 0, 0,
174 QEventDispatcherMacPrivate::activateTimer, &info);
175 if (t->runLoopTimer == 0) {
176 qFatal("QEventDispatcherMac::registerTimer: Cannot create timer");
177 }
178 CFRunLoopAddTimer(mainRunLoop(), t->runLoopTimer, kCFRunLoopCommonModes);
179}
180
181bool QEventDispatcherMac::unregisterTimer(int identifier)
182{
183#ifndef QT_NO_DEBUG
184 if (identifier < 1) {
185 qWarning("QEventDispatcherMac::unregisterTimer: invalid argument");
186 return false;
187 } else if (thread() != QThread::currentThread()) {
188 qWarning("QObject::killTimer: timers cannot be stopped from another thread");
189 return false;
190 }
191#endif
192 if (identifier <= 0)
193 return false; // not init'd or invalid timer
194
195 MacTimerInfo *timerInfo = QEventDispatcherMacPrivate::macTimerHash.take(identifier);
196 if (timerInfo == 0)
197 return false;
198
199 if (!QObjectPrivate::get(timerInfo->obj)->inThreadChangeEvent)
200 QAbstractEventDispatcherPrivate::releaseTimerId(identifier);
201 CFRunLoopTimerInvalidate(timerInfo->runLoopTimer);
202 CFRelease(timerInfo->runLoopTimer);
203 delete timerInfo;
204
205 return true;
206}
207
208bool QEventDispatcherMac::unregisterTimers(QObject *obj)
209{
210#ifndef QT_NO_DEBUG
211 if (!obj) {
212 qWarning("QEventDispatcherMac::unregisterTimers: invalid argument");
213 return false;
214 } else if (obj->thread() != thread() || thread() != QThread::currentThread()) {
215 qWarning("QObject::killTimers: timers cannot be stopped from another thread");
216 return false;
217 }
218#endif
219
220 MacTimerHash::iterator it = QEventDispatcherMacPrivate::macTimerHash.begin();
221 while (it != QEventDispatcherMacPrivate::macTimerHash.end()) {
222 MacTimerInfo *timerInfo = it.value();
223 if (timerInfo->obj != obj) {
224 ++it;
225 } else {
226 if (!QObjectPrivate::get(timerInfo->obj)->inThreadChangeEvent)
227 QAbstractEventDispatcherPrivate::releaseTimerId(timerInfo->id);
228 CFRunLoopTimerInvalidate(timerInfo->runLoopTimer);
229 CFRelease(timerInfo->runLoopTimer);
230 delete timerInfo;
231 it = QEventDispatcherMacPrivate::macTimerHash.erase(it);
232 }
233 }
234 return true;
235}
236
237QList<QEventDispatcherMac::TimerInfo>
238QEventDispatcherMac::registeredTimers(QObject *object) const
239{
240 if (!object) {
241 qWarning("QEventDispatcherMac:registeredTimers: invalid argument");
242 return QList<TimerInfo>();
243 }
244
245 QList<TimerInfo> list;
246
247 MacTimerHash::const_iterator it = QEventDispatcherMacPrivate::macTimerHash.constBegin();
248 while (it != QEventDispatcherMacPrivate::macTimerHash.constEnd()) {
249 MacTimerInfo *t = it.value();
250 if (t->obj == object)
251 list << TimerInfo(t->id, t->interval);
252 ++it;
253 }
254 return list;
255}
256
257/**************************************************************************
258 Socket Notifiers
259 *************************************************************************/
260void qt_mac_socket_callback(CFSocketRef s, CFSocketCallBackType callbackType, CFDataRef,
261 const void *, void *info) {
262 QEventDispatcherMacPrivate *const eventDispatcher
263 = static_cast<QEventDispatcherMacPrivate *>(info);
264 int nativeSocket = CFSocketGetNative(s);
265 MacSocketInfo *socketInfo = eventDispatcher->macSockets.value(nativeSocket);
266 QEvent notifierEvent(QEvent::SockAct);
267 if (callbackType == kCFSocketReadCallBack) {
268 Q_ASSERT(socketInfo->readNotifier);
269 QApplication::sendEvent(socketInfo->readNotifier, &notifierEvent);
270 } 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.
273 if (socketInfo->writeNotifier)
274 QApplication::sendEvent(socketInfo->writeNotifier, &notifierEvent);
275 }
276}
277
278/*
279 Adds a loop source for the given socket to the current run loop.
280*/
281CFRunLoopSourceRef qt_mac_add_socket_to_runloop(const CFSocketRef socket)
282{
283 CFRunLoopSourceRef loopSource = CFSocketCreateRunLoopSource(kCFAllocatorDefault, socket, 0);
284 if (!loopSource)
285 return 0;
286
287 CFRunLoopAddSource(mainRunLoop(), loopSource, kCFRunLoopCommonModes);
288 return loopSource;
289}
290
291/*
292 Removes the loop source for the given socket from the current run loop.
293*/
294void qt_mac_remove_socket_from_runloop(const CFSocketRef socket, CFRunLoopSourceRef runloop)
295{
296 Q_ASSERT(runloop);
297 CFRunLoopRemoveSource(mainRunLoop(), runloop, kCFRunLoopCommonModes);
298 CFSocketDisableCallBacks(socket, kCFSocketReadCallBack);
299 CFSocketDisableCallBacks(socket, kCFSocketWriteCallBack);
300 CFRunLoopSourceInvalidate(runloop);
301}
302
303/*
304 Register a QSocketNotifier with the mac event system by creating a CFSocket with
305 with a read/write callback.
306
307 Qt has separate socket notifiers for reading and writing, but on the mac there is
308 a limitation of one CFSocket object for each native socket.
309*/
310void QEventDispatcherMac::registerSocketNotifier(QSocketNotifier *notifier)
311{
312 Q_ASSERT(notifier);
313 int nativeSocket = notifier->socket();
314 int type = notifier->type();
315#ifndef QT_NO_DEBUG
316 if (nativeSocket < 0 || nativeSocket > FD_SETSIZE) {
317 qWarning("QSocketNotifier: Internal error");
318 return;
319 } else if (notifier->thread() != thread()
320 || thread() != QThread::currentThread()) {
321 qWarning("QSocketNotifier: socket notifiers cannot be enabled from another thread");
322 return;
323 }
324#endif
325
326 Q_D(QEventDispatcherMac);
327
328 if (type == QSocketNotifier::Exception) {
329 qWarning("QSocketNotifier::Exception is not supported on Mac OS X");
330 return;
331 }
332
333 // Check if we have a CFSocket for the native socket, create one if not.
334 MacSocketInfo *socketInfo = d->macSockets.value(nativeSocket);
335 if (!socketInfo) {
336 socketInfo = new MacSocketInfo();
337
338 // Create CFSocket, specify that we want both read and write callbacks (the callbacks
339 // are enabled/disabled later on).
340 const int callbackTypes = kCFSocketReadCallBack | kCFSocketWriteCallBack;
341 CFSocketContext context = {0, d, 0, 0, 0};
342 socketInfo->socket = CFSocketCreateWithNative(kCFAllocatorDefault, nativeSocket, callbackTypes, qt_mac_socket_callback, &context);
343 if (CFSocketIsValid(socketInfo->socket) == false) {
344 qWarning("QEventDispatcherMac::registerSocketNotifier: Failed to create CFSocket");
345 return;
346 }
347
348 CFOptionFlags flags = CFSocketGetSocketFlags(socketInfo->socket);
349 flags |= kCFSocketAutomaticallyReenableWriteCallBack; //QSocketNotifier stays enabled after a write
350 flags &= ~kCFSocketCloseOnInvalidate; //QSocketNotifier doesn't close the socket upon destruction/invalidation
351 CFSocketSetSocketFlags(socketInfo->socket, flags);
352
353 // Add CFSocket to runloop.
354 if(!(socketInfo->runloop = qt_mac_add_socket_to_runloop(socketInfo->socket))) {
355 qWarning("QEventDispatcherMac::registerSocketNotifier: Failed to add CFSocket to runloop");
356 CFSocketInvalidate(socketInfo->socket);
357 CFRelease(socketInfo->socket);
358 return;
359 }
360
361 // Disable both callback types by default. This must be done after
362 // we add the CFSocket to the runloop, or else these calls will have
363 // no effect.
364 CFSocketDisableCallBacks(socketInfo->socket, kCFSocketReadCallBack);
365 CFSocketDisableCallBacks(socketInfo->socket, kCFSocketWriteCallBack);
366
367 d->macSockets.insert(nativeSocket, socketInfo);
368 }
369
370 // Increment read/write counters and select enable callbacks if necessary.
371 if (type == QSocketNotifier::Read) {
372 Q_ASSERT(socketInfo->readNotifier == 0);
373 socketInfo->readNotifier = notifier;
374 CFSocketEnableCallBacks(socketInfo->socket, kCFSocketReadCallBack);
375 } else if (type == QSocketNotifier::Write) {
376 Q_ASSERT(socketInfo->writeNotifier == 0);
377 socketInfo->writeNotifier = notifier;
378 CFSocketEnableCallBacks(socketInfo->socket, kCFSocketWriteCallBack);
379 }
380}
381
382/*
383 Unregister QSocketNotifer. The CFSocket correspoding to this notifier is
384 removed from the runloop of this is the last notifier that users
385 that CFSocket.
386*/
387void QEventDispatcherMac::unregisterSocketNotifier(QSocketNotifier *notifier)
388{
389 Q_ASSERT(notifier);
390 int nativeSocket = notifier->socket();
391 int type = notifier->type();
392#ifndef QT_NO_DEBUG
393 if (nativeSocket < 0 || nativeSocket > FD_SETSIZE) {
394 qWarning("QSocketNotifier: Internal error");
395 return;
396 } else if (notifier->thread() != thread() || thread() != QThread::currentThread()) {
397 qWarning("QSocketNotifier: socket notifiers cannot be disabled from another thread");
398 return;
399 }
400#endif
401
402 Q_D(QEventDispatcherMac);
403
404 if (type == QSocketNotifier::Exception) {
405 qWarning("QSocketNotifier::Exception is not supported on Mac OS X");
406 return;
407 }
408 MacSocketInfo *socketInfo = d->macSockets.value(nativeSocket);
409 if (!socketInfo) {
410 qWarning("QEventDispatcherMac::unregisterSocketNotifier: Tried to unregister a not registered notifier");
411 return;
412 }
413
414 // Decrement read/write counters and disable callbacks if necessary.
415 if (type == QSocketNotifier::Read) {
416 Q_ASSERT(notifier == socketInfo->readNotifier);
417 socketInfo->readNotifier = 0;
418 CFSocketDisableCallBacks(socketInfo->socket, kCFSocketReadCallBack);
419 } else if (type == QSocketNotifier::Write) {
420 Q_ASSERT(notifier == socketInfo->writeNotifier);
421 socketInfo->writeNotifier = 0;
422 CFSocketDisableCallBacks(socketInfo->socket, kCFSocketWriteCallBack);
423 }
424
425 // Remove CFSocket from runloop if this was the last QSocketNotifier.
426 if (socketInfo->readNotifier == 0 && socketInfo->writeNotifier == 0) {
427 if (CFSocketIsValid(socketInfo->socket))
428 qt_mac_remove_socket_from_runloop(socketInfo->socket, socketInfo->runloop);
429 CFRunLoopSourceInvalidate(socketInfo->runloop);
430 CFRelease(socketInfo->runloop);
431 CFSocketInvalidate(socketInfo->socket);
432 CFRelease(socketInfo->socket);
433 delete socketInfo;
434 d->macSockets.remove(nativeSocket);
435 }
436}
437
438bool QEventDispatcherMac::hasPendingEvents()
439{
440 extern uint qGlobalPostedEventsCount();
441 return qGlobalPostedEventsCount() || (qt_is_gui_used && GetNumEventsInQueue(GetMainEventQueue()));
442}
443
444
445static bool qt_mac_send_event(QEventLoop::ProcessEventsFlags, OSEventRef event, OSWindowRef pt)
446{
447#ifndef QT_MAC_USE_COCOA
448 if(pt && SendEventToWindow(event, pt) != eventNotHandledErr)
449 return true;
450 return !SendEventToEventTarget(event, GetEventDispatcherTarget());
451#else // QT_MAC_USE_COCOA
452 if (pt)
453 [pt sendEvent:event];
454 else
455 [NSApp sendEvent:event];
456 return true;
457#endif
458}
459
460#ifdef QT_MAC_USE_COCOA
461static bool IsMouseOrKeyEvent( NSEvent* event )
462{
463 bool result = false;
464
465 switch( [event type] )
466 {
467 case NSLeftMouseDown:
468 case NSLeftMouseUp:
469 case NSRightMouseDown:
470 case NSRightMouseUp:
471 case NSMouseMoved: // ??
472 case NSLeftMouseDragged:
473 case NSRightMouseDragged:
474 case NSMouseEntered:
475 case NSMouseExited:
476 case NSKeyDown:
477 case NSKeyUp:
478 case NSFlagsChanged: // key modifiers changed?
479 case NSCursorUpdate: // ??
480 case NSScrollWheel:
481 case NSTabletPoint:
482 case NSTabletProximity:
483 case NSOtherMouseDown:
484 case NSOtherMouseUp:
485 case NSOtherMouseDragged:
486 result = true;
487 break;
488
489 default:
490 break;
491 }
492 return result;
493}
494#endif
495
496bool QEventDispatcherMac::processEvents(QEventLoop::ProcessEventsFlags flags)
497{
498 Q_D(QEventDispatcherMac);
499 d->interrupt = false;
500 // In case we end up recursing while we now process events, make sure
501 // that we send remaining posted Qt events before this call returns:
502 wakeUp();
503 emit awake();
504
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
515 bool retVal = false;
516 forever {
517 if (d->interrupt)
518 break;
519
520#ifdef QT_MAC_USE_COCOA
521 QMacCocoaAutoReleasePool pool;
522 NSEvent* event = 0;
523
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];
544 }
545 else
546 [NSApp run];
547
548 d->rebuildModalSessionStack(false);
549 retVal = true;
550 } 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);
556 bool mustRelease = false;
557
558 if (!(flags & QEventLoop::ExcludeUserInputEvents) && !d->queuedUserInputEvents.isEmpty()) {
559 // process a pending user input event
560 mustRelease = true;
561 event = static_cast<NSEvent *>(d->queuedUserInputEvents.takeFirst());
562 } 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];
572 }
573 [NSApp runModalSession:session];
574 retVal = true;
575 break;
576 } else {
577 event = [NSApp nextEventMatchingMask:NSAnyEventMask
578 untilDate:nil
579 inMode:NSDefaultRunLoopMode
580 dequeue: YES];
581
582 if (event != nil) {
583 if (flags & QEventLoop::ExcludeUserInputEvents) {
584 if (IsMouseOrKeyEvent(event)) {
585 // retain event here?
586 [event retain];
587 d->queuedUserInputEvents.append(event);
588 continue;
589 }
590 }
591 }
592 }
593 }
594 if (event) {
595 if (!filterEvent(event) && qt_mac_send_event(flags, event, 0))
596 retVal = true;
597 if (mustRelease)
598 [event release];
599 }
600 } while(!d->interrupt && event != nil);
601
602#else
603 do {
604 EventRef event;
605 if (!(flags & QEventLoop::ExcludeUserInputEvents)
606 && !d->queuedUserInputEvents.isEmpty()) {
607 // process a pending user input event
608 event = static_cast<EventRef>(d->queuedUserInputEvents.takeFirst());
609 } else {
610 OSStatus err = ReceiveNextEvent(0,0, kEventDurationNoWait, true, &event);
611 if(err != noErr)
612 continue;
613 // else
614 if (flags & QEventLoop::ExcludeUserInputEvents) {
615 UInt32 ekind = GetEventKind(event),
616 eclass = GetEventClass(event);
617 switch(eclass) {
618 case kEventClassQt:
619 if(ekind != kEventQtRequestContext)
620 break;
621 // fall through
622 case kEventClassMouse:
623 case kEventClassKeyboard:
624 d->queuedUserInputEvents.append(event);
625 continue;
626 }
627 }
628 }
629
630 if (!filterEvent(&event) && qt_mac_send_event(flags, event, 0))
631 retVal = true;
632 ReleaseEvent(event);
633 } while(!d->interrupt && GetNumEventsInQueue(GetMainEventQueue()) > 0);
634
635#endif
636
637 bool canWait = (d->threadData->canWait
638 && !retVal
639 && !d->interrupt
640 && (flags & QEventLoop::WaitForMoreEvents));
641 if (canWait) {
642 // 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
655 flags &= ~QEventLoop::WaitForMoreEvents;
656 } else {
657 // Done with event processing for now. Leave the function:
658 break;
659 }
660 }
661
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.
676 interrupt();
677 }
678 return retVal;
679}
680
681void QEventDispatcherMac::wakeUp()
682{
683 Q_D(QEventDispatcherMac);
684 d->serialNumber.ref();
685 CFRunLoopSourceSignal(d->postedEventsSource);
686 CFRunLoopWakeUp(mainRunLoop());
687}
688
689void QEventDispatcherMac::flush()
690{
691 if(qApp) {
692 QWidgetList tlws = QApplication::topLevelWidgets();
693 for(int i = 0; i < tlws.size(); i++) {
694 QWidget *tlw = tlws.at(i);
695 if(tlw->isVisible())
696 macWindowFlush(qt_mac_window_for(tlw));
697 }
698 }
699}
700
701/*****************************************************************************
702 QEventDispatcherMac Implementation
703 *****************************************************************************/
704MacTimerHash QEventDispatcherMacPrivate::macTimerHash;
705bool QEventDispatcherMacPrivate::blockSendPostedEvents = false;
706
707#ifdef QT_MAC_USE_COCOA
708QStack<QCocoaModalSessionInfo> QEventDispatcherMacPrivate::cocoaModalSessionStack;
709bool QEventDispatcherMacPrivate::blockCocoaRequestModal = false;
710
711static void qt_mac_setChildDialogsResponsive(QWidget *widget, bool responsive)
712{
713 QList<QDialog *> dialogs = widget->findChildren<QDialog *>();
714 for (int i=0; i<dialogs.size(); ++i){
715 NSWindow *window = qt_mac_window_for(dialogs[i]);
716 if (window && [window isKindOfClass:[NSPanel class]]) {
717 [static_cast<NSPanel *>(window) setWorksWhenModal:responsive];
718 if (responsive && dialogs[i]->isVisible()){
719 [window orderFront:window];
720 }
721 }
722 }
723}
724
725NSModalSession 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();
740 if (size > 1){
741 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
770void 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){
787 QCocoaModalSessionInfo &info = cocoaModalSessionStack[i];
788 if (info.session) {
789 [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 }
807}
808
809#endif
810
811QEventDispatcherMacPrivate::QEventDispatcherMacPrivate()
812 : interrupt(false)
813{
814}
815
816QEventDispatcherMac::QEventDispatcherMac(QObject *parent)
817 : QAbstractEventDispatcher(*new QEventDispatcherMacPrivate, parent)
818{
819 Q_D(QEventDispatcherMac);
820 CFRunLoopSourceContext context;
821 bzero(&context, sizeof(CFRunLoopSourceContext));
822 context.info = d;
823 context.equal = QEventDispatcherMacPrivate::postedEventSourceEqualCallback;
824 context.perform = QEventDispatcherMacPrivate::postedEventsSourcePerformCallback;
825 d->postedEventsSource = CFRunLoopSourceCreate(0, 0, &context);
826 Q_ASSERT(d->postedEventsSource);
827 CFRunLoopAddSource(mainRunLoop(), d->postedEventsSource, kCFRunLoopCommonModes);
828
829 CFRunLoopObserverContext observerContext;
830 bzero(&observerContext, sizeof(CFRunLoopObserverContext));
831 observerContext.info = this;
832 d->waitingObserver = CFRunLoopObserverCreate(kCFAllocatorDefault,
833 kCFRunLoopBeforeWaiting | kCFRunLoopAfterWaiting,
834 true, 0,
835 QEventDispatcherMacPrivate::waitingObserverCallback,
836 &observerContext);
837 CFRunLoopAddObserver(mainRunLoop(), d->waitingObserver, kCFRunLoopCommonModes);
838}
839
840void QEventDispatcherMacPrivate::waitingObserverCallback(CFRunLoopObserverRef,
841 CFRunLoopActivity activity, void *info)
842{
843 if (activity == kCFRunLoopBeforeWaiting)
844 emit static_cast<QEventDispatcherMac*>(info)->aboutToBlock();
845 else
846 emit static_cast<QEventDispatcherMac*>(info)->awake();
847}
848
849Boolean QEventDispatcherMacPrivate::postedEventSourceEqualCallback(const void *info1, const void *info2)
850{
851 return info1 == info2;
852}
853
854void QEventDispatcherMacPrivate::postedEventsSourcePerformCallback(void *info)
855{
856 QEventDispatcherMacPrivate *d = static_cast<QEventDispatcherMacPrivate *>(info);
857 if (blockSendPostedEvents) {
858 CFRunLoopSourceSignal(d->postedEventsSource);
859 } else {
860 if (!d->threadData->canWait || (d->serialNumber != d->lastSerial)) {
861 d->lastSerial = d->serialNumber;
862 QApplicationPrivate::sendPostedEvents(0, 0, d->threadData);
863 }
864 }
865}
866
867#ifdef QT_MAC_USE_COCOA
868static 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
878
879void QEventDispatcherMac::interrupt()
880{
881 Q_D(QEventDispatcherMac);
882 d->interrupt = true;
883 wakeUp();
884#ifndef QT_MAC_USE_COCOA
885 CFRunLoopStop(mainRunLoop());
886#else
887 stopNSApp();
888#endif
889}
890
891QEventDispatcherMac::~QEventDispatcherMac()
892{
893 Q_D(QEventDispatcherMac);
894 //timer cleanup
895 MacTimerHash::iterator it = QEventDispatcherMacPrivate::macTimerHash.begin();
896 while (it != QEventDispatcherMacPrivate::macTimerHash.end()) {
897 MacTimerInfo *t = it.value();
898 if (t->runLoopTimer) {
899 CFRunLoopTimerInvalidate(t->runLoopTimer);
900 CFRelease(t->runLoopTimer);
901 }
902 delete t;
903 ++it;
904 }
905 QEventDispatcherMacPrivate::macTimerHash.clear();
906
907 // Remove CFSockets from the runloop.
908 for (MacSocketHash::ConstIterator it = d->macSockets.constBegin(); it != d->macSockets.constEnd(); ++it) {
909 MacSocketInfo *socketInfo = (*it);
910 if (CFSocketIsValid(socketInfo->socket)) {
911 qt_mac_remove_socket_from_runloop(socketInfo->socket, socketInfo->runloop);
912 CFRunLoopSourceInvalidate(socketInfo->runloop);
913 CFRelease(socketInfo->runloop);
914 CFSocketInvalidate(socketInfo->socket);
915 CFRelease(socketInfo->socket);
916 }
917 }
918 CFRunLoopRemoveSource(mainRunLoop(), d->postedEventsSource, kCFRunLoopCommonModes);
919 CFRelease(d->postedEventsSource);
920
921 CFRunLoopObserverInvalidate(d->waitingObserver);
922 CFRelease(d->waitingObserver);
923}
924
925
926QT_END_NAMESPACE
Note: See TracBrowser for help on using the repository browser.