1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the demonstration applications of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:BSD$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** BSD License Usage
18** Alternatively, you may use this file under the terms of the BSD license
19** as follows:
20**
21** "Redistribution and use in source and binary forms, with or without
22** modification, are permitted provided that the following conditions are
23** met:
24** * Redistributions of source code must retain the above copyright
25** notice, this list of conditions and the following disclaimer.
26** * Redistributions in binary form must reproduce the above copyright
27** notice, this list of conditions and the following disclaimer in
28** the documentation and/or other materials provided with the
29** distribution.
30** * Neither the name of The Qt Company Ltd nor the names of its
31** contributors may be used to endorse or promote products derived
32** from this software without specific prior written permission.
33**
34**
35** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
36** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
37** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
38** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
39** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
40** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
41** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
42** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
43** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
44** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
45** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
46**
47** $QT_END_LICENSE$
48**
49****************************************************************************/
50
51#include "arthurwidgets.h"
52#include "hoverpoints.h"
53
54#include <algorithm>
55
56#define printf
57
58HoverPoints::HoverPoints(QWidget *widget, PointShape shape)
59 : QObject(widget)
60{
61 m_widget = widget;
62 widget->installEventFilter(filterObj: this);
63 widget->setAttribute(Qt::WA_AcceptTouchEvents);
64
65 m_connectionType = CurveConnection;
66 m_sortType = NoSort;
67 m_shape = shape;
68 m_pointPen = QPen(QColor(255, 255, 255, 191), 1);
69 m_connectionPen = QPen(QColor(255, 255, 255, 127), 2);
70 m_pointBrush = QBrush(QColor(191, 191, 191, 127));
71 m_pointSize = QSize(11, 11);
72 m_currentIndex = -1;
73 m_editable = true;
74 m_enabled = true;
75
76 connect(sender: this, signal: &HoverPoints::pointsChanged,
77 receiver: m_widget, slot: QOverload<>::of(ptr: &QWidget::update));
78}
79
80
81void HoverPoints::setEnabled(bool enabled)
82{
83 if (m_enabled != enabled) {
84 m_enabled = enabled;
85 m_widget->update();
86 }
87}
88
89
90bool HoverPoints::eventFilter(QObject *object, QEvent *event)
91{
92 if (object == m_widget && m_enabled) {
93 switch (event->type()) {
94
95 case QEvent::MouseButtonPress:
96 {
97 if (!m_fingerPointMapping.isEmpty())
98 return true;
99 QMouseEvent *me = (QMouseEvent *) event;
100
101 QPointF clickPos = me->pos();
102 int index = -1;
103 for (int i=0; i<m_points.size(); ++i) {
104 QPainterPath path;
105 if (m_shape == CircleShape)
106 path.addEllipse(rect: pointBoundingRect(i));
107 else
108 path.addRect(rect: pointBoundingRect(i));
109
110 if (path.contains(pt: clickPos)) {
111 index = i;
112 break;
113 }
114 }
115
116 if (me->button() == Qt::LeftButton) {
117 if (index == -1) {
118 if (!m_editable)
119 return false;
120 int pos = 0;
121 // Insert sort for x or y
122 if (m_sortType == XSort) {
123 for (int i=0; i<m_points.size(); ++i)
124 if (m_points.at(i).x() > clickPos.x()) {
125 pos = i;
126 break;
127 }
128 } else if (m_sortType == YSort) {
129 for (int i=0; i<m_points.size(); ++i)
130 if (m_points.at(i).y() > clickPos.y()) {
131 pos = i;
132 break;
133 }
134 }
135
136 m_points.insert(i: pos, t: clickPos);
137 m_locks.insert(i: pos, t: 0);
138 m_currentIndex = pos;
139 firePointChange();
140 } else {
141 m_currentIndex = index;
142 }
143 return true;
144
145 } else if (me->button() == Qt::RightButton) {
146 if (index >= 0 && m_editable) {
147 if (m_locks[index] == 0) {
148 m_locks.remove(i: index);
149 m_points.remove(i: index);
150 }
151 firePointChange();
152 return true;
153 }
154 }
155
156 }
157 break;
158
159 case QEvent::MouseButtonRelease:
160 if (!m_fingerPointMapping.isEmpty())
161 return true;
162 m_currentIndex = -1;
163 break;
164
165 case QEvent::MouseMove:
166 if (!m_fingerPointMapping.isEmpty())
167 return true;
168 if (m_currentIndex >= 0)
169 movePoint(i: m_currentIndex, newPos: ((QMouseEvent *)event)->pos());
170 break;
171 case QEvent::TouchBegin:
172 case QEvent::TouchUpdate:
173 {
174 const QTouchEvent *const touchEvent = static_cast<const QTouchEvent*>(event);
175 const QList<QTouchEvent::TouchPoint> points = touchEvent->touchPoints();
176 const qreal pointSize = qMax(a: m_pointSize.width(), b: m_pointSize.height());
177 for (const QTouchEvent::TouchPoint &touchPoint : points) {
178 const int id = touchPoint.id();
179 switch (touchPoint.state()) {
180 case Qt::TouchPointPressed:
181 {
182 // find the point, move it
183 const auto mappedPoints = m_fingerPointMapping.values();
184 QSet<int> activePoints = QSet<int>(mappedPoints.begin(), mappedPoints.end());
185 int activePoint = -1;
186 qreal distance = -1;
187 const int pointsCount = m_points.size();
188 const int activePointCount = activePoints.size();
189 if (pointsCount == 2 && activePointCount == 1) { // only two points
190 activePoint = activePoints.contains(value: 0) ? 1 : 0;
191 } else {
192 for (int i=0; i<pointsCount; ++i) {
193 if (activePoints.contains(value: i))
194 continue;
195
196 qreal d = QLineF(touchPoint.pos(), m_points.at(i)).length();
197 if ((distance < 0 && d < 12 * pointSize) || d < distance) {
198 distance = d;
199 activePoint = i;
200 }
201
202 }
203 }
204 if (activePoint != -1) {
205 m_fingerPointMapping.insert(akey: touchPoint.id(), avalue: activePoint);
206 movePoint(i: activePoint, newPos: touchPoint.pos());
207 }
208 }
209 break;
210 case Qt::TouchPointReleased:
211 {
212 // move the point and release
213 QHash<int,int>::iterator it = m_fingerPointMapping.find(akey: id);
214 movePoint(i: it.value(), newPos: touchPoint.pos());
215 m_fingerPointMapping.erase(it);
216 }
217 break;
218 case Qt::TouchPointMoved:
219 {
220 // move the point
221 const int pointIdx = m_fingerPointMapping.value(akey: id, adefaultValue: -1);
222 if (pointIdx >= 0) // do we track this point?
223 movePoint(i: pointIdx, newPos: touchPoint.pos());
224 }
225 break;
226 default:
227 break;
228 }
229 }
230 if (m_fingerPointMapping.isEmpty()) {
231 event->ignore();
232 return false;
233 } else {
234 return true;
235 }
236 }
237 break;
238 case QEvent::TouchEnd:
239 if (m_fingerPointMapping.isEmpty()) {
240 event->ignore();
241 return false;
242 }
243 return true;
244 break;
245
246 case QEvent::Resize:
247 {
248 QResizeEvent *e = (QResizeEvent *) event;
249 if (e->oldSize().width() == 0 || e->oldSize().height() == 0)
250 break;
251 qreal stretch_x = e->size().width() / qreal(e->oldSize().width());
252 qreal stretch_y = e->size().height() / qreal(e->oldSize().height());
253 for (int i=0; i<m_points.size(); ++i) {
254 QPointF p = m_points[i];
255 movePoint(i, newPos: QPointF(p.x() * stretch_x, p.y() * stretch_y), emitChange: false);
256 }
257
258 firePointChange();
259 break;
260 }
261
262 case QEvent::Paint:
263 {
264 QWidget *that_widget = m_widget;
265 m_widget = nullptr;
266 QCoreApplication::sendEvent(receiver: object, event);
267 m_widget = that_widget;
268 paintPoints();
269 return true;
270 }
271 default:
272 break;
273 }
274 }
275
276 return false;
277}
278
279
280void HoverPoints::paintPoints()
281{
282 QPainter p;
283#if QT_CONFIG(opengl)
284 ArthurFrame *af = qobject_cast<ArthurFrame *>(object: m_widget);
285 if (af && af->usesOpenGL() && af->glWindow()->isValid()) {
286 af->glWindow()->makeCurrent();
287 p.begin(af->glWindow());
288 } else {
289 p.begin(m_widget);
290 }
291#else
292 p.begin(m_widget);
293#endif
294
295 p.setRenderHint(hint: QPainter::Antialiasing);
296
297 if (m_connectionPen.style() != Qt::NoPen && m_connectionType != NoConnection) {
298 p.setPen(m_connectionPen);
299
300 if (m_connectionType == CurveConnection) {
301 QPainterPath path;
302 path.moveTo(p: m_points.at(i: 0));
303 for (int i=1; i<m_points.size(); ++i) {
304 QPointF p1 = m_points.at(i: i-1);
305 QPointF p2 = m_points.at(i);
306 qreal distance = p2.x() - p1.x();
307
308 path.cubicTo(ctrlPt1x: p1.x() + distance / 2, ctrlPt1y: p1.y(),
309 ctrlPt2x: p1.x() + distance / 2, ctrlPt2y: p2.y(),
310 endPtx: p2.x(), endPty: p2.y());
311 }
312 p.drawPath(path);
313 } else {
314 p.drawPolyline(polyline: m_points);
315 }
316 }
317
318 p.setPen(m_pointPen);
319 p.setBrush(m_pointBrush);
320
321 for (int i=0; i<m_points.size(); ++i) {
322 QRectF bounds = pointBoundingRect(i);
323 if (m_shape == CircleShape)
324 p.drawEllipse(r: bounds);
325 else
326 p.drawRect(rect: bounds);
327 }
328}
329
330static QPointF bound_point(const QPointF &point, const QRectF &bounds, int lock)
331{
332 QPointF p = point;
333
334 qreal left = bounds.left();
335 qreal right = bounds.right();
336 qreal top = bounds.top();
337 qreal bottom = bounds.bottom();
338
339 if (p.x() < left || (lock & HoverPoints::LockToLeft)) p.setX(left);
340 else if (p.x() > right || (lock & HoverPoints::LockToRight)) p.setX(right);
341
342 if (p.y() < top || (lock & HoverPoints::LockToTop)) p.setY(top);
343 else if (p.y() > bottom || (lock & HoverPoints::LockToBottom)) p.setY(bottom);
344
345 return p;
346}
347
348void HoverPoints::setPoints(const QPolygonF &points)
349{
350 if (points.size() != m_points.size())
351 m_fingerPointMapping.clear();
352 m_points.clear();
353 for (int i=0; i<points.size(); ++i)
354 m_points << bound_point(point: points.at(i), bounds: boundingRect(), lock: 0);
355
356 m_locks.clear();
357 if (m_points.size() > 0) {
358 m_locks.resize(asize: m_points.size());
359
360 m_locks.fill(from: 0);
361 }
362}
363
364
365void HoverPoints::movePoint(int index, const QPointF &point, bool emitUpdate)
366{
367 m_points[index] = bound_point(point, bounds: boundingRect(), lock: m_locks.at(i: index));
368 if (emitUpdate)
369 firePointChange();
370}
371
372
373inline static bool x_less_than(const QPointF &p1, const QPointF &p2)
374{
375 return p1.x() < p2.x();
376}
377
378
379inline static bool y_less_than(const QPointF &p1, const QPointF &p2)
380{
381 return p1.y() < p2.y();
382}
383
384void HoverPoints::firePointChange()
385{
386// printf("HoverPoints::firePointChange(), current=%d\n", m_currentIndex);
387
388 if (m_sortType != NoSort) {
389
390 QPointF oldCurrent;
391 if (m_currentIndex != -1) {
392 oldCurrent = m_points[m_currentIndex];
393 }
394
395 if (m_sortType == XSort)
396 std::sort(first: m_points.begin(), last: m_points.end(), comp: x_less_than);
397 else if (m_sortType == YSort)
398 std::sort(first: m_points.begin(), last: m_points.end(), comp: y_less_than);
399
400 // Compensate for changed order...
401 if (m_currentIndex != -1) {
402 for (int i=0; i<m_points.size(); ++i) {
403 if (m_points[i] == oldCurrent) {
404 m_currentIndex = i;
405 break;
406 }
407 }
408 }
409
410// printf(" - firePointChange(), current=%d\n", m_currentIndex);
411
412 }
413
414// for (int i=0; i<m_points.size(); ++i) {
415// printf(" - point(%2d)=[%.2f, %.2f], lock=%d\n",
416// i, m_points.at(i).x(), m_points.at(i).y(), m_locks.at(i));
417// }
418
419 emit pointsChanged(points: m_points);
420}
421

source code of qtbase/examples/widgets/painting/shared/hoverpoints.cpp