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 "pathstroke.h"
52#include "arthurstyle.h"
53#include "arthurwidgets.h"
54
55extern void draw_round_rect(QPainter *p, const QRect &bounds, int radius);
56
57
58PathStrokeControls::PathStrokeControls(QWidget* parent, PathStrokeRenderer* renderer, bool smallScreen)
59 : QWidget(parent)
60{
61 m_renderer = renderer;
62
63 if (smallScreen)
64 layoutForSmallScreens();
65 else
66 layoutForDesktop();
67}
68
69void PathStrokeControls::createCommonControls(QWidget* parent)
70{
71 m_capGroup = new QGroupBox(parent);
72 m_capGroup->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed);
73 QRadioButton *flatCap = new QRadioButton(m_capGroup);
74 QRadioButton *squareCap = new QRadioButton(m_capGroup);
75 QRadioButton *roundCap = new QRadioButton(m_capGroup);
76 m_capGroup->setTitle(tr("Cap Style"));
77 flatCap->setText(tr("Flat"));
78 squareCap->setText(tr("Square"));
79 roundCap->setText(tr("Round"));
80 flatCap->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
81 squareCap->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
82 roundCap->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
83
84 m_joinGroup = new QGroupBox(parent);
85 m_joinGroup->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed);
86 QRadioButton *bevelJoin = new QRadioButton(m_joinGroup);
87 QRadioButton *miterJoin = new QRadioButton(m_joinGroup);
88 QRadioButton *svgMiterJoin = new QRadioButton(m_joinGroup);
89 QRadioButton *roundJoin = new QRadioButton(m_joinGroup);
90 m_joinGroup->setTitle(tr("Join Style"));
91 bevelJoin->setText(tr("Bevel"));
92 miterJoin->setText(tr("Miter"));
93 svgMiterJoin->setText(tr("SvgMiter"));
94 roundJoin->setText(tr("Round"));
95
96 m_styleGroup = new QGroupBox(parent);
97 m_styleGroup->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed);
98 QRadioButton *solidLine = new QRadioButton(m_styleGroup);
99 QRadioButton *dashLine = new QRadioButton(m_styleGroup);
100 QRadioButton *dotLine = new QRadioButton(m_styleGroup);
101 QRadioButton *dashDotLine = new QRadioButton(m_styleGroup);
102 QRadioButton *dashDotDotLine = new QRadioButton(m_styleGroup);
103 QRadioButton *customDashLine = new QRadioButton(m_styleGroup);
104 m_styleGroup->setTitle(tr("Pen Style"));
105
106 QPixmap line_solid(":res/images/line_solid.png");
107 solidLine->setIcon(line_solid);
108 solidLine->setIconSize(line_solid.size());
109 QPixmap line_dashed(":res/images/line_dashed.png");
110 dashLine->setIcon(line_dashed);
111 dashLine->setIconSize(line_dashed.size());
112 QPixmap line_dotted(":res/images/line_dotted.png");
113 dotLine->setIcon(line_dotted);
114 dotLine->setIconSize(line_dotted.size());
115 QPixmap line_dash_dot(":res/images/line_dash_dot.png");
116 dashDotLine->setIcon(line_dash_dot);
117 dashDotLine->setIconSize(line_dash_dot.size());
118 QPixmap line_dash_dot_dot(":res/images/line_dash_dot_dot.png");
119 dashDotDotLine->setIcon(line_dash_dot_dot);
120 dashDotDotLine->setIconSize(line_dash_dot_dot.size());
121 customDashLine->setText(tr("Custom"));
122
123 int fixedHeight = bevelJoin->sizeHint().height();
124 solidLine->setFixedHeight(fixedHeight);
125 dashLine->setFixedHeight(fixedHeight);
126 dotLine->setFixedHeight(fixedHeight);
127 dashDotLine->setFixedHeight(fixedHeight);
128 dashDotDotLine->setFixedHeight(fixedHeight);
129
130 m_pathModeGroup = new QGroupBox(parent);
131 m_pathModeGroup->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed);
132 QRadioButton *curveMode = new QRadioButton(m_pathModeGroup);
133 QRadioButton *lineMode = new QRadioButton(m_pathModeGroup);
134 m_pathModeGroup->setTitle(tr("Line Style"));
135 curveMode->setText(tr("Curves"));
136 lineMode->setText(tr("Lines"));
137
138
139 // Layouts
140 QVBoxLayout *capGroupLayout = new QVBoxLayout(m_capGroup);
141 capGroupLayout->addWidget(flatCap);
142 capGroupLayout->addWidget(squareCap);
143 capGroupLayout->addWidget(roundCap);
144
145 QVBoxLayout *joinGroupLayout = new QVBoxLayout(m_joinGroup);
146 joinGroupLayout->addWidget(bevelJoin);
147 joinGroupLayout->addWidget(miterJoin);
148 joinGroupLayout->addWidget(svgMiterJoin);
149 joinGroupLayout->addWidget(roundJoin);
150
151 QVBoxLayout *styleGroupLayout = new QVBoxLayout(m_styleGroup);
152 styleGroupLayout->addWidget(solidLine);
153 styleGroupLayout->addWidget(dashLine);
154 styleGroupLayout->addWidget(dotLine);
155 styleGroupLayout->addWidget(dashDotLine);
156 styleGroupLayout->addWidget(dashDotDotLine);
157 styleGroupLayout->addWidget(customDashLine);
158
159 QVBoxLayout *pathModeGroupLayout = new QVBoxLayout(m_pathModeGroup);
160 pathModeGroupLayout->addWidget(curveMode);
161 pathModeGroupLayout->addWidget(lineMode);
162
163
164 // Connections
165 connect(flatCap, &QAbstractButton::clicked,
166 m_renderer, &PathStrokeRenderer::setFlatCap);
167 connect(squareCap, &QAbstractButton::clicked,
168 m_renderer, &PathStrokeRenderer::setSquareCap);
169 connect(roundCap, &QAbstractButton::clicked,
170 m_renderer, &PathStrokeRenderer::setRoundCap);
171
172 connect(bevelJoin, &QAbstractButton::clicked,
173 m_renderer, &PathStrokeRenderer::setBevelJoin);
174 connect(miterJoin, &QAbstractButton::clicked,
175 m_renderer, &PathStrokeRenderer::setMiterJoin);
176 connect(svgMiterJoin, &QAbstractButton::clicked,
177 m_renderer, &PathStrokeRenderer::setSvgMiterJoin);
178 connect(roundJoin, &QAbstractButton::clicked,
179 m_renderer, &PathStrokeRenderer::setRoundJoin);
180
181 connect(curveMode, &QAbstractButton::clicked,
182 m_renderer, &PathStrokeRenderer::setCurveMode);
183 connect(lineMode, &QAbstractButton::clicked,
184 m_renderer, &PathStrokeRenderer::setLineMode);
185
186 connect(solidLine, &QAbstractButton::clicked,
187 m_renderer, &PathStrokeRenderer::setSolidLine);
188 connect(dashLine, &QAbstractButton::clicked,
189 m_renderer, &PathStrokeRenderer::setDashLine);
190 connect(dotLine, &QAbstractButton::clicked,
191 m_renderer, &PathStrokeRenderer::setDotLine);
192 connect(dashDotLine, &QAbstractButton::clicked,
193 m_renderer, &PathStrokeRenderer::setDashDotLine);
194 connect(dashDotDotLine, &QAbstractButton::clicked,
195 m_renderer, &PathStrokeRenderer::setDashDotDotLine);
196 connect(customDashLine, &QAbstractButton::clicked,
197 m_renderer, &PathStrokeRenderer::setCustomDashLine);
198
199 // Set the defaults:
200 flatCap->setChecked(true);
201 bevelJoin->setChecked(true);
202 curveMode->setChecked(true);
203 solidLine->setChecked(true);
204}
205
206
207void PathStrokeControls::layoutForDesktop()
208{
209 QGroupBox *mainGroup = new QGroupBox(this);
210 mainGroup->setFixedWidth(180);
211 mainGroup->setTitle(tr("Path Stroking"));
212
213 createCommonControls(mainGroup);
214
215 QGroupBox* penWidthGroup = new QGroupBox(mainGroup);
216 QSlider *penWidth = new QSlider(Qt::Horizontal, penWidthGroup);
217 penWidth->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
218 penWidthGroup->setTitle(tr("Pen Width"));
219 penWidth->setRange(0, 500);
220
221 QPushButton *animated = new QPushButton(mainGroup);
222 animated->setText(tr("Animate"));
223 animated->setCheckable(true);
224
225 QPushButton *showSourceButton = new QPushButton(mainGroup);
226 showSourceButton->setText(tr("Show Source"));
227#if QT_CONFIG(opengl)
228 QPushButton *enableOpenGLButton = new QPushButton(mainGroup);
229 enableOpenGLButton->setText(tr("Use OpenGL"));
230 enableOpenGLButton->setCheckable(true);
231 enableOpenGLButton->setChecked(m_renderer->usesOpenGL());
232#endif
233 QPushButton *whatsThisButton = new QPushButton(mainGroup);
234 whatsThisButton->setText(tr("What's This?"));
235 whatsThisButton->setCheckable(true);
236
237
238 // Layouts:
239 QVBoxLayout *penWidthLayout = new QVBoxLayout(penWidthGroup);
240 penWidthLayout->addWidget(penWidth);
241
242 QVBoxLayout * mainLayout = new QVBoxLayout(this);
243 mainLayout->setContentsMargins(QMargins());
244 mainLayout->addWidget(mainGroup);
245
246 QVBoxLayout *mainGroupLayout = new QVBoxLayout(mainGroup);
247 mainGroupLayout->setContentsMargins(3, 3, 3, 3);
248 mainGroupLayout->addWidget(m_capGroup);
249 mainGroupLayout->addWidget(m_joinGroup);
250 mainGroupLayout->addWidget(m_styleGroup);
251 mainGroupLayout->addWidget(penWidthGroup);
252 mainGroupLayout->addWidget(m_pathModeGroup);
253 mainGroupLayout->addWidget(animated);
254 mainGroupLayout->addStretch(1);
255 mainGroupLayout->addWidget(showSourceButton);
256#if QT_CONFIG(opengl)
257 mainGroupLayout->addWidget(enableOpenGLButton);
258#endif
259 mainGroupLayout->addWidget(whatsThisButton);
260
261
262 // Set up connections
263 connect(animated, &QAbstractButton::toggled,
264 m_renderer, &PathStrokeRenderer::setAnimation);
265
266 connect(penWidth, &QAbstractSlider::valueChanged,
267 m_renderer, &PathStrokeRenderer::setPenWidth);
268
269 connect(showSourceButton, &QAbstractButton::clicked,
270 m_renderer, &ArthurFrame::showSource);
271#if QT_CONFIG(opengl)
272 connect(enableOpenGLButton, &QAbstractButton::clicked,
273 m_renderer, &ArthurFrame::enableOpenGL);
274#endif
275 connect(whatsThisButton, &QAbstractButton::clicked,
276 m_renderer, &ArthurFrame::setDescriptionEnabled);
277 connect(m_renderer, &ArthurFrame::descriptionEnabledChanged,
278 whatsThisButton, &QAbstractButton::setChecked);
279
280
281 // Set the defaults
282 animated->setChecked(true);
283 penWidth->setValue(50);
284
285}
286
287void PathStrokeControls::layoutForSmallScreens()
288{
289 createCommonControls(this);
290
291 m_capGroup->layout()->setContentsMargins(QMargins());
292 m_joinGroup->layout()->setContentsMargins(QMargins());
293 m_styleGroup->layout()->setContentsMargins(QMargins());
294 m_pathModeGroup->layout()->setContentsMargins(QMargins());
295
296 QPushButton* okBtn = new QPushButton(tr("OK"), this);
297 okBtn->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
298 okBtn->setMinimumSize(100,okBtn->minimumSize().height());
299
300 QPushButton* quitBtn = new QPushButton(tr("Quit"), this);
301 quitBtn->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
302 quitBtn->setMinimumSize(100, okBtn->minimumSize().height());
303
304 QLabel *penWidthLabel = new QLabel(tr(" Width:"));
305 QSlider *penWidth = new QSlider(Qt::Horizontal, this);
306 penWidth->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
307 penWidth->setRange(0, 500);
308
309#if QT_CONFIG(opengl)
310 QPushButton *enableOpenGLButton = new QPushButton(this);
311 enableOpenGLButton->setText(tr("Use OpenGL"));
312 enableOpenGLButton->setCheckable(true);
313 enableOpenGLButton->setChecked(m_renderer->usesOpenGL());
314#endif
315
316 // Layouts:
317 QHBoxLayout *penWidthLayout = new QHBoxLayout;
318 penWidthLayout->addWidget(penWidthLabel, 0, Qt::AlignRight);
319 penWidthLayout->addWidget(penWidth);
320
321 QVBoxLayout *leftLayout = new QVBoxLayout;
322 leftLayout->addWidget(m_capGroup);
323 leftLayout->addWidget(m_joinGroup);
324#if QT_CONFIG(opengl)
325 leftLayout->addWidget(enableOpenGLButton);
326#endif
327 leftLayout->addLayout(penWidthLayout);
328
329 QVBoxLayout *rightLayout = new QVBoxLayout;
330 rightLayout->addWidget(m_styleGroup);
331 rightLayout->addWidget(m_pathModeGroup);
332
333 QGridLayout *mainLayout = new QGridLayout(this);
334 mainLayout->setContentsMargins(QMargins());
335
336 // Add spacers around the form items so we don't look stupid at higher resolutions
337 mainLayout->addItem(new QSpacerItem(0,0), 0, 0, 1, 4);
338 mainLayout->addItem(new QSpacerItem(0,0), 1, 0, 2, 1);
339 mainLayout->addItem(new QSpacerItem(0,0), 1, 3, 2, 1);
340 mainLayout->addItem(new QSpacerItem(0,0), 3, 0, 1, 4);
341
342 mainLayout->addLayout(leftLayout, 1, 1);
343 mainLayout->addLayout(rightLayout, 1, 2);
344 mainLayout->addWidget(quitBtn, 2, 1, Qt::AlignHCenter | Qt::AlignTop);
345 mainLayout->addWidget(okBtn, 2, 2, Qt::AlignHCenter | Qt::AlignTop);
346
347#if QT_CONFIG(opengl)
348 connect(enableOpenGLButton, &QAbstractButton::clicked, m_renderer, &ArthurFrame::enableOpenGL);
349#endif
350
351 connect(penWidth, &QAbstractSlider::valueChanged, m_renderer, &PathStrokeRenderer::setPenWidth);
352 connect(quitBtn, &QAbstractButton::clicked, this, &PathStrokeControls::emitQuitSignal);
353 connect(okBtn, &QAbstractButton::clicked, this, &PathStrokeControls::emitOkSignal);
354
355 m_renderer->setAnimation(true);
356 penWidth->setValue(50);
357}
358
359void PathStrokeControls::emitQuitSignal()
360{
361 emit quitPressed();
362}
363
364void PathStrokeControls::emitOkSignal()
365{
366 emit okPressed();
367}
368
369
370PathStrokeWidget::PathStrokeWidget(bool smallScreen)
371{
372 setWindowTitle(tr("Path Stroking"));
373
374 // Widget construction and property setting
375 m_renderer = new PathStrokeRenderer(this, smallScreen);
376
377 m_controls = new PathStrokeControls(nullptr, m_renderer, smallScreen);
378
379 // Layouting
380 QHBoxLayout *viewLayout = new QHBoxLayout(this);
381 viewLayout->addWidget(m_renderer);
382
383 if (!smallScreen)
384 viewLayout->addWidget(m_controls);
385
386 m_renderer->loadSourceFile(":res/pathstroke/pathstroke.cpp");
387 m_renderer->loadDescription(":res/pathstroke/pathstroke.html");
388
389 connect(m_renderer, &PathStrokeRenderer::clicked, this, &PathStrokeWidget::showControls);
390 connect(m_controls, &PathStrokeControls::okPressed, this, &PathStrokeWidget::hideControls);
391 connect(m_controls, SIGNAL(quitPressed()), QApplication::instance(), SLOT(quit()));
392}
393
394void PathStrokeWidget::showControls()
395{
396 m_controls->showFullScreen();
397}
398
399void PathStrokeWidget::hideControls()
400{
401 m_controls->hide();
402}
403
404void PathStrokeWidget::setStyle(QStyle *style)
405{
406 QWidget::setStyle(style);
407 if (m_controls != nullptr)
408 {
409 m_controls->setStyle(style);
410
411 const QList<QWidget *> widgets = m_controls->findChildren<QWidget *>();
412 for (QWidget *w : widgets)
413 w->setStyle(style);
414 }
415}
416
417PathStrokeRenderer::PathStrokeRenderer(QWidget *parent, bool smallScreen)
418 : ArthurFrame(parent)
419{
420 m_smallScreen = smallScreen;
421 m_pointSize = 10;
422 m_activePoint = -1;
423 m_capStyle = Qt::FlatCap;
424 m_joinStyle = Qt::BevelJoin;
425 m_pathMode = CurveMode;
426 m_penWidth = 1;
427 m_penStyle = Qt::SolidLine;
428 m_wasAnimated = true;
429 setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
430 setAttribute(Qt::WA_AcceptTouchEvents);
431}
432
433void PathStrokeRenderer::paint(QPainter *painter)
434{
435 if (m_points.isEmpty())
436 initializePoints();
437
438 painter->setRenderHint(QPainter::Antialiasing);
439
440 QPalette pal = palette();
441 painter->setPen(Qt::NoPen);
442
443 // Construct the path
444 QPainterPath path;
445 path.moveTo(m_points.at(0));
446
447 if (m_pathMode == LineMode) {
448 for (int i=1; i<m_points.size(); ++i)
449 path.lineTo(m_points.at(i));
450 } else {
451 int i=1;
452 while (i + 2 < m_points.size()) {
453 path.cubicTo(m_points.at(i), m_points.at(i+1), m_points.at(i+2));
454 i += 3;
455 }
456 while (i < m_points.size()) {
457 path.lineTo(m_points.at(i));
458 ++i;
459 }
460 }
461
462 // Draw the path
463 {
464 QColor lg = Qt::red;
465
466 // The "custom" pen
467 if (m_penStyle == Qt::NoPen) {
468 QPainterPathStroker stroker;
469 stroker.setWidth(m_penWidth);
470 stroker.setJoinStyle(m_joinStyle);
471 stroker.setCapStyle(m_capStyle);
472
473 QList<qreal> dashes;
474 qreal space = 4;
475 dashes << 1 << space
476 << 3 << space
477 << 9 << space
478 << 27 << space
479 << 9 << space
480 << 3 << space;
481 stroker.setDashPattern(dashes);
482 QPainterPath stroke = stroker.createStroke(path);
483 painter->fillPath(stroke, lg);
484
485 } else {
486 QPen pen(lg, m_penWidth, m_penStyle, m_capStyle, m_joinStyle);
487 painter->strokePath(path, pen);
488 }
489 }
490
491 if (1) {
492 // Draw the control points
493 painter->setPen(QColor(50, 100, 120, 200));
494 painter->setBrush(QColor(200, 200, 210, 120));
495 for (int i=0; i<m_points.size(); ++i) {
496 QPointF pos = m_points.at(i);
497 painter->drawEllipse(QRectF(pos.x() - m_pointSize,
498 pos.y() - m_pointSize,
499 m_pointSize*2, m_pointSize*2));
500 }
501 painter->setPen(QPen(Qt::lightGray, 0, Qt::SolidLine));
502 painter->setBrush(Qt::NoBrush);
503 painter->drawPolyline(m_points);
504 }
505
506}
507
508void PathStrokeRenderer::initializePoints()
509{
510 const int count = 7;
511 m_points.clear();
512 m_vectors.clear();
513
514 QTransform m;
515 qreal rot = 360.0 / count;
516 QPointF center(width() / 2, height() / 2);
517 QTransform vm;
518 vm.shear(2, -1);
519 vm.scale(3, 3);
520
521 for (int i=0; i<count; ++i) {
522 m_vectors << QPointF(.1f, .25f) * (m * vm);
523 m_points << QPointF(0, 100) * m + center;
524 m.rotate(rot);
525 }
526}
527
528void PathStrokeRenderer::updatePoints()
529{
530 qreal pad = 10;
531 qreal left = pad;
532 qreal right = width() - pad;
533 qreal top = pad;
534 qreal bottom = height() - pad;
535
536 Q_ASSERT(m_points.size() == m_vectors.size());
537 for (int i = 0; i < m_points.size(); ++i) {
538 QPointF pos = m_points.at(i);
539 QPointF vec = m_vectors.at(i);
540 pos += vec;
541 if (pos.x() < left || pos.x() > right) {
542 vec.setX(-vec.x());
543 pos.setX(pos.x() < left ? left : right);
544 } if (pos.y() < top || pos.y() > bottom) {
545 vec.setY(-vec.y());
546 pos.setY(pos.y() < top ? top : bottom);
547 }
548 m_points[i] = pos;
549 m_vectors[i] = vec;
550 }
551 update();
552}
553
554void PathStrokeRenderer::mousePressEvent(QMouseEvent *e)
555{
556 if (!m_fingerPointMapping.isEmpty())
557 return;
558 setDescriptionEnabled(false);
559 m_activePoint = -1;
560 qreal distance = -1;
561 for (int i = 0; i < m_points.size(); ++i) {
562 qreal d = QLineF(e->position().toPoint(), m_points.at(i)).length();
563 if ((distance < 0 && d < 8 * m_pointSize) || d < distance) {
564 distance = d;
565 m_activePoint = i;
566 }
567 }
568
569 if (m_activePoint != -1) {
570 m_wasAnimated = m_timer.isActive();
571 setAnimation(false);
572 mouseMoveEvent(e);
573 }
574
575 // If we're not running in small screen mode, always assume we're dragging
576 m_mouseDrag = !m_smallScreen;
577 m_mousePress = e->position().toPoint();
578}
579
580void PathStrokeRenderer::mouseMoveEvent(QMouseEvent *e)
581{
582 if (!m_fingerPointMapping.isEmpty())
583 return;
584 // If we've moved more then 25 pixels, assume user is dragging
585 if (!m_mouseDrag && QPoint(m_mousePress - e->position().toPoint()).manhattanLength() > 25)
586 m_mouseDrag = true;
587
588 if (m_mouseDrag && m_activePoint >= 0 && m_activePoint < m_points.size()) {
589 m_points[m_activePoint] = e->position().toPoint();
590 update();
591 }
592}
593
594void PathStrokeRenderer::mouseReleaseEvent(QMouseEvent *)
595{
596 if (!m_fingerPointMapping.isEmpty())
597 return;
598 m_activePoint = -1;
599 setAnimation(m_wasAnimated);
600
601 if (!m_mouseDrag && m_smallScreen)
602 emit clicked();
603}
604
605void PathStrokeRenderer::timerEvent(QTimerEvent *e)
606{
607 if (e->timerId() == m_timer.timerId()) {
608 updatePoints();
609 } // else if (e->timerId() == m_fpsTimer.timerId()) {
610// emit frameRate(m_frameCount);
611// m_frameCount = 0;
612// }
613}
614
615bool PathStrokeRenderer::event(QEvent *e)
616{
617 bool touchBegin = false;
618 switch (e->type()) {
619 case QEvent::TouchBegin:
620 touchBegin = true;
621 Q_FALLTHROUGH();
622 case QEvent::TouchUpdate:
623 {
624 const QTouchEvent *const event = static_cast<const QTouchEvent*>(e);
625 const QList<QTouchEvent::TouchPoint> points = event->touchPoints();
626 for (const QTouchEvent::TouchPoint &touchPoint : points) {
627 const int id = touchPoint.id();
628 switch (touchPoint.state()) {
629 case Qt::TouchPointPressed:
630 {
631 // find the point, move it
632 const auto mappedPoints = m_fingerPointMapping.values();
633 QSet<int> activePoints = QSet<int>(mappedPoints.begin(), mappedPoints.end());
634 int activePoint = -1;
635 qreal distance = -1;
636 const int pointsCount = m_points.size();
637 for (int i=0; i<pointsCount; ++i) {
638 if (activePoints.contains(i))
639 continue;
640
641 qreal d = QLineF(touchPoint.position(), m_points.at(i)).length();
642 if ((distance < 0 && d < 12 * m_pointSize) || d < distance) {
643 distance = d;
644 activePoint = i;
645 }
646 }
647 if (activePoint != -1) {
648 m_fingerPointMapping.insert(touchPoint.id(), activePoint);
649 m_points[activePoint] = touchPoint.position();
650 }
651 break;
652 }
653 case Qt::TouchPointReleased:
654 {
655 // move the point and release
656 QHash<int,int>::iterator it = m_fingerPointMapping.find(id);
657 m_points[it.value()] = touchPoint.position();
658 m_fingerPointMapping.erase(it);
659 break;
660 }
661 case Qt::TouchPointMoved:
662 {
663 // move the point
664 const int pointIdx = m_fingerPointMapping.value(id, -1);
665 if (pointIdx >= 0)
666 m_points[pointIdx] = touchPoint.position();
667 break;
668 }
669 default:
670 break;
671 }
672 }
673 if (m_fingerPointMapping.isEmpty()) {
674 e->ignore();
675 return false;
676 } else {
677 if (touchBegin) {
678 m_wasAnimated = m_timer.isActive();
679 setAnimation(false);
680 }
681 update();
682 return true;
683 }
684 }
685 break;
686 case QEvent::TouchEnd:
687 if (m_fingerPointMapping.isEmpty()) {
688 e->ignore();
689 return false;
690 }
691 m_fingerPointMapping.clear();
692 setAnimation(m_wasAnimated);
693 return true;
694 default:
695 break;
696 }
697 return QWidget::event(e);
698}
699
700void PathStrokeRenderer::setAnimation(bool animation)
701{
702 m_timer.stop();
703// m_fpsTimer.stop();
704
705 if (animation) {
706 m_timer.start(25, this);
707// m_fpsTimer.start(1000, this);
708// m_frameCount = 0;
709 }
710}
711