Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
Loading...
Searching...
No Matches
qioswindow.mm
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qioswindow.h"
5
7#include "qiosglobal.h"
8#include "qiosintegration.h"
9#include "qiosscreen.h"
10#include "qiosviewcontroller.h"
11#include "quiview.h"
12#include "qiosinputcontext.h"
13
14#include <QtCore/private/qcore_mac_p.h>
15
16#include <QtGui/private/qwindow_p.h>
17#include <QtGui/private/qhighdpiscaling_p.h>
18#include <qpa/qplatformintegration.h>
19
20#if QT_CONFIG(opengl)
21#import <QuartzCore/CAEAGLLayer.h>
22#endif
23
24#if QT_CONFIG(metal)
25#import <QuartzCore/CAMetalLayer.h>
26#endif
27
28#include <QtDebug>
29
31
32enum {
35};
36
39 , m_windowLevel(0)
40{
41 if (nativeHandle) {
42 m_view = reinterpret_cast<UIView *>(nativeHandle);
43 [m_view retain];
44 } else {
45#if QT_CONFIG(metal)
47 window->setSurfaceType(QSurface::MetalSurface);
48
50 m_view = [[QUIMetalView alloc] initWithQIOSWindow:this];
51 else
52#endif
53 m_view = [[QUIView alloc] initWithQIOSWindow:this];
54 }
55
56 connect(qGuiApp, &QGuiApplication::applicationStateChanged, this, &QIOSWindow::applicationStateChanged);
57
58 // Always set parent, even if we don't have a parent window,
59 // as we use setParent to reparent top levels into our desktop
60 // manager view.
62
63 if (!isForeignWindow()) {
64 // Resolve default window geometry in case it was not set before creating the
65 // platform window. This picks up eg. minimum-size if set.
68
69 setWindowState(window->windowStates());
72 } else {
73 // Pick up essential foreign window state
74 QPlatformWindow::setGeometry(QRectF::fromCGRect(m_view.frame).toRect());
75 }
76
77 Qt::ScreenOrientation initialOrientation = window->contentOrientation();
78 if (initialOrientation != Qt::PrimaryOrientation) {
79 // Start up in portrait, then apply possible content orientation,
80 // as per Apple's documentation.
81 dispatch_async(dispatch_get_main_queue(), ^{
82 handleContentOrientationChange(initialOrientation);
83 });
84 }
85}
86
88{
89 // According to the UIResponder documentation, Cocoa Touch should react to system interruptions
90 // that "might cause the view to be removed from the window" by sending touchesCancelled, but in
91 // practice this doesn't seem to happen when removing the view from its superview. To ensure that
92 // Qt's internal state for touch and mouse handling is kept consistent, we therefore have to force
93 // cancellation of all touch events.
94 [m_view touchesCancelled:[NSSet set] withEvent:0];
95
97
98 quiview_cast(m_view).platformWindow = nullptr;
99
100 // Remove from superview, unless we're a foreign window without a
101 // Qt window parent, in which case the foreign window is used as
102 // a window container for a Qt UI hierarchy inside a native UI.
104 [m_view removeFromSuperview];
105
106 [m_view release];
107}
108
109
111{
112 return window()->requestedFormat();
113}
114
115
116bool QIOSWindow::blockedByModal()
117{
118 QWindow *modalWindow = QGuiApplication::modalWindow();
119 return modalWindow && modalWindow != window();
120}
121
122void QIOSWindow::setVisible(bool visible)
123{
124 m_view.hidden = !visible;
125 [m_view setNeedsDisplay];
126
127 if (!isQtApplication() || !window()->isTopLevel())
128 return;
129
130 // Since iOS doesn't do window management the way a Qt application
131 // expects, we need to raise and activate windows ourselves:
132 if (visible)
133 updateWindowLevel();
134
135 if (blockedByModal()) {
136 if (visible)
137 raise();
138 return;
139 }
140
141 if (visible && shouldAutoActivateWindow()) {
142 if (!window()->property("_q_showWithoutActivating").toBool())
144 } else if (!visible && [quiview_cast(m_view) isActiveWindow]) {
145 // Our window was active/focus window but now hidden, so relinquish
146 // focus to the next possible window in the stack.
147 NSArray<UIView *> *subviews = m_view.viewController.view.subviews;
148 for (int i = int(subviews.count) - 1; i >= 0; --i) {
149 UIView *view = [subviews objectAtIndex:i];
150 if (view.hidden)
151 continue;
152
153 QWindow *w = view.qwindow;
154 if (!w || !w->isTopLevel())
155 continue;
156
157 QIOSWindow *iosWindow = static_cast<QIOSWindow *>(w->handle());
158 if (!iosWindow->shouldAutoActivateWindow())
159 continue;
160
161 iosWindow->requestActivateWindow();
162 break;
163 }
164 }
165}
166
168{
169 if (![m_view canBecomeFirstResponder])
170 return false;
171
172 // We don't want to do automatic window activation for popup windows
173 // that are unlikely to contain editable controls (to avoid hiding
174 // the keyboard while the popup is showing)
175 const Qt::WindowType type = window()->type();
176 return (type != Qt::Popup && type != Qt::ToolTip) || !window()->isActive();
177}
178
180{
181 m_view.alpha = qBound(0.0, level, 1.0);
182}
183
185{
186 m_normalGeometry = rect;
187
188 if (window()->windowState() != Qt::WindowNoState) {
190
191 // The layout will realize the requested geometry was not applied, and
192 // send geometry-change events that match the actual geometry.
193 [m_view setNeedsLayout];
194
195 if (window()->inherits("QWidgetWindow")) {
196 // QWidget wrongly assumes that setGeometry resets the window
197 // state back to Qt::NoWindowState, so we need to inform it that
198 // that his is not the case by re-issuing the current window state.
200
201 // It also needs to be told immediately that the geometry it requested
202 // did not apply, otherwise it will continue on as if it did, instead
203 // of waiting for a resize event.
204 [m_view layoutIfNeeded];
205 }
206
207 return;
208 }
209
210 applyGeometry(rect);
211}
212
213void QIOSWindow::applyGeometry(const QRect &rect)
214{
215 // Geometry changes are asynchronous, but QWindow::geometry() is
216 // expected to report back the 'requested geometry' until we get
217 // a callback with the updated geometry from the window system.
218 // The baseclass takes care of persisting this for us.
220
221 m_view.frame = rect.toCGRect();
222
223 // iOS will automatically trigger -[layoutSubviews:] for resize,
224 // but not for move, so we force it just in case.
225 [m_view setNeedsLayout];
226
227 if (window()->inherits("QWidgetWindow"))
228 [m_view layoutIfNeeded];
229}
230
232{
233 UIEdgeInsets safeAreaInsets = m_view.safeAreaInsets;
234 return QMargins(safeAreaInsets.left, safeAreaInsets.top,
235 safeAreaInsets.right, safeAreaInsets.bottom);
236}
237
239{
240 return qApp->applicationState() != Qt::ApplicationSuspended
241 && window()->isVisible() && !window()->geometry().isEmpty();
242}
243
244void QIOSWindow::setWindowState(Qt::WindowStates state)
245{
246 // Update the QWindow representation straight away, so that
247 // we can update the statusbar visibility based on the new
248 // state before applying geometry changes.
249 qt_window_private(window())->windowState = state;
250
251 if (window()->isTopLevel() && window()->isVisible() && window()->isActive())
252 [m_view.qtViewController updateProperties];
253
255 applyGeometry(QRect());
257 QRect uiWindowBounds = QRectF::fromCGRect(m_view.window.bounds).toRect();
258 if (NSProcessInfo.processInfo.iOSAppOnMac) {
259 // iOS apps running as "Designed for iPad" on macOS do not match
260 // our current window management implementation where a single
261 // UIWindow is tied to a single screen. And even if we're on the
262 // right screen, the UIScreen does not account for the 77% scale
263 // of the UIUserInterfaceIdiomPad environment, so we can't use
264 // it to clamp the window geometry. Instead just use the UIWindow
265 // directly, which represents our "screen".
266 applyGeometry(uiWindowBounds);
267 } else if (isRunningOnVisionOS()) {
268 // On visionOS there is no concept of a screen, and hence no concept of
269 // screen-relative system UI that we should keep top level windows away
270 // from, so don't apply the UIWindow safe area insets to the screen.
271 applyGeometry(uiWindowBounds);
272 } else {
273 QRect fullscreenGeometry = screen()->geometry();
274 QRect maximizedGeometry = fullscreenGeometry;
275
276#if !defined(Q_OS_VISIONOS)
278 // If the safe area margins reflect the screen's outer edges,
279 // then reduce the maximized geometry accordingly. Otherwise
280 // leave it as is, and assume the client will take the safe
281 // are margins into account explicitly.
282 UIScreen *uiScreen = m_view.window.windowScene.screen;
283 UIEdgeInsets safeAreaInsets = m_view.window.safeAreaInsets;
284 if (m_view.window.bounds.size.width == uiScreen.bounds.size.width)
285 maximizedGeometry.adjust(safeAreaInsets.left, 0, -safeAreaInsets.right, 0);
286 if (m_view.window.bounds.size.height == uiScreen.bounds.size.height)
287 maximizedGeometry.adjust(0, safeAreaInsets.top, 0, -safeAreaInsets.bottom);
288 }
289#endif
290
292 applyGeometry(fullscreenGeometry.intersected(uiWindowBounds));
293 else
294 applyGeometry(maximizedGeometry.intersected(uiWindowBounds));
295 }
296 } else {
297 applyGeometry(m_normalGeometry);
298 }
299}
300
301void QIOSWindow::setParent(const QPlatformWindow *parentWindow)
302{
303 UIView *superview = nullptr;
304 if (parentWindow)
305 superview = reinterpret_cast<UIView *>(parentWindow->winId());
306 else if (isQtApplication() && !isForeignWindow())
307 superview = rootViewForScreen(window()->screen());
308
309 if (superview)
310 [superview addSubview:m_view];
311 else if (quiview_cast(m_view.superview))
312 [m_view removeFromSuperview];
313}
314
316{
317 // Note that several windows can be active at the same time if they exist in the same
318 // hierarchy (transient children). But only one window can be QGuiApplication::focusWindow().
319 // Despite the name, 'requestActivateWindow' means raise and transfer focus to the window:
320 if (blockedByModal())
321 return;
322
323 [m_view.window makeKeyWindow];
324 [m_view becomeFirstResponder];
325
326 if (window()->isTopLevel())
327 raise();
328}
329
330void QIOSWindow::raiseOrLower(bool raise)
331{
332 if (!isQtApplication())
333 return;
334
335 NSArray<UIView *> *subviews = m_view.superview.subviews;
336 if (subviews.count == 1)
337 return;
338
339 if (m_view.superview == m_view.qtViewController.view) {
340 // We're a top level window, so we need to take window
341 // levels into account.
342 for (int i = int(subviews.count) - 1; i >= 0; --i) {
343 UIView *view = static_cast<UIView *>([subviews objectAtIndex:i]);
344 if (view.hidden || view == m_view || !view.qwindow)
345 continue;
346 int level = static_cast<QIOSWindow *>(view.qwindow->handle())->m_windowLevel;
347 if (m_windowLevel > level || (raise && m_windowLevel == level)) {
348 [m_view.superview insertSubview:m_view aboveSubview:view];
349 return;
350 }
351 }
352 [m_view.superview insertSubview:m_view atIndex:0];
353 } else {
354 // Child window, or embedded into a non-Qt view controller
355 if (raise)
356 [m_view.superview bringSubviewToFront:m_view];
357 else
358 [m_view.superview sendSubviewToBack:m_view];
359 }
360}
361
362void QIOSWindow::updateWindowLevel()
363{
364 Qt::WindowType type = window()->type();
365
366 if (type == Qt::ToolTip)
367 m_windowLevel = 120;
368 else if (window()->flags() & Qt::WindowStaysOnTopHint)
369 m_windowLevel = 100;
370 else if (window()->isModal())
371 m_windowLevel = 40;
372 else if (type == Qt::Popup)
373 m_windowLevel = 30;
374 else if (type == Qt::SplashScreen)
375 m_windowLevel = 20;
376 else if (type == Qt::Tool)
377 m_windowLevel = 10;
378 else
379 m_windowLevel = 0;
380
381 // A window should be in at least the same m_windowLevel as its parent:
382 QWindow *transientParent = window()->transientParent();
383 QIOSWindow *transientParentWindow = transientParent ? static_cast<QIOSWindow *>(transientParent->handle()) : 0;
384 if (transientParentWindow)
385 m_windowLevel = qMax(transientParentWindow->m_windowLevel, m_windowLevel);
386}
387
388void QIOSWindow::applicationStateChanged(Qt::ApplicationState)
389{
390 if (isForeignWindow())
391 return;
392
393 if (window()->isExposed() != isExposed())
394 [quiview_cast(m_view) sendUpdatedExposeEvent];
395}
396
398{
399 return m_view.contentScaleFactor;
400}
401
403{
404 if (isForeignWindow())
405 return;
406
408}
409
411{
412 static_cast<QIOSScreen *>(screen())->setUpdatesPaused(false);
413}
414
415void QIOSWindow::setMask(const QRegion &region)
416{
417 if (!region.isEmpty()) {
418 QCFType<CGMutablePathRef> maskPath = CGPathCreateMutable();
419 for (const QRect &r : region)
420 CGPathAddRect(maskPath, nullptr, r.toCGRect());
421 CAShapeLayer *maskLayer = [CAShapeLayer layer];
422 maskLayer.path = maskPath;
423 m_view.layer.mask = maskLayer;
424 } else {
425 m_view.layer.mask = nil;
426 }
427}
428
429#if QT_CONFIG(opengl)
430CAEAGLLayer *QIOSWindow::eaglLayer() const
431{
432 Q_ASSERT([m_view.layer isKindOfClass:[CAEAGLLayer class]]);
433 return static_cast<CAEAGLLayer *>(m_view.layer);
434}
435#endif
436
437#ifndef QT_NO_DEBUG_STREAM
439{
440 QDebugStateSaver saver(debug);
441 debug.nospace();
442 debug << "QIOSWindow(" << (const void *)window;
443 if (window)
444 debug << ", window=" << window->window();
445 debug << ')';
446 return debug;
447}
448#endif // !QT_NO_DEBUG_STREAM
449
467{
468 return qt_objc_cast<QUIView *>(view);
469}
470
472{
473 return ![m_view isKindOfClass:QUIView.class];
474}
475
476UIView *QIOSWindow::view() const
477{
478 return m_view;
479}
480
482
483#include "moc_qioswindow.cpp"
\inmodule QtCore
\inmodule QtCore
static QWindow * modalWindow()
Returns the most recently shown modal window.
void applicationStateChanged(Qt::ApplicationState state)
bool isForeignWindow() const override
bool shouldAutoActivateWindow() const
UIView * view() const
void setParent(const QPlatformWindow *window) override
This function is called to enable native child window in QPA.
void clearAccessibleCache()
void setWindowState(Qt::WindowStates state) override
Requests setting the window state of this surface to type.
void setGeometry(const QRect &rect) override
This function is called by Qt whenever a window is moved or resized using the QWindow API.
void requestUpdate() override
Requests an QEvent::UpdateRequest event.
void setMask(const QRegion &region) override
Reimplement to be able to let Qt set the mask of a window.
void setOpacity(qreal level) override
Reimplement to be able to let Qt set the opacity level of a window.
QIOSWindow(QWindow *window, WId nativeHandle=0)
Definition qioswindow.mm:37
qreal devicePixelRatio() const override
Reimplement this function in subclass to return the device pixel ratio for the window.
bool isExposed() const override
Returns if this window is exposed in the windowing system.
void raise() override
Reimplement to be able to let Qt raise windows to the top of the desktop.
Definition qioswindow.h:39
QMargins safeAreaMargins() const override
The safe area margins of a window represent the area that is safe to place content within,...
void setVisible(bool visible) override
Reimplemented in subclasses to show the surface if visible is true, and hide it if visible is false.
void requestActivateWindow() override
Reimplement to let Qt be able to request activation/focus for a window.
QSurfaceFormat format() const override
Returns the actual surface format of the window.
\inmodule QtCore
Definition qmargins.h:24
static QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *member, Qt::ConnectionType=Qt::AutoConnection)
\threadsafe
Definition qobject.cpp:2960
bool inherits(const char *classname) const
Returns true if this object is an instance of a class that inherits className or a QObject subclass t...
Definition qobject.h:348
virtual QRect geometry() const =0
Reimplement in subclass to return the pixel geometry of the screen.
The QPlatformWindow class provides an abstraction for top-level windows.
QWindow * window() const
Returns the window which belongs to the QPlatformWindow.
QPlatformScreen * screen() const override
Returns the platform screen handle corresponding to this platform window, or null if the window is no...
QPlatformWindow * parent() const
Returns the parent platform window (or \nullptr if orphan).
virtual void handleContentOrientationChange(Qt::ScreenOrientation orientation)
Handle changes to the orientation of the platform window's contents.
virtual void setGeometry(const QRect &rect)
This function is called by Qt whenever a window is moved or resized using the QWindow API.
virtual QRect geometry() const
Returns the current geometry of a window.
virtual bool isActive() const
Returns true if the window should appear active from a style perspective.
virtual WId winId() const
Reimplement in subclasses to return a handle to the native window.
static QRect initialGeometry(const QWindow *w, const QRect &initialGeometry, int defaultWidth, int defaultHeight, const QScreen **resultingScreenReturn=nullptr)
Helper function to get initial geometry on windowing systems which do not do smart positioning and al...
\inmodule QtCore\reentrant
Definition qrect.h:30
constexpr bool isEmpty() const noexcept
Returns true if the rectangle is empty, otherwise returns false.
Definition qrect.h:167
The QRegion class specifies a clip region for a painter.
Definition qregion.h:27
bool isEmpty() const
Returns true if the region is empty; otherwise returns false.
The QSurfaceFormat class represents the format of a QSurface. \inmodule QtGui.
@ RasterSurface
Definition qsurface.h:31
@ MetalSurface
Definition qsurface.h:36
static void handleWindowStateChanged(QWindow *window, Qt::WindowStates newState, int oldState=-1)
\inmodule QtGui
Definition qwindow.h:63
SurfaceType surfaceType() const override
Returns the surface type of the window.
Definition qwindow.cpp:665
Qt::ScreenOrientation contentOrientation
the orientation of the window's contents
Definition qwindow.h:95
qreal opacity
The opacity of the window in the windowing system.
Definition qwindow.h:96
rect
[4]
else opt state
[0]
QRegion toNativeLocalRegion(const QRegion &pointRegion, const QWindow *window)
Combined button and popup list for selecting options.
@ WindowFullScreen
Definition qnamespace.h:255
@ WindowNoState
Definition qnamespace.h:252
@ WindowMinimized
Definition qnamespace.h:253
@ WindowMaximized
Definition qnamespace.h:254
ScreenOrientation
Definition qnamespace.h:271
@ PrimaryOrientation
Definition qnamespace.h:272
ApplicationState
Definition qnamespace.h:262
@ ApplicationSuspended
Definition qnamespace.h:263
WindowType
Definition qnamespace.h:205
@ ToolTip
Definition qnamespace.h:213
@ Popup
Definition qnamespace.h:211
@ SplashScreen
Definition qnamespace.h:214
@ WindowStaysOnTopHint
Definition qnamespace.h:233
@ MaximizeUsingFullscreenGeometryHint
Definition qnamespace.h:237
@ Tool
Definition qnamespace.h:212
@ defaultWindowHeight
@ defaultWindowWidth
#define qApp
EGLOutputLayerEXT layer
#define qGuiApp
bool isQtApplication()
Definition qiosglobal.mm:20
UIView * rootViewForScreen(QScreen *)
bool isRunningOnVisionOS()
Definition qiosglobal.mm:31
QDebug operator<<(QDebug debug, const QIOSWindow *window)
@ defaultWindowHeight
Definition qioswindow.mm:34
@ defaultWindowWidth
Definition qioswindow.mm:33
QUIView * quiview_cast(UIView *view)
Returns the view cast to a QUIview if possible.
constexpr const T & qBound(const T &min, const T &val, const T &max)
Definition qminmax.h:44
constexpr const T & qMax(const T &a, const T &b)
Definition qminmax.h:42
GLenum GLuint GLint level
GLfloat GLfloat GLfloat w
[0]
GLboolean r
[2]
GLenum type
GLbitfield flags
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
double qreal
Definition qtypes.h:187
Q_GUI_EXPORT QWindowPrivate * qt_window_private(QWindow *window)
Definition qwindow.cpp:2950
const char property[13]
Definition qwizard.cpp:101
QFuture< QSet< QChar > > set
[10]
sem release()
edit isVisible()
aWidget window() -> setWindowTitle("New Window Title")
[2]
QQuickView * view
[0]
QT_BEGIN_NAMESPACE bool toBool(const QString &str)
Definition utils.h:14