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.
This commit is contained in:
Mark Liversedge
2021-08-13 10:08:20 +01:00
parent 09de27b0d2
commit 2b30f9aa50
2 changed files with 239 additions and 13 deletions

View File

@@ -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; j<rows && offset+j < values.count(); j++) {
for (int j=startrow; j<rows && offset+j < values.count(); j++) {
QString value = values[offset+j];
// highlight rows when hovering and click thru not available
if (j == hoverrow) {
if (j == hoverrow && !scrollbar->isDragging()) {
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;
}

View File

@@ -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<QPointF> 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
{