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 | |
55 | extern void draw_round_rect(QPainter *p, const QRect &bounds, int radius); |
56 | |
57 | |
58 | PathStrokeControls::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 | |
69 | void PathStrokeControls::createCommonControls(QWidget* parent) |
70 | { |
71 | m_capGroup = new QGroupBox(parent); |
72 | m_capGroup->setSizePolicy(hor: QSizePolicy::Minimum, ver: 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(s: "Cap Style" )); |
77 | flatCap->setText(tr(s: "Flat" )); |
78 | squareCap->setText(tr(s: "Square" )); |
79 | roundCap->setText(tr(s: "Round" )); |
80 | flatCap->setSizePolicy(hor: QSizePolicy::Fixed, ver: QSizePolicy::Fixed); |
81 | squareCap->setSizePolicy(hor: QSizePolicy::Fixed, ver: QSizePolicy::Fixed); |
82 | roundCap->setSizePolicy(hor: QSizePolicy::Fixed, ver: QSizePolicy::Fixed); |
83 | |
84 | m_joinGroup = new QGroupBox(parent); |
85 | m_joinGroup->setSizePolicy(hor: QSizePolicy::Minimum, ver: 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(s: "Join Style" )); |
91 | bevelJoin->setText(tr(s: "Bevel" )); |
92 | miterJoin->setText(tr(s: "Miter" )); |
93 | svgMiterJoin->setText(tr(s: "SvgMiter" )); |
94 | roundJoin->setText(tr(s: "Round" )); |
95 | |
96 | m_styleGroup = new QGroupBox(parent); |
97 | m_styleGroup->setSizePolicy(hor: QSizePolicy::Minimum, ver: 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(s: "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(s: "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(hor: QSizePolicy::Minimum, ver: QSizePolicy::Fixed); |
132 | QRadioButton *curveMode = new QRadioButton(m_pathModeGroup); |
133 | QRadioButton *lineMode = new QRadioButton(m_pathModeGroup); |
134 | m_pathModeGroup->setTitle(tr(s: "Line Style" )); |
135 | curveMode->setText(tr(s: "Curves" )); |
136 | lineMode->setText(tr(s: "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(sender: flatCap, signal: &QAbstractButton::clicked, |
166 | receiver: m_renderer, slot: &PathStrokeRenderer::setFlatCap); |
167 | connect(sender: squareCap, signal: &QAbstractButton::clicked, |
168 | receiver: m_renderer, slot: &PathStrokeRenderer::setSquareCap); |
169 | connect(sender: roundCap, signal: &QAbstractButton::clicked, |
170 | receiver: m_renderer, slot: &PathStrokeRenderer::setRoundCap); |
171 | |
172 | connect(sender: bevelJoin, signal: &QAbstractButton::clicked, |
173 | receiver: m_renderer, slot: &PathStrokeRenderer::setBevelJoin); |
174 | connect(sender: miterJoin, signal: &QAbstractButton::clicked, |
175 | receiver: m_renderer, slot: &PathStrokeRenderer::setMiterJoin); |
176 | connect(sender: svgMiterJoin, signal: &QAbstractButton::clicked, |
177 | receiver: m_renderer, slot: &PathStrokeRenderer::setSvgMiterJoin); |
178 | connect(sender: roundJoin, signal: &QAbstractButton::clicked, |
179 | receiver: m_renderer, slot: &PathStrokeRenderer::setRoundJoin); |
180 | |
181 | connect(sender: curveMode, signal: &QAbstractButton::clicked, |
182 | receiver: m_renderer, slot: &PathStrokeRenderer::setCurveMode); |
183 | connect(sender: lineMode, signal: &QAbstractButton::clicked, |
184 | receiver: m_renderer, slot: &PathStrokeRenderer::setLineMode); |
185 | |
186 | connect(sender: solidLine, signal: &QAbstractButton::clicked, |
187 | receiver: m_renderer, slot: &PathStrokeRenderer::setSolidLine); |
188 | connect(sender: dashLine, signal: &QAbstractButton::clicked, |
189 | receiver: m_renderer, slot: &PathStrokeRenderer::setDashLine); |
190 | connect(sender: dotLine, signal: &QAbstractButton::clicked, |
191 | receiver: m_renderer, slot: &PathStrokeRenderer::setDotLine); |
192 | connect(sender: dashDotLine, signal: &QAbstractButton::clicked, |
193 | receiver: m_renderer, slot: &PathStrokeRenderer::setDashDotLine); |
194 | connect(sender: dashDotDotLine, signal: &QAbstractButton::clicked, |
195 | receiver: m_renderer, slot: &PathStrokeRenderer::setDashDotDotLine); |
196 | connect(sender: customDashLine, signal: &QAbstractButton::clicked, |
197 | receiver: m_renderer, slot: &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 | |
207 | void PathStrokeControls::layoutForDesktop() |
208 | { |
209 | QGroupBox *mainGroup = new QGroupBox(this); |
210 | mainGroup->setFixedWidth(180); |
211 | mainGroup->setTitle(tr(s: "Path Stroking" )); |
212 | |
213 | createCommonControls(parent: mainGroup); |
214 | |
215 | QGroupBox* penWidthGroup = new QGroupBox(mainGroup); |
216 | QSlider *penWidth = new QSlider(Qt::Horizontal, penWidthGroup); |
217 | penWidth->setSizePolicy(hor: QSizePolicy::Preferred, ver: QSizePolicy::Fixed); |
218 | penWidthGroup->setTitle(tr(s: "Pen Width" )); |
219 | penWidth->setRange(min: 0, max: 500); |
220 | |
221 | QPushButton *animated = new QPushButton(mainGroup); |
222 | animated->setText(tr(s: "Animate" )); |
223 | animated->setCheckable(true); |
224 | |
225 | QPushButton *showSourceButton = new QPushButton(mainGroup); |
226 | showSourceButton->setText(tr(s: "Show Source" )); |
227 | #if QT_CONFIG(opengl) |
228 | QPushButton *enableOpenGLButton = new QPushButton(mainGroup); |
229 | enableOpenGLButton->setText(tr(s: "Use OpenGL" )); |
230 | enableOpenGLButton->setCheckable(true); |
231 | enableOpenGLButton->setChecked(m_renderer->usesOpenGL()); |
232 | #endif |
233 | QPushButton *whatsThisButton = new QPushButton(mainGroup); |
234 | whatsThisButton->setText(tr(s: "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(left: 3, top: 3, right: 3, bottom: 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(stretch: 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(sender: animated, signal: &QAbstractButton::toggled, |
264 | receiver: m_renderer, slot: &PathStrokeRenderer::setAnimation); |
265 | |
266 | connect(sender: penWidth, signal: &QAbstractSlider::valueChanged, |
267 | receiver: m_renderer, slot: &PathStrokeRenderer::setPenWidth); |
268 | |
269 | connect(sender: showSourceButton, signal: &QAbstractButton::clicked, |
270 | receiver: m_renderer, slot: &ArthurFrame::showSource); |
271 | #if QT_CONFIG(opengl) |
272 | connect(sender: enableOpenGLButton, signal: &QAbstractButton::clicked, |
273 | receiver: m_renderer, slot: &ArthurFrame::enableOpenGL); |
274 | #endif |
275 | connect(sender: whatsThisButton, signal: &QAbstractButton::clicked, |
276 | receiver: m_renderer, slot: &ArthurFrame::setDescriptionEnabled); |
277 | connect(sender: m_renderer, signal: &ArthurFrame::descriptionEnabledChanged, |
278 | receiver: whatsThisButton, slot: &QAbstractButton::setChecked); |
279 | |
280 | |
281 | // Set the defaults |
282 | animated->setChecked(true); |
283 | penWidth->setValue(50); |
284 | |
285 | } |
286 | |
287 | void PathStrokeControls::layoutForSmallScreens() |
288 | { |
289 | createCommonControls(parent: 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(s: "OK" ), this); |
297 | okBtn->setSizePolicy(hor: QSizePolicy::Fixed, ver: QSizePolicy::Fixed); |
298 | okBtn->setMinimumSize(minw: 100,minh: okBtn->minimumSize().height()); |
299 | |
300 | QPushButton* quitBtn = new QPushButton(tr(s: "Quit" ), this); |
301 | quitBtn->setSizePolicy(hor: QSizePolicy::Fixed, ver: QSizePolicy::Fixed); |
302 | quitBtn->setMinimumSize(minw: 100, minh: okBtn->minimumSize().height()); |
303 | |
304 | QLabel *penWidthLabel = new QLabel(tr(s: " Width:" )); |
305 | QSlider *penWidth = new QSlider(Qt::Horizontal, this); |
306 | penWidth->setSizePolicy(hor: QSizePolicy::Preferred, ver: QSizePolicy::Fixed); |
307 | penWidth->setRange(min: 0, max: 500); |
308 | |
309 | #if QT_CONFIG(opengl) |
310 | QPushButton *enableOpenGLButton = new QPushButton(this); |
311 | enableOpenGLButton->setText(tr(s: "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, stretch: 0, alignment: 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(layout: 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(item: new QSpacerItem(0,0), row: 0, column: 0, rowSpan: 1, columnSpan: 4); |
338 | mainLayout->addItem(item: new QSpacerItem(0,0), row: 1, column: 0, rowSpan: 2, columnSpan: 1); |
339 | mainLayout->addItem(item: new QSpacerItem(0,0), row: 1, column: 3, rowSpan: 2, columnSpan: 1); |
340 | mainLayout->addItem(item: new QSpacerItem(0,0), row: 3, column: 0, rowSpan: 1, columnSpan: 4); |
341 | |
342 | mainLayout->addLayout(leftLayout, row: 1, column: 1); |
343 | mainLayout->addLayout(rightLayout, row: 1, column: 2); |
344 | mainLayout->addWidget(quitBtn, row: 2, column: 1, Qt::AlignHCenter | Qt::AlignTop); |
345 | mainLayout->addWidget(okBtn, row: 2, column: 2, Qt::AlignHCenter | Qt::AlignTop); |
346 | |
347 | #if QT_CONFIG(opengl) |
348 | connect(sender: enableOpenGLButton, signal: &QAbstractButton::clicked, receiver: m_renderer, slot: &ArthurFrame::enableOpenGL); |
349 | #endif |
350 | |
351 | connect(sender: penWidth, signal: &QAbstractSlider::valueChanged, receiver: m_renderer, slot: &PathStrokeRenderer::setPenWidth); |
352 | connect(sender: quitBtn, signal: &QAbstractButton::clicked, receiver: this, slot: &PathStrokeControls::emitQuitSignal); |
353 | connect(sender: okBtn, signal: &QAbstractButton::clicked, receiver: this, slot: &PathStrokeControls::emitOkSignal); |
354 | |
355 | m_renderer->setAnimation(true); |
356 | penWidth->setValue(50); |
357 | } |
358 | |
359 | void PathStrokeControls::emitQuitSignal() |
360 | { |
361 | emit quitPressed(); |
362 | } |
363 | |
364 | void PathStrokeControls::emitOkSignal() |
365 | { |
366 | emit okPressed(); |
367 | } |
368 | |
369 | |
370 | PathStrokeWidget::PathStrokeWidget(bool smallScreen) |
371 | { |
372 | setWindowTitle(tr(s: "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(fileName: ":res/pathstroke/pathstroke.cpp" ); |
387 | m_renderer->loadDescription(filename: ":res/pathstroke/pathstroke.html" ); |
388 | |
389 | connect(sender: m_renderer, signal: &PathStrokeRenderer::clicked, receiver: this, slot: &PathStrokeWidget::showControls); |
390 | connect(sender: m_controls, signal: &PathStrokeControls::okPressed, receiver: this, slot: &PathStrokeWidget::hideControls); |
391 | connect(sender: m_controls, SIGNAL(quitPressed()), receiver: QApplication::instance(), SLOT(quit())); |
392 | } |
393 | |
394 | void PathStrokeWidget::showControls() |
395 | { |
396 | m_controls->showFullScreen(); |
397 | } |
398 | |
399 | void PathStrokeWidget::hideControls() |
400 | { |
401 | m_controls->hide(); |
402 | } |
403 | |
404 | void 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 | |
417 | PathStrokeRenderer::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(hor: QSizePolicy::Expanding, ver: QSizePolicy::Expanding); |
430 | setAttribute(Qt::WA_AcceptTouchEvents); |
431 | } |
432 | |
433 | void PathStrokeRenderer::paint(QPainter *painter) |
434 | { |
435 | if (m_points.isEmpty()) |
436 | initializePoints(); |
437 | |
438 | painter->setRenderHint(hint: QPainter::Antialiasing); |
439 | |
440 | QPalette pal = palette(); |
441 | painter->setPen(Qt::NoPen); |
442 | |
443 | // Construct the path |
444 | QPainterPath path; |
445 | path.moveTo(p: m_points.at(i: 0)); |
446 | |
447 | if (m_pathMode == LineMode) { |
448 | for (int i=1; i<m_points.size(); ++i) |
449 | path.lineTo(p: m_points.at(i)); |
450 | } else { |
451 | int i=1; |
452 | while (i + 2 < m_points.size()) { |
453 | path.cubicTo(ctrlPt1: m_points.at(i), ctrlPt2: m_points.at(i: i+1), endPt: m_points.at(i: i+2)); |
454 | i += 3; |
455 | } |
456 | while (i < m_points.size()) { |
457 | path.lineTo(p: 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 | QVector<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(path: stroke, brush: 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(r: 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(polyline: m_points); |
504 | } |
505 | |
506 | } |
507 | |
508 | void 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(sh: 2, sv: -1); |
519 | vm.scale(sx: 3, sy: 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(a: rot); |
525 | } |
526 | } |
527 | |
528 | void 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 | |
554 | void 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->pos(), 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->pos(); |
578 | } |
579 | |
580 | void 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->pos()).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->pos(); |
590 | update(); |
591 | } |
592 | } |
593 | |
594 | void 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 | |
605 | void 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 | |
615 | bool 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(value: i)) |
639 | continue; |
640 | |
641 | qreal d = QLineF(touchPoint.pos(), 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(akey: touchPoint.id(), avalue: activePoint); |
649 | m_points[activePoint] = touchPoint.pos(); |
650 | } |
651 | break; |
652 | } |
653 | case Qt::TouchPointReleased: |
654 | { |
655 | // move the point and release |
656 | QHash<int,int>::iterator it = m_fingerPointMapping.find(akey: id); |
657 | m_points[it.value()] = touchPoint.pos(); |
658 | m_fingerPointMapping.erase(it); |
659 | break; |
660 | } |
661 | case Qt::TouchPointMoved: |
662 | { |
663 | // move the point |
664 | const int pointIdx = m_fingerPointMapping.value(akey: id, adefaultValue: -1); |
665 | if (pointIdx >= 0) |
666 | m_points[pointIdx] = touchPoint.pos(); |
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(event: e); |
698 | } |
699 | |
700 | void PathStrokeRenderer::setAnimation(bool animation) |
701 | { |
702 | m_timer.stop(); |
703 | // m_fpsTimer.stop(); |
704 | |
705 | if (animation) { |
706 | m_timer.start(msec: 25, obj: this); |
707 | // m_fpsTimer.start(1000, this); |
708 | // m_frameCount = 0; |
709 | } |
710 | } |
711 | |