Cocoa: Don't stop NSApp when showing a modal dialog

We manage embedded modal sessions with a stack and only run
the top-most session. We also stop the last modal session
before starting a new one. However, if there is no modal
session running yet, we end up stopping NSApp. This seems
to cause ill side effects on OS X 10.9. Notably, starting
a new modal session outside QCocoaEventDispatcher, like when
opening a native file dialog, makes this last modal session
impossible for the user to quit.

In this patch, we make sure NSApp is kept running if there's
no modal session running yet, akin to calling QDialog::exec()
at the event dispatcher level. The behavior for ensuing modal
sessions remains unchanged.

Task-number: QTBUG-34677
Change-Id: I6a23b191e4dce18514504b8e953f8caa7fad8731
Reviewed-by: Gabriel de Dietrich <gabriel.dedietrich@digia.com>
Reviewed-by: Richard Moe Gustavsen <richard.gustavsen@digia.com>
This commit is contained in:
Gabriel de Dietrich 2014-03-17 19:21:49 +01:00 committed by The Qt Project
parent 9a9feab102
commit ff3dcc49c4
2 changed files with 16 additions and 1 deletions

View File

@ -163,6 +163,7 @@ public:
// The following variables help organizing modal sessions: // The following variables help organizing modal sessions:
QStack<QCocoaModalSessionInfo> cocoaModalSessionStack; QStack<QCocoaModalSessionInfo> cocoaModalSessionStack;
bool currentExecIsNSAppRun; bool currentExecIsNSAppRun;
bool modalSessionOnNSAppRun;
bool nsAppRunCalledByQt; bool nsAppRunCalledByQt;
bool cleanupModalSessionsNeeded; bool cleanupModalSessionsNeeded;
uint processEventsCalled; uint processEventsCalled;

View File

@ -721,7 +721,6 @@ void QCocoaEventDispatcherPrivate::beginModalSession(QWindow *window)
// setting currentModalSessionCached to zero, so that interrupt() calls // setting currentModalSessionCached to zero, so that interrupt() calls
// [NSApp abortModal] if another modal session is currently running // [NSApp abortModal] if another modal session is currently running
Q_Q(QCocoaEventDispatcher); Q_Q(QCocoaEventDispatcher);
q->interrupt();
// Add a new, empty (null), NSModalSession to the stack. // Add a new, empty (null), NSModalSession to the stack.
// It will become active the next time QEventDispatcher::processEvents is called. // It will become active the next time QEventDispatcher::processEvents is called.
@ -734,6 +733,12 @@ void QCocoaEventDispatcherPrivate::beginModalSession(QWindow *window)
cocoaModalSessionStack.push(info); cocoaModalSessionStack.push(info);
updateChildrenWorksWhenModal(); updateChildrenWorksWhenModal();
currentModalSessionCached = 0; currentModalSessionCached = 0;
if (currentExecIsNSAppRun) {
modalSessionOnNSAppRun = true;
q->wakeUp();
} else {
q->interrupt();
}
} }
void QCocoaEventDispatcherPrivate::endModalSession(QWindow *window) void QCocoaEventDispatcherPrivate::endModalSession(QWindow *window)
@ -772,6 +777,7 @@ QCocoaEventDispatcherPrivate::QCocoaEventDispatcherPrivate()
runLoopTimerRef(0), runLoopTimerRef(0),
blockSendPostedEvents(false), blockSendPostedEvents(false),
currentExecIsNSAppRun(false), currentExecIsNSAppRun(false),
modalSessionOnNSAppRun(false),
nsAppRunCalledByQt(false), nsAppRunCalledByQt(false),
cleanupModalSessionsNeeded(false), cleanupModalSessionsNeeded(false),
processEventsCalled(0), processEventsCalled(0),
@ -902,6 +908,14 @@ void QCocoaEventDispatcherPrivate::postedEventsSourceCallback(void *info)
// processEvents() was called "manually," ignore this source for now // processEvents() was called "manually," ignore this source for now
d->maybeCancelWaitForMoreEvents(); d->maybeCancelWaitForMoreEvents();
return; return;
} else if (d->modalSessionOnNSAppRun) {
// We're about to spawn the 1st modal session on top of the main runloop.
// Instead of calling processPostedEvents(), which would need us stop
// NSApp, we just re-enter processEvents(). This is equivalent to calling
// QDialog::exec() except that it's done in a non-blocking way.
d->modalSessionOnNSAppRun = false;
d->q_func()->processEvents(QEventLoop::DialogExec | QEventLoop::EventLoopExec | QEventLoop::WaitForMoreEvents);
return;
} }
d->processPostedEvents(); d->processPostedEvents();
d->maybeCancelWaitForMoreEvents(); d->maybeCancelWaitForMoreEvents();