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 <bstoeger@mail.tuwien.ac.at>
This commit is contained in:
Berthold Stoeger 2023-06-25 08:56:21 +02:00
parent 13c9218ecf
commit a527415ac9
7 changed files with 114 additions and 65 deletions

View file

@ -9,9 +9,9 @@
#include <QSGFlatColorMaterial> #include <QSGFlatColorMaterial>
#include <QSGImageNode> #include <QSGImageNode>
ChartItem::ChartItem(ChartView &v, size_t z) : ChartItem::ChartItem(ChartView &v, size_t z, bool dragable) :
dirty(false), prev(nullptr), next(nullptr), 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 // Register before the derived constructors run, so that the
// derived classes can mark the item as dirty in the constructor. // derived classes can mark the item as dirty in the constructor.
@ -32,12 +32,21 @@ void ChartItem::markDirty()
view.registerDirtyChartItem(*this); view.registerDirtyChartItem(*this);
} }
QRectF ChartItem::getRect() const
{
return rect;
}
void ChartItem::setPos(QPointF)
{
}
static int round_up(double f) static int round_up(double f)
{ {
return static_cast<int>(ceil(f)); return static_cast<int>(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) positionDirty(false), textureDirty(false)
{ {
} }
@ -104,11 +113,6 @@ void ChartPixmapItem::setPos(QPointF pos)
setPositionDirty(); setPositionDirty();
} }
QRectF ChartPixmapItem::getRect() const
{
return rect;
}
void ChartGraphicsSceneItem::draw(QSizeF s, QColor background, QGraphicsScene &scene) void ChartGraphicsSceneItem::draw(QSizeF s, QColor background, QGraphicsScene &scene)
{ {
resize(s); // Noop if size doesn't change resize(s); // Noop if size doesn't change
@ -119,8 +123,9 @@ void ChartGraphicsSceneItem::draw(QSizeF s, QColor background, QGraphicsScene &s
setTextureDirty(); setTextureDirty();
} }
ChartRectItem::ChartRectItem(ChartView &v, size_t z, ChartRectItem::ChartRectItem(ChartView &v, size_t z, const QPen &pen,
const QPen &pen, const QBrush &brush, double radius) : ChartPixmapItem(v, z), const QBrush &brush, double radius, bool dragable) :
ChartPixmapItem(v, z, dragable),
pen(pen), brush(brush), radius(radius) 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; rect = QRectF(from, to);
to = toIn;
positionDirty = true; positionDirty = true;
markDirty(); markDirty();
} }
@ -215,8 +219,8 @@ void ChartLineItem::render()
// Attention: width is a geometry property and therefore handled by position dirty! // Attention: width is a geometry property and therefore handled by position dirty!
geometry->setLineWidth(static_cast<float>(width)); geometry->setLineWidth(static_cast<float>(width));
auto vertices = geometry->vertexDataAsPoint2D(); auto vertices = geometry->vertexDataAsPoint2D();
setPoint(vertices[0], from); setPoint(vertices[0], rect.topLeft());
setPoint(vertices[1], to); setPoint(vertices[1], rect.bottomRight());
node->markDirty(QSGNode::DirtyGeometry); node->markDirty(QSGNode::DirtyGeometry);
} }
@ -246,11 +250,11 @@ void ChartRectLineItem::render()
// Attention: width is a geometry property and therefore handled by position dirty! // Attention: width is a geometry property and therefore handled by position dirty!
geometry->setLineWidth(static_cast<float>(width)); geometry->setLineWidth(static_cast<float>(width));
auto vertices = geometry->vertexDataAsPoint2D(); auto vertices = geometry->vertexDataAsPoint2D();
setPoint(vertices[0], from); setPoint(vertices[0], rect.topLeft());
setPoint(vertices[1], QPointF(from.x(), to.y())); setPoint(vertices[1], rect.bottomLeft());
setPoint(vertices[2], to); setPoint(vertices[2], rect.bottomRight());
setPoint(vertices[3], QPointF(to.x(), from.y())); setPoint(vertices[3], rect.topRight());
setPoint(vertices[4], from); setPoint(vertices[4], rect.topLeft());
node->markDirty(QSGNode::DirtyGeometry); node->markDirty(QSGNode::DirtyGeometry);
} }

