From a527415ac94220b0a5dabd3a90d1e95d8189dd34 Mon Sep 17 00:00:00 2001 From: Berthold Stoeger Date: Sun, 25 Jun 2023 08:56:21 +0200 Subject: [PATCH] profile: move dragging code from stats to general qt-code So far, the only dragable item was the legend in the statistics code. On the profile, we will have multiple dragable items. Therefore, move the code up and make it more general. This took some reorganization. Now, the size of the ChartItem is saved in the base class. Also, setPos() became a virtual function. The dragable items are kept as an unsorted list. If there will be many of them, this should be changed to some sort of sorted list (maybe quadtree?). Signed-off-by: Berthold Stoeger --- qt-quick/chartitem.cpp | 44 +++++++++++++++++++---------------- qt-quick/chartitem.h | 23 +++++++++++-------- qt-quick/chartview.cpp | 52 ++++++++++++++++++++++++++++++++++++++++++ qt-quick/chartview.h | 13 ++++++++++- stats/legend.cpp | 3 ++- stats/statsview.cpp | 41 ++++++++------------------------- stats/statsview.h | 3 +-- 7 files changed, 114 insertions(+), 65 deletions(-) diff --git a/qt-quick/chartitem.cpp b/qt-quick/chartitem.cpp index e63303ed1..bd6b2a637 100644 --- a/qt-quick/chartitem.cpp +++ b/qt-quick/chartitem.cpp @@ -9,9 +9,9 @@ #include #include -ChartItem::ChartItem(ChartView &v, size_t z) : +ChartItem::ChartItem(ChartView &v, size_t z, bool dragable) : dirty(false), prev(nullptr), next(nullptr), - zValue(z), view(v) + zValue(z), dragable(dragable), view(v) { // Register before the derived constructors run, so that the // derived classes can mark the item as dirty in the constructor. @@ -32,12 +32,21 @@ void ChartItem::markDirty() view.registerDirtyChartItem(*this); } +QRectF ChartItem::getRect() const +{ + return rect; +} + +void ChartItem::setPos(QPointF) +{ +} + static int round_up(double f) { return static_cast(ceil(f)); } -ChartPixmapItem::ChartPixmapItem(ChartView &v, size_t z) : HideableChartItem(v, z), +ChartPixmapItem::ChartPixmapItem(ChartView &v, size_t z, bool dragable) : HideableChartItem(v, z, dragable), positionDirty(false), textureDirty(false) { } @@ -104,11 +113,6 @@ void ChartPixmapItem::setPos(QPointF pos) setPositionDirty(); } -QRectF ChartPixmapItem::getRect() const -{ - return rect; -} - void ChartGraphicsSceneItem::draw(QSizeF s, QColor background, QGraphicsScene &scene) { resize(s); // Noop if size doesn't change @@ -119,8 +123,9 @@ void ChartGraphicsSceneItem::draw(QSizeF s, QColor background, QGraphicsScene &s setTextureDirty(); } -ChartRectItem::ChartRectItem(ChartView &v, size_t z, - const QPen &pen, const QBrush &brush, double radius) : ChartPixmapItem(v, z), +ChartRectItem::ChartRectItem(ChartView &v, size_t z, const QPen &pen, + const QBrush &brush, double radius, bool dragable) : + ChartPixmapItem(v, z, dragable), pen(pen), brush(brush), radius(radius) { } @@ -189,10 +194,9 @@ ChartLineItemBase::~ChartLineItemBase() { } -void ChartLineItemBase::setLine(QPointF fromIn, QPointF toIn) +void ChartLineItemBase::setLine(QPointF from, QPointF to) { - from = fromIn; - to = toIn; + rect = QRectF(from, to); positionDirty = true; markDirty(); } @@ -215,8 +219,8 @@ void ChartLineItem::render() // Attention: width is a geometry property and therefore handled by position dirty! geometry->setLineWidth(static_cast(width)); auto vertices = geometry->vertexDataAsPoint2D(); - setPoint(vertices[0], from); - setPoint(vertices[1], to); + setPoint(vertices[0], rect.topLeft()); + setPoint(vertices[1], rect.bottomRight()); node->markDirty(QSGNode::DirtyGeometry); } @@ -246,11 +250,11 @@ void ChartRectLineItem::render() // Attention: width is a geometry property and therefore handled by position dirty! geometry->setLineWidth(static_cast(width)); auto vertices = geometry->vertexDataAsPoint2D(); - setPoint(vertices[0], from); - setPoint(vertices[1], QPointF(from.x(), to.y())); - setPoint(vertices[2], to); - setPoint(vertices[3], QPointF(to.x(), from.y())); - setPoint(vertices[4], from); + setPoint(vertices[0], rect.topLeft()); + setPoint(vertices[1], rect.bottomLeft()); + setPoint(vertices[2], rect.bottomRight()); + setPoint(vertices[3], rect.topRight()); + setPoint(vertices[4], rect.topLeft()); node->markDirty(QSGNode::DirtyGeometry); } diff --git a/qt-quick/chartitem.h b/qt-quick/chartitem.h index 2b4e89cd1..aed6a3c6e 100644 --- a/qt-quick/chartitem.h +++ b/qt-quick/chartitem.h @@ -27,18 +27,22 @@ public: bool dirty; // If true, call render() when rebuilding the scene ChartItem *prev, *next; // Double linked list of items const size_t zValue; + const bool dragable; // Item can be dragged with the mouse. Must be set in constructor. virtual ~ChartItem(); // Attention: must only be called by render thread. + QRectF getRect() const; + virtual void setPos(QPointF pos); // Called when dragging the item protected: - ChartItem(ChartView &v, size_t z); + ChartItem(ChartView &v, size_t z, bool dragable = false); QSizeF sceneSize() const; ChartView &view; void markDirty(); + QRectF rect; }; template class HideableChartItem : public ChartItem { protected: - HideableChartItem(ChartView &v, size_t z); + HideableChartItem(ChartView &v, size_t z, bool dragable = false); std::unique_ptr node; bool visible; bool visibleChanged; @@ -56,19 +60,17 @@ using HideableChartProxyItem = HideableChartItem { public: - ChartPixmapItem(ChartView &v, size_t z); + ChartPixmapItem(ChartView &v, size_t z, bool dragable = false); ~ChartPixmapItem(); - void setPos(QPointF pos); + void setPos(QPointF pos) override; void render() override; - QRectF getRect() const; protected: void resize(QSizeF size); // Resets the canvas. Attention: image is *unitialized*. std::unique_ptr painter; std::unique_ptr img; void setTextureDirty(); void setPositionDirty(); - QRectF rect; private: bool positionDirty; // true if the position changed since last render bool textureDirty; // true if the pixmap changed since last render @@ -86,7 +88,7 @@ public: // Draw a rectangular background after resize. Children are responsible for calling update(). class ChartRectItem : public ChartPixmapItem { public: - ChartRectItem(ChartView &v, size_t z, const QPen &pen, const QBrush &brush, double radius); + ChartRectItem(ChartView &v, size_t z, const QPen &pen, const QBrush &brush, double radius, bool dragable = false); ~ChartRectItem(); void resize(QSizeF size); private: @@ -113,14 +115,15 @@ private: std::vector items; }; -// Common data for line and rect items. Both are represented by two points. +// Common data for line and rect items. +// Both use the "QRect rect" item of the base class. +// Lines are drawn from top-left to bottom-right. class ChartLineItemBase : public HideableChartItem> { public: ChartLineItemBase(ChartView &v, size_t z, QColor color, double width); ~ChartLineItemBase(); void setLine(QPointF from, QPointF to); protected: - QPointF from, to; QColor color; double width; bool positionDirty; @@ -162,7 +165,7 @@ void HideableChartItem::createNode(Args&&... args) } template -HideableChartItem::HideableChartItem(ChartView &v, size_t z) : ChartItem(v, z), +HideableChartItem::HideableChartItem(ChartView &v, size_t z, bool dragable) : ChartItem(v, z, dragable), visible(true), visibleChanged(false) { } diff --git a/qt-quick/chartview.cpp b/qt-quick/chartview.cpp index aae8991d2..f4e90af0c 100644 --- a/qt-quick/chartview.cpp +++ b/qt-quick/chartview.cpp @@ -105,6 +105,8 @@ void ChartView::emergencyShutdown() // Mark clean and dirty chart items for deletion... cleanItems.splice(deletedItems); dirtyItems.splice(deletedItems); + dragableItems.clear(); + draggedItem.reset(); // ...and delete them. freeDeletedChartItems(); @@ -134,6 +136,8 @@ void ChartView::addQSGNode(QSGNode *node, size_t z) void ChartView::registerChartItem(ChartItem &item) { cleanItems.append(item); + if (item.dragable) + dragableItems.push_back(&item); } void ChartView::registerDirtyChartItem(ChartItem &item) @@ -152,6 +156,14 @@ void ChartView::deleteChartItemInternal(ChartItem &item) else cleanItems.remove(item); deletedItems.append(item); + if (item.dragable) { + // This becomes inefficient if there are many dragable items! + auto it = std::find(dragableItems.begin(), dragableItems.end(), &item); + if (it == dragableItems.end()) + fprintf(stderr, "warning: dragable item not found\n"); + else + dragableItems.erase(it); + } } ChartView::ChartItemList::ChartItemList() : first(nullptr), last(nullptr) @@ -246,3 +258,43 @@ void ChartView::setLayerVisibility(size_t z, bool visible) if (rootNode && z < rootNode->zNodes.size()) rootNode->zNodes[z]->setVisible(visible); } + +void ChartView::mousePressEvent(QMouseEvent *event) +{ + QPointF pos = event->localPos(); + + for (auto item: dragableItems) { + QRectF rect = item->getRect(); + if (rect.contains(pos)) { + dragStartMouse = pos; + dragStartItem = rect.topLeft(); + draggedItem = item; + grabMouse(); + setKeepMouseGrab(true); // don't allow Qt to steal the grab + event->accept(); + return; + } + } + + event->ignore(); +} + +void ChartView::mouseReleaseEvent(QMouseEvent *event) +{ + if (draggedItem) { + draggedItem.reset(); + ungrabMouse(); + event->accept(); + } +} + +void ChartView::mouseMoveEvent(QMouseEvent *event) +{ + if (draggedItem) { + QSizeF sceneSize = size(); + if (sceneSize.width() <= 1.0 || sceneSize.height() <= 1.0) + return; + draggedItem->setPos(event->pos() - dragStartMouse + dragStartItem); + update(); + } +} diff --git a/qt-quick/chartview.h b/qt-quick/chartview.h index 192665d85..90b6180d6 100644 --- a/qt-quick/chartview.h +++ b/qt-quick/chartview.h @@ -43,6 +43,10 @@ protected: // This is called when Qt decided to reset our rootNode, which invalidates all items on the chart. // The base class must invalidate all pointers and references. virtual void resetPointers() = 0; + + void mousePressEvent(QMouseEvent *event) override; + void mouseReleaseEvent(QMouseEvent *event) override; + void mouseMoveEvent(QMouseEvent *event) override; private: // QtQuick related things size_t maxZ; @@ -77,10 +81,17 @@ private: ChartItemList cleanItems, dirtyItems, deletedItems; void deleteChartItemInternal(ChartItem &item); void freeDeletedChartItems(); + + // Keep a list of dragable items. For now, there are no many, + // so keep it unsorted. In the future we might want to sort by + // coordinates. + std::vector dragableItems; + ChartItemPtr draggedItem; + QPointF dragStartMouse, dragStartItem; }; // This implementation detail must be known to users of the class. -// Perhaps move it into a statsview_impl.h file. +// Perhaps move it into a chartview_impl.h file. template ChartItemPtr ChartView::createChartItem(Args&&... args) { diff --git a/stats/legend.cpp b/stats/legend.cpp index 029854800..f70c1d7fe 100644 --- a/stats/legend.cpp +++ b/stats/legend.cpp @@ -17,7 +17,8 @@ static const double legendInternalBorderSize = 2.0; Legend::Legend(ChartView &view, const StatsTheme &theme, const std::vector &names) : ChartRectItem(view, ChartZValue::Legend, QPen(theme.legendBorderColor, legendBorderSize), - QBrush(theme.legendColor), legendBoxBorderRadius), + QBrush(theme.legendColor), legendBoxBorderRadius, + true), displayedItems(0), width(0.0), height(0.0), theme(theme), posInitialized(false) diff --git a/stats/statsview.cpp b/stats/statsview.cpp index 319173297..0cffd89a2 100644 --- a/stats/statsview.cpp +++ b/stats/statsview.cpp @@ -33,7 +33,6 @@ StatsView::StatsView(QQuickItem *parent) : ChartView(parent, ChartZValue::Count) highlightedSeries(nullptr), xAxis(nullptr), yAxis(nullptr), - draggedItem(nullptr), restrictDives(false) { setBackgroundColor(currentTheme->backgroundColor); @@ -60,22 +59,12 @@ StatsView::~StatsView() void StatsView::mousePressEvent(QMouseEvent *event) { + // Handle dragging of items + ChartView::mousePressEvent(event); + if (event->isAccepted()) + return; + QPointF pos = event->localPos(); - - // Currently, we only support dragging of the legend. If other objects - // should be made draggable, this needs to be generalized. - if (legend) { - QRectF rect = legend->getRect(); - if (legend->getRect().contains(pos)) { - dragStartMouse = pos; - dragStartItem = rect.topLeft(); - draggedItem = &*legend; - grabMouse(); - setKeepMouseGrab(true); // don't allow Qt to steal the grab - return; - } - } - SelectionModifier modifier; modifier.shift = (event->modifiers() & Qt::ShiftModifier) != 0; modifier.ctrl = (event->modifiers() & Qt::ControlModifier) != 0; @@ -90,7 +79,7 @@ void StatsView::mousePressEvent(QMouseEvent *event) { return s->supportsLassoSelection(); })) { if (selectionRect) deleteChartItem(selectionRect); // Ooops. Already a selection in place. - dragStartMouse = pos; + selectionStartMouse = pos; selectionRect = createChartItem(ChartZValue::Selection, currentTheme->selectionLassoColor, selectionLassoWidth); selectionModifier = modifier; oldSelection = modifier.ctrl ? getDiveSelection() : std::vector(); @@ -100,12 +89,9 @@ void StatsView::mousePressEvent(QMouseEvent *event) } } -void StatsView::mouseReleaseEvent(QMouseEvent *) +void StatsView::mouseReleaseEvent(QMouseEvent *event) { - if (draggedItem) { - draggedItem = nullptr; - ungrabMouse(); - } + ChartView::mouseReleaseEvent(event); if (selectionRect) { deleteChartItem(selectionRect); @@ -192,17 +178,11 @@ void StatsView::divesSelected(const QVector &dives) void StatsView::mouseMoveEvent(QMouseEvent *event) { - if (draggedItem) { - QSizeF sceneSize = size(); - if (sceneSize.width() <= 1.0 || sceneSize.height() <= 1.0) - return; - draggedItem->setPos(event->pos() - dragStartMouse + dragStartItem); - update(); - } + ChartView::mouseMoveEvent(event); if (selectionRect) { QPointF p1 = event->pos(); - QPointF p2 = dragStartMouse; + QPointF p2 = selectionStartMouse; selectionRect->setLine(p1, p2); QRectF rect(std::min(p1.x(), p2.x()), std::min(p1.y(), p2.y()), fabs(p2.x() - p1.x()), fabs(p2.y() - p1.y())); @@ -292,7 +272,6 @@ void StatsView::resetPointers() { highlightedSeries = nullptr; xAxis = yAxis = nullptr; - draggedItem = nullptr; title.reset(); legend.reset(); regressionItem.reset(); diff --git a/stats/statsview.h b/stats/statsview.h index 59e43bb34..503fc065d 100644 --- a/stats/statsview.h +++ b/stats/statsview.h @@ -120,10 +120,9 @@ private: StatsAxis *xAxis, *yAxis; ChartItemPtr title; ChartItemPtr legend; - Legend *draggedItem; ChartItemPtr regressionItem; ChartItemPtr selectionRect; - QPointF dragStartMouse, dragStartItem; + QPointF selectionStartMouse; SelectionModifier selectionModifier; std::vector oldSelection; bool restrictDives;