Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qmultitouch_mac.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// Qt-Security score:significant reason:default
4
5#include <AppKit/AppKit.h>
6
9#include "qcocoascreen.h"
10#include <private/qpointingdevice_p.h>
11
13
14using namespace Qt::StringLiterals;
15
16NSMutableDictionary<NSObject<NSCopying> *, NSValue *> *QCocoaTouch::_currentTouches = [NSMutableDictionary new];
17Q_CONSTINIT QHash<quint64, QPointingDevice*> QCocoaTouch::_touchDevices;
18Q_CONSTINIT QPointF QCocoaTouch::_screenReferencePos;
19Q_CONSTINIT QPointF QCocoaTouch::_trackpadReferencePos;
20int QCocoaTouch::_idAssignmentCount = 0;
21NSUInteger QCocoaTouch::_touchCount = 0;
22bool QCocoaTouch::_updateInternalStateOnly = true;
23
24QCocoaTouch::QCocoaTouch(NSTouch *nstouch)
25{
26 if (_currentTouches.count == 0)
27 _idAssignmentCount = 0;
28
29 _touchPoint.id = _idAssignmentCount++;
30 _touchPoint.pressure = 1.0;
31 _identity = [nstouch.identity retain];
32 _currentTouches[_identity] = [NSValue valueWithPointer:this];
33 updateTouchData(nstouch, NSTouchPhaseBegan);
34}
35
36QCocoaTouch::~QCocoaTouch()
37{
38 [_currentTouches removeObjectForKey:_identity];
39 [_identity release];
40}
41
42void QCocoaTouch::updateTouchData(NSTouch *nstouch, NSTouchPhase phase)
43{
44 _touchPoint.state = toTouchPointState(phase);
45
46 // From the normalized position on the trackpad, calculate
47 // where on screen the touchpoint should be according to the
48 // reference position:
49 NSPoint npos = [nstouch normalizedPosition];
50 QPointF qnpos = QPointF(npos.x, 1 - npos.y);
51
52 if (_touchPoint.id == 0 && phase == NSTouchPhaseBegan) {
53 _trackpadReferencePos = qnpos;
54 _screenReferencePos = QCocoaScreen::mapFromNative([NSEvent mouseLocation]);
55 }
56
57 QPointF screenPos = _screenReferencePos;
58
59 NSSize dsize = [nstouch deviceSize];
60 float ppiX = (qnpos.x() - _trackpadReferencePos.x()) * dsize.width;
61 float ppiY = (qnpos.y() - _trackpadReferencePos.y()) * dsize.height;
62 QPointF relativePos = _trackpadReferencePos - QPointF(ppiX, ppiY);
63 screenPos -= relativePos;
64 // Mac does not support area touch, only points, hence set width/height to 1.
65 // The touch point is supposed to be in the center of '_touchPoint.area', and
66 // since width/height is 1 it means we must subtract 0.5 from x and y.
67 screenPos.rx() -= 0.5;
68 screenPos.ry() -= 0.5;
69 _touchPoint.area = QRectF(screenPos, QSize(1, 1));
70}
71
72QCocoaTouch *QCocoaTouch::findQCocoaTouch(NSTouch *nstouch)
73{
74 if (NSValue *touch = _currentTouches[nstouch.identity])
75 return static_cast<QCocoaTouch *>(touch.pointerValue);
76 return nullptr;
77}
78
79QEventPoint::State QCocoaTouch::toTouchPointState(NSTouchPhase nsState)
80{
81 QEventPoint::State qtState = QEventPoint::State::Released;
82 switch (nsState) {
83 case NSTouchPhaseBegan:
84 qtState = QEventPoint::State::Pressed;
85 break;
86 case NSTouchPhaseMoved:
87 qtState = QEventPoint::State::Updated;
88 break;
89 case NSTouchPhaseStationary:
90 qtState = QEventPoint::State::Stationary;
91 break;
92 case NSTouchPhaseEnded:
93 case NSTouchPhaseCancelled:
94 qtState = QEventPoint::State::Released;
95 break;
96 default:
97 break;
98 }
99 return qtState;
100}
101
102QList<QWindowSystemInterface::TouchPoint>
103QCocoaTouch::getCurrentTouchPointList(NSEvent *event, bool acceptSingleTouch)
104{
105 QMap<int, QWindowSystemInterface::TouchPoint> touchPoints;
106 NSSet *ended = [event touchesMatchingPhase:NSTouchPhaseEnded | NSTouchPhaseCancelled inView:nil];
107 NSSet *active = [event
108 touchesMatchingPhase:NSTouchPhaseBegan | NSTouchPhaseMoved | NSTouchPhaseStationary
109 inView:nil];
110 _touchCount = [active count];
111
112 // First: remove touches that were ended by the user. If we are
113 // currently not accepting single touches, a corresponding 'begin'
114 // has never been send to the app for these events.
115 // So should therefore not send the following removes either.
116
117 for (int i=0; i<int([ended count]); ++i) {
118 NSTouch *touch = [[ended allObjects] objectAtIndex:i];
119 QCocoaTouch *qcocoaTouch = findQCocoaTouch(touch);
120 if (qcocoaTouch) {
121 qcocoaTouch->updateTouchData(touch, [touch phase]);
122 if (!_updateInternalStateOnly)
123 touchPoints.insert(qcocoaTouch->_touchPoint.id, qcocoaTouch->_touchPoint);
124 delete qcocoaTouch;
125 }
126 }
127
128 bool wasUpdateInternalStateOnly = _updateInternalStateOnly;
129 _updateInternalStateOnly = !acceptSingleTouch && _touchCount < 2;
130
131 // Next: update, or create, existing touches.
132 // We always keep track of all touch points, even
133 // when not accepting single touches.
134
135 for (int i=0; i<int([active count]); ++i) {
136 NSTouch *touch = [[active allObjects] objectAtIndex:i];
137 QCocoaTouch *qcocoaTouch = findQCocoaTouch(touch);
138 if (!qcocoaTouch)
139 qcocoaTouch = new QCocoaTouch(touch);
140 else
141 qcocoaTouch->updateTouchData(touch, wasUpdateInternalStateOnly ? NSTouchPhaseBegan : [touch phase]);
142 if (!_updateInternalStateOnly)
143 touchPoints.insert(qcocoaTouch->_touchPoint.id, qcocoaTouch->_touchPoint);
144 }
145
146 // Next: sadly, we need to check that our touch hash is in
147 // sync with cocoa. This is typically not the case after a system
148 // gesture happened (like a four-finger-swipe to show expose).
149
150 if (_touchCount != _currentTouches.count) {
151 // Remove all instances, and basically start from scratch:
152 touchPoints.clear();
153 // Deleting touch points will remove them from current touches,
154 // so we make a copy of the touches before iterating them.
155 NSArray<NSValue *> *currentTouchesSnapshot = _currentTouches.allValues;
156 for (NSValue *touch in currentTouchesSnapshot) {
157 QCocoaTouch *qcocoaTouch = static_cast<QCocoaTouch *>(touch.pointerValue);
158 if (!_updateInternalStateOnly) {
159 qcocoaTouch->_touchPoint.state = QEventPoint::State::Released;
160 touchPoints.insert(qcocoaTouch->_touchPoint.id, qcocoaTouch->_touchPoint);
161 }
162 delete qcocoaTouch;
163 }
164 [_currentTouches removeAllObjects];
165 _updateInternalStateOnly = !acceptSingleTouch;
166 return touchPoints.values();
167 }
168
169 // Finally: If this call _started_ to reject single
170 // touches, we need to fake a release for the remaining
171 // touch now (and refake a begin for it later, if needed).
172
173 if (_updateInternalStateOnly && !wasUpdateInternalStateOnly && _currentTouches.count > 0) {
174 QCocoaTouch *qcocoaTouch = static_cast<QCocoaTouch *>(_currentTouches.allValues.firstObject.pointerValue);
175 qcocoaTouch->_touchPoint.state = QEventPoint::State::Released;
176 touchPoints.insert(qcocoaTouch->_touchPoint.id, qcocoaTouch->_touchPoint);
177 // Since this last touch also will end up being the first
178 // touch (if the user adds a second finger without lifting
179 // the first), we promote it to be the primary touch:
180 qcocoaTouch->_touchPoint.id = 0;
181 _idAssignmentCount = 1;
182 }
183
184 return touchPoints.values();
185}
186
187QPointingDevice *QCocoaTouch::getTouchDevice(QInputDevice::DeviceType type, quint64 id)
188{
189 QPointingDevice *ret = _touchDevices.value(id);
190 if (!ret) {
191 ret = new QPointingDevice(type == QInputDevice::DeviceType::TouchScreen ? "touchscreen"_L1 : "trackpad"_L1,
192 id, type, QPointingDevice::PointerType::Finger,
193 QInputDevice::Capability::Position |
194 QInputDevice::Capability::MouseEmulation,
195 10, 0);
196 QWindowSystemInterface::registerInputDevice(ret);
197 _touchDevices.insert(id, ret);
198 }
199 return ret;
200}
201
202QT_END_NAMESPACE
Combined button and popup list for selecting options.