blob: cdec57de716c4aed44f6c0e6f5a3fd1e127a7f93 [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the plugins of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "qioswindow.h"
#include "qiosapplicationdelegate.h"
#include "qioscontext.h"
#include "qiosglobal.h"
#include "qiosintegration.h"
#include "qiosscreen.h"
#include "qiosviewcontroller.h"
#include "quiview.h"
#include <QtGui/private/qwindow_p.h>
#include <qpa/qplatformintegration.h>
#import <QuartzCore/CAEAGLLayer.h>
#include <QtDebug>
QT_BEGIN_NAMESPACE
QIOSWindow::QIOSWindow(QWindow *window)
: QPlatformWindow(window)
, m_view([[QUIView alloc] initWithQIOSWindow:this])
, m_windowLevel(0)
{
connect(qGuiApp, &QGuiApplication::applicationStateChanged, this, &QIOSWindow::applicationStateChanged);
setParent(QPlatformWindow::parent());
// Resolve default window geometry in case it was not set before creating the
// platform window. This picks up eg. minimum-size if set, and defaults to
// the "maxmized" geometry (even though we're not in that window state).
// FIXME: Detect if we apply a maximized geometry and send a window state
// change event in that case.
m_normalGeometry = initialGeometry(window, QPlatformWindow::geometry(),
screen()->availableGeometry().width(), screen()->availableGeometry().height());
setWindowState(window->windowStates());
setOpacity(window->opacity());
Qt::ScreenOrientation initialOrientation = window->contentOrientation();
if (initialOrientation != Qt::PrimaryOrientation) {
// Start up in portrait, then apply possible content orientation,
// as per Apple's documentation.
dispatch_async(dispatch_get_main_queue(), ^{
handleContentOrientationChange(initialOrientation);
});
}
}
QIOSWindow::~QIOSWindow()
{
// According to the UIResponder documentation, Cocoa Touch should react to system interruptions
// that "might cause the view to be removed from the window" by sending touchesCancelled, but in
// practice this doesn't seem to happen when removing the view from its superview. To ensure that
// Qt's internal state for touch and mouse handling is kept consistent, we therefor have to force
// cancellation of all touch events.
[m_view touchesCancelled:[NSSet set] withEvent:0];
clearAccessibleCache();
m_view.platformWindow = 0;
[m_view removeFromSuperview];
[m_view release];
}
QSurfaceFormat QIOSWindow::format() const
{
return window()->requestedFormat();
}
bool QIOSWindow::blockedByModal()
{
QWindow *modalWindow = QGuiApplication::modalWindow();
return modalWindow && modalWindow != window();
}
void QIOSWindow::setVisible(bool visible)
{
m_view.hidden = !visible;
[m_view setNeedsDisplay];
if (!isQtApplication() || !window()->isTopLevel())
return;
// Since iOS doesn't do window management the way a Qt application
// expects, we need to raise and activate windows ourselves:
if (visible)
updateWindowLevel();
if (blockedByModal()) {
if (visible)
raise();
return;
}
if (visible && shouldAutoActivateWindow()) {
if (!window()->property("_q_showWithoutActivating").toBool())
requestActivateWindow();
} else if (!visible && [m_view isActiveWindow]) {
// Our window was active/focus window but now hidden, so relinquish
// focus to the next possible window in the stack.
NSArray<UIView *> *subviews = m_view.viewController.view.subviews;
for (int i = int(subviews.count) - 1; i >= 0; --i) {
UIView *view = [subviews objectAtIndex:i];
if (view.hidden)
continue;
QWindow *w = view.qwindow;
if (!w || !w->isTopLevel())
continue;
QIOSWindow *iosWindow = static_cast<QIOSWindow *>(w->handle());
if (!iosWindow->shouldAutoActivateWindow())
continue;
iosWindow->requestActivateWindow();
break;
}
}
}
bool QIOSWindow::shouldAutoActivateWindow() const
{
if (![m_view canBecomeFirstResponder])
return false;
// We don't want to do automatic window activation for popup windows
// that are unlikely to contain editable controls (to avoid hiding
// the keyboard while the popup is showing)
const Qt::WindowType type = window()->type();
return (type != Qt::Popup && type != Qt::ToolTip) || !window()->isActive();
}
void QIOSWindow::setOpacity(qreal level)
{
m_view.alpha = qBound(0.0, level, 1.0);
}
void QIOSWindow::setGeometry(const QRect &rect)
{
m_normalGeometry = rect;
if (window()->windowState() != Qt::WindowNoState) {
QPlatformWindow::setGeometry(rect);
// The layout will realize the requested geometry was not applied, and
// send geometry-change events that match the actual geometry.
[m_view setNeedsLayout];
if (window()->inherits("QWidgetWindow")) {
// QWidget wrongly assumes that setGeometry resets the window
// state back to Qt::NoWindowState, so we need to inform it that
// that his is not the case by re-issuing the current window state.
QWindowSystemInterface::handleWindowStateChanged(window(), window()->windowState());
// It also needs to be told immediately that the geometry it requested
// did not apply, otherwise it will continue on as if it did, instead
// of waiting for a resize event.
[m_view layoutIfNeeded];
}
return;
}
applyGeometry(rect);
}
void QIOSWindow::applyGeometry(const QRect &rect)
{
// Geometry changes are asynchronous, but QWindow::geometry() is
// expected to report back the 'requested geometry' until we get
// a callback with the updated geometry from the window system.
// The baseclass takes care of persisting this for us.
QPlatformWindow::setGeometry(rect);
m_view.frame = rect.toCGRect();
// iOS will automatically trigger -[layoutSubviews:] for resize,
// but not for move, so we force it just in case.
[m_view setNeedsLayout];
if (window()->inherits("QWidgetWindow"))
[m_view layoutIfNeeded];
}
QMargins QIOSWindow::safeAreaMargins() const
{
UIEdgeInsets safeAreaInsets = m_view.qt_safeAreaInsets;
return QMargins(safeAreaInsets.left, safeAreaInsets.top,
safeAreaInsets.right, safeAreaInsets.bottom);
}
bool QIOSWindow::isExposed() const
{
return qApp->applicationState() != Qt::ApplicationSuspended
&& window()->isVisible() && !window()->geometry().isEmpty();
}
void QIOSWindow::setWindowState(Qt::WindowStates state)
{
// Update the QWindow representation straight away, so that
// we can update the statusbar visibility based on the new
// state before applying geometry changes.
qt_window_private(window())->windowState = state;
if (window()->isTopLevel() && window()->isVisible() && window()->isActive())
[m_view.qtViewController updateProperties];
if (state & Qt::WindowMinimized) {
applyGeometry(QRect());
} else if (state & (Qt::WindowFullScreen | Qt::WindowMaximized)) {
// When an application is in split-view mode, the UIScreen still has the
// same geometry, but the UIWindow is resized to the area reserved for the
// application. We use this to constrain the geometry used when applying the
// fullscreen or maximized window states. Note that we do not do this
// in applyGeometry(), as we don't want to artificially limit window
// placement "outside" of the screen bounds if that's what the user wants.
QRect uiWindowBounds = QRectF::fromCGRect(m_view.window.bounds).toRect();
QRect fullscreenGeometry = screen()->geometry().intersected(uiWindowBounds);
QRect maximizedGeometry = window()->flags() & Qt::MaximizeUsingFullscreenGeometryHint ?
fullscreenGeometry : screen()->availableGeometry().intersected(uiWindowBounds);
if (state & Qt::WindowFullScreen)
applyGeometry(fullscreenGeometry);
else
applyGeometry(maximizedGeometry);
} else {
applyGeometry(m_normalGeometry);
}
}
void QIOSWindow::setParent(const QPlatformWindow *parentWindow)
{
UIView *parentView = parentWindow ? reinterpret_cast<UIView *>(parentWindow->winId())
: isQtApplication() ? static_cast<QIOSScreen *>(screen())->uiWindow().rootViewController.view : 0;
[parentView addSubview:m_view];
}
void QIOSWindow::requestActivateWindow()
{
// Note that several windows can be active at the same time if they exist in the same
// hierarchy (transient children). But only one window can be QGuiApplication::focusWindow().
// Dispite the name, 'requestActivateWindow' means raise and transfer focus to the window:
if (blockedByModal())
return;
Q_ASSERT(m_view.window);
[m_view.window makeKeyWindow];
[m_view becomeFirstResponder];
if (window()->isTopLevel())
raise();
}
void QIOSWindow::raiseOrLower(bool raise)
{
// Re-insert m_view at the correct index among its sibling views
// (QWindows) according to their current m_windowLevel:
if (!isQtApplication())
return;
NSArray<UIView *> *subviews = m_view.superview.subviews;
if (subviews.count == 1)
return;
for (int i = int(subviews.count) - 1; i >= 0; --i) {
UIView *view = static_cast<UIView *>([subviews objectAtIndex:i]);
if (view.hidden || view == m_view || !view.qwindow)
continue;
int level = static_cast<QIOSWindow *>(view.qwindow->handle())->m_windowLevel;
if (m_windowLevel > level || (raise && m_windowLevel == level)) {
[m_view.superview insertSubview:m_view aboveSubview:view];
return;
}
}
[m_view.superview insertSubview:m_view atIndex:0];
}
void QIOSWindow::updateWindowLevel()
{
Qt::WindowType type = window()->type();
if (type == Qt::ToolTip)
m_windowLevel = 120;
else if (window()->flags() & Qt::WindowStaysOnTopHint)
m_windowLevel = 100;
else if (window()->isModal())
m_windowLevel = 40;
else if (type == Qt::Popup)
m_windowLevel = 30;
else if (type == Qt::SplashScreen)
m_windowLevel = 20;
else if (type == Qt::Tool)
m_windowLevel = 10;
else
m_windowLevel = 0;
// A window should be in at least the same m_windowLevel as its parent:
QWindow *transientParent = window()->transientParent();
QIOSWindow *transientParentWindow = transientParent ? static_cast<QIOSWindow *>(transientParent->handle()) : 0;
if (transientParentWindow)
m_windowLevel = qMax(transientParentWindow->m_windowLevel, m_windowLevel);
}
void QIOSWindow::handleContentOrientationChange(Qt::ScreenOrientation orientation)
{
// Update the QWindow representation straight away, so that
// we can update the statusbar orientation based on the new
// content orientation.
qt_window_private(window())->contentOrientation = orientation;
[m_view.qtViewController updateProperties];
}
void QIOSWindow::applicationStateChanged(Qt::ApplicationState)
{
if (window()->isExposed() != isExposed())
[m_view sendUpdatedExposeEvent];
}
qreal QIOSWindow::devicePixelRatio() const
{
return m_view.contentScaleFactor;
}
void QIOSWindow::clearAccessibleCache()
{
[m_view clearAccessibleCache];
}
void QIOSWindow::requestUpdate()
{
static_cast<QIOSScreen *>(screen())->setUpdatesPaused(false);
}
CAEAGLLayer *QIOSWindow::eaglLayer() const
{
Q_ASSERT([m_view.layer isKindOfClass:[CAEAGLLayer class]]);
return static_cast<CAEAGLLayer *>(m_view.layer);
}
#ifndef QT_NO_DEBUG_STREAM
QDebug operator<<(QDebug debug, const QIOSWindow *window)
{
QDebugStateSaver saver(debug);
debug.nospace();
debug << "QIOSWindow(" << (const void *)window;
if (window)
debug << ", window=" << window->window();
debug << ')';
return debug;
}
#endif // !QT_NO_DEBUG_STREAM
#include "moc_qioswindow.cpp"
QT_END_NAMESPACE