Qt 6.x
The Qt SDK
Loading...
Searching...
No Matches
quiview.mm
Go to the documentation of this file.
1// Copyright (C) 2021 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 "quiview.h"
5
6#include "qiosglobal.h"
7#include "qiosintegration.h"
9#include "qiostextresponder.h"
10#include "qiosscreen.h"
11#include "qioswindow.h"
12#include "qiosinputcontext.h"
13#ifndef Q_OS_TVOS
14#include "qiosmenu.h"
15#endif
16
17#include <QtCore/qmath.h>
18#include <QtGui/qpointingdevice.h>
19#include <QtGui/private/qguiapplication_p.h>
20#include <QtGui/private/qwindow_p.h>
21#include <QtGui/private/qapplekeymapper_p.h>
22#include <qpa/qwindowsysteminterface_p.h>
23
24Q_LOGGING_CATEGORY(lcQpaTablet, "qt.qpa.input.tablet")
25Q_LOGGING_CATEGORY(lcQpaInputEvents, "qt.qpa.input.events")
26
27namespace {
28inline ulong getTimeStamp(UIEvent *event)
29{
30#if TARGET_OS_SIMULATOR == 1
31 // We currently build Qt for simulator using X86_64, even on ARM based macs.
32 // This results in the simulator running on ARM, while the app is running
33 // inside it using Rosetta. And with this combination, the event.timestamp, which is
34 // documented to be in seconds, looks to be something else, and is not progressing
35 // in sync with a normal clock.
36 // Sending out mouse events with a timestamp that doesn't follow normal clock time
37 // will cause problems for mouse-, and pointer handlers that uses them to e.g calculate
38 // the time between a press and release, and to decide if the user is performing a tap
39 // or a drag.
40 // For that reason, we choose to ignore UIEvent.timestamp under the mentioned condition, and
41 // instead rely on NSProcessInfo. Note that if we force the whole simulator to use Rosetta
42 // (and not only the Qt app), the timestamps will progress normally.
43#if defined(Q_PROCESSOR_ARM)
44 #warning The timestamp work-around for x86_64 can (probably) be removed when building for ARM
45#endif
46 return ulong(NSProcessInfo.processInfo.systemUptime * 1000);
47#endif
48
49 return ulong(event.timestamp * 1000);
50}
51}
52
53@implementation QUIView {
57 NSMutableArray<UIAccessibilityElement *> *m_accessibleElements;
58 UIPanGestureRecognizer *m_scrollGestureRecognizer;
61}
62
63+ (void)load
64{
65#ifndef Q_OS_TVOS
67 // iOS 11 handles this though [UIView safeAreaInsetsDidChange], but there's no signal for
68 // the corresponding top and bottom layout guides that we use on earlier versions. Note
69 // that we use the _will_ change version of the notification, because we want to react
70 // to the change as early was possible. But since the top and bottom layout guides have
71 // not been updated at this point we use asynchronous delivery of the event, so that the
72 // event is processed by QtGui just after iOS has updated the layout margins.
73 [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillChangeStatusBarFrameNotification
74 object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *) {
75 for (QWindow *window : QGuiApplication::allWindows())
76 QWindowSystemInterface::handleSafeAreaMarginsChanged<QWindowSystemInterface::AsynchronousDelivery>(window);
77 }
78 ];
79 }
80#endif
81}
82
83+ (Class)layerClass
84{
85#if QT_CONFIG(opengl)
86 return [CAEAGLLayer class];
87#endif
88 return [super layerClass];
89}
90
91- (instancetype)initWithQIOSWindow:(QT_PREPEND_NAMESPACE(QIOSWindow) *)window
92{
93 if (self = [self initWithFrame:window->geometry().toCGRect()]) {
94 self.platformWindow = window;
95 m_accessibleElements = [[NSMutableArray<UIAccessibilityElement *> alloc] init];
96 m_scrollGestureRecognizer = [[UIPanGestureRecognizer alloc]
97 initWithTarget:self
98 action:@selector(handleScroll:)];
99 // The gesture recognizer should only care about scroll gestures (for now)
100 // Set allowedTouchTypes to empty array here to not interfere with touch events
101 // handled by the UIView. Scroll gestures, even those coming from touch devices,
102 // such as trackpads will still be received as they are not touch events
103 m_scrollGestureRecognizer.allowedTouchTypes = [NSArray array];
104 if (@available(ios 13.4, *)) {
105 m_scrollGestureRecognizer.allowedScrollTypesMask = UIScrollTypeMaskAll;
106 }
107 m_scrollGestureRecognizer.maximumNumberOfTouches = 0;
108 m_lastScrollDelta = CGPointZero;
109 m_lastScrollCursorPos = CGPointZero;
110 [self addGestureRecognizer:m_scrollGestureRecognizer];
111
112 if ([self.layer isKindOfClass:CAMetalLayer.class]) {
113 QWindow *window = self.platformWindow->window();
114 if (QColorSpace colorSpace = window->format().colorSpace(); colorSpace.isValid()) {
115 QCFType<CFDataRef> iccData = colorSpace.iccProfile().toCFData();
116 QCFType<CGColorSpaceRef> cgColorSpace = CGColorSpaceCreateWithICCData(iccData);
117 CAMetalLayer *metalLayer = static_cast<CAMetalLayer *>(self.layer);
118 metalLayer.colorspace = cgColorSpace;
119 qCDebug(lcQpaWindow) << "Set" << self << "color space to" << metalLayer.colorspace;
120 }
121 }
122 }
123
124 return self;
125}
126
127- (instancetype)initWithFrame:(CGRect)frame
128{
129 if ((self = [super initWithFrame:frame])) {
130#if QT_CONFIG(opengl)
131 if ([self.layer isKindOfClass:[CAEAGLLayer class]]) {
132 // Set up EAGL layer
133 CAEAGLLayer *eaglLayer = static_cast<CAEAGLLayer *>(self.layer);
134 eaglLayer.opaque = TRUE;
135 eaglLayer.drawableProperties = @{
136 kEAGLDrawablePropertyRetainedBacking: @(YES),
137 kEAGLDrawablePropertyColorFormat: kEAGLColorFormatRGBA8
138 };
139 }
140#endif
141
142 if (isQtApplication())
143 self.hidden = YES;
144
145#ifndef Q_OS_TVOS
146 self.multipleTouchEnabled = YES;
147#endif
148
149 if (qEnvironmentVariableIntValue("QT_IOS_DEBUG_WINDOW_MANAGEMENT")) {
150 static CGFloat hue = 0.0;
151 CGFloat lastHue = hue;
152 for (CGFloat diff = 0; diff < 0.1 || diff > 0.9; diff = fabs(hue - lastHue))
153 hue = drand48();
154
155 #define colorWithBrightness(br) \
156 [UIColor colorWithHue:hue saturation:0.5 brightness:br alpha:1.0].CGColor
157
158 self.layer.borderColor = colorWithBrightness(1.0);
159 self.layer.borderWidth = 1.0;
160 }
161
162 if (qEnvironmentVariableIsSet("QT_IOS_DEBUG_WINDOW_SAFE_AREAS")) {
163 UIView *safeAreaOverlay = [[UIView alloc] initWithFrame:CGRectZero];
164 [safeAreaOverlay setBackgroundColor:[UIColor colorWithRed:0.3 green:0.7 blue:0.9 alpha:0.3]];
165 [self addSubview:safeAreaOverlay];
166
167 safeAreaOverlay.translatesAutoresizingMaskIntoConstraints = NO;
168 [safeAreaOverlay.topAnchor constraintEqualToAnchor:self.safeAreaLayoutGuide.topAnchor].active = YES;
169 [safeAreaOverlay.leftAnchor constraintEqualToAnchor:self.safeAreaLayoutGuide.leftAnchor].active = YES;
170 [safeAreaOverlay.rightAnchor constraintEqualToAnchor:self.safeAreaLayoutGuide.rightAnchor].active = YES;
171 [safeAreaOverlay.bottomAnchor constraintEqualToAnchor:self.safeAreaLayoutGuide.bottomAnchor].active = YES;
172 }
173 }
174
175 return self;
176}
177
178- (void)dealloc
179{
180 [m_accessibleElements release];
181 [m_scrollGestureRecognizer release];
182
183 [super dealloc];
184}
185
186- (NSString *)description
187{
188 NSMutableString *description = [NSMutableString stringWithString:[super description]];
189
190#ifndef QT_NO_DEBUG_STREAM
191 QString platformWindowDescription;
192 QDebug debug(&platformWindowDescription);
193 debug.nospace() << "; " << self.platformWindow << ">";
194 NSRange lastCharacter = [description rangeOfComposedCharacterSequenceAtIndex:description.length - 1];
195 [description replaceCharactersInRange:lastCharacter withString:platformWindowDescription.toNSString()];
196#endif
197
198 return description;
199}
200
201- (void)willMoveToWindow:(UIWindow *)newWindow
202{
203 // UIKIt will normally set the scale factor of a view to match the corresponding
204 // screen scale factor, but views backed by CAEAGLLayers need to do this manually.
205 self.contentScaleFactor = newWindow && newWindow.screen ?
206 newWindow.screen.scale : [[UIScreen mainScreen] scale];
207
208 // FIXME: Allow the scale factor to be customized through QSurfaceFormat.
209}
210
211- (void)didAddSubview:(UIView *)subview
212{
213 if ([subview isKindOfClass:[QUIView class]])
214 self.clipsToBounds = YES;
215}
216
217- (void)willRemoveSubview:(UIView *)subview
218{
219 for (UIView *view in self.subviews) {
220 if (view != subview && [view isKindOfClass:[QUIView class]])
221 return;
222 }
223
224 self.clipsToBounds = NO;
225}
226
227- (void)setNeedsDisplay
228{
229 [super setNeedsDisplay];
230
231 // We didn't implement drawRect: so we have to manually
232 // mark the layer as needing display.
233 [self.layer setNeedsDisplay];
234}
235
236- (void)layoutSubviews
237{
238 // This method is the de facto way to know that view has been resized,
239 // or otherwise needs invalidation of its buffers. Note though that we
240 // do not get this callback when the view just changes its position, so
241 // the position of our QWindow (and platform window) will only get updated
242 // when the size is also changed.
243
244 if (!CGAffineTransformIsIdentity(self.transform))
245 qWarning() << self << "has a transform set. This is not supported.";
246
247 QWindow *window = self.platformWindow->window();
248 QRect lastReportedGeometry = qt_window_private(window)->geometry;
249 QRect currentGeometry = QRectF::fromCGRect(self.frame).toRect();
250 qCDebug(lcQpaWindow) << self.platformWindow << "new geometry is" << currentGeometry;
252
253 if (currentGeometry.size() != lastReportedGeometry.size()) {
254 // Trigger expose event on resize
255 [self setNeedsDisplay];
256
257 // A new size means we also need to resize the FBO's corresponding buffers,
258 // but we defer that to when the application calls makeCurrent.
259 }
260}
261
262- (void)displayLayer:(CALayer *)layer
263{
265 Q_ASSERT(layer == self.layer);
266
267 [self sendUpdatedExposeEvent];
268}
269
270- (void)sendUpdatedExposeEvent
271{
272 QRegion region;
273
274 if (self.platformWindow->isExposed()) {
275 QSize bounds = QRectF::fromCGRect(self.layer.bounds).toRect().size();
276
277 Q_ASSERT(self.platformWindow->geometry().size() == bounds);
278 Q_ASSERT(self.hidden == !self.platformWindow->window()->isVisible());
279
280 region = QRect(QPoint(), bounds);
281 }
282
283 qCDebug(lcQpaWindow) << self.platformWindow << region << "isExposed" << self.platformWindow->isExposed();
284 QWindowSystemInterface::handleExposeEvent(self.platformWindow->window(), region);
285}
286
287- (void)safeAreaInsetsDidChange
288{
290}
291
292// -------------------------------------------------------------------------
293
294- (BOOL)canBecomeFirstResponder
295{
296 return !(self.platformWindow->window()->flags() & (Qt::WindowDoesNotAcceptFocus
298}
299
300- (BOOL)becomeFirstResponder
301{
302 {
303 // Scope for the duration of becoming first responder only, as the window
304 // activation event may trigger new responders, which we don't want to be
305 // blocked by this guard.
306 FirstResponderCandidate firstResponderCandidate(self);
307
308 qImDebug() << "self:" << self << "first:" << [UIResponder currentFirstResponder];
309
310 if (![super becomeFirstResponder]) {
311 qImDebug() << self << "was not allowed to become first responder";
312 return NO;
313 }
314
315 qImDebug() << self << "became first responder";
316 }
317
318 if (qGuiApp->focusWindow() != self.platformWindow->window())
320 else
321 qImDebug() << self.platformWindow->window() << "already active, not sending window activation";
322
323 return YES;
324}
325
326- (BOOL)responderShouldTriggerWindowDeactivation:(UIResponder *)responder
327{
328 // We don't want to send window deactivation in case the resign
329 // was a result of another Qt window becoming first responder.
330 if ([responder isKindOfClass:[QUIView class]])
331 return NO;
332
333 // Nor do we want to deactivate the Qt window if the new responder
334 // is temporarily handling text input on behalf of a Qt window.
335 if ([responder isKindOfClass:[QIOSTextResponder class]]) {
336 while ((responder = [responder nextResponder])) {
337 if ([responder isKindOfClass:[QUIView class]])
338 return NO;
339 }
340 }
341
342 return YES;
343}
344
345- (BOOL)resignFirstResponder
346{
347 qImDebug() << "self:" << self << "first:" << [UIResponder currentFirstResponder];
348
349 if (![super resignFirstResponder])
350 return NO;
351
352 qImDebug() << self << "resigned first responder";
353
354 UIResponder *newResponder = FirstResponderCandidate::currentCandidate();
355 if ([self responderShouldTriggerWindowDeactivation:newResponder])
357
358 return YES;
359}
360
361- (BOOL)isActiveWindow
362{
363 // Normally this is determined exclusivly by being firstResponder, but
364 // since we employ a separate first responder for text input we need to
365 // handle both cases as this view being the active Qt window.
366
367 if ([self isFirstResponder])
368 return YES;
369
370 UIResponder *firstResponder = [UIResponder currentFirstResponder];
371 if ([firstResponder isKindOfClass:[QIOSTextInputResponder class]]
372 && [firstResponder nextResponder] == self)
373 return YES;
374
375 return NO;
376}
377
378// -------------------------------------------------------------------------
379
380- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection
381{
382 [super traitCollectionDidChange: previousTraitCollection];
383
385 QPointingDevice::Capabilities touchCapabilities = touchDevice->capabilities();
386
387 touchCapabilities.setFlag(QPointingDevice::Capability::Pressure,
388 (self.traitCollection.forceTouchCapability == UIForceTouchCapabilityAvailable));
389
390 touchDevice->setCapabilities(touchCapabilities);
391}
392
393-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
394{
395 if (self.platformWindow->window()->flags() & Qt::WindowTransparentForInput)
396 return NO;
397 return [super pointInside:point withEvent:event];
398}
399
400- (void)handleTouches:(NSSet *)touches withEvent:(UIEvent *)event withState:(QEventPoint::State)state withTimestamp:(ulong)timeStamp
401{
402 QIOSIntegration *iosIntegration = QIOSIntegration::instance();
404
405#if QT_CONFIG(tabletevent)
406 if (m_activePencilTouch && [touches containsObject:m_activePencilTouch]) {
407 NSArray<UITouch *> *cTouches = [event coalescedTouchesForTouch:m_activePencilTouch];
408 int i = 0;
409 for (UITouch *cTouch in cTouches) {
410 QPointF localViewPosition = QPointF::fromCGPoint([cTouch preciseLocationInView:self]);
411 QPoint localViewPositionI = localViewPosition.toPoint();
412 QPointF globalScreenPosition = self.platformWindow->mapToGlobal(localViewPositionI) +
413 (localViewPosition - localViewPositionI);
414 qreal pressure = cTouch.force / cTouch.maximumPossibleForce;
415 // azimuth unit vector: +x to the right, +y going downwards
416 CGVector azimuth = [cTouch azimuthUnitVectorInView: self];
417 // azimuthAngle given in radians, zero when the stylus points towards +x axis; converted to degrees with 0 pointing straight up
418 qreal azimuthAngle = qRadiansToDegrees([cTouch azimuthAngleInView: self]) + 90;
419 // altitudeAngle given in radians, pi / 2 is with the stylus perpendicular to the iPad, smaller values mean more tilted, but never negative.
420 // Convert to degrees with zero being perpendicular.
421 qreal altitudeAngle = 90 - qRadiansToDegrees(cTouch.altitudeAngle);
422 qCDebug(lcQpaTablet) << i << ":" << timeStamp << localViewPosition << pressure << state << "azimuth" << azimuth.dx << azimuth.dy
423 << "angle" << azimuthAngle << "altitude" << cTouch.altitudeAngle
424 << "xTilt" << qBound(-60.0, altitudeAngle * azimuth.dx, 60.0) << "yTilt" << qBound(-60.0, altitudeAngle * azimuth.dy, 60.0);
425 QWindowSystemInterface::handleTabletEvent(self.platformWindow->window(), timeStamp, localViewPosition, globalScreenPosition,
426 // device, pointerType, buttons
428 // pressure, xTilt, yTilt
429 pressure, qBound(-60.0, altitudeAngle * azimuth.dx, 60.0), qBound(-60.0, altitudeAngle * azimuth.dy, 60.0),
430 // tangentialPressure, rotation, z, uid, modifiers
431 0, azimuthAngle, 0, 0, Qt::NoModifier);
432 ++i;
433 }
434 }
435#endif
436
437 if (m_activeTouches.isEmpty())
438 return;
439 for (auto it = m_activeTouches.begin(); it != m_activeTouches.end(); ++it) {
440 auto hash = it.key();
441 QWindowSystemInterface::TouchPoint &touchPoint = it.value();
442 UITouch *uiTouch = nil;
443 for (UITouch *touch in touches) {
444 if (touch.hash == hash) {
445 uiTouch = touch;
446 break;
447 }
448 }
449 if (!uiTouch) {
451 } else {
452 touchPoint.state = state;
453
454 // Touch positions are expected to be in QScreen global coordinates, and
455 // as we already have the QWindow positioned at the right place, we can
456 // just map from the local view position to global coordinates.
457 // tvOS: all touches start at the center of the screen and move from there.
458 QPoint localViewPosition = QPointF::fromCGPoint([uiTouch locationInView:self]).toPoint();
459 QPoint globalScreenPosition = self.platformWindow->mapToGlobal(localViewPosition);
460
461 touchPoint.area = QRectF(globalScreenPosition, QSize(0, 0));
462
463 // FIXME: Do we really need to support QPointingDevice::Capability::NormalizedPosition?
464 QSize screenSize = self.platformWindow->screen()->geometry().size();
465 touchPoint.normalPosition = QPointF(globalScreenPosition.x() / screenSize.width(),
466 globalScreenPosition.y() / screenSize.height());
467
468 if (supportsPressure) {
469 // Note: iOS will deliver touchesBegan with a touch force of 0, which
470 // we will reflect/propagate as a 0 pressure, but there is no clear
471 // alternative, as we don't want to wait for a touchedMoved before
472 // sending a touch press event to Qt, just to have a valid pressure.
473 touchPoint.pressure = uiTouch.force / uiTouch.maximumPossibleForce;
474 } else {
475 // We don't claim that our touch device supports QPointingDevice::Capability::Pressure,
476 // but fill in a meaningful value in case clients use it anyway.
477 touchPoint.pressure = (state == QEventPoint::State::Released) ? 0.0 : 1.0;
478 }
479 }
480 }
481
482 if ([self.window isKindOfClass:[QUIWindow class]] &&
483 !static_cast<QUIWindow *>(self.window).sendingEvent) {
484 // The event is likely delivered as part of delayed touch delivery, via
485 // _UIGestureEnvironmentSortAndSendDelayedTouches, due to one of the two
486 // _UISystemGestureGateGestureRecognizer instances on the top level window
487 // having its delaysTouchesBegan set to YES. During this delivery, it's not
488 // safe to spin up a recursive event loop, as our calling function is not
489 // reentrant, so any gestures used by the recursive code, e.g. a native
490 // alert dialog, will fail to recognize. To be on the safe side, we deliver
491 // the event asynchronously.
492 QWindowSystemInterface::handleTouchEvent<QWindowSystemInterface::AsynchronousDelivery>(
493 self.platformWindow->window(), timeStamp, iosIntegration->touchDevice(), m_activeTouches.values());
494 } else {
495 // Send the touch event asynchronously, as the application might spin a recursive
496 // event loop in response to the touch event (a dialog e.g.), which will deadlock
497 // the UIKit event delivery system (QTBUG-98651).
498 QWindowSystemInterface::handleTouchEvent<QWindowSystemInterface::AsynchronousDelivery>(
499 self.platformWindow->window(), timeStamp, iosIntegration->touchDevice(), m_activeTouches.values());
500 }
501}
502
503- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
504{
505 // UIKit generates [Began -> Moved -> Ended] event sequences for
506 // each touch point. Internally we keep a hashmap of active UITouch
507 // points to QWindowSystemInterface::TouchPoints, and assigns each TouchPoint
508 // an id for use by Qt.
509 for (UITouch *touch in touches) {
510#if QT_CONFIG(tabletevent)
511 if (touch.type == UITouchTypeStylus) {
513 qWarning("ignoring additional Pencil while first is still active");
514 continue;
515 }
516 m_activePencilTouch = touch;
517 } else
518 {
519 Q_ASSERT(!m_activeTouches.contains(touch.hash));
520#endif
521 m_activeTouches[touch.hash].id = m_nextTouchId++;
522#if QT_CONFIG(tabletevent)
523 }
524#endif
525 }
526
527 if (self.platformWindow->shouldAutoActivateWindow() && m_activeTouches.size() == 1) {
528 QPlatformWindow *topLevel = self.platformWindow;
529 while (QPlatformWindow *p = topLevel->parent())
530 topLevel = p;
531 if (topLevel->window() != QGuiApplication::focusWindow())
532 topLevel->requestActivateWindow();
533 }
534
535 [self handleTouches:touches withEvent:event withState:QEventPoint::State::Pressed withTimestamp:getTimeStamp(event)];
536}
537
538- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
539{
540 [self handleTouches:touches withEvent:event withState:QEventPoint::State::Updated withTimestamp:getTimeStamp(event)];
541}
542
543- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
544{
545 [self handleTouches:touches withEvent:event withState:QEventPoint::State::Released withTimestamp:getTimeStamp(event)];
546
547 // Remove ended touch points from the active set:
548#ifndef Q_OS_TVOS
549 for (UITouch *touch in touches) {
550#if QT_CONFIG(tabletevent)
551 if (touch.type == UITouchTypeStylus) {
553 } else
554#endif
555 {
556 m_activeTouches.remove(touch.hash);
557 }
558 }
559#else
560 // tvOS only supports single touch
561 m_activeTouches.clear();
562#endif
563
564 if (m_activeTouches.isEmpty() && !m_activePencilTouch)
565 m_nextTouchId = 0;
566}
567
568- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
569{
570 if (m_activeTouches.isEmpty() && !m_activePencilTouch)
571 return;
572
573 // When four-finger swiping, we get a touchesCancelled callback
574 // which includes all four touch points. The swipe gesture is
575 // then active until all four touches have been released, and
576 // we start getting touchesBegan events again.
577
578 // When five-finger pinching, we also get a touchesCancelled
579 // callback with all five touch points, but the pinch gesture
580 // ends when the second to last finger is released from the
581 // screen. The last finger will not emit any more touch
582 // events, _but_, will contribute to starting another pinch
583 // gesture. That second pinch gesture will _not_ trigger a
584 // touchesCancelled event when starting, but as each finger
585 // is released, and we may get touchesMoved events for the
586 // remaining fingers. [event allTouches] also contains one
587 // less touch point than it should, so this behavior is
588 // likely a bug in the iOS system gesture recognizer, but we
589 // have to take it into account when maintaining the Qt state.
590 // We do this by assuming that there are no cases where a
591 // sub-set of the active touch events are intentionally cancelled.
592
593 NSInteger count = static_cast<NSInteger>([touches count]);
594 if (count != 0 && count != m_activeTouches.count() && !m_activePencilTouch)
595 qWarning("Subset of active touches cancelled by UIKit");
596
597 m_activeTouches.clear();
598 m_nextTouchId = 0;
600
601 ulong timestamp = event ? getTimeStamp(event) : ([[NSProcessInfo processInfo] systemUptime] * 1000);
602
604
605 // Send the touch event asynchronously, as the application might spin a recursive
606 // event loop in response to the touch event (a dialog e.g.), which will deadlock
607 // the UIKit event delivery system (QTBUG-98651).
608 QWindowSystemInterface::handleTouchCancelEvent<QWindowSystemInterface::AsynchronousDelivery>(
609 self.platformWindow->window(), timestamp, iosIntegration->touchDevice());
610}
611
612- (int)mapPressTypeToKey:(UIPress*)press withModifiers:(Qt::KeyboardModifiers)qtModifiers text:(QString &)text
613{
614 switch (press.type) {
615 case UIPressTypeUpArrow: return Qt::Key_Up;
616 case UIPressTypeDownArrow: return Qt::Key_Down;
617 case UIPressTypeLeftArrow: return Qt::Key_Left;
618 case UIPressTypeRightArrow: return Qt::Key_Right;
619 case UIPressTypeSelect: return Qt::Key_Select;
620 case UIPressTypeMenu: return Qt::Key_Menu;
621 case UIPressTypePlayPause: return Qt::Key_MediaTogglePlayPause;
622 }
623 if (@available(ios 13.4, *)) {
624 NSString *charactersIgnoringModifiers = press.key.charactersIgnoringModifiers;
625 Qt::Key key = QAppleKeyMapper::fromUIKitKey(charactersIgnoringModifiers);
626 if (key != Qt::Key_unknown)
627 return key;
628 return QAppleKeyMapper::fromNSString(qtModifiers, press.key.characters,
629 charactersIgnoringModifiers, text);
630 }
631 return Qt::Key_unknown;
632}
633
634- (bool)isControlKey:(Qt::Key)key
635{
636 switch (key) {
637 case Qt::Key_Up:
638 case Qt::Key_Down:
639 case Qt::Key_Left:
640 case Qt::Key_Right:
641 return true;
642 default:
643 break;
644 }
645
646 return false;
647}
648
649- (bool)handlePresses:(NSSet<UIPress *> *)presses eventType:(QEvent::Type)type
650{
651 // Presses on Menu button will generate a Menu key event. By default, not handling
652 // this event will cause the application to return to Headboard (tvOS launcher).
653 // When handling the event (for example, as a back button), both press and
654 // release events must be handled accordingly.
655 if (!qApp->focusWindow())
656 return false;
657
658 bool eventHandled = false;
659 const bool imEnabled = QIOSInputContext::instance()->inputMethodAccepted();
660
661 for (UIPress* press in presses) {
662 Qt::KeyboardModifiers qtModifiers = Qt::NoModifier;
663 if (@available(ios 13.4, *))
664 qtModifiers = QAppleKeyMapper::fromUIKitModifiers(press.key.modifierFlags);
666 int key = [self mapPressTypeToKey:press withModifiers:qtModifiers text:text];
667 if (key == Qt::Key_unknown)
668 continue;
669 if (imEnabled && ![self isControlKey:Qt::Key(key)])
670 continue;
671
673 self.platformWindow->window(), type, key, qtModifiers, text);
674 eventHandled = eventHandled || keyHandled;
675 }
676
677 return eventHandled;
678}
679
680- (void)pressesBegan:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event
681{
682 if (![self handlePresses:presses eventType:QEvent::KeyPress])
683 [super pressesBegan:presses withEvent:event];
684}
685
686- (void)pressesChanged:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event
687{
688 if (![self handlePresses:presses eventType:QEvent::KeyPress])
689 [super pressesChanged:presses withEvent:event];
690 [super pressesChanged:presses withEvent:event];
691}
692
693- (void)pressesEnded:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event
694{
695 if (![self handlePresses:presses eventType:QEvent::KeyRelease])
696 [super pressesEnded:presses withEvent:event];
697 [super pressesEnded:presses withEvent:event];
698}
699
700- (BOOL)canPerformAction:(SEL)action withSender:(id)sender
701{
702#ifndef Q_OS_TVOS
703 // Check first if QIOSMenu should handle the action before continuing up the responder chain
704 return [QIOSMenu::menuActionTarget() targetForAction:action withSender:sender] != 0;
705#else
706 Q_UNUSED(action);
707 Q_UNUSED(sender);
708 return false;
709#endif
710}
711
712- (id)forwardingTargetForSelector:(SEL)selector
713{
715#ifndef Q_OS_TVOS
717#else
718 return nil;
719#endif
720}
721
722- (void)addInteraction:(id<UIInteraction>)interaction
723{
724 if ([NSStringFromClass(interaction.class) isEqualToString:@"UITextInteraction"])
725 return;
726
727 [super addInteraction:interaction];
728}
729
730- (UIEditingInteractionConfiguration)editingInteractionConfiguration
731{
732 // We only want the three-finger-tap edit menu to be available when there's
733 // actually something to edit. Otherwise the OS will cause a slight delay
734 // before delivering the release of three finger touch input. Note that we
735 // do not do any hit testing here to check that the focus object is the one
736 // being tapped, as the behavior of native iOS apps is to trigger the menu
737 // regardless of where the gesture is being made.
739 UIEditingInteractionConfigurationDefault : UIEditingInteractionConfigurationNone;
740}
741
742#if QT_CONFIG(wheelevent)
743- (void)handleScroll:(UIPanGestureRecognizer *)recognizer
744{
745 if (!self.platformWindow->window())
746 return;
747
748 if (!self.canBecomeFirstResponder)
749 return;
750
751 CGPoint translation = [recognizer translationInView:self];
752 CGFloat deltaX = translation.x - m_lastScrollDelta.x;
753 CGFloat deltaY = translation.y - m_lastScrollDelta.y;
754
755 QPoint angleDelta;
756 // From QNSView implementation:
757 // "Since deviceDelta is delivered as pixels rather than degrees, we need to
758 // convert from pixels to degrees in a sensible manner.
759 // It looks like 1/4 degrees per pixel behaves most native.
760 // (NB: Qt expects the unit for delta to be 8 per degree):"
761 const int pixelsToDegrees = 2; // 8 * 1/4
762 angleDelta.setX(deltaX * pixelsToDegrees);
763 angleDelta.setY(deltaY * pixelsToDegrees);
764
765 QPoint pixelDelta;
766 pixelDelta.setX(deltaX);
767 pixelDelta.setY(deltaY);
768
769 NSTimeInterval time_stamp = [[NSProcessInfo processInfo] systemUptime];
770 ulong qt_timestamp = time_stamp * 1000;
771
772 Qt::KeyboardModifiers qt_modifierFlags = Qt::NoModifier;
773 if (@available(ios 13.4, *))
774 qt_modifierFlags = QAppleKeyMapper::fromUIKitModifiers(recognizer.modifierFlags);
775
776 if (recognizer.state == UIGestureRecognizerStateBegan)
777 // locationInView: doesn't return the cursor position at the time of the wheel event,
778 // but rather gives us the position with the deltas applied, so we need to save the
779 // cursor position at the beginning of the gesture
780 m_lastScrollCursorPos = [recognizer locationInView:self];
781
782 if (recognizer.state != UIGestureRecognizerStateEnded) {
783 m_lastScrollDelta.x = translation.x;
784 m_lastScrollDelta.y = translation.y;
785 } else {
786 m_lastScrollDelta = CGPointZero;
787 }
788
789 QPoint qt_local = QPointF::fromCGPoint(m_lastScrollCursorPos).toPoint();
790 QPoint qt_global = self.platformWindow->mapToGlobal(qt_local);
791
792 qCInfo(lcQpaInputEvents).nospace() << "wheel event" << " at " << qt_local
793 << " pixelDelta=" << pixelDelta << " angleDelta=" << angleDelta;
794
795 QWindowSystemInterface::handleWheelEvent(self.platformWindow->window(), qt_timestamp, qt_local, qt_global, pixelDelta, angleDelta, qt_modifierFlags);
796}
797#endif // QT_CONFIG(wheelevent)
798
799@end
800
801@implementation UIView (QtHelpers)
802
803- (QWindow *)qwindow
804{
805 if ([self isKindOfClass:[QUIView class]]) {
806 if (QT_PREPEND_NAMESPACE(QIOSWindow) *w = static_cast<QUIView *>(self).platformWindow)
807 return w->window();
808 }
809 return nil;
810}
811
812- (UIViewController *)viewController
813{
814 id responder = self;
815 while ((responder = [responder nextResponder])) {
816 if ([responder isKindOfClass:UIViewController.class])
817 return responder;
818 }
819 return nil;
820}
821
823{
824 UIViewController *vc = self.viewController;
825 if ([vc isKindOfClass:QIOSViewController.class])
826 return static_cast<QIOSViewController *>(vc);
827
828 return nil;
829}
830
831- (UIEdgeInsets)qt_safeAreaInsets
832{
833 return self.safeAreaInsets;
834}
835
836@end
837
838#ifdef Q_OS_IOS
839@implementation QUIMetalView
840
841+ (Class)layerClass
842{
843 return [CAMetalLayer class];
844}
845
846@end
847#endif
848
849#if QT_CONFIG(accessibility)
850// Include category as an alternative to using -ObjC (Apple QA1490)
852#endif
QIOSViewController * qtViewController()
Definition quiview.mm:822
UIViewController * viewController()
Definition quiview.mm:812
UIEdgeInsets qt_safeAreaInsets
Definition quiview.h:35
QWindow * qwindow()
Definition quiview.mm:803
static UIResponder * currentCandidate()
Definition qiosglobal.h:49
static Qt::Key fromNSString(Qt::KeyboardModifiers qtMods, NSString *characters, NSString *charactersIgnoringModifiers, QString &text)
static Qt::Key fromUIKitKey(NSString *keyCode)
static Qt::KeyboardModifiers fromUIKitModifiers(ulong uikitModifiers)
The QColorSpace class provides a color space abstraction.
Definition qcolorspace.h:21
\inmodule QtCore
The QEventPoint class provides information about a point in a QPointerEvent.
Definition qeventpoint.h:20
\inmodule QtCore
Definition qcoreevent.h:45
static QPlatformIntegration * platformIntegration()
static QWindow * focusWindow()
Returns the QWindow that receives events tied to focus, such as key events.
\inmodule QtCore
Definition qhash.h:818
static QIOSInputContext * instance()
bool inputMethodAccepted() const
static QIOSIntegration * instance()
QPointingDevice * touchDevice()
static id menuActionTarget()
Definition qiosmenu.h:75
Capabilities capabilities
static QOperatingSystemVersion current()
[0]
The QPlatformWindow class provides an abstraction for top-level windows.
QWindow * window() const
Returns the window which belongs to the QPlatformWindow.
QPlatformWindow * parent() const
Returns the parent platform window (or \nullptr if orphan).
virtual void requestActivateWindow()
Reimplement to let Qt be able to request activation/focus for a window.
\inmodule QtCore\reentrant
Definition qpoint.h:214
constexpr QPoint toPoint() const
Rounds the coordinates of this point to the nearest integer, and returns a QPoint object with the rou...
Definition qpoint.h:394
\inmodule QtCore\reentrant
Definition qpoint.h:23
constexpr int x() const noexcept
Returns the x coordinate of this point.
Definition qpoint.h:127
constexpr void setY(int y) noexcept
Sets the y coordinate of this point to the given y coordinate.
Definition qpoint.h:142
constexpr int y() const noexcept
Returns the y coordinate of this point.
Definition qpoint.h:132
constexpr void setX(int x) noexcept
Sets the x coordinate of this point to the given x coordinate.
Definition qpoint.h:137
The QPointingDevice class describes a device from which mouse, touch or tablet events originate.
\inmodule QtCore\reentrant
Definition qrect.h:483
\inmodule QtCore\reentrant
Definition qrect.h:30
constexpr QSize size() const noexcept
Returns the size of the rectangle.
Definition qrect.h:241
The QRegion class specifies a clip region for a painter.
Definition qregion.h:27
iterator begin()
Definition qset.h:136
iterator end()
Definition qset.h:140
\inmodule QtCore
Definition qsize.h:25
constexpr int height() const noexcept
Returns the height.
Definition qsize.h:132
constexpr int width() const noexcept
Returns the width.
Definition qsize.h:129
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:127
qsizetype size() const
Returns the number of characters in this string.
Definition qstring.h:182
The QWindowSystemInterface provides an event queue for the QPA platform.
static void handleSafeAreaMarginsChanged(QWindow *window)
static bool handleTabletEvent(QWindow *window, ulong timestamp, const QPointingDevice *device, const QPointF &local, const QPointF &global, Qt::MouseButtons buttons, qreal pressure, int xTilt, int yTilt, qreal tangentialPressure, qreal rotation, int z, Qt::KeyboardModifiers modifiers=Qt::NoModifier)
static void handleGeometryChange(QWindow *window, const QRect &newRect)
static bool handleKeyEvent(QWindow *window, QEvent::Type t, int k, Qt::KeyboardModifiers mods, const QString &text=QString(), bool autorep=false, ushort count=1)
static bool handleExposeEvent(QWindow *window, const QRegion &region)
static void handleWindowActivated(QWindow *window, Qt::FocusReason r=Qt::OtherFocusReason)
static bool handleWheelEvent(QWindow *window, const QPointF &local, const QPointF &global, QPoint pixelDelta, QPoint angleDelta, Qt::KeyboardModifiers mods=Qt::NoModifier, Qt::ScrollPhase phase=Qt::NoScrollPhase, Qt::MouseEventSource source=Qt::MouseEventNotSynthesized)
\inmodule QtGui
Definition qwindow.h:63
QHash< int, QWidget * > hash
[35multi]
p1 load("image.bmp")
QString text
QSet< QString >::iterator it
else opt state
[0]
@ LeftButton
Definition qnamespace.h:57
@ NoButton
Definition qnamespace.h:56
@ Key_Select
@ Key_Right
Definition qnamespace.h:674
@ Key_MediaTogglePlayPause
Definition qnamespace.h:859
@ Key_Left
Definition qnamespace.h:672
@ Key_Up
Definition qnamespace.h:673
@ Key_Down
Definition qnamespace.h:675
@ Key_Menu
Definition qnamespace.h:722
@ Key_unknown
@ NoModifier
@ WindowDoesNotAcceptFocus
Definition qnamespace.h:235
@ WindowTransparentForInput
Definition qnamespace.h:233
@ ActiveWindowFocusReason
QString self
Definition language.cpp:57
float CGFloat
long NSInteger
#define Q_UNLIKELY(x)
#define qApp
DBusConnection const char DBusError DBusBusType DBusError return DBusConnection DBusHandleMessageFunction void DBusFreeFunction return DBusConnection return DBusConnection return const char DBusError return DBusConnection DBusMessage dbus_uint32_t return DBusConnection dbus_bool_t DBusConnection DBusAddWatchFunction DBusRemoveWatchFunction DBusWatchToggledFunction void DBusFreeFunction return DBusConnection DBusDispatchStatusFunction void DBusFreeFunction DBusTimeout return DBusTimeout return DBusWatch return DBusWatch unsigned int return DBusError const DBusError return const DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessageIter int const void return DBusMessageIter DBusMessageIter return DBusMessageIter void DBusMessageIter void int return DBusMessage DBusMessageIter return DBusMessageIter return DBusMessageIter DBusMessageIter const char const char const char const char return DBusMessage return DBusMessage const char return DBusMessage dbus_bool_t return DBusMessage dbus_uint32_t return DBusMessage void
EGLOutputLayerEXT layer
#define qGuiApp
bool isQtApplication()
Definition qiosglobal.mm:17
#define qImDebug
Definition qiosglobal.h:19
#define qWarning
Definition qlogging.h:162
#define Q_LOGGING_CATEGORY(name,...)
#define qCInfo(category,...)
#define qCDebug(category,...)
constexpr float qRadiansToDegrees(float radians)
Definition qmath.h:281
constexpr const T & qBound(const T &min, const T &val, const T &max)
Definition qminmax.h:44
GLint GLint GLint GLint GLint x
[0]
GLuint64 key
GLfloat GLfloat GLfloat w
[0]
GLenum GLuint id
[7]
GLenum GLenum GLsizei count
GLenum type
GLint y
struct _cl_event * event
GLuint in
GLfloat GLfloat p
[1]
GLenum GLenum GLenum input
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
Q_CORE_EXPORT bool qEnvironmentVariableIsSet(const char *varName) noexcept
Q_CORE_EXPORT int qEnvironmentVariableIntValue(const char *varName, bool *ok=nullptr) noexcept
#define Q_UNUSED(x)
unsigned long ulong
Definition qtypes.h:30
double qreal
Definition qtypes.h:92
CGPoint m_lastScrollDelta
Definition quiview.mm:60
UITouch * m_activePencilTouch
Definition quiview.mm:55
UIPanGestureRecognizer * m_scrollGestureRecognizer
Definition quiview.mm:58
NSMutableArray< UIAccessibilityElement * > * m_accessibleElements
Definition quiview.mm:57
#define colorWithBrightness(br)
int m_nextTouchId
Definition quiview.mm:56
CGPoint m_lastScrollCursorPos
Definition quiview.mm:59
Q_GUI_EXPORT QWindowPrivate * qt_window_private(QWindow *window)
Definition qwindow.cpp:2864
QT_END_NAMESPACE typedef QT_PREPEND_NAMESPACE(quintptr) WId
QFileSelector selector
[1]
QObject::connect nullptr
aWidget window() -> setWindowTitle("New Window Title")
[2]
QFrame frame
[0]
QQuickView * view
[0]
Definition moc.h:24