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 <QSGImageNode>
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<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)
{
}
@ -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<float>(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<float>(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);
}

View file

@ -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 <typename Node>
class HideableChartItem : public ChartItem {
protected:
HideableChartItem(ChartView &v, size_t z);
HideableChartItem(ChartView &v, size_t z, bool dragable = false);
std::unique_ptr<Node> node;
bool visible;
bool visibleChanged;
@ -56,19 +60,17 @@ using HideableChartProxyItem = HideableChartItem<HideableQSGNode<QSGProxyNode<No
// A chart item that blits a precalculated pixmap onto the scene.
class ChartPixmapItem : public HideableChartProxyItem<QSGImageNode> {
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<QPainter> painter;
std::unique_ptr<QImage> 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<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>> {
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<Node>::createNode(Args&&... args)
}
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)
{
}

View file

@ -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();
}
}

View file

@ -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<ChartItem *> dragableItems;
ChartItemPtr<ChartItem> 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 <typename T, class... 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) :
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)

View file

@ -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<ChartRectLineItem>(ChartZValue::Selection, currentTheme->selectionLassoColor, selectionLassoWidth);
selectionModifier = modifier;
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) {
draggedItem = nullptr;
ungrabMouse();
}
ChartView::mouseReleaseEvent(event);
if (selectionRect) {
deleteChartItem(selectionRect);
@ -192,17 +178,11 @@ void StatsView::divesSelected(const QVector<dive *> &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();

View file

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