From fc9d1fe08a1f3fb036a9a2ad4e0f4c72c7c267c3 Mon Sep 17 00:00:00 2001 From: jgrogan Date: Wed, 10 Apr 2024 20:10:01 +0100 Subject: [PATCH] Add some demo examples. --- .gitignore | 3 +- src/app/CMakeLists.txt | 4 + src/app/Edge.cpp | 101 ++++++++++++++++++ src/app/Edge.h | 35 +++++++ src/app/GraphWidget.cpp | 215 +++++++++++++++++++++++++++++++++++++++ src/app/GraphWidget.h | 39 +++++++ src/app/MainWindow.cpp | 26 +++-- src/app/MainWindow.h | 17 ++-- src/app/MainWindow.ui | 2 +- src/app/Node.cpp | 172 +++++++++++++++++++++++++++++++ src/app/Node.h | 42 ++++++++ src/app/ScribbleArea.cpp | 178 ++++++++++++++++++++++++++++++++ src/app/ScribbleArea.h | 50 +++++++++ 13 files changed, 864 insertions(+), 20 deletions(-) create mode 100644 src/app/Edge.cpp create mode 100644 src/app/Edge.h create mode 100644 src/app/GraphWidget.cpp create mode 100644 src/app/GraphWidget.h create mode 100644 src/app/Node.cpp create mode 100644 src/app/Node.h create mode 100644 src/app/ScribbleArea.cpp create mode 100644 src/app/ScribbleArea.h diff --git a/.gitignore b/.gitignore index 496ee2c..2608ec2 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -.DS_Store \ No newline at end of file +.DS_Store +.vscode \ No newline at end of file diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt index 247043b..41463f9 100644 --- a/src/app/CMakeLists.txt +++ b/src/app/CMakeLists.txt @@ -3,6 +3,10 @@ qt_add_executable(scribbles main.cpp MainWindow.cpp MainWindow.ui + ScribbleArea.cpp + Edge.cpp + Node.cpp + GraphWidget.cpp ) target_link_libraries(scribbles PRIVATE Qt6::Widgets) diff --git a/src/app/Edge.cpp b/src/app/Edge.cpp new file mode 100644 index 0000000..5f73955 --- /dev/null +++ b/src/app/Edge.cpp @@ -0,0 +1,101 @@ +#include "Edge.h" +#include "Node.h" + +#include +#include + +//! [0] +Edge::Edge(Node *sourceNode, Node *destNode) + : source(sourceNode), dest(destNode) +{ + setAcceptedMouseButtons(Qt::NoButton); + source->addEdge(this); + dest->addEdge(this); + adjust(); +} +//! [0] + +//! [1] +Node *Edge::sourceNode() const +{ + return source; +} + +Node *Edge::destNode() const +{ + return dest; +} +//! [1] + +//! [2] +void Edge::adjust() +{ + if (!source || !dest) + return; + + QLineF line(mapFromItem(source, 0, 0), mapFromItem(dest, 0, 0)); + qreal length = line.length(); + + prepareGeometryChange(); + + if (length > qreal(20.)) { + QPointF edgeOffset((line.dx() * 10) / length, (line.dy() * 10) / length); + sourcePoint = line.p1() + edgeOffset; + destPoint = line.p2() - edgeOffset; + } else { + sourcePoint = destPoint = line.p1(); + } +} +//! [2] + +//! [3] +QRectF Edge::boundingRect() const +{ + if (!source || !dest) + return QRectF(); + + qreal penWidth = 1; + qreal extra = (penWidth + arrowSize) / 2.0; + + return QRectF(sourcePoint, QSizeF(destPoint.x() - sourcePoint.x(), + destPoint.y() - sourcePoint.y())) + .normalized() + .adjusted(-extra, -extra, extra, extra); +} +//! [3] + +//! [4] +void Edge::paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *) +{ + if (!source || !dest) + return; + + QLineF line(sourcePoint, destPoint); + if (qFuzzyCompare(line.length(), qreal(0.))) + return; +//! [4] + +//! [5] + // Draw the line itself + painter->setPen(QPen(Qt::black, 1, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin)); + painter->drawLine(line); +//! [5] + +//! [6] + // Draw the arrows + double angle = std::atan2(-line.dy(), line.dx()); + + QPointF sourceArrowP1 = sourcePoint + QPointF(sin(angle + M_PI / 3) * arrowSize, + cos(angle + M_PI / 3) * arrowSize); + QPointF sourceArrowP2 = sourcePoint + QPointF(sin(angle + M_PI - M_PI / 3) * arrowSize, + cos(angle + M_PI - M_PI / 3) * arrowSize); + QPointF destArrowP1 = destPoint + QPointF(sin(angle - M_PI / 3) * arrowSize, + cos(angle - M_PI / 3) * arrowSize); + QPointF destArrowP2 = destPoint + QPointF(sin(angle - M_PI + M_PI / 3) * arrowSize, + cos(angle - M_PI + M_PI / 3) * arrowSize); + + painter->setBrush(Qt::black); + painter->drawPolygon(QPolygonF() << line.p1() << sourceArrowP1 << sourceArrowP2); + painter->drawPolygon(QPolygonF() << line.p2() << destArrowP1 << destArrowP2); +} +//! [6] \ No newline at end of file diff --git a/src/app/Edge.h b/src/app/Edge.h new file mode 100644 index 0000000..e9e1c16 --- /dev/null +++ b/src/app/Edge.h @@ -0,0 +1,35 @@ +#ifndef EDGE_H +#define EDGE_H + +#include + +class Node; + +//! [0] +class Edge : public QGraphicsItem +{ +public: + Edge(Node *sourceNode, Node *destNode); + + Node *sourceNode() const; + Node *destNode() const; + + void adjust(); + + enum { Type = UserType + 2 }; + int type() const override { return Type; } + +protected: + QRectF boundingRect() const override; + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override; + +private: + Node *source, *dest; + + QPointF sourcePoint; + QPointF destPoint; + qreal arrowSize = 10; +}; +//! [0] + +#endif // EDGE_H \ No newline at end of file diff --git a/src/app/GraphWidget.cpp b/src/app/GraphWidget.cpp new file mode 100644 index 0000000..61c1200 --- /dev/null +++ b/src/app/GraphWidget.cpp @@ -0,0 +1,215 @@ +#include "GraphWidget.h" +#include "Edge.h" +#include "Node.h" + +#include + +#include +#include + +//! [0] +GraphWidget::GraphWidget(QWidget *parent) + : QGraphicsView(parent) +{ + QGraphicsScene *scene = new QGraphicsScene(this); + scene->setItemIndexMethod(QGraphicsScene::NoIndex); + scene->setSceneRect(-200, -200, 400, 400); + setScene(scene); + setCacheMode(CacheBackground); + setViewportUpdateMode(BoundingRectViewportUpdate); + setRenderHint(QPainter::Antialiasing); + setTransformationAnchor(AnchorUnderMouse); + scale(qreal(0.8), qreal(0.8)); + setMinimumSize(400, 400); + setWindowTitle(tr("Elastic Nodes")); +//! [0] + +//! [1] + Node *node1 = new Node(this); + Node *node2 = new Node(this); + Node *node3 = new Node(this); + Node *node4 = new Node(this); + centerNode = new Node(this); + Node *node6 = new Node(this); + Node *node7 = new Node(this); + Node *node8 = new Node(this); + Node *node9 = new Node(this); + scene->addItem(node1); + scene->addItem(node2); + scene->addItem(node3); + scene->addItem(node4); + scene->addItem(centerNode); + scene->addItem(node6); + scene->addItem(node7); + scene->addItem(node8); + scene->addItem(node9); + scene->addItem(new Edge(node1, node2)); + scene->addItem(new Edge(node2, node3)); + scene->addItem(new Edge(node2, centerNode)); + scene->addItem(new Edge(node3, node6)); + scene->addItem(new Edge(node4, node1)); + scene->addItem(new Edge(node4, centerNode)); + scene->addItem(new Edge(centerNode, node6)); + scene->addItem(new Edge(centerNode, node8)); + scene->addItem(new Edge(node6, node9)); + scene->addItem(new Edge(node7, node4)); + scene->addItem(new Edge(node8, node7)); + scene->addItem(new Edge(node9, node8)); + + node1->setPos(-50, -50); + node2->setPos(0, -50); + node3->setPos(50, -50); + node4->setPos(-50, 0); + centerNode->setPos(0, 0); + node6->setPos(50, 0); + node7->setPos(-50, 50); + node8->setPos(0, 50); + node9->setPos(50, 50); +} +//! [1] + +//! [2] +void GraphWidget::itemMoved() +{ + if (!timerId) + timerId = startTimer(1000 / 25); +} +//! [2] + +//! [3] +void GraphWidget::keyPressEvent(QKeyEvent *event) +{ + switch (event->key()) { + case Qt::Key_Up: + centerNode->moveBy(0, -20); + break; + case Qt::Key_Down: + centerNode->moveBy(0, 20); + break; + case Qt::Key_Left: + centerNode->moveBy(-20, 0); + break; + case Qt::Key_Right: + centerNode->moveBy(20, 0); + break; + case Qt::Key_Plus: + zoomIn(); + break; + case Qt::Key_Minus: + zoomOut(); + break; + case Qt::Key_Space: + case Qt::Key_Enter: + shuffle(); + break; + default: + QGraphicsView::keyPressEvent(event); + } +} +//! [3] + +//! [4] +void GraphWidget::timerEvent(QTimerEvent *event) +{ + Q_UNUSED(event); + + QList nodes; + const QList items = scene()->items(); + for (QGraphicsItem *item : items) { + if (Node *node = qgraphicsitem_cast(item)) + nodes << node; + } + + for (Node *node : std::as_const(nodes)) + node->calculateForces(); + + bool itemsMoved = false; + for (Node *node : std::as_const(nodes)) { + if (node->advancePosition()) + itemsMoved = true; + } + + if (!itemsMoved) { + killTimer(timerId); + timerId = 0; + } +} +//! [4] + +#if QT_CONFIG(wheelevent) +//! [5] +void GraphWidget::wheelEvent(QWheelEvent *event) +{ + scaleView(pow(2., -event->angleDelta().y() / 240.0)); +} +//! [5] +#endif + +//! [6] +void GraphWidget::drawBackground(QPainter *painter, const QRectF &rect) +{ + Q_UNUSED(rect); + + // Shadow + QRectF sceneRect = this->sceneRect(); + QRectF rightShadow(sceneRect.right(), sceneRect.top() + 5, 5, sceneRect.height()); + QRectF bottomShadow(sceneRect.left() + 5, sceneRect.bottom(), sceneRect.width(), 5); + if (rightShadow.intersects(rect) || rightShadow.contains(rect)) + painter->fillRect(rightShadow, Qt::darkGray); + if (bottomShadow.intersects(rect) || bottomShadow.contains(rect)) + painter->fillRect(bottomShadow, Qt::darkGray); + + // Fill + QLinearGradient gradient(sceneRect.topLeft(), sceneRect.bottomRight()); + gradient.setColorAt(0, Qt::white); + gradient.setColorAt(1, Qt::lightGray); + painter->fillRect(rect.intersected(sceneRect), gradient); + painter->setBrush(Qt::NoBrush); + painter->drawRect(sceneRect); + + // Text + QRectF textRect(sceneRect.left() + 4, sceneRect.top() + 4, + sceneRect.width() - 4, sceneRect.height() - 4); + QString message(tr("Click and drag the nodes around, and zoom with the mouse " + "wheel or the '+' and '-' keys")); + + QFont font = painter->font(); + font.setBold(true); + font.setPointSize(14); + painter->setFont(font); + painter->setPen(Qt::lightGray); + painter->drawText(textRect.translated(2, 2), message); + painter->setPen(Qt::black); + painter->drawText(textRect, message); +} +//! [6] + +//! [7] +void GraphWidget::scaleView(qreal scaleFactor) +{ + qreal factor = transform().scale(scaleFactor, scaleFactor).mapRect(QRectF(0, 0, 1, 1)).width(); + if (factor < 0.07 || factor > 100) + return; + + scale(scaleFactor, scaleFactor); +} +//! [7] + +void GraphWidget::shuffle() +{ + const QList items = scene()->items(); + for (QGraphicsItem *item : items) { + if (qgraphicsitem_cast(item)) + item->setPos(-150 + QRandomGenerator::global()->bounded(300), -150 + QRandomGenerator::global()->bounded(300)); + } +} + +void GraphWidget::zoomIn() +{ + scaleView(qreal(1.2)); +} + +void GraphWidget::zoomOut() +{ + scaleView(1 / qreal(1.2)); +} \ No newline at end of file diff --git a/src/app/GraphWidget.h b/src/app/GraphWidget.h new file mode 100644 index 0000000..5e583c0 --- /dev/null +++ b/src/app/GraphWidget.h @@ -0,0 +1,39 @@ +#ifndef GRAPHWIDGET_H +#define GRAPHWIDGET_H + +#include + +class Node; + +//! [0] +class GraphWidget : public QGraphicsView +{ + Q_OBJECT + +public: + GraphWidget(QWidget *parent = nullptr); + + void itemMoved(); + +public slots: + void shuffle(); + void zoomIn(); + void zoomOut(); + +protected: + void keyPressEvent(QKeyEvent *event) override; + void timerEvent(QTimerEvent *event) override; +#if QT_CONFIG(wheelevent) + void wheelEvent(QWheelEvent *event) override; +#endif + void drawBackground(QPainter *painter, const QRectF &rect) override; + + void scaleView(qreal scaleFactor); + +private: + int timerId = 0; + Node *centerNode; +}; +//! [0] + +#endif // GRAPHWIDGET_H \ No newline at end of file diff --git a/src/app/MainWindow.cpp b/src/app/MainWindow.cpp index b31e068..bf0840f 100644 --- a/src/app/MainWindow.cpp +++ b/src/app/MainWindow.cpp @@ -1,11 +1,6 @@ #include "MainWindow.h" #include "ui_MainWindow.h" -//#include "viewerfactory.h" -//#include "abstractviewer.h" -//#include "recentfiles.h" -//#include "recentfilemenu.h" - #include #include #include @@ -13,16 +8,27 @@ #include #include +//#include "viewerfactory.h" +//#include "abstractviewer.h" +//#include "recentfiles.h" +//#include "recentfilemenu.h" + +#include "ScribbleArea.h" +#include "GraphWidget.h" + + using namespace Qt::StringLiterals; MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), - ui(new Ui::MainWindow) + //ui(new Ui::MainWindow), + m_scribble_area(new ScribbleArea), + m_graph_widget(new GraphWidget) { - ui->setupUi(this); - connect(ui->actionOpen, &QAction::triggered, this, &MainWindow::onActionOpenTriggered); - connect(ui->actionAbout, &QAction::triggered, this, &MainWindow::onActionAboutTriggered); - connect(ui->actionAboutQt, &QAction::triggered, this, &MainWindow::onActionAboutQtTriggered); + setCentralWidget(m_graph_widget.get()); + + setWindowTitle(tr("Scribbles")); + //resize(500, 500); /* m_recentFiles.reset(new RecentFiles(ui->actionRecent)); diff --git a/src/app/MainWindow.h b/src/app/MainWindow.h index 8fee9e0..4d9cbd4 100644 --- a/src/app/MainWindow.h +++ b/src/app/MainWindow.h @@ -1,5 +1,4 @@ -#ifndef MAINWINDOW_H -#define MAINWINDOW_H +#pragma once #include #include @@ -9,11 +8,12 @@ class AbstractViewer; class RecentFiles; class ViewerFactory; -QT_BEGIN_NAMESPACE namespace Ui { class MainWindow; } -QT_END_NAMESPACE + +class ScribbleArea; +class GraphWidget; class MainWindow : public QMainWindow { @@ -38,9 +38,12 @@ private: void resetViewer() const; void saveViewerSettings() const; + std::unique_ptr m_scribble_area; + std::unique_ptr m_graph_widget; + QDir m_currentDir; AbstractViewer *m_viewer = nullptr; - std::unique_ptr ui; + //std::unique_ptr ui; //std::unique_ptr m_recentFiles; //std::unique_ptr m_factory; @@ -48,6 +51,4 @@ private: static constexpr QLatin1StringView settingsMainWindow = QLatin1StringView("MainWindow"); static constexpr QLatin1StringView settingsViewers = QLatin1StringView("Viewers"); static constexpr QLatin1StringView settingsFiles = QLatin1StringView("RecentFiles"); -}; - -#endif // MAINWINDOW_H \ No newline at end of file +}; \ No newline at end of file diff --git a/src/app/MainWindow.ui b/src/app/MainWindow.ui index 088dd3f..ff10e40 100644 --- a/src/app/MainWindow.ui +++ b/src/app/MainWindow.ui @@ -11,7 +11,7 @@ - Document Viewer Demo + Scribbles diff --git a/src/app/Node.cpp b/src/app/Node.cpp new file mode 100644 index 0000000..6e41a25 --- /dev/null +++ b/src/app/Node.cpp @@ -0,0 +1,172 @@ +#include "Edge.h" +#include "Node.h" +#include "GraphWidget.h" + +#include +#include +#include +#include + +//! [0] +Node::Node(GraphWidget *graphWidget) + : graph(graphWidget) +{ + setFlag(ItemIsMovable); + setFlag(ItemSendsGeometryChanges); + setCacheMode(DeviceCoordinateCache); + setZValue(-1); +} +//! [0] + +//! [1] +void Node::addEdge(Edge *edge) +{ + edgeList << edge; + edge->adjust(); +} + +QList Node::edges() const +{ + return edgeList; +} +//! [1] + +//! [2] +void Node::calculateForces() +{ + if (!scene() || scene()->mouseGrabberItem() == this) { + newPos = pos(); + return; + } +//! [2] + +//! [3] + // Sum up all forces pushing this item away + qreal xvel = 0; + qreal yvel = 0; + const QList items = scene()->items(); + for (QGraphicsItem *item : items) { + Node *node = qgraphicsitem_cast(item); + if (!node) + continue; + + QPointF vec = mapToItem(node, 0, 0); + qreal dx = vec.x(); + qreal dy = vec.y(); + double l = 2.0 * (dx * dx + dy * dy); + if (l > 0) { + xvel += (dx * 150.0) / l; + yvel += (dy * 150.0) / l; + } + } +//! [3] + +//! [4] + // Now subtract all forces pulling items together + double weight = (edgeList.size() + 1) * 10; + for (const Edge *edge : std::as_const(edgeList)) { + QPointF vec; + if (edge->sourceNode() == this) + vec = mapToItem(edge->destNode(), 0, 0); + else + vec = mapToItem(edge->sourceNode(), 0, 0); + xvel -= vec.x() / weight; + yvel -= vec.y() / weight; + } +//! [4] + +//! [5] + if (qAbs(xvel) < 0.1 && qAbs(yvel) < 0.1) + xvel = yvel = 0; +//! [5] + +//! [6] + QRectF sceneRect = scene()->sceneRect(); + newPos = pos() + QPointF(xvel, yvel); + newPos.setX(qMin(qMax(newPos.x(), sceneRect.left() + 10), sceneRect.right() - 10)); + newPos.setY(qMin(qMax(newPos.y(), sceneRect.top() + 10), sceneRect.bottom() - 10)); +} +//! [6] + +//! [7] +bool Node::advancePosition() +{ + if (newPos == pos()) + return false; + + setPos(newPos); + return true; +} +//! [7] + +//! [8] +QRectF Node::boundingRect() const +{ + qreal adjust = 2; + return QRectF( -10 - adjust, -10 - adjust, 23 + adjust, 23 + adjust); +} +//! [8] + +//! [9] +QPainterPath Node::shape() const +{ + QPainterPath path; + path.addEllipse(-10, -10, 20, 20); + return path; +} +//! [9] + +//! [10] +void Node::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *) +{ + painter->setPen(Qt::NoPen); + painter->setBrush(Qt::darkGray); + painter->drawEllipse(-7, -7, 20, 20); + + QRadialGradient gradient(-3, -3, 10); + if (option->state & QStyle::State_Sunken) { + gradient.setCenter(3, 3); + gradient.setFocalPoint(3, 3); + gradient.setColorAt(1, QColor(Qt::yellow).lighter(120)); + gradient.setColorAt(0, QColor(Qt::darkYellow).lighter(120)); + } else { + gradient.setColorAt(0, Qt::yellow); + gradient.setColorAt(1, Qt::darkYellow); + } + painter->setBrush(gradient); + + painter->setPen(QPen(Qt::black, 0)); + painter->drawEllipse(-10, -10, 20, 20); +} +//! [10] + +//! [11] +QVariant Node::itemChange(GraphicsItemChange change, const QVariant &value) +{ + switch (change) { + case ItemPositionHasChanged: + for (Edge *edge : std::as_const(edgeList)) + edge->adjust(); + graph->itemMoved(); + break; + default: + break; + }; + + return QGraphicsItem::itemChange(change, value); +} +//! [11] + +//! [12] +void Node::mousePressEvent(QGraphicsSceneMouseEvent *event) +{ + update(); + QGraphicsItem::mousePressEvent(event); +} + +void Node::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) +{ + update(); + QGraphicsItem::mouseReleaseEvent(event); +} +//! [12] \ No newline at end of file diff --git a/src/app/Node.h b/src/app/Node.h new file mode 100644 index 0000000..aeaf514 --- /dev/null +++ b/src/app/Node.h @@ -0,0 +1,42 @@ +#ifndef NODE_H +#define NODE_H + +#include +#include + +class Edge; +class GraphWidget; + +//! [0] +class Node : public QGraphicsItem +{ +public: + Node(GraphWidget *graphWidget); + + void addEdge(Edge *edge); + QList edges() const; + + enum { Type = UserType + 1 }; + int type() const override { return Type; } + + void calculateForces(); + bool advancePosition(); + + QRectF boundingRect() const override; + QPainterPath shape() const override; + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override; + +protected: + QVariant itemChange(GraphicsItemChange change, const QVariant &value) override; + + void mousePressEvent(QGraphicsSceneMouseEvent *event) override; + void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override; + +private: + QList edgeList; + QPointF newPos; + GraphWidget *graph; +}; +//! [0] + +#endif // NODE_H \ No newline at end of file diff --git a/src/app/ScribbleArea.cpp b/src/app/ScribbleArea.cpp new file mode 100644 index 0000000..0dd0cbc --- /dev/null +++ b/src/app/ScribbleArea.cpp @@ -0,0 +1,178 @@ +#include "ScribbleArea.h" + +#include +#include + +#if defined(QT_PRINTSUPPORT_LIB) +#include +#if QT_CONFIG(printdialog) +#include +#include +#endif +#endif + +//! [0] +ScribbleArea::ScribbleArea(QWidget *parent) + : QWidget(parent) +{ + setAttribute(Qt::WA_StaticContents); +} +//! [0] + +//! [1] +bool ScribbleArea::openImage(const QString &fileName) +//! [1] //! [2] +{ + QImage loadedImage; + if (!loadedImage.load(fileName)) + return false; + + QSize newSize = loadedImage.size().expandedTo(size()); + resizeImage(&loadedImage, newSize); + image = loadedImage; + modified = false; + update(); + return true; +} +//! [2] + +//! [3] +bool ScribbleArea::saveImage(const QString &fileName, const char *fileFormat) +//! [3] //! [4] +{ + QImage visibleImage = image; + resizeImage(&visibleImage, size()); + + if (visibleImage.save(fileName, fileFormat)) { + modified = false; + return true; + } + return false; +} +//! [4] + +//! [5] +void ScribbleArea::setPenColor(const QColor &newColor) +//! [5] //! [6] +{ + myPenColor = newColor; +} +//! [6] + +//! [7] +void ScribbleArea::setPenWidth(int newWidth) +//! [7] //! [8] +{ + myPenWidth = newWidth; +} +//! [8] + +//! [9] +void ScribbleArea::clearImage() +//! [9] //! [10] +{ + image.fill(qRgb(255, 255, 255)); + modified = true; + update(); +} +//! [10] + +//! [11] +void ScribbleArea::mousePressEvent(QMouseEvent *event) +//! [11] //! [12] +{ + if (event->button() == Qt::LeftButton) { + lastPoint = event->position().toPoint(); + scribbling = true; + } +} + +void ScribbleArea::mouseMoveEvent(QMouseEvent *event) +{ + if ((event->buttons() & Qt::LeftButton) && scribbling) + drawLineTo(event->position().toPoint()); +} + +void ScribbleArea::mouseReleaseEvent(QMouseEvent *event) +{ + if (event->button() == Qt::LeftButton && scribbling) { + drawLineTo(event->position().toPoint()); + scribbling = false; + } +} + +//! [12] //! [13] +void ScribbleArea::paintEvent(QPaintEvent *event) +//! [13] //! [14] +{ + QPainter painter(this); + QRect dirtyRect = event->rect(); + painter.drawImage(dirtyRect, image, dirtyRect); +} +//! [14] + +//! [15] +void ScribbleArea::resizeEvent(QResizeEvent *event) +//! [15] //! [16] +{ + if (width() > image.width() || height() > image.height()) { + int newWidth = qMax(width() + 128, image.width()); + int newHeight = qMax(height() + 128, image.height()); + resizeImage(&image, QSize(newWidth, newHeight)); + update(); + } + QWidget::resizeEvent(event); +} +//! [16] + +//! [17] +void ScribbleArea::drawLineTo(const QPoint &endPoint) +//! [17] //! [18] +{ + QPainter painter(&image); + painter.setPen(QPen(myPenColor, myPenWidth, Qt::SolidLine, Qt::RoundCap, + Qt::RoundJoin)); + painter.drawLine(lastPoint, endPoint); + modified = true; + + int rad = (myPenWidth / 2) + 2; + update(QRect(lastPoint, endPoint).normalized() + .adjusted(-rad, -rad, +rad, +rad)); + lastPoint = endPoint; +} +//! [18] + +//! [19] +void ScribbleArea::resizeImage(QImage *image, const QSize &newSize) +//! [19] //! [20] +{ + if (image->size() == newSize) + return; + + QImage newImage(newSize, QImage::Format_RGB32); + newImage.fill(qRgb(255, 255, 255)); + QPainter painter(&newImage); + painter.drawImage(QPoint(0, 0), *image); + *image = newImage; +} +//! [20] + +//! [21] +void ScribbleArea::print() +{ +#if defined(QT_PRINTSUPPORT_LIB) && QT_CONFIG(printdialog) + QPrinter printer(QPrinter::HighResolution); + + QPrintDialog printDialog(&printer, this); +//! [21] //! [22] + if (printDialog.exec() == QDialog::Accepted) { + QPainter painter(&printer); + QRect rect = painter.viewport(); + QSize size = image.size(); + size.scale(rect.size(), Qt::KeepAspectRatio); + painter.setViewport(rect.x(), rect.y(), size.width(), size.height()); + painter.setWindow(image.rect()); + painter.drawImage(0, 0, image); + } +#endif // QT_CONFIG(printdialog) +} \ No newline at end of file diff --git a/src/app/ScribbleArea.h b/src/app/ScribbleArea.h new file mode 100644 index 0000000..e05692b --- /dev/null +++ b/src/app/ScribbleArea.h @@ -0,0 +1,50 @@ +#ifndef SCRIBBLEAREA_H +#define SCRIBBLEAREA_H + +#include +#include +#include +#include + +//! [0] +class ScribbleArea : public QWidget +{ + Q_OBJECT + +public: + ScribbleArea(QWidget *parent = nullptr); + + bool openImage(const QString &fileName); + bool saveImage(const QString &fileName, const char *fileFormat); + void setPenColor(const QColor &newColor); + void setPenWidth(int newWidth); + + bool isModified() const { return modified; } + QColor penColor() const { return myPenColor; } + int penWidth() const { return myPenWidth; } + +public slots: + void clearImage(); + void print(); + +protected: + void mousePressEvent(QMouseEvent *event) override; + void mouseMoveEvent(QMouseEvent *event) override; + void mouseReleaseEvent(QMouseEvent *event) override; + void paintEvent(QPaintEvent *event) override; + void resizeEvent(QResizeEvent *event) override; + +private: + void drawLineTo(const QPoint &endPoint); + void resizeImage(QImage *image, const QSize &newSize); + + bool modified = false; + bool scribbling = false; + int myPenWidth = 1; + QColor myPenColor = Qt::blue; + QImage image; + QPoint lastPoint; +}; +//! [0] + +#endif \ No newline at end of file