/**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the demonstration applications of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:BSD$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** BSD License Usage ** Alternatively, you may use this file under the terms of the BSD license ** as follows: ** ** "Redistribution and use in source and binary forms, with or without ** modification, are permitted provided that the following conditions are ** met: ** * Redistributions of source code must retain the above copyright ** notice, this list of conditions and the following disclaimer. ** * Redistributions in binary form must reproduce the above copyright ** notice, this list of conditions and the following disclaimer in ** the documentation and/or other materials provided with the ** distribution. ** * Neither the name of The Qt Company Ltd nor the names of its ** contributors may be used to endorse or promote products derived ** from this software without specific prior written permission. ** ** ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "arthurstyle.h" #include "arthurwidgets.h" #include "pathstroke.h" #include extern void draw_round_rect(QPainter *p, const QRect &bounds, int radius); PathStrokeControls::PathStrokeControls(QWidget* parent, PathStrokeRenderer* renderer, bool smallScreen) : QWidget(parent) { m_renderer = renderer; if (smallScreen) layoutForSmallScreens(); else layoutForDesktop(); } void PathStrokeControls::createCommonControls(QWidget* parent) { m_capGroup = new QGroupBox(parent); m_capGroup->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); QRadioButton *flatCap = new QRadioButton(m_capGroup); QRadioButton *squareCap = new QRadioButton(m_capGroup); QRadioButton *roundCap = new QRadioButton(m_capGroup); m_capGroup->setTitle(tr("Cap Style")); flatCap->setText(tr("Flat")); squareCap->setText(tr("Square")); roundCap->setText(tr("Round")); flatCap->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); squareCap->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); roundCap->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); m_joinGroup = new QGroupBox(parent); m_joinGroup->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); QRadioButton *bevelJoin = new QRadioButton(m_joinGroup); QRadioButton *miterJoin = new QRadioButton(m_joinGroup); QRadioButton *svgMiterJoin = new QRadioButton(m_joinGroup); QRadioButton *roundJoin = new QRadioButton(m_joinGroup); m_joinGroup->setTitle(tr("Join Style")); bevelJoin->setText(tr("Bevel")); miterJoin->setText(tr("Miter")); svgMiterJoin->setText(tr("SvgMiter")); roundJoin->setText(tr("Round")); m_styleGroup = new QGroupBox(parent); m_styleGroup->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); QRadioButton *solidLine = new QRadioButton(m_styleGroup); QRadioButton *dashLine = new QRadioButton(m_styleGroup); QRadioButton *dotLine = new QRadioButton(m_styleGroup); QRadioButton *dashDotLine = new QRadioButton(m_styleGroup); QRadioButton *dashDotDotLine = new QRadioButton(m_styleGroup); QRadioButton *customDashLine = new QRadioButton(m_styleGroup); m_styleGroup->setTitle(tr("Pen Style")); QPixmap line_solid(":res/images/line_solid.png"); solidLine->setIcon(line_solid); solidLine->setIconSize(line_solid.size()); QPixmap line_dashed(":res/images/line_dashed.png"); dashLine->setIcon(line_dashed); dashLine->setIconSize(line_dashed.size()); QPixmap line_dotted(":res/images/line_dotted.png"); dotLine->setIcon(line_dotted); dotLine->setIconSize(line_dotted.size()); QPixmap line_dash_dot(":res/images/line_dash_dot.png"); dashDotLine->setIcon(line_dash_dot); dashDotLine->setIconSize(line_dash_dot.size()); QPixmap line_dash_dot_dot(":res/images/line_dash_dot_dot.png"); dashDotDotLine->setIcon(line_dash_dot_dot); dashDotDotLine->setIconSize(line_dash_dot_dot.size()); customDashLine->setText(tr("Custom")); int fixedHeight = bevelJoin->sizeHint().height(); solidLine->setFixedHeight(fixedHeight); dashLine->setFixedHeight(fixedHeight); dotLine->setFixedHeight(fixedHeight); dashDotLine->setFixedHeight(fixedHeight); dashDotDotLine->setFixedHeight(fixedHeight); m_pathModeGroup = new QGroupBox(parent); m_pathModeGroup->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); QRadioButton *curveMode = new QRadioButton(m_pathModeGroup); QRadioButton *lineMode = new QRadioButton(m_pathModeGroup); m_pathModeGroup->setTitle(tr("Line Style")); curveMode->setText(tr("Curves")); lineMode->setText(tr("Lines")); // Layouts QVBoxLayout *capGroupLayout = new QVBoxLayout(m_capGroup); capGroupLayout->addWidget(flatCap); capGroupLayout->addWidget(squareCap); capGroupLayout->addWidget(roundCap); QVBoxLayout *joinGroupLayout = new QVBoxLayout(m_joinGroup); joinGroupLayout->addWidget(bevelJoin); joinGroupLayout->addWidget(miterJoin); joinGroupLayout->addWidget(svgMiterJoin); joinGroupLayout->addWidget(roundJoin); QVBoxLayout *styleGroupLayout = new QVBoxLayout(m_styleGroup); styleGroupLayout->addWidget(solidLine); styleGroupLayout->addWidget(dashLine); styleGroupLayout->addWidget(dotLine); styleGroupLayout->addWidget(dashDotLine); styleGroupLayout->addWidget(dashDotDotLine); styleGroupLayout->addWidget(customDashLine); QVBoxLayout *pathModeGroupLayout = new QVBoxLayout(m_pathModeGroup); pathModeGroupLayout->addWidget(curveMode); pathModeGroupLayout->addWidget(lineMode); // Connections connect(flatCap, &QAbstractButton::clicked, m_renderer, &PathStrokeRenderer::setFlatCap); connect(squareCap, &QAbstractButton::clicked, m_renderer, &PathStrokeRenderer::setSquareCap); connect(roundCap, &QAbstractButton::clicked, m_renderer, &PathStrokeRenderer::setRoundCap); connect(bevelJoin, &QAbstractButton::clicked, m_renderer, &PathStrokeRenderer::setBevelJoin); connect(miterJoin, &QAbstractButton::clicked, m_renderer, &PathStrokeRenderer::setMiterJoin); connect(svgMiterJoin, &QAbstractButton::clicked, m_renderer, &PathStrokeRenderer::setSvgMiterJoin); connect(roundJoin, &QAbstractButton::clicked, m_renderer, &PathStrokeRenderer::setRoundJoin); connect(curveMode, &QAbstractButton::clicked, m_renderer, &PathStrokeRenderer::setCurveMode); connect(lineMode, &QAbstractButton::clicked, m_renderer, &PathStrokeRenderer::setLineMode); connect(solidLine, &QAbstractButton::clicked, m_renderer, &PathStrokeRenderer::setSolidLine); connect(dashLine, &QAbstractButton::clicked, m_renderer, &PathStrokeRenderer::setDashLine); connect(dotLine, &QAbstractButton::clicked, m_renderer, &PathStrokeRenderer::setDotLine); connect(dashDotLine, &QAbstractButton::clicked, m_renderer, &PathStrokeRenderer::setDashDotLine); connect(dashDotDotLine, &QAbstractButton::clicked, m_renderer, &PathStrokeRenderer::setDashDotDotLine); connect(customDashLine, &QAbstractButton::clicked, m_renderer, &PathStrokeRenderer::setCustomDashLine); // Set the defaults: flatCap->setChecked(true); bevelJoin->setChecked(true); curveMode->setChecked(true); solidLine->setChecked(true); } void PathStrokeControls::layoutForDesktop() { QGroupBox *mainGroup = new QGroupBox(this); mainGroup->setFixedWidth(180); mainGroup->setTitle(tr("Path Stroking")); createCommonControls(mainGroup); QGroupBox* penWidthGroup = new QGroupBox(mainGroup); QSlider *penWidth = new QSlider(Qt::Horizontal, penWidthGroup); penWidth->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); penWidthGroup->setTitle(tr("Pen Width")); penWidth->setRange(0, 500); QPushButton *animated = new QPushButton(mainGroup); animated->setText(tr("Animate")); animated->setCheckable(true); QPushButton *showSourceButton = new QPushButton(mainGroup); showSourceButton->setText(tr("Show Source")); #if QT_CONFIG(opengl) QPushButton *enableOpenGLButton = new QPushButton(mainGroup); enableOpenGLButton->setText(tr("Use OpenGL")); enableOpenGLButton->setCheckable(true); enableOpenGLButton->setChecked(m_renderer->usesOpenGL()); #endif QPushButton *whatsThisButton = new QPushButton(mainGroup); whatsThisButton->setText(tr("What's This?")); whatsThisButton->setCheckable(true); // Layouts: QVBoxLayout *penWidthLayout = new QVBoxLayout(penWidthGroup); penWidthLayout->addWidget(penWidth); QVBoxLayout * mainLayout = new QVBoxLayout(this); mainLayout->setMargin(0); mainLayout->addWidget(mainGroup); QVBoxLayout *mainGroupLayout = new QVBoxLayout(mainGroup); mainGroupLayout->setMargin(3); mainGroupLayout->addWidget(m_capGroup); mainGroupLayout->addWidget(m_joinGroup); mainGroupLayout->addWidget(m_styleGroup); mainGroupLayout->addWidget(penWidthGroup); mainGroupLayout->addWidget(m_pathModeGroup); mainGroupLayout->addWidget(animated); mainGroupLayout->addStretch(1); mainGroupLayout->addWidget(showSourceButton); #if QT_CONFIG(opengl) mainGroupLayout->addWidget(enableOpenGLButton); #endif mainGroupLayout->addWidget(whatsThisButton); // Set up connections connect(animated, &QAbstractButton::toggled, m_renderer, &PathStrokeRenderer::setAnimation); connect(penWidth, &QAbstractSlider::valueChanged, m_renderer, &PathStrokeRenderer::setPenWidth); connect(showSourceButton, &QAbstractButton::clicked, m_renderer, &ArthurFrame::showSource); #if QT_CONFIG(opengl) connect(enableOpenGLButton, &QAbstractButton::clicked, m_renderer, &ArthurFrame::enableOpenGL); #endif connect(whatsThisButton, &QAbstractButton::clicked, m_renderer, &ArthurFrame::setDescriptionEnabled); connect(m_renderer, &ArthurFrame::descriptionEnabledChanged, whatsThisButton, &QAbstractButton::setChecked); // Set the defaults animated->setChecked(true); penWidth->setValue(50); } void PathStrokeControls::layoutForSmallScreens() { createCommonControls(this); m_capGroup->layout()->setMargin(0); m_joinGroup->layout()->setMargin(0); m_styleGroup->layout()->setMargin(0); m_pathModeGroup->layout()->setMargin(0); QPushButton* okBtn = new QPushButton(tr("OK"), this); okBtn->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); okBtn->setMinimumSize(100,okBtn->minimumSize().height()); QPushButton* quitBtn = new QPushButton(tr("Quit"), this); quitBtn->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); quitBtn->setMinimumSize(100, okBtn->minimumSize().height()); QLabel *penWidthLabel = new QLabel(tr(" Width:")); QSlider *penWidth = new QSlider(Qt::Horizontal, this); penWidth->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); penWidth->setRange(0, 500); #if QT_CONFIG(opengl) QPushButton *enableOpenGLButton = new QPushButton(this); enableOpenGLButton->setText(tr("Use OpenGL")); enableOpenGLButton->setCheckable(true); enableOpenGLButton->setChecked(m_renderer->usesOpenGL()); #endif // Layouts: QHBoxLayout *penWidthLayout = new QHBoxLayout(0); penWidthLayout->addWidget(penWidthLabel, 0, Qt::AlignRight); penWidthLayout->addWidget(penWidth); QVBoxLayout *leftLayout = new QVBoxLayout(0); leftLayout->addWidget(m_capGroup); leftLayout->addWidget(m_joinGroup); #if QT_CONFIG(opengl) leftLayout->addWidget(enableOpenGLButton); #endif leftLayout->addLayout(penWidthLayout); QVBoxLayout *rightLayout = new QVBoxLayout(0); rightLayout->addWidget(m_styleGroup); rightLayout->addWidget(m_pathModeGroup); QGridLayout *mainLayout = new QGridLayout(this); mainLayout->setMargin(0); // Add spacers around the form items so we don't look stupid at higher resolutions mainLayout->addItem(new QSpacerItem(0,0), 0, 0, 1, 4); mainLayout->addItem(new QSpacerItem(0,0), 1, 0, 2, 1); mainLayout->addItem(new QSpacerItem(0,0), 1, 3, 2, 1); mainLayout->addItem(new QSpacerItem(0,0), 3, 0, 1, 4); mainLayout->addLayout(leftLayout, 1, 1); mainLayout->addLayout(rightLayout, 1, 2); mainLayout->addWidget(quitBtn, 2, 1, Qt::AlignHCenter | Qt::AlignTop); mainLayout->addWidget(okBtn, 2, 2, Qt::AlignHCenter | Qt::AlignTop); #if QT_CONFIG(opengl) connect(enableOpenGLButton, &QAbstractButton::clicked, m_renderer, &ArthurFrame::enableOpenGL); #endif connect(penWidth, &QAbstractSlider::valueChanged, m_renderer, &PathStrokeRenderer::setPenWidth); connect(quitBtn, &QAbstractButton::clicked, this, &PathStrokeControls::emitQuitSignal); connect(okBtn, &QAbstractButton::clicked, this, &PathStrokeControls::emitOkSignal); m_renderer->setAnimation(true); penWidth->setValue(50); } void PathStrokeControls::emitQuitSignal() { emit quitPressed(); } void PathStrokeControls::emitOkSignal() { emit okPressed(); } PathStrokeWidget::PathStrokeWidget(bool smallScreen) { setWindowTitle(tr("Path Stroking")); // Widget construction and property setting m_renderer = new PathStrokeRenderer(this, smallScreen); m_controls = new PathStrokeControls(0, m_renderer, smallScreen); // Layouting QHBoxLayout *viewLayout = new QHBoxLayout(this); viewLayout->addWidget(m_renderer); if (!smallScreen) viewLayout->addWidget(m_controls); m_renderer->loadSourceFile(":res/pathstroke/pathstroke.cpp"); m_renderer->loadDescription(":res/pathstroke/pathstroke.html"); connect(m_renderer, &PathStrokeRenderer::clicked, this, &PathStrokeWidget::showControls); connect(m_controls, &PathStrokeControls::okPressed, this, &PathStrokeWidget::hideControls); connect(m_controls, SIGNAL(quitPressed()), QApplication::instance(), SLOT(quit())); } void PathStrokeWidget::showControls() { m_controls->showFullScreen(); } void PathStrokeWidget::hideControls() { m_controls->hide(); } void PathStrokeWidget::setStyle( QStyle * style ) { QWidget::setStyle(style); if (m_controls != 0) { m_controls->setStyle(style); const QList widgets = m_controls->findChildren(); for (QWidget *w : widgets) w->setStyle(style); } } PathStrokeRenderer::PathStrokeRenderer(QWidget *parent, bool smallScreen) : ArthurFrame(parent) { m_smallScreen = smallScreen; m_pointSize = 10; m_activePoint = -1; m_capStyle = Qt::FlatCap; m_joinStyle = Qt::BevelJoin; m_pathMode = CurveMode; m_penWidth = 1; m_penStyle = Qt::SolidLine; m_wasAnimated = true; setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); setAttribute(Qt::WA_AcceptTouchEvents); } void PathStrokeRenderer::paint(QPainter *painter) { if (m_points.isEmpty()) initializePoints(); painter->setRenderHint(QPainter::Antialiasing); QPalette pal = palette(); painter->setPen(Qt::NoPen); // Construct the path QPainterPath path; path.moveTo(m_points.at(0)); if (m_pathMode == LineMode) { for (int i=1; i dashes; qreal space = 4; dashes << 1 << space << 3 << space << 9 << space << 27 << space << 9 << space << 3 << space; stroker.setDashPattern(dashes); QPainterPath stroke = stroker.createStroke(path); painter->fillPath(stroke, lg); } else { QPen pen(lg, m_penWidth, m_penStyle, m_capStyle, m_joinStyle); painter->strokePath(path, pen); } } if (1) { // Draw the control points painter->setPen(QColor(50, 100, 120, 200)); painter->setBrush(QColor(200, 200, 210, 120)); for (int i=0; idrawEllipse(QRectF(pos.x() - m_pointSize, pos.y() - m_pointSize, m_pointSize*2, m_pointSize*2)); } painter->setPen(QPen(Qt::lightGray, 0, Qt::SolidLine)); painter->setBrush(Qt::NoBrush); painter->drawPolyline(m_points); } } void PathStrokeRenderer::initializePoints() { const int count = 7; m_points.clear(); m_vectors.clear(); QMatrix m; qreal rot = 360.0 / count; QPointF center(width() / 2, height() / 2); QMatrix vm; vm.shear(2, -1); vm.scale(3, 3); for (int i=0; i right) { vec.setX(-vec.x()); pos.setX(pos.x() < left ? left : right); } if (pos.y() < top || pos.y() > bottom) { vec.setY(-vec.y()); pos.setY(pos.y() < top ? top : bottom); } m_points[i] = pos; m_vectors[i] = vec; } update(); } void PathStrokeRenderer::mousePressEvent(QMouseEvent *e) { if (!m_fingerPointMapping.isEmpty()) return; setDescriptionEnabled(false); m_activePoint = -1; qreal distance = -1; for (int i=0; ipos(), m_points.at(i)).length(); if ((distance < 0 && d < 8 * m_pointSize) || d < distance) { distance = d; m_activePoint = i; } } if (m_activePoint != -1) { m_wasAnimated = m_timer.isActive(); setAnimation(false); mouseMoveEvent(e); } // If we're not running in small screen mode, always assume we're dragging m_mouseDrag = !m_smallScreen; m_mousePress = e->pos(); } void PathStrokeRenderer::mouseMoveEvent(QMouseEvent *e) { if (!m_fingerPointMapping.isEmpty()) return; // If we've moved more then 25 pixels, assume user is dragging if (!m_mouseDrag && QPoint(m_mousePress - e->pos()).manhattanLength() > 25) m_mouseDrag = true; if (m_mouseDrag && m_activePoint >= 0 && m_activePoint < m_points.size()) { m_points[m_activePoint] = e->pos(); update(); } } void PathStrokeRenderer::mouseReleaseEvent(QMouseEvent *) { if (!m_fingerPointMapping.isEmpty()) return; m_activePoint = -1; setAnimation(m_wasAnimated); if (!m_mouseDrag && m_smallScreen) emit clicked(); } void PathStrokeRenderer::timerEvent(QTimerEvent *e) { if (e->timerId() == m_timer.timerId()) { updatePoints(); } // else if (e->timerId() == m_fpsTimer.timerId()) { // emit frameRate(m_frameCount); // m_frameCount = 0; // } } bool PathStrokeRenderer::event(QEvent *e) { bool touchBegin = false; switch (e->type()) { case QEvent::TouchBegin: touchBegin = true; Q_FALLTHROUGH(); case QEvent::TouchUpdate: { const QTouchEvent *const event = static_cast(e); const QList points = event->touchPoints(); for (const QTouchEvent::TouchPoint &touchPoint : points) { const int id = touchPoint.id(); switch (touchPoint.state()) { case Qt::TouchPointPressed: { // find the point, move it QSet activePoints = QSet::fromList(m_fingerPointMapping.values()); int activePoint = -1; qreal distance = -1; const int pointsCount = m_points.size(); for (int i=0; i::iterator it = m_fingerPointMapping.find(id); m_points[it.value()] = touchPoint.pos(); m_fingerPointMapping.erase(it); break; } case Qt::TouchPointMoved: { // move the point const int pointIdx = m_fingerPointMapping.value(id, -1); if (pointIdx >= 0) m_points[pointIdx] = touchPoint.pos(); break; } default: break; } } if (m_fingerPointMapping.isEmpty()) { e->ignore(); return false; } else { if (touchBegin) { m_wasAnimated = m_timer.isActive(); setAnimation(false); } update(); return true; } } break; case QEvent::TouchEnd: if (m_fingerPointMapping.isEmpty()) { e->ignore(); return false; } m_fingerPointMapping.clear(); setAnimation(m_wasAnimated); return true; break; default: break; } return QWidget::event(e); } void PathStrokeRenderer::setAnimation(bool animation) { m_timer.stop(); // m_fpsTimer.stop(); if (animation) { m_timer.start(25, this); // m_fpsTimer.start(1000, this); // m_frameCount = 0; } }