View file

@ -27,18 +27,22 @@ public:
bool dirty; // If true, call render() when rebuilding the scene bool dirty; // If true, call render() when rebuilding the scene
ChartItem *prev, *next; // Double linked list of items ChartItem *prev, *next; // Double linked list of items
const size_t zValue; 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. virtual ~ChartItem(); // Attention: must only be called by render thread.
QRectF getRect() const;
virtual void setPos(QPointF pos); // Called when dragging the item
protected: protected:
ChartItem(ChartView &v, size_t z); ChartItem(ChartView &v, size_t z, bool dragable = false);
QSizeF sceneSize() const; QSizeF sceneSize() const;
ChartView &view; ChartView &view;
void markDirty(); void markDirty();
QRectF rect;
}; };
template <typename Node> template <typename Node>
class HideableChartItem : public ChartItem { class HideableChartItem : public ChartItem {
protected: protected:
HideableChartItem(ChartView &v, size_t z); HideableChartItem(ChartView &v, size_t z, bool dragable = false);
std::unique_ptr<Node> node; std::unique_ptr<Node> node;
bool visible; bool visible;
bool visibleChanged; bool visibleChanged;
@ -56,19 +60,17 @@ using HideableChartProxyItem = HideableChartItem<HideableQSGNode<QSGProxyNode<No
// A chart item that blits a precalculated pixmap onto the scene. // A chart item that blits a precalculated pixmap onto the scene.
class ChartPixmapItem : public HideableChartProxyItem<QSGImageNode> { class ChartPixmapItem : public HideableChartProxyItem<QSGImageNode> {
public: public:
ChartPixmapItem(ChartView &v, size_t z); ChartPixmapItem(ChartView &v, size_t z, bool dragable = false);
~ChartPixmapItem(); ~ChartPixmapItem();
void setPos(QPointF pos); void setPos(QPointF pos) override;
void render() override; void render() override;
QRectF getRect() const;
protected: protected:
void resize(QSizeF size); // Resets the canvas. Attention: image is *unitialized*. void resize(QSizeF size); // Resets the canvas. Attention: image is *unitialized*.
std::unique_ptr<QPainter> painter; std::unique_ptr<QPainter> painter;
std::unique_ptr<QImage> img; std::unique_ptr<QImage> img;
void setTextureDirty(); void setTextureDirty();
void setPositionDirty(); void setPositionDirty();
QRectF rect;
private: private:
bool positionDirty; // true if the position changed since last render bool positionDirty; // true if the position changed since last render
bool textureDirty; // true if the pixmap 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(). // Draw a rectangular background after resize. Children are responsible for calling update().
class ChartRectItem : public ChartPixmapItem { class ChartRectItem : public ChartPixmapItem {
public: 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(); ~ChartRectItem();
void resize(QSizeF size); void resize(QSizeF size);
private: private:
@ -113,14 +115,15 @@ private:
std::vector<Item> items; std::vector<Item> 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<HideableQSGNode<QSGGeometryNode>> { class ChartLineItemBase : public HideableChartItem<HideableQSGNode<QSGGeometryNode>> {
public: public:
ChartLineItemBase(ChartView &v, size_t z, QColor color, double width); ChartLineItemBase(ChartView &v, size_t z, QColor color, double width);
~ChartLineItemBase(); ~ChartLineItemBase();
void setLine(QPointF from, QPointF to); void setLine(QPointF from, QPointF to);
protected: protected:
QPointF from, to;
QColor color; QColor color;
double width; double width;
bool positionDirty; bool positionDirty;
@ -162,7 +165,7 @@ void HideableChartItem<Node>::createNode(Args&&... args)
} }
template <typename Node> template <typename Node>
HideableChartItem<Node>::HideableChartItem(ChartView &v, size_t z) : ChartItem(v, z), HideableChartItem<Node>::HideableChartItem(ChartView &v, size_t z, bool dragable) : ChartItem(v, z, dragable),
visible(true), visibleChanged(false) visible(true), visibleChanged(false)
{ {
} }

View file

@ -105,6 +105,8 @@ void ChartView::emergencyShutdown()
// Mark clean and dirty chart items for deletion... // Mark clean and dirty chart items for deletion...
cleanItems.splice(deletedItems); cleanItems.splice(deletedItems);
dirtyItems.splice(deletedItems); dirtyItems.splice(deletedItems);
dragableItems.clear();
draggedItem.reset();
// ...and delete them. // ...and delete them.
freeDeletedChartItems(); freeDeletedChartItems();
@ -134,6 +136,8 @@ void ChartView::addQSGNode(QSGNode *node, size_t z)
void ChartView::registerChartItem(ChartItem &item) void ChartView::registerChartItem(ChartItem &item)
{ {
cleanItems.append(item); cleanItems.append(item);
if (item.dragable)
dragableItems.push_back(&item);
} }
void ChartView::registerDirtyChartItem(ChartItem &item) void ChartView::registerDirtyChartItem(ChartItem &item)
@ -152,6 +156,14 @@ void ChartView::deleteChartItemInternal(ChartItem &item)
else else
cleanItems.remove(item); cleanItems.remove(item);
deletedItems.append(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) 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()) if (rootNode && z < rootNode->zNodes.size())
rootNode->zNodes[z]->setVisible(visible); 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();
}
}

View file

@ -43,6 +43,10 @@ protected:
// This is called when Qt decided to reset our rootNode, which invalidates all items on the chart. // 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. // The base class must invalidate all pointers and references.
virtual void resetPointers() = 0; virtual void resetPointers() = 0;
void mousePressEvent(QMouseEvent *event) override;
void mouseReleaseEvent(QMouseEvent *event) override;
void mouseMoveEvent(QMouseEvent *event) override;
private: private:
// QtQuick related things // QtQuick related things
size_t maxZ; size_t maxZ;
@ -77,10 +81,17 @@ private:
ChartItemList cleanItems, dirtyItems, deletedItems; ChartItemList cleanItems, dirtyItems, deletedItems;
void deleteChartItemInternal(ChartItem &item); void deleteChartItemInternal(ChartItem &item);
void freeDeletedChartItems(); 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<ChartItem *> dragableItems;
ChartItemPtr<ChartItem> draggedItem;
QPointF dragStartMouse, dragStartItem;
}; };
// This implementation detail must be known to users of the class. // 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 <typename T, class... Args> template <typename T, class... Args>
ChartItemPtr<T> ChartView::createChartItem(Args&&... args) ChartItemPtr<T> ChartView::createChartItem(Args&&... args)
{ {

View file

@ -17,7 +17,8 @@ static const double legendInternalBorderSize = 2.0;
Legend::Legend(ChartView &view, const StatsTheme &theme, const std::vector<QString> &names) : Legend::Legend(ChartView &view, const StatsTheme &theme, const std::vector<QString> &names) :
ChartRectItem(view, ChartZValue::Legend, ChartRectItem(view, ChartZValue::Legend,
QPen(theme.legendBorderColor, legendBorderSize), QPen(theme.legendBorderColor, legendBorderSize),
QBrush(theme.legendColor), legendBoxBorderRadius), QBrush(theme.legendColor), legendBoxBorderRadius,
true),
displayedItems(0), width(0.0), height(0.0), displayedItems(0), width(0.0), height(0.0),
theme(theme), theme(theme),
posInitialized(false) posInitialized(false)

View file

@ -33,7 +33,6 @@ StatsView::StatsView(QQuickItem *parent) : ChartView(parent, ChartZValue::Count)
highlightedSeries(nullptr), highlightedSeries(nullptr),
xAxis(nullptr), xAxis(nullptr),
yAxis(nullptr), yAxis(nullptr),
draggedItem(nullptr),
restrictDives(false) restrictDives(false)
{ {
setBackgroundColor(currentTheme->backgroundColor); setBackgroundColor(currentTheme->backgroundColor);
@ -60,22 +59,12 @@ StatsView::~StatsView()
void StatsView::mousePressEvent(QMouseEvent *event) void StatsView::mousePressEvent(QMouseEvent *event)
{ {
// Handle dragging of items
ChartView::mousePressEvent(event);
if (event->isAccepted())
return;
QPointF pos = event->localPos(); 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; SelectionModifier modifier;
modifier.shift = (event->modifiers() & Qt::ShiftModifier) != 0; modifier.shift = (event->modifiers() & Qt::ShiftModifier) != 0;
modifier.ctrl = (event->modifiers() & Qt::ControlModifier) != 0; modifier.ctrl = (event->modifiers() & Qt::ControlModifier) != 0;
@ -90,7 +79,7 @@ void StatsView::mousePressEvent(QMouseEvent *event)
{ return s->supportsLassoSelection(); })) { { return s->supportsLassoSelection(); })) {
if (selectionRect) if (selectionRect)
deleteChartItem(selectionRect); // Ooops. Already a selection in place. deleteChartItem(selectionRect); // Ooops. Already a selection in place.
dragStartMouse = pos; selectionStartMouse = pos;
selectionRect = createChartItem<ChartRectLineItem>(ChartZValue::Selection, currentTheme->selectionLassoColor, selectionLassoWidth); selectionRect = createChartItem<ChartRectLineItem>(ChartZValue::Selection, currentTheme->selectionLassoColor, selectionLassoWidth);
selectionModifier = modifier; selectionModifier = modifier;
oldSelection = modifier.ctrl ? getDiveSelection() : std::vector<dive *>(); oldSelection = modifier.ctrl ? getDiveSelection() : std::vector<dive *>();
@ -100,12 +89,9 @@ void StatsView::mousePressEvent(QMouseEvent *event)
} }
} }
void StatsView::mouseReleaseEvent(QMouseEvent *) void StatsView::mouseReleaseEvent(QMouseEvent *event)
{ {
if (draggedItem) { ChartView::mouseReleaseEvent(event);
draggedItem = nullptr;
ungrabMouse();
}
if (selectionRect) { if (selectionRect) {
deleteChartItem(selectionRect); deleteChartItem(selectionRect);
@ -192,17 +178,11 @@ void StatsView::divesSelected(const QVector<dive *> &dives)
void StatsView::mouseMoveEvent(QMouseEvent *event) void StatsView::mouseMoveEvent(QMouseEvent *event)
{ {
if (draggedItem) { ChartView::mouseMoveEvent(event);
QSizeF sceneSize = size();
if (sceneSize.width() <= 1.0 || sceneSize.height() <= 1.0)
return;
draggedItem->setPos(event->pos() - dragStartMouse + dragStartItem);
update();
}
if (selectionRect) { if (selectionRect) {
QPointF p1 = event->pos(); QPointF p1 = event->pos();
QPointF p2 = dragStartMouse; QPointF p2 = selectionStartMouse;
selectionRect->setLine(p1, p2); selectionRect->setLine(p1, p2);
QRectF rect(std::min(p1.x(), p2.x()), std::min(p1.y(), p2.y()), QRectF rect(std::min(p1.x(), p2.x()), std::min(p1.y(), p2.y()),
fabs(p2.x() - p1.x()), fabs(p2.y() - p1.y())); fabs(p2.x() - p1.x()), fabs(p2.y() - p1.y()));
@ -292,7 +272,6 @@ void StatsView::resetPointers()
{ {
highlightedSeries = nullptr; highlightedSeries = nullptr;
xAxis = yAxis = nullptr; xAxis = yAxis = nullptr;
draggedItem = nullptr;
title.reset(); title.reset();
legend.reset(); legend.reset();
regressionItem.reset(); regressionItem.reset();

View file

@ -120,10 +120,9 @@ private:
StatsAxis *xAxis, *yAxis; StatsAxis *xAxis, *yAxis;
ChartItemPtr<ChartTextItem> title; ChartItemPtr<ChartTextItem> title;
ChartItemPtr<Legend> legend; ChartItemPtr<Legend> legend;
Legend *draggedItem;
ChartItemPtr<RegressionItem> regressionItem; ChartItemPtr<RegressionItem> regressionItem;
ChartItemPtr<ChartRectLineItem> selectionRect; ChartItemPtr<ChartRectLineItem> selectionRect;
QPointF dragStartMouse, dragStartItem; QPointF selectionStartMouse;
SelectionModifier selectionModifier; SelectionModifier selectionModifier;
std::vector<dive *> oldSelection; std::vector<dive *> oldSelection;
bool restrictDives; bool restrictDives;