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;