From 2b30f9aa5008a239f69beec52594589d8f85690d Mon Sep 17 00:00:00 2001 From: Mark Liversedge Date: Fri, 13 Aug 2021 10:08:20 +0100 Subject: [PATCH] DataOverviewItem Vertical Scrollbar .. progressively boldens as you hover and overlays contents to avoid taking screen real estate and being a distraction. .. only watches mouse events and ignores wheel events since these clash with chartspace scrolling. Fixes #4006. --- src/Charts/OverviewItems.cpp | 198 ++++++++++++++++++++++++++++++++--- src/Charts/OverviewItems.h | 54 ++++++++++ 2 files changed, 239 insertions(+), 13 deletions(-) diff --git a/src/Charts/OverviewItems.cpp b/src/Charts/OverviewItems.cpp index b2853006e..7581421dc 100644 --- a/src/Charts/OverviewItems.cpp +++ b/src/Charts/OverviewItems.cpp @@ -147,6 +147,8 @@ DataOverviewItem::DataOverviewItem(ChartSpace *parent, QString name, QString pro configwidget = new OverviewItemConfig(this); configwidget->hide(); + + scrollbar = new VScrollBar(this, parent); } DataOverviewItem::~DataOverviewItem() @@ -510,6 +512,13 @@ DataOverviewItem::create(ChartSpace *parent) { else return new DataOverviewItem(parent, "Activities", getLegacyProgram(DATA_TABLE_TRENDS, df.rt)); } +void +DataOverviewItem::dragChanged(bool x) +{ + if (x) scrollbar->hide(); + else scrollbar->show(); +} + RouteOverviewItem::RouteOverviewItem(ChartSpace *parent, QString name) : ChartSpaceItem(parent, name) { this->type = OverviewItemType::ROUTE; @@ -933,9 +942,6 @@ DataOverviewItem::setData(RideItem *item) postProcess(); } - - // show/hide widgets on the basis of geometry - itemGeometryChanged(); } void @@ -987,6 +993,9 @@ DataOverviewItem::postProcess() } columnWidths << maxwidth; + // tell the scrollbar we don't need scrolling + scrollbar->setAreaHeight(0); + } else { // One column per series - name1 name2 name3 name4 @@ -1010,10 +1019,18 @@ DataOverviewItem::postProcess() columnWidths << maxwidth; } + + // tell scrollbar how big the scroll area is + QFontMetrics fm(multirow ? parent->smallfont : parent->midfont, parent->device()); + double lineheight = fm.boundingRect("XXX").height() * 1.2f; + scrollbar->setAreaHeight(rows * lineheight); // no scrolling } // keep the same sorting... if (lastsort != -1) sort(lastsort, lastorder); + + // show/hide widgets on the basis of geometry + itemGeometryChanged(); } void @@ -1135,9 +1152,6 @@ DataOverviewItem::setDateRange(DateRange dr) postProcess(); } - - // show/hide widgets on the basis of geometry - itemGeometryChanged(); } void @@ -2318,7 +2332,38 @@ KPIOverviewItem::itemGeometryChanged() { } void -DataOverviewItem::itemGeometryChanged() { } // only a data table and we crop when painting +DataOverviewItem::itemGeometryChanged() +{ + if (names.count() > 0) { + + // if we are multirow we need to set the scrollbar + int rows = values.count() / names.count(); + + if (rows > 2) { + + // lets work out the dataarea, bit of a faff and need to keep aligned with paint + // method - todo: refactor out duplicated code + QFontMetrics fm(multirow ? parent->smallfont : parent->midfont, parent->device()); + double lineheight = fm.boundingRect("XXX").height() * 1.2f; + double scrollwidth = fm.boundingRect("X").width(); + QRectF paintarea = QRectF(20,ROWHEIGHT*2, geometry().width()-40, geometry().height()-20-(ROWHEIGHT*2)); + QRectF dataarea = paintarea; + dataarea.setY(dataarea.y() + (lineheight*2) + (lineheight*0.25f)); // 0.2 is the line spacing + + // set geometry to rhs + scrollbar->setGeometry(dataarea.right() - scrollwidth, dataarea.top(), scrollwidth, dataarea.height()); + + } else { + // not needed + scrollbar->setGeometry(0,0,0,0); + } + + } else { + + // not needed- optimising out just adds another calc + scrollbar->setGeometry(0,0,0,0); + } +} void MetricOverviewItem::itemGeometryChanged() { @@ -2616,6 +2661,11 @@ DataOverviewItem::itemPaint(QPainter *painter, const QStyleOptionGraphicsItem *, int hoverrow = -1; // remember if the mouse is over a particular row. + // scroll position, only really relevant for multirow + double scrollareapos = scrollbar->pos(); + int startrow = 0; + if (scrollareapos) startrow = scrollareapos/lineheight + 1; + // paint the hover background, only matters when have multiple rows if (multirow && underMouse()) { @@ -2668,21 +2718,21 @@ DataOverviewItem::itemPaint(QPainter *painter, const QStyleOptionGraphicsItem *, if (click) sortcolumn = column; } - } else if (itemarea.contains(cpos)) { + } else if (!scrollbar->isDragging() && itemarea.contains(cpos)) { - if (files.count() && row < files.count()) { + if (files.count() && row+startrow < files.count()) { painter->setPen(Qt::NoPen); QColor darkgray(120,120,120,120); painter->setBrush(darkgray); painter->drawRect(itemarea); if (click) { - clickthru = parent->context->athlete->rideCache->getRide(files[row]); + clickthru = parent->context->athlete->rideCache->getRide(files[row+startrow]); click = false; } } else { // clicktru is not available bit lets at least make the - if (multirow) hoverrow = row; + if (multirow) hoverrow = row+startrow; } } } @@ -2746,11 +2796,11 @@ DataOverviewItem::itemPaint(QPainter *painter, const QStyleOptionGraphicsItem *, // paint values in columns int offset = rows * i; - for (int j=0; jisDragging()) { painter->setFont(bold); painter->setPen(GColor(CPLOTMARKER)); } else { @@ -4993,3 +5043,125 @@ Button::sceneEvent(QEvent *event) } return false; } + +VScrollBar::VScrollBar(QGraphicsWidget *parent, ChartSpace *space) : QGraphicsItem(parent), parent(parent), space(space), height(0) +{ + setZValue(11); // slightly higher than host + + // hovering and dragging state set to initial conditions + origin = QPointF(0,0); + barhover=hover=false; + obarpos=barpos=0; + state=NONE; + + // we need to watch events... + setAcceptHoverEvents(true); +} + +double +VScrollBar::pos() const +{ + if (geom.height() == 0) return 0; + + // return pos as point in scrollarea thats at the top + return height * (barpos / geom.height()); +} +void +VScrollBar::setGeometry(double x, double y, double width, double height) +{ + geom = QRectF(x,y,width,height); + barpos = 0; +} + +void +VScrollBar::setAreaHeight(double n) +{ + height = n; + barpos = 0; +} + +void +VScrollBar::setPos(double x) +{ + // xxx todo +} + +// the usual +void +VScrollBar::paint(QPainter*painter, const QStyleOptionGraphicsItem *, QWidget*) +{ + if (isVisible() && geom.height() && geom.height() < height) { + + double barheight = geom.height() * (geom.height() / height); + QColor barcolor(127,127,127,64); + if (state == DRAG) { + barcolor = GColor(CPLOTMARKER); + } else if (hover) { + if (barhover) barcolor = QColor(127,127,127,255); + else barcolor = QColor(127,127,127,127); + } + + painter->setBrush(barcolor); + painter->setPen(Qt::NoPen); + painter->drawRect(geom.x(), geom.y()+barpos, geom.width(), barheight); + } +} + +// spotting mouse events hover, click move and wheel (but only in small area of scrollbar) +bool +VScrollBar::sceneEvent(QEvent *event) +{ + // we are not needed as scrollable area fits anyway + if (height == 0 || geom.height() >= height) return false; + + // set value based upon the location of the mouse + QPoint vpos = space->mapFromGlobal(QCursor::pos()); + QPointF spos = space->view->mapToScene(vpos); + QPointF cpos = spos - parent->geometry().topLeft(); + + // remember what it was.... + bool oldhover = hover; + bool oldbarhover = barhover; + + // scrollbar space + if (geom.contains(cpos)) hover = true; + else hover=false; + + // the actual bar + double barheight = geom.height() * (geom.height() / height); + QRectF barrect = QRectF(geom.x(), geom.y()+barpos, geom.width(), barheight); + if (barrect.contains(cpos)) barhover = true; + else barhover = false; + + // plain old mouse move + if (oldhover != hover || oldbarhover != barhover) update(); + + if (event->type() == QEvent::GraphicsSceneMouseMove && state == DRAG) { + + // mouse moved so hover paint anyway + barpos = obarpos + cpos.y() - origin.y(); + if (barpos < 0) barpos = 0; + if (barpos + barheight > geom.height()) barpos = geom.height() - barheight; + update(); + + return true; + + } else if (barhover && event->type() == QEvent::GraphicsSceneMousePress) { + + state = DRAG; + origin = cpos; + obarpos = barpos; + update(); + + return true; + + } else if (event->type() == QEvent::GraphicsSceneMouseRelease) { + + // remember what it was + state = NONE; + update(); + + return true; + } + return false; +} diff --git a/src/Charts/OverviewItems.h b/src/Charts/OverviewItems.h index 10c83dc68..ecdf17c95 100644 --- a/src/Charts/OverviewItems.h +++ b/src/Charts/OverviewItems.h @@ -38,6 +38,7 @@ class Sparkline; class BubbleViz; class Routeline; class ProgressBar; +class VScrollBar; // sparklines number of points - look back 6 weeks #define SPARKDAYS 42 @@ -151,6 +152,7 @@ class DataOverviewItem : public ChartSpaceItem void setData(RideItem *item); void setDateRange(DateRange); void postProcess(); // work with data returned from program + void dragChanged(bool x); QWidget *config() { return configwidget; } @@ -182,6 +184,8 @@ class DataOverviewItem : public ChartSpaceItem int lastsort; // the column we last sorted on Qt::SortOrder lastorder; // the order we last sorted on + + VScrollBar *scrollbar; }; class RPEOverviewItem : public ChartSpaceItem @@ -667,6 +671,56 @@ class Sparkline : public QGraphicsItem QList points; }; +// vertical scrollbar +class VScrollBar : public QGraphicsItem +{ + public: + VScrollBar(QGraphicsWidget *parent, ChartSpace *space); // create empty, geometry and boundary gets changed all the time + + // we monkey around with this *A LOT* + void setGeometry(double x, double y, double width, double height); + QRectF geometry() { return geom; } + + // the size of the scroll area + void setAreaHeight(double n); + + // pos - this is the position in the area being scrolled + // that should be painted in the viewable area + // since its a vertical scrollbar its the y position + // it can be set by parent when e.g. catch key events + void setPos(double x); + double pos() const; + + bool isDragging() { return state == DRAG; } + + // needed as pure virtual in QGraphicsItem + QRectF boundingRect() const override { return geom; } + QVariant itemChange(GraphicsItemChange change, const QVariant &value) override + { + if (change == ItemPositionChange && parent->scene()) prepareGeometryChange(); + return QGraphicsItem::itemChange(change, value); + } + + // the usual + void paint(QPainter*, const QStyleOptionGraphicsItem *, QWidget*) override; + + // spotting mouse events hover, click move and wheel (but only in small area of scrollbar) + bool sceneEvent(QEvent *event) override; + + private: + + QGraphicsWidget *parent; + ChartSpace *space; + QRectF geom; + double height; + bool hover; // is mouse in our rect? + bool barhover; // is mouse over the scrollbar itself? + + QPointF origin; + enum { NONE, DRAG } state; + double barpos, obarpos; +}; + // visualisation of a GPS route as a shape class Routeline : public QObject, public QGraphicsItem {