macOS: Group window state handling in QCocoaWindow
The flow of changing the window state, especially for fullscreen, is complicated enough that it helps to keep the code together. Change-Id: I216ff95c03fc149f42a0199f7c3bc4fb04ff5e6e Reviewed-by: Timur Pocheptsov <timur.pocheptsov@qt.io>
This commit is contained in:
parent
d74ced697e
commit
b724a13c88
@ -591,12 +591,236 @@ void QCocoaWindow::setWindowFlags(Qt::WindowFlags flags)
|
|||||||
m_view.window.ignoresMouseEvents = ignoreMouse;
|
m_view.window.ignoresMouseEvents = ignoreMouse;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ----------------------- Window state -----------------------
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Changes the state of the NSWindow, going in/out of minimize/zoomed/fullscreen
|
||||||
|
|
||||||
|
When this is called from QWindow::setWindowState(), the QWindow state has not been
|
||||||
|
updated yet, so window()->windowState() will reflect the previous state that was
|
||||||
|
reported to QtGui.
|
||||||
|
*/
|
||||||
void QCocoaWindow::setWindowState(Qt::WindowStates state)
|
void QCocoaWindow::setWindowState(Qt::WindowStates state)
|
||||||
{
|
{
|
||||||
if (window()->isVisible())
|
if (window()->isVisible())
|
||||||
applyWindowState(state); // Window state set for hidden windows take effect when show() is called
|
applyWindowState(state); // Window state set for hidden windows take effect when show() is called
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void QCocoaWindow::applyWindowState(Qt::WindowStates requestedState)
|
||||||
|
{
|
||||||
|
if (!isContentView())
|
||||||
|
return;
|
||||||
|
|
||||||
|
const Qt::WindowState currentState = windowState();
|
||||||
|
const Qt::WindowState newState = QWindowPrivate::effectiveState(requestedState);
|
||||||
|
|
||||||
|
if (newState == currentState)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const NSSize contentSize = m_view.frame.size;
|
||||||
|
if (contentSize.width <= 0 || contentSize.height <= 0) {
|
||||||
|
// If content view width or height is 0 then the window animations will crash so
|
||||||
|
// do nothing. We report the current state back to reflect the failed operation.
|
||||||
|
qWarning("invalid window content view size, check your window geometry");
|
||||||
|
handleWindowStateChanged(HandleUnconditionally);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const NSWindow *nsWindow = m_view.window;
|
||||||
|
|
||||||
|
if (nsWindow.styleMask & NSUtilityWindowMask
|
||||||
|
&& newState & (Qt::WindowMinimized | Qt::WindowFullScreen)) {
|
||||||
|
qWarning() << window()->type() << "windows can not be made" << newState;
|
||||||
|
handleWindowStateChanged(HandleUnconditionally);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const id sender = nsWindow;
|
||||||
|
|
||||||
|
// First we need to exit states that can't transition directly to other states
|
||||||
|
switch (currentState) {
|
||||||
|
case Qt::WindowMinimized:
|
||||||
|
[nsWindow deminiaturize:sender];
|
||||||
|
Q_ASSERT_X(windowState() != Qt::WindowMinimized, "QCocoaWindow",
|
||||||
|
"[NSWindow deminiaturize:] is synchronous");
|
||||||
|
break;
|
||||||
|
case Qt::WindowFullScreen: {
|
||||||
|
toggleFullScreen();
|
||||||
|
// Exiting fullscreen is not synchronous, so we need to wait for the
|
||||||
|
// NSWindowDidExitFullScreenNotification before continuing to apply
|
||||||
|
// the new state.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
default:;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then we apply the new state if needed
|
||||||
|
if (newState == windowState())
|
||||||
|
return;
|
||||||
|
|
||||||
|
switch (newState) {
|
||||||
|
case Qt::WindowFullScreen:
|
||||||
|
toggleFullScreen();
|
||||||
|
break;
|
||||||
|
case Qt::WindowMaximized:
|
||||||
|
toggleMaximized();
|
||||||
|
break;
|
||||||
|
case Qt::WindowMinimized:
|
||||||
|
[nsWindow miniaturize:sender];
|
||||||
|
break;
|
||||||
|
case Qt::WindowNoState:
|
||||||
|
if (windowState() == Qt::WindowMaximized)
|
||||||
|
toggleMaximized();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
Q_UNREACHABLE();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Qt::WindowState QCocoaWindow::windowState() const
|
||||||
|
{
|
||||||
|
// FIXME: Support compound states (Qt::WindowStates)
|
||||||
|
|
||||||
|
NSWindow *window = m_view.window;
|
||||||
|
if (window.miniaturized)
|
||||||
|
return Qt::WindowMinimized;
|
||||||
|
if (window.qt_fullScreen)
|
||||||
|
return Qt::WindowFullScreen;
|
||||||
|
if ((window.zoomed && !isTransitioningToFullScreen())
|
||||||
|
|| (m_lastReportedWindowState == Qt::WindowMaximized && isTransitioningToFullScreen()))
|
||||||
|
return Qt::WindowMaximized;
|
||||||
|
|
||||||
|
// Note: We do not report Qt::WindowActive, even if isActive()
|
||||||
|
// is true, as QtGui does not expect this window state to be set.
|
||||||
|
|
||||||
|
return Qt::WindowNoState;
|
||||||
|
}
|
||||||
|
|
||||||
|
void QCocoaWindow::toggleMaximized()
|
||||||
|
{
|
||||||
|
const NSWindow *window = m_view.window;
|
||||||
|
|
||||||
|
// The NSWindow needs to be resizable, otherwise the window will
|
||||||
|
// not be possible to zoom back to non-zoomed state.
|
||||||
|
const bool wasResizable = window.styleMask & NSResizableWindowMask;
|
||||||
|
window.styleMask |= NSResizableWindowMask;
|
||||||
|
|
||||||
|
const id sender = window;
|
||||||
|
[window zoom:sender];
|
||||||
|
|
||||||
|
if (!wasResizable)
|
||||||
|
window.styleMask &= ~NSResizableWindowMask;
|
||||||
|
}
|
||||||
|
|
||||||
|
void QCocoaWindow::toggleFullScreen()
|
||||||
|
{
|
||||||
|
const NSWindow *window = m_view.window;
|
||||||
|
|
||||||
|
// The window needs to have the correct collection behavior for the
|
||||||
|
// toggleFullScreen call to have an effect. The collection behavior
|
||||||
|
// will be reset in windowDidEnterFullScreen/windowDidLeaveFullScreen.
|
||||||
|
window.collectionBehavior |= NSWindowCollectionBehaviorFullScreenPrimary;
|
||||||
|
|
||||||
|
const id sender = window;
|
||||||
|
[window toggleFullScreen:sender];
|
||||||
|
}
|
||||||
|
|
||||||
|
void QCocoaWindow::windowWillEnterFullScreen()
|
||||||
|
{
|
||||||
|
if (!isContentView())
|
||||||
|
return;
|
||||||
|
|
||||||
|
// The NSWindow needs to be resizable, otherwise we'll end up with
|
||||||
|
// the normal window geometry, centered in the middle of the screen
|
||||||
|
// on a black background. The styleMask will be reset below.
|
||||||
|
m_view.window.styleMask |= NSResizableWindowMask;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool QCocoaWindow::isTransitioningToFullScreen() const
|
||||||
|
{
|
||||||
|
NSWindow *window = m_view.window;
|
||||||
|
return window.styleMask & NSFullScreenWindowMask && !window.qt_fullScreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
void QCocoaWindow::windowDidEnterFullScreen()
|
||||||
|
{
|
||||||
|
if (!isContentView())
|
||||||
|
return;
|
||||||
|
|
||||||
|
Q_ASSERT_X(m_view.window.qt_fullScreen, "QCocoaWindow",
|
||||||
|
"FullScreen category processes window notifications first");
|
||||||
|
|
||||||
|
// Reset to original styleMask
|
||||||
|
setWindowFlags(window()->flags());
|
||||||
|
|
||||||
|
handleWindowStateChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void QCocoaWindow::windowWillExitFullScreen()
|
||||||
|
{
|
||||||
|
if (!isContentView())
|
||||||
|
return;
|
||||||
|
|
||||||
|
// The NSWindow needs to be resizable, otherwise we'll end up with
|
||||||
|
// a weird zoom animation. The styleMask will be reset below.
|
||||||
|
m_view.window.styleMask |= NSResizableWindowMask;
|
||||||
|
}
|
||||||
|
|
||||||
|
void QCocoaWindow::windowDidExitFullScreen()
|
||||||
|
{
|
||||||
|
if (!isContentView())
|
||||||
|
return;
|
||||||
|
|
||||||
|
Q_ASSERT_X(!m_view.window.qt_fullScreen, "QCocoaWindow",
|
||||||
|
"FullScreen category processes window notifications first");
|
||||||
|
|
||||||
|
// Reset to original styleMask
|
||||||
|
setWindowFlags(window()->flags());
|
||||||
|
|
||||||
|
Qt::WindowState requestedState = window()->windowState();
|
||||||
|
|
||||||
|
// Deliver update of QWindow state
|
||||||
|
handleWindowStateChanged();
|
||||||
|
|
||||||
|
if (requestedState != windowState() && requestedState != Qt::WindowFullScreen) {
|
||||||
|
// We were only going out of full screen as an intermediate step before
|
||||||
|
// progressing into the final step, so re-sync the desired state.
|
||||||
|
applyWindowState(requestedState);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void QCocoaWindow::windowDidMiniaturize()
|
||||||
|
{
|
||||||
|
if (!isContentView())
|
||||||
|
return;
|
||||||
|
|
||||||
|
handleWindowStateChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void QCocoaWindow::windowDidDeminiaturize()
|
||||||
|
{
|
||||||
|
if (!isContentView())
|
||||||
|
return;
|
||||||
|
|
||||||
|
handleWindowStateChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void QCocoaWindow::handleWindowStateChanged(HandleFlags flags)
|
||||||
|
{
|
||||||
|
Qt::WindowState currentState = windowState();
|
||||||
|
if (!(flags & HandleUnconditionally) && currentState == m_lastReportedWindowState)
|
||||||
|
return;
|
||||||
|
|
||||||
|
qCDebug(lcQpaWindow) << "QCocoaWindow::handleWindowStateChanged" <<
|
||||||
|
m_lastReportedWindowState << "-->" << currentState;
|
||||||
|
|
||||||
|
QWindowSystemInterface::handleWindowStateChanged<QWindowSystemInterface::SynchronousDelivery>(
|
||||||
|
window(), currentState, m_lastReportedWindowState);
|
||||||
|
m_lastReportedWindowState = currentState;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------
|
||||||
|
|
||||||
void QCocoaWindow::setWindowTitle(const QString &title)
|
void QCocoaWindow::setWindowTitle(const QString &title)
|
||||||
{
|
{
|
||||||
if (!isContentView())
|
if (!isContentView())
|
||||||
@ -973,80 +1197,6 @@ void QCocoaWindow::windowDidResignKey()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void QCocoaWindow::windowDidMiniaturize()
|
|
||||||
{
|
|
||||||
if (!isContentView())
|
|
||||||
return;
|
|
||||||
|
|
||||||
handleWindowStateChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
void QCocoaWindow::windowDidDeminiaturize()
|
|
||||||
{
|
|
||||||
if (!isContentView())
|
|
||||||
return;
|
|
||||||
|
|
||||||
handleWindowStateChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
void QCocoaWindow::windowWillEnterFullScreen()
|
|
||||||
{
|
|
||||||
if (!isContentView())
|
|
||||||
return;
|
|
||||||
|
|
||||||
// The NSWindow needs to be resizable, otherwise we'll end up with
|
|
||||||
// the normal window geometry, centered in the middle of the screen
|
|
||||||
// on a black background. The styleMask will be reset below.
|
|
||||||
m_view.window.styleMask |= NSResizableWindowMask;
|
|
||||||
}
|
|
||||||
|
|
||||||
void QCocoaWindow::windowDidEnterFullScreen()
|
|
||||||
{
|
|
||||||
if (!isContentView())
|
|
||||||
return;
|
|
||||||
|
|
||||||
Q_ASSERT_X(m_view.window.qt_fullScreen, "QCocoaWindow",
|
|
||||||
"FullScreen category processes window notifications first");
|
|
||||||
|
|
||||||
// Reset to original styleMask
|
|
||||||
setWindowFlags(window()->flags());
|
|
||||||
|
|
||||||
handleWindowStateChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
void QCocoaWindow::windowWillExitFullScreen()
|
|
||||||
{
|
|
||||||
if (!isContentView())
|
|
||||||
return;
|
|
||||||
|
|
||||||
// The NSWindow needs to be resizable, otherwise we'll end up with
|
|
||||||
// a weird zoom animation. The styleMask will be reset below.
|
|
||||||
m_view.window.styleMask |= NSResizableWindowMask;
|
|
||||||
}
|
|
||||||
|
|
||||||
void QCocoaWindow::windowDidExitFullScreen()
|
|
||||||
{
|
|
||||||
if (!isContentView())
|
|
||||||
return;
|
|
||||||
|
|
||||||
Q_ASSERT_X(!m_view.window.qt_fullScreen, "QCocoaWindow",
|
|
||||||
"FullScreen category processes window notifications first");
|
|
||||||
|
|
||||||
// Reset to original styleMask
|
|
||||||
setWindowFlags(window()->flags());
|
|
||||||
|
|
||||||
Qt::WindowState requestedState = window()->windowState();
|
|
||||||
|
|
||||||
// Deliver update of QWindow state
|
|
||||||
handleWindowStateChanged();
|
|
||||||
|
|
||||||
if (requestedState != windowState() && requestedState != Qt::WindowFullScreen) {
|
|
||||||
// We were only going out of full screen as an intermediate step before
|
|
||||||
// progressing into the final step, so re-sync the desired state.
|
|
||||||
applyWindowState(requestedState);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void QCocoaWindow::windowDidOrderOnScreen()
|
void QCocoaWindow::windowDidOrderOnScreen()
|
||||||
{
|
{
|
||||||
[m_view setNeedsDisplay:YES];
|
[m_view setNeedsDisplay:YES];
|
||||||
@ -1166,20 +1316,6 @@ void QCocoaWindow::handleExposeEvent(const QRegion ®ion)
|
|||||||
QWindowSystemInterface::handleExposeEvent<QWindowSystemInterface::SynchronousDelivery>(window(), region);
|
QWindowSystemInterface::handleExposeEvent<QWindowSystemInterface::SynchronousDelivery>(window(), region);
|
||||||
}
|
}
|
||||||
|
|
||||||
void QCocoaWindow::handleWindowStateChanged(HandleFlags flags)
|
|
||||||
{
|
|
||||||
Qt::WindowState currentState = windowState();
|
|
||||||
if (!(flags & HandleUnconditionally) && currentState == m_lastReportedWindowState)
|
|
||||||
return;
|
|
||||||
|
|
||||||
qCDebug(lcQpaWindow) << "QCocoaWindow::handleWindowStateChanged" <<
|
|
||||||
m_lastReportedWindowState << "-->" << currentState;
|
|
||||||
|
|
||||||
QWindowSystemInterface::handleWindowStateChanged<QWindowSystemInterface::SynchronousDelivery>(
|
|
||||||
window(), currentState, m_lastReportedWindowState);
|
|
||||||
m_lastReportedWindowState = currentState;
|
|
||||||
}
|
|
||||||
|
|
||||||
// --------------------------------------------------------------------------
|
// --------------------------------------------------------------------------
|
||||||
|
|
||||||
bool QCocoaWindow::windowIsPopupType(Qt::WindowType type) const
|
bool QCocoaWindow::windowIsPopupType(Qt::WindowType type) const
|
||||||
@ -1478,138 +1614,6 @@ void QCocoaWindow::removeMonitor()
|
|||||||
monitor = nil;
|
monitor = nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
|
||||||
Applies the given state to the NSWindow, going in/out of minimize/zoomed/fullscreen
|
|
||||||
|
|
||||||
When this is called from QWindow::setWindowState(), the QWindow state has not been
|
|
||||||
updated yet, so window()->windowState() will reflect the previous state that was
|
|
||||||
reported to QtGui.
|
|
||||||
*/
|
|
||||||
void QCocoaWindow::applyWindowState(Qt::WindowStates requestedState)
|
|
||||||
{
|
|
||||||
if (!isContentView())
|
|
||||||
return;
|
|
||||||
|
|
||||||
const Qt::WindowState currentState = windowState();
|
|
||||||
const Qt::WindowState newState = QWindowPrivate::effectiveState(requestedState);
|
|
||||||
|
|
||||||
if (newState == currentState)
|
|
||||||
return;
|
|
||||||
|
|
||||||
const NSSize contentSize = m_view.frame.size;
|
|
||||||
if (contentSize.width <= 0 || contentSize.height <= 0) {
|
|
||||||
// If content view width or height is 0 then the window animations will crash so
|
|
||||||
// do nothing. We report the current state back to reflect the failed operation.
|
|
||||||
qWarning("invalid window content view size, check your window geometry");
|
|
||||||
handleWindowStateChanged(HandleUnconditionally);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const NSWindow *nsWindow = m_view.window;
|
|
||||||
|
|
||||||
if (nsWindow.styleMask & NSUtilityWindowMask
|
|
||||||
&& newState & (Qt::WindowMinimized | Qt::WindowFullScreen)) {
|
|
||||||
qWarning() << window()->type() << "windows can not be made" << newState;
|
|
||||||
handleWindowStateChanged(HandleUnconditionally);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const id sender = nsWindow;
|
|
||||||
|
|
||||||
// First we need to exit states that can't transition directly to other states
|
|
||||||
switch (currentState) {
|
|
||||||
case Qt::WindowMinimized:
|
|
||||||
[nsWindow deminiaturize:sender];
|
|
||||||
Q_ASSERT_X(windowState() != Qt::WindowMinimized, "QCocoaWindow",
|
|
||||||
"[NSWindow deminiaturize:] is synchronous");
|
|
||||||
break;
|
|
||||||
case Qt::WindowFullScreen: {
|
|
||||||
toggleFullScreen();
|
|
||||||
// Exiting fullscreen is not synchronous, so we need to wait for the
|
|
||||||
// NSWindowDidExitFullScreenNotification before continuing to apply
|
|
||||||
// the new state.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
default:;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Then we apply the new state if needed
|
|
||||||
if (newState == windowState())
|
|
||||||
return;
|
|
||||||
|
|
||||||
switch (newState) {
|
|
||||||
case Qt::WindowFullScreen:
|
|
||||||
toggleFullScreen();
|
|
||||||
break;
|
|
||||||
case Qt::WindowMaximized:
|
|
||||||
toggleMaximized();
|
|
||||||
break;
|
|
||||||
case Qt::WindowMinimized:
|
|
||||||
[nsWindow miniaturize:sender];
|
|
||||||
break;
|
|
||||||
case Qt::WindowNoState:
|
|
||||||
if (windowState() == Qt::WindowMaximized)
|
|
||||||
toggleMaximized();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
Q_UNREACHABLE();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void QCocoaWindow::toggleMaximized()
|
|
||||||
{
|
|
||||||
const NSWindow *window = m_view.window;
|
|
||||||
|
|
||||||
// The NSWindow needs to be resizable, otherwise the window will
|
|
||||||
// not be possible to zoom back to non-zoomed state.
|
|
||||||
const bool wasResizable = window.styleMask & NSResizableWindowMask;
|
|
||||||
window.styleMask |= NSResizableWindowMask;
|
|
||||||
|
|
||||||
const id sender = window;
|
|
||||||
[window zoom:sender];
|
|
||||||
|
|
||||||
if (!wasResizable)
|
|
||||||
window.styleMask &= ~NSResizableWindowMask;
|
|
||||||
}
|
|
||||||
|
|
||||||
void QCocoaWindow::toggleFullScreen()
|
|
||||||
{
|
|
||||||
const NSWindow *window = m_view.window;
|
|
||||||
|
|
||||||
// The window needs to have the correct collection behavior for the
|
|
||||||
// toggleFullScreen call to have an effect. The collection behavior
|
|
||||||
// will be reset in windowDidEnterFullScreen/windowDidLeaveFullScreen.
|
|
||||||
window.collectionBehavior |= NSWindowCollectionBehaviorFullScreenPrimary;
|
|
||||||
|
|
||||||
const id sender = window;
|
|
||||||
[window toggleFullScreen:sender];
|
|
||||||
}
|
|
||||||
|
|
||||||
bool QCocoaWindow::isTransitioningToFullScreen() const
|
|
||||||
{
|
|
||||||
NSWindow *window = m_view.window;
|
|
||||||
return window.styleMask & NSFullScreenWindowMask && !window.qt_fullScreen;
|
|
||||||
}
|
|
||||||
|
|
||||||
Qt::WindowState QCocoaWindow::windowState() const
|
|
||||||
{
|
|
||||||
// FIXME: Support compound states (Qt::WindowStates)
|
|
||||||
|
|
||||||
NSWindow *window = m_view.window;
|
|
||||||
if (window.miniaturized)
|
|
||||||
return Qt::WindowMinimized;
|
|
||||||
if (window.qt_fullScreen)
|
|
||||||
return Qt::WindowFullScreen;
|
|
||||||
if ((window.zoomed && !isTransitioningToFullScreen())
|
|
||||||
|| (m_lastReportedWindowState == Qt::WindowMaximized && isTransitioningToFullScreen()))
|
|
||||||
return Qt::WindowMaximized;
|
|
||||||
|
|
||||||
// Note: We do not report Qt::WindowActive, even if isActive()
|
|
||||||
// is true, as QtGui does not expect this window state to be set.
|
|
||||||
|
|
||||||
return Qt::WindowNoState;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool QCocoaWindow::setWindowModified(bool modified)
|
bool QCocoaWindow::setWindowModified(bool modified)
|
||||||
{
|
{
|
||||||
if (!isContentView())
|
if (!isContentView())
|
||||||
|
Loading…
x
Reference in New Issue
Block a user