mirror of
https://github.com/GoldenCheetah/GoldenCheetah.git
synced 2026-02-13 08:08:42 +00:00
Refactor OverviewWindow for ChartSpace
.. the OverviewWindow class has been refactored to extract out the core dashboard UX/UI into a new class ChartSpace. .. additionally, a ChartSpace contains a set of ChartSpaceItem which need to be subclassed by the developer. .. for the Overview a set of OverwiewItem classes have been introduced such as RPEOverviewItem, MetricOverviewItem and so on. These are subclasses of the ChartSpaceItem. .. The overview window implementation is now mostly configuration and assembly of a dashboard using OverviewItems and the ChartSpace. .. This refactor is to enable the ChartSpace to be used as a drop in replacement for the existing TabView. .. There are no functional enhancements in this commit, but the overview chart shouls appear to be unchanged by the user.
This commit is contained in:
@@ -65,12 +65,6 @@ OverviewWindow::OverviewWindow(Context *context) :
|
||||
view->setRenderHint(QPainter::Antialiasing, true);
|
||||
view->setFrameStyle(QFrame::NoFrame);
|
||||
view->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||
#ifdef Q_OS_LINUX
|
||||
if (QGLFormat::openGLVersionFlags().testFlag(QGLFormat::OpenGL_Version_2_0)) {
|
||||
view->setViewport(new QGLWidget(QGLFormat(QGL::SampleBuffers)));
|
||||
view->setViewportUpdateMode(QGraphicsView::FullViewportUpdate);
|
||||
}
|
||||
#endif
|
||||
view->setScene(scene);
|
||||
|
||||
// layout
|
||||
325
src/Charts/Overview.cpp
Normal file
325
src/Charts/Overview.cpp
Normal file
@@ -0,0 +1,325 @@
|
||||
/*
|
||||
* Copyright (c) 2017 Mark Liversedge (liversedge@gmail.com)
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc., 51
|
||||
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#include "Overview.h"
|
||||
#include "ChartSpace.h"
|
||||
#include "OverviewItems.h"
|
||||
|
||||
static QIcon grayConfig, whiteConfig, accentConfig;
|
||||
|
||||
OverviewWindow::OverviewWindow(Context *context) : GcChartWindow(context), context(context), configured(false)
|
||||
{
|
||||
setContentsMargins(0,0,0,0);
|
||||
setProperty("color", GColor(COVERVIEWBACKGROUND));
|
||||
//setProperty("nomenu", true);
|
||||
setShowTitle(false);
|
||||
setControls(NULL);
|
||||
|
||||
QHBoxLayout *main = new QHBoxLayout;
|
||||
|
||||
space = new ChartSpace(context);
|
||||
main->addWidget(space);
|
||||
|
||||
// all the widgets
|
||||
setChartLayout(main);
|
||||
|
||||
// tell space when a ride is selected
|
||||
connect(this, SIGNAL(rideItemChanged(RideItem*)), space, SLOT(rideSelected(RideItem*)));
|
||||
}
|
||||
|
||||
QString
|
||||
OverviewWindow::getConfiguration() const
|
||||
{
|
||||
// return a JSON snippet to represent the entire config
|
||||
QString config;
|
||||
|
||||
// setup
|
||||
config = "{\n \"version\":\"2.0\",\n";
|
||||
config += " \"CHARTS\":[\n";
|
||||
|
||||
// do cards
|
||||
foreach(ChartSpaceItem *item, space->allItems()) {
|
||||
|
||||
// basic stuff first - name, type etc
|
||||
config += " { ";
|
||||
config += "\"type\":" + QString("%1").arg(static_cast<int>(item->type)) + ",";
|
||||
config += "\"deep\":" + QString("%1").arg(item->deep) + ",";
|
||||
config += "\"column\":" + QString("%1").arg(item->column) + ",";
|
||||
config += "\"order\":" + QString("%1").arg(item->order) + ",";
|
||||
|
||||
// now the actual card settings
|
||||
switch(item->type) {
|
||||
case OverviewItemType::RPE:
|
||||
{
|
||||
RPEOverviewItem *rpe = reinterpret_cast<RPEOverviewItem*>(item);
|
||||
}
|
||||
break;
|
||||
case OverviewItemType::METRIC:
|
||||
{
|
||||
MetricOverviewItem *metric = reinterpret_cast<MetricOverviewItem*>(item);
|
||||
config += "\"symbol\":\"" + QString("%1").arg(metric->symbol) + "\",";
|
||||
}
|
||||
break;
|
||||
case OverviewItemType::META:
|
||||
{
|
||||
MetaOverviewItem *meta = reinterpret_cast<MetaOverviewItem*>(item);
|
||||
config += "\"symbol\":\"" + QString("%1").arg(meta->symbol) + "\",";
|
||||
}
|
||||
break;
|
||||
case OverviewItemType::PMC:
|
||||
{
|
||||
PMCOverviewItem *pmc = reinterpret_cast<PMCOverviewItem*>(item);
|
||||
config += "\"symbol\":\"" + QString("%1").arg(pmc->symbol) + "\",";
|
||||
}
|
||||
break;
|
||||
case OverviewItemType::ROUTE:
|
||||
{
|
||||
RouteOverviewItem *route = reinterpret_cast<RouteOverviewItem*>(item);
|
||||
}
|
||||
break;
|
||||
case OverviewItemType::INTERVAL:
|
||||
{
|
||||
IntervalOverviewItem *interval = reinterpret_cast<IntervalOverviewItem*>(item);
|
||||
config += "\"xsymbol\":\"" + QString("%1").arg(interval->xsymbol) + "\",";
|
||||
config += "\"ysymbol\":\"" + QString("%1").arg(interval->ysymbol) + "\",";
|
||||
config += "\"zsymbol\":\"" + QString("%1").arg(interval->zsymbol) + "\"" + ",";
|
||||
}
|
||||
break;
|
||||
case OverviewItemType::ZONE:
|
||||
{
|
||||
ZoneOverviewItem *zone = reinterpret_cast<ZoneOverviewItem*>(item);
|
||||
config += "\"series\":" + QString("%1").arg(static_cast<int>(zone->series)) + ",";
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
config += "\"name\":\"" + item->name + "\"";
|
||||
|
||||
config += " }";
|
||||
|
||||
if (space->allItems().last() != item) config += ",";
|
||||
|
||||
config += "\n";
|
||||
}
|
||||
|
||||
config += " ]\n}\n";
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
void
|
||||
OverviewWindow::setConfiguration(QString config)
|
||||
{
|
||||
// XXX hack because we're not in the default layout and don't want to
|
||||
// XXX this is just to handle setup for the very first time its run !
|
||||
if (configured == true) return;
|
||||
configured = true;
|
||||
|
||||
// DEFAULT CONFIG (FOR NOW WHEN NOT IN THE DEFAULT LAYOUT)
|
||||
//
|
||||
// default column widths - max 10 columns;
|
||||
// note the sizing is such that each card is the equivalent of a full screen
|
||||
// so we can embed charts etc without compromising what they can display
|
||||
|
||||
defaultsetup: // I know, but its easier than lots of nested if clauses above
|
||||
|
||||
if (config == "") {
|
||||
|
||||
// column 0
|
||||
ChartSpaceItem *add;
|
||||
add = new PMCOverviewItem(space, "coggan_tss");
|
||||
space->addItem(1,0,9, add);
|
||||
|
||||
add = new MetaOverviewItem(space, "Sport", "Sport");
|
||||
space->addItem(2,0,5, add);
|
||||
|
||||
add = new MetaOverviewItem(space, "Workout Code", "Workout Code");
|
||||
space->addItem(3,0,5, add);
|
||||
|
||||
add = new MetricOverviewItem(space, "Duration", "workout_time");
|
||||
space->addItem(4,0,9, add);
|
||||
|
||||
add = new MetaOverviewItem(space, "Notes", "Notes");
|
||||
space->addItem(5,0,13, add);
|
||||
|
||||
// column 1
|
||||
add = new MetricOverviewItem(space, "HRV rMSSD", "rMSSD");
|
||||
space->addItem(1,1,9, add);
|
||||
|
||||
add = new MetricOverviewItem(space, "Heartrate", "average_hr");
|
||||
space->addItem(2,1,5, add);
|
||||
|
||||
add = new ZoneOverviewItem(space, "Heartrate Zones", RideFile::hr);
|
||||
space->addItem(3,1,11, add);
|
||||
|
||||
add = new MetricOverviewItem(space, "Climbing", "elevation_gain");
|
||||
space->addItem(4,1,5, add);
|
||||
|
||||
add = new MetricOverviewItem(space, "Cadence", "average_cad");
|
||||
space->addItem(5,1,5, add);
|
||||
|
||||
add = new MetricOverviewItem(space, "Work", "total_work");
|
||||
space->addItem(6,1,5, add);
|
||||
|
||||
// column 2
|
||||
add = new RPEOverviewItem(space, "RPE");
|
||||
space->addItem(1,2,9, add);
|
||||
|
||||
add = new MetricOverviewItem(space, "Stress", "coggan_tss");
|
||||
space->addItem(2,2,5, add);
|
||||
|
||||
add = new ZoneOverviewItem(space, "Fatigue Zones", RideFile::wbal);
|
||||
space->addItem(3,2,11, add);
|
||||
|
||||
add = new IntervalOverviewItem(space, "Intervals", "elapsed_time", "average_power", "workout_time");
|
||||
space->addItem(4,2,17, add);
|
||||
|
||||
// column 3
|
||||
add = new MetricOverviewItem(space, "Power", "average_power");
|
||||
space->addItem(1,3,9, add);
|
||||
|
||||
add = new MetricOverviewItem(space, "IsoPower", "coggan_np");
|
||||
space->addItem(2,3,5, add);
|
||||
|
||||
add = new ZoneOverviewItem(space, "Power Zones", RideFile::watts);
|
||||
space->addItem(3,3,11, add);
|
||||
|
||||
add = new MetricOverviewItem(space, "Peak Power Index", "peak_power_index");
|
||||
space->addItem(4,3,8, add);
|
||||
|
||||
add = new MetricOverviewItem(space, "Variability", "coggam_variability_index");
|
||||
space->addItem(5,3,8, add);
|
||||
|
||||
// column 4
|
||||
add = new MetricOverviewItem(space, "Distance", "total_distance");
|
||||
space->addItem(1,4,9, add);
|
||||
|
||||
add = new MetricOverviewItem(space, "Speed", "average_speed");
|
||||
space->addItem(2,4,5, add);
|
||||
|
||||
add = new ZoneOverviewItem(space, "Pace Zones", RideFile::kph);
|
||||
space->addItem(3,4,11, add);
|
||||
|
||||
add = new RouteOverviewItem(space, "Route");
|
||||
space->addItem(4,4,17, add);
|
||||
|
||||
} else {
|
||||
|
||||
//
|
||||
// But by default we parse and apply (dropping back to default setup on error)
|
||||
//
|
||||
// parse
|
||||
QJsonDocument doc = QJsonDocument::fromJson(config.toUtf8());
|
||||
if (doc.isEmpty() || doc.isNull()) {
|
||||
config="";
|
||||
goto defaultsetup;
|
||||
}
|
||||
|
||||
// parsed so lets work through it and setup the overview
|
||||
QJsonObject root = doc.object();
|
||||
|
||||
// check version
|
||||
QString version = root["version"].toString();
|
||||
if (version != "2.0") {
|
||||
config="";
|
||||
goto defaultsetup;
|
||||
}
|
||||
|
||||
// cards
|
||||
QJsonArray CHARTS = root["CHARTS"].toArray();
|
||||
foreach(const QJsonValue val, CHARTS) {
|
||||
|
||||
// convert so we can inspect
|
||||
QJsonObject obj = val.toObject();
|
||||
|
||||
// get the basics
|
||||
QString name = obj["name"].toString();
|
||||
int column = obj["column"].toInt();
|
||||
int order = obj["order"].toInt();
|
||||
int deep = obj["deep"].toInt();
|
||||
int type = obj["type"].toInt();
|
||||
|
||||
|
||||
// lets create the cards
|
||||
ChartSpaceItem *add=NULL;
|
||||
switch(type) {
|
||||
|
||||
case OverviewItemType::RPE :
|
||||
{
|
||||
add = new RPEOverviewItem(space, name);
|
||||
space->addItem(order,column,deep, add);
|
||||
}
|
||||
break;
|
||||
|
||||
case OverviewItemType::METRIC :
|
||||
{
|
||||
QString symbol=obj["symbol"].toString();
|
||||
add = new MetricOverviewItem(space, name,symbol);
|
||||
space->addItem(order,column,deep, add);
|
||||
}
|
||||
break;
|
||||
|
||||
case OverviewItemType::META :
|
||||
{
|
||||
QString symbol=obj["symbol"].toString();
|
||||
add = new MetaOverviewItem(space, name,symbol);
|
||||
space->addItem(order,column,deep, add);
|
||||
}
|
||||
break;
|
||||
|
||||
case OverviewItemType::PMC :
|
||||
{
|
||||
QString symbol=obj["symbol"].toString();
|
||||
add = new PMCOverviewItem(space, symbol); // doesn't have a title
|
||||
space->addItem(order,column,deep, add);
|
||||
}
|
||||
break;
|
||||
|
||||
case OverviewItemType::ZONE :
|
||||
{
|
||||
RideFile::SeriesType series = static_cast<RideFile::SeriesType>(obj["series"].toInt());
|
||||
add = new ZoneOverviewItem(space, name, series);
|
||||
space->addItem(order,column,deep, add);
|
||||
|
||||
}
|
||||
break;
|
||||
|
||||
case OverviewItemType::ROUTE :
|
||||
{
|
||||
add = new RouteOverviewItem(space, name); // doesn't have a title
|
||||
space->addItem(order,column,deep, add);
|
||||
}
|
||||
break;
|
||||
|
||||
case OverviewItemType::INTERVAL :
|
||||
{
|
||||
QString xsymbol=obj["xsymbol"].toString();
|
||||
QString ysymbol=obj["ysymbol"].toString();
|
||||
QString zsymbol=obj["zsymbol"].toString();
|
||||
|
||||
add = new IntervalOverviewItem(space, name, xsymbol, ysymbol, zsymbol); // doesn't have a title
|
||||
space->addItem(order,column,deep, add);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// put in place
|
||||
space->updateGeometry();
|
||||
}
|
||||
61
src/Charts/Overview.h
Normal file
61
src/Charts/Overview.h
Normal file
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Mark Liversedge (liversedge@gmail.com)
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc., 51
|
||||
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#ifndef _GC_OverviewWindow_h
|
||||
#define _GC_OverviewWindow_h 1
|
||||
|
||||
// basics
|
||||
#include "GoldenCheetah.h"
|
||||
#include "Settings.h"
|
||||
#include "Units.h"
|
||||
#include "Colors.h"
|
||||
#include "Context.h"
|
||||
#include "Athlete.h"
|
||||
#include "RideItem.h"
|
||||
#include "RideMetric.h"
|
||||
#include "HrZones.h"
|
||||
|
||||
#include "ChartSpace.h"
|
||||
|
||||
class OverviewWindow : public GcChartWindow
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(QString config READ getConfiguration WRITE setConfiguration USER true)
|
||||
|
||||
public:
|
||||
|
||||
OverviewWindow(Context *context);
|
||||
|
||||
// used by children
|
||||
Context *context;
|
||||
|
||||
public slots:
|
||||
|
||||
// get/set config
|
||||
QString getConfiguration() const;
|
||||
void setConfiguration(QString x);
|
||||
|
||||
private:
|
||||
|
||||
// gui setup
|
||||
ChartSpace *space;
|
||||
bool configured;
|
||||
};
|
||||
|
||||
#endif // _GC_OverviewWindow_h
|
||||
2054
src/Charts/OverviewItems.cpp
Normal file
2054
src/Charts/OverviewItems.cpp
Normal file
File diff suppressed because it is too large
Load Diff
381
src/Charts/OverviewItems.h
Normal file
381
src/Charts/OverviewItems.h
Normal file
@@ -0,0 +1,381 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Mark Liversedge (liversedge@gmail.com)
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc., 51
|
||||
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#ifndef _GC_OverviewItem_h
|
||||
#define _GC_OverviewItem_h 1
|
||||
|
||||
// basics
|
||||
#include "ChartSpace.h"
|
||||
#include <QGraphicsItem>
|
||||
|
||||
// qt charts for zone chart
|
||||
#include <QtCharts>
|
||||
#include <QBarSet>
|
||||
#include <QBarSeries>
|
||||
#include <QLineSeries>
|
||||
|
||||
// subwidgets for viz inside each overview item
|
||||
class RPErating;
|
||||
class BPointF;
|
||||
class Sparkline;
|
||||
class BubbleViz;
|
||||
class Routeline;
|
||||
|
||||
// sparklines number of points - look back 6 weeks
|
||||
#define SPARKDAYS 42
|
||||
|
||||
// number of points in a route viz - trial and error gets 250 being reasonable
|
||||
#define ROUTEPOINTS 250
|
||||
|
||||
// types we use
|
||||
enum OverviewItemType { RPE, METRIC, META, ZONE, INTERVAL, PMC, ROUTE };
|
||||
|
||||
class RPEOverviewItem : public ChartSpaceItem
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
RPEOverviewItem(ChartSpace *parent, QString name);
|
||||
|
||||
void itemPaint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *);
|
||||
void itemGeometryChanged();
|
||||
void setData(RideItem *item);
|
||||
|
||||
// RPE meta field
|
||||
Sparkline *sparkline;
|
||||
RPErating *rperating;
|
||||
|
||||
// for setting sparkline & painting
|
||||
bool up, showrange;
|
||||
QString value, upper, lower, mean;
|
||||
};
|
||||
|
||||
class MetricOverviewItem : public ChartSpaceItem
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
MetricOverviewItem(ChartSpace *parent, QString name, QString symbol);
|
||||
|
||||
void itemPaint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *);
|
||||
void itemGeometryChanged();
|
||||
void setData(RideItem *item);
|
||||
|
||||
QString symbol;
|
||||
RideMetric *metric;
|
||||
QString units;
|
||||
|
||||
bool up, showrange;
|
||||
QString value, upper, lower, mean;
|
||||
|
||||
Sparkline *sparkline;
|
||||
};
|
||||
|
||||
class MetaOverviewItem : public ChartSpaceItem
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
MetaOverviewItem(ChartSpace *parent, QString name, QString symbol);
|
||||
|
||||
void itemPaint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *);
|
||||
void itemGeometryChanged();
|
||||
void setData(RideItem *item);
|
||||
|
||||
QString symbol;
|
||||
int fieldtype;
|
||||
|
||||
// for numeric metadata items
|
||||
bool up, showrange;
|
||||
QString value, upper, lower, mean;
|
||||
|
||||
Sparkline *sparkline;
|
||||
};
|
||||
|
||||
class PMCOverviewItem : public ChartSpaceItem
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
PMCOverviewItem(ChartSpace *parent, QString symbol);
|
||||
|
||||
void itemPaint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *);
|
||||
void itemGeometryChanged();
|
||||
void setData(RideItem *item);
|
||||
|
||||
QString symbol;
|
||||
|
||||
double sts, lts, sb, rr, stress;
|
||||
};
|
||||
|
||||
class ZoneOverviewItem : public ChartSpaceItem
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
ZoneOverviewItem(ChartSpace *parent, QString name, RideFile::seriestype);
|
||||
|
||||
void itemPaint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *);
|
||||
void itemGeometryChanged();
|
||||
void setData(RideItem *item);
|
||||
void dragChanged(bool x);
|
||||
|
||||
RideFile::seriestype series;
|
||||
|
||||
QChart *chart;
|
||||
QBarSet *barset;
|
||||
QBarSeries *barseries;
|
||||
QStringList categories;
|
||||
QBarCategoryAxis *barcategoryaxis;
|
||||
};
|
||||
|
||||
class RouteOverviewItem : public ChartSpaceItem
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
RouteOverviewItem(ChartSpace *parent, QString name);
|
||||
|
||||
void itemPaint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *);
|
||||
void itemGeometryChanged();
|
||||
void setData(RideItem *item);
|
||||
|
||||
Routeline *routeline;
|
||||
};
|
||||
|
||||
class IntervalOverviewItem : public ChartSpaceItem
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
IntervalOverviewItem(ChartSpace *parent, QString name, QString xs, QString ys, QString zs);
|
||||
|
||||
void itemPaint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *);
|
||||
void itemGeometryChanged();
|
||||
void setData(RideItem *item);
|
||||
|
||||
QString xsymbol, ysymbol, zsymbol;
|
||||
BubbleViz *bubble;
|
||||
};
|
||||
|
||||
//
|
||||
// below are theviz widgets used by the overview items
|
||||
//
|
||||
|
||||
// for now the basics are x and y and a radius z, color fill
|
||||
class BPointF {
|
||||
public:
|
||||
|
||||
BPointF() : x(0), y(0), z(0), fill(GColor(Qt::gray)) {}
|
||||
|
||||
double score(BPointF &other);
|
||||
|
||||
double x,y,z;
|
||||
QColor fill;
|
||||
QString label;
|
||||
};
|
||||
|
||||
|
||||
// bubble chart, very very basic just a visualisation
|
||||
class BubbleViz : public QObject, public QGraphicsItem
|
||||
{
|
||||
// need to be a qobject for metaproperties
|
||||
Q_OBJECT
|
||||
Q_INTERFACES(QGraphicsItem)
|
||||
|
||||
// want a meta property for property animation
|
||||
Q_PROPERTY(int transition READ getTransition WRITE setTransition)
|
||||
|
||||
public:
|
||||
BubbleViz(IntervalOverviewItem *parent, QString name=""); // create and say how many days
|
||||
|
||||
// we monkey around with this *A LOT*
|
||||
void setGeometry(double x, double y, double width, double height);
|
||||
QRectF geometry() { return geom; }
|
||||
|
||||
// transition animation 0-255
|
||||
int getTransition() const {return transition;}
|
||||
void setTransition(int x) { if (transition !=x) {transition=x; update();}}
|
||||
|
||||
// null members for now just get hooked up
|
||||
void setPoints(QList<BPointF>points);
|
||||
|
||||
void setRange(double minx, double maxx, double miny, double maxy) {
|
||||
oldminx = this->minx;
|
||||
oldminy = this->miny;
|
||||
oldmaxx = this->maxx;
|
||||
oldmaxy = this->maxy;
|
||||
this->minx=minx;
|
||||
this->maxx=maxx;
|
||||
this->miny=miny;
|
||||
this->maxy=maxy;
|
||||
}
|
||||
void setAxisNames(QString xlabel, QString ylabel) { this->xlabel=xlabel; this->ylabel=ylabel; update(); }
|
||||
|
||||
// needed as pure virtual in QGraphicsItem
|
||||
QVariant itemChange(GraphicsItemChange change, const QVariant &value);
|
||||
void paint(QPainter*, const QStyleOptionGraphicsItem *, QWidget*);
|
||||
QRectF boundingRect() const { return QRectF(parent->geometry().x() + geom.x(),
|
||||
parent->geometry().y() + geom.y(),
|
||||
geom.width(), geom.height());
|
||||
}
|
||||
|
||||
// watch hovering
|
||||
bool sceneEvent(QEvent *event);
|
||||
|
||||
private:
|
||||
IntervalOverviewItem *parent;
|
||||
QRectF geom;
|
||||
QString name;
|
||||
|
||||
// where is the cursor?
|
||||
bool hover;
|
||||
QPointF plotpos;
|
||||
|
||||
// for animated transition
|
||||
QList <BPointF> oldpoints; // for animation
|
||||
int transition;
|
||||
double oldmean;
|
||||
double oldminx,oldmaxx,oldminy,oldmaxy;
|
||||
QPropertyAnimation *animator;
|
||||
|
||||
// chart settings
|
||||
QList <BPointF> points;
|
||||
double minx,maxx,miny,maxy;
|
||||
QString xlabel, ylabel;
|
||||
double mean, max;
|
||||
};
|
||||
|
||||
// RPE rating viz and widget to set value
|
||||
class RPErating : public QGraphicsItem
|
||||
{
|
||||
|
||||
public:
|
||||
RPErating(RPEOverviewItem *parent, QString name=""); // create and say how many days
|
||||
|
||||
// we monkey around with this *A LOT*
|
||||
void setGeometry(double x, double y, double width, double height);
|
||||
QRectF geometry() { return geom; }
|
||||
|
||||
void setValue(QString);
|
||||
|
||||
// needed as pure virtual in QGraphicsItem
|
||||
QVariant itemChange(GraphicsItemChange change, const QVariant &value);
|
||||
void paint(QPainter*, const QStyleOptionGraphicsItem *, QWidget*);
|
||||
QRectF boundingRect() const { return QRectF(parent->geometry().x() + geom.x(),
|
||||
parent->geometry().y() + geom.y(),
|
||||
geom.width(), geom.height());
|
||||
}
|
||||
|
||||
// for interaction
|
||||
bool sceneEvent(QEvent *event);
|
||||
void cancelEdit();
|
||||
void applyEdit();
|
||||
|
||||
private:
|
||||
RPEOverviewItem *parent;
|
||||
QString name;
|
||||
QString description;
|
||||
QRectF geom;
|
||||
QString value, oldvalue;
|
||||
QColor color;
|
||||
|
||||
// interaction
|
||||
bool hover;
|
||||
|
||||
};
|
||||
|
||||
// tufte style sparkline to plot metric history
|
||||
class Sparkline : public QGraphicsItem
|
||||
{
|
||||
public:
|
||||
Sparkline(QGraphicsWidget *parent, int count,QString name=""); // create and say how many days
|
||||
|
||||
// we monkey around with this *A LOT*
|
||||
void setGeometry(double x, double y, double width, double height);
|
||||
QRectF geometry() { return geom; }
|
||||
|
||||
void setPoints(QList<QPointF>);
|
||||
void setRange(double min, double max); // upper lower
|
||||
|
||||
// needed as pure virtual in QGraphicsItem
|
||||
QVariant itemChange(GraphicsItemChange change, const QVariant &value);
|
||||
void paint(QPainter*, const QStyleOptionGraphicsItem *, QWidget*);
|
||||
QRectF boundingRect() const { return QRectF(parent->geometry().x() + geom.x(),
|
||||
parent->geometry().y() + geom.y(),
|
||||
geom.width(), geom.height());
|
||||
}
|
||||
|
||||
private:
|
||||
QGraphicsWidget *parent;
|
||||
QRectF geom;
|
||||
QString name;
|
||||
double min, max;
|
||||
QList<QPointF> points;
|
||||
};
|
||||
|
||||
// visualisation of a GPS route as a shape
|
||||
class Routeline : public QObject, public QGraphicsItem
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_INTERFACES(QGraphicsItem)
|
||||
|
||||
Q_PROPERTY(int transition READ getTransition WRITE setTransition)
|
||||
|
||||
public:
|
||||
Routeline(QGraphicsWidget *parent, QString name=""); // create and say how many days
|
||||
|
||||
// transition animation 0-255
|
||||
int getTransition() const {return transition;}
|
||||
void setTransition(int x) { if (transition !=x) {transition=x; update();}}
|
||||
|
||||
// we monkey around with this *A LOT*
|
||||
void setGeometry(double x, double y, double width, double height);
|
||||
QRectF geometry() { return geom; }
|
||||
|
||||
void setData(RideItem *item);
|
||||
|
||||
// needed as pure virtual in QGraphicsItem
|
||||
QVariant itemChange(GraphicsItemChange change, const QVariant &value);
|
||||
void paint(QPainter*, const QStyleOptionGraphicsItem *, QWidget*);
|
||||
QRectF boundingRect() const { return QRectF(parent->geometry().x() + geom.x(),
|
||||
parent->geometry().y() + geom.y(),
|
||||
geom.width(), geom.height());
|
||||
}
|
||||
|
||||
private:
|
||||
QGraphicsWidget *parent;
|
||||
QRectF geom;
|
||||
QString name;
|
||||
QPainterPath path, oldpath;
|
||||
double width, height; // size of painterpath, so we scale to fit on paint
|
||||
|
||||
// animating
|
||||
int transition;
|
||||
QPropertyAnimation *animator;
|
||||
double owidth, oheight; // size of painterpath, so we scale to fit on paint
|
||||
};
|
||||
|
||||
#endif // _GC_OverviewItem_h
|
||||
946
src/Gui/ChartSpace.cpp
Normal file
946
src/Gui/ChartSpace.cpp
Normal file
@@ -0,0 +1,946 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Mark Liversedge (liversedge@gmail.com)
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc., 51
|
||||
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#include "ChartSpace.h"
|
||||
|
||||
#include "TabView.h"
|
||||
#include "Athlete.h"
|
||||
#include "RideCache.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <QGraphicsSceneMouseEvent>
|
||||
#include <QGLWidget>
|
||||
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonValue>
|
||||
|
||||
static QIcon grayConfig, whiteConfig, accentConfig;
|
||||
|
||||
ChartSpace::ChartSpace(Context *context) :
|
||||
state(NONE), context(context), group(NULL), _viewY(0),
|
||||
yresizecursor(false), xresizecursor(false), block(false), scrolling(false),
|
||||
setscrollbar(false), lasty(-1)
|
||||
{
|
||||
setContentsMargins(0,0,0,0);
|
||||
|
||||
// no longer a gc chart ...
|
||||
//setProperty("color", GColor(COVERVIEWBACKGROUND)); XXX??
|
||||
//setProperty("nomenu", true);
|
||||
//setShowTitle(false);
|
||||
//setControls(NULL);
|
||||
|
||||
QHBoxLayout *main = new QHBoxLayout;
|
||||
|
||||
// add a view and scene and centre
|
||||
scene = new QGraphicsScene(this);
|
||||
view = new QGraphicsView(this);
|
||||
view->viewport()->setAttribute(Qt::WA_AcceptTouchEvents, false); // stops it stealing focus on mouseover
|
||||
scrollbar = new QScrollBar(Qt::Vertical, this);
|
||||
|
||||
// how to move etc
|
||||
//view->setDragMode(QGraphicsView::ScrollHandDrag);
|
||||
view->setRenderHint(QPainter::Antialiasing, true);
|
||||
view->setFrameStyle(QFrame::NoFrame);
|
||||
view->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||
view->setScene(scene);
|
||||
|
||||
// layout
|
||||
main->addWidget(view);
|
||||
main->addWidget(scrollbar);
|
||||
|
||||
// all the widgets
|
||||
setLayout(main);
|
||||
|
||||
// by default these are the column sizes (user can adjust)
|
||||
columns << 1200 << 1200 << 1200 << 1200 << 1200 << 1200 << 1200 << 1200 << 1200 << 1200;
|
||||
|
||||
// for changing the view
|
||||
group = new QParallelAnimationGroup(this);
|
||||
viewchange = new QPropertyAnimation(this, "viewRect");
|
||||
viewchange->setEasingCurve(QEasingCurve(QEasingCurve::OutQuint));
|
||||
|
||||
// for scrolling the view
|
||||
scroller = new QPropertyAnimation(this, "viewY");
|
||||
scroller->setEasingCurve(QEasingCurve(QEasingCurve::Linear));
|
||||
|
||||
// watch the view for mouse events
|
||||
view->setMouseTracking(true);
|
||||
scene->installEventFilter(this);
|
||||
|
||||
// once all widgets created we can connect the signals
|
||||
connect(context, SIGNAL(configChanged(qint32)), this, SLOT(configChanged(qint32)));
|
||||
connect(scroller, SIGNAL(finished()), this, SLOT(scrollFinished()));
|
||||
connect(scrollbar, SIGNAL(valueChanged(int)), this, SLOT(scrollbarMoved(int)));
|
||||
|
||||
// set the widgets etc
|
||||
configChanged(CONFIG_APPEARANCE);
|
||||
|
||||
// we're ready to plot, but not configured
|
||||
configured=false;
|
||||
stale=true;
|
||||
current=NULL;
|
||||
}
|
||||
|
||||
// add the item
|
||||
void
|
||||
ChartSpace::addItem(int order, int column, int deep, ChartSpaceItem *item)
|
||||
{
|
||||
item->order= order;
|
||||
item->column = column;
|
||||
item->deep = deep;
|
||||
items.append(item);
|
||||
}
|
||||
|
||||
// when a ride is selected we need to notify all the ChartSpaceItems
|
||||
void
|
||||
ChartSpace::rideSelected(RideItem *item)
|
||||
{
|
||||
// don't plot when we're not visible, unless we have nothing plotted yet
|
||||
if (!isVisible() && current != NULL && item != NULL) {
|
||||
stale=true;
|
||||
return;
|
||||
}
|
||||
|
||||
// don't replot .. we already did this one
|
||||
if (current == item && stale == false) {
|
||||
return;
|
||||
}
|
||||
|
||||
// profiling the code
|
||||
//QTime timer;
|
||||
//timer.start();
|
||||
|
||||
// ride item changed
|
||||
foreach(ChartSpaceItem *ChartSpaceItem, items) ChartSpaceItem->setData(item);
|
||||
|
||||
// profiling the code
|
||||
//qDebug()<<"took:"<<timer.elapsed();
|
||||
|
||||
// update
|
||||
updateView();
|
||||
|
||||
// ok, remember we did this one
|
||||
current = item;
|
||||
stale=false;
|
||||
}
|
||||
|
||||
void
|
||||
ChartSpaceItem::setData(RideItem *item)
|
||||
{
|
||||
Q_UNUSED(item);
|
||||
// ignored
|
||||
}
|
||||
|
||||
void
|
||||
ChartSpaceItem::setDrag(bool x)
|
||||
{
|
||||
if (drag == x) return; // unchanged
|
||||
|
||||
drag = x;
|
||||
|
||||
// hide stuff
|
||||
dragChanged(drag);
|
||||
if (!drag) geometryChanged();
|
||||
}
|
||||
|
||||
void
|
||||
ChartSpaceItem::geometryChanged()
|
||||
{
|
||||
itemGeometryChanged();
|
||||
}
|
||||
|
||||
bool
|
||||
ChartSpaceItem::sceneEvent(QEvent *event)
|
||||
{
|
||||
// skip whilst dragging and resizing
|
||||
if (parent->state != ChartSpace::NONE) return false;
|
||||
|
||||
// repaint when mouse enters and leaves
|
||||
if (event->type() == QEvent::GraphicsSceneHoverLeave ||
|
||||
event->type() == QEvent::GraphicsSceneHoverEnter) {
|
||||
|
||||
// force repaint
|
||||
update();
|
||||
scene()->update();
|
||||
|
||||
// repaint when in the corner
|
||||
} else if (event->type() == QEvent::GraphicsSceneHoverMove && inCorner() != incorner) {
|
||||
|
||||
incorner = inCorner();
|
||||
update();
|
||||
scene()->update();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
ChartSpaceItem::inCorner()
|
||||
{
|
||||
QPoint vpos = parent->view->mapFromGlobal(QCursor::pos());
|
||||
QPointF spos = parent->view->mapToScene(vpos);
|
||||
|
||||
if (geometry().contains(spos.x(), spos.y())) {
|
||||
if (spos.y() - geometry().top() < (ROWHEIGHT+40) &&
|
||||
geometry().width() - (spos.x() - geometry().x()) < (ROWHEIGHT+40))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
bool
|
||||
ChartSpaceItem::underMouse()
|
||||
{
|
||||
QPoint vpos = parent->view->mapFromGlobal(QCursor::pos());
|
||||
QPointF spos = parent->view->mapToScene(vpos);
|
||||
|
||||
if (geometry().contains(spos.x(), spos.y())) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
// ChartSpaceItems need to show they are in config mode
|
||||
void
|
||||
ChartSpaceItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *opt, QWidget *widget) {
|
||||
|
||||
if (drag) painter->setBrush(QBrush(GColor(CPLOTMARKER)));
|
||||
else painter->setBrush(GColor(CCARDBACKGROUND));
|
||||
|
||||
QPainterPath path;
|
||||
path.addRoundedRect(QRectF(0,0,geometry().width(),geometry().height()), ROWHEIGHT/5, ROWHEIGHT/5);
|
||||
painter->setPen(Qt::NoPen);
|
||||
//painter->fillPath(path, brush.color());
|
||||
painter->drawPath(path);
|
||||
painter->setPen(GColor(CPLOTGRID));
|
||||
//XXXpainter->drawLine(QLineF(0,ROWHEIGHT*2,geometry().width(),ROWHEIGHT*2));
|
||||
//painter->fillRect(QRectF(0,0,geometry().width()+1,geometry().height()+1), brush);
|
||||
//titlefont.setWeight(QFont::Bold);
|
||||
if (GCColor::luminance(GColor(CCARDBACKGROUND)) < 127) painter->setPen(QColor(200,200,200));
|
||||
else painter->setPen(QColor(70,70,70));
|
||||
|
||||
painter->setFont(parent->titlefont);
|
||||
painter->drawText(QPointF(ROWHEIGHT /2.0f, QFontMetrics(parent->titlefont, parent->device()).height()), name);
|
||||
|
||||
// only paint contents if not dragging
|
||||
if (drag) return;
|
||||
|
||||
// not dragging so we can get to work painting the rest
|
||||
if (parent->state != ChartSpace::DRAG && underMouse()) {
|
||||
|
||||
if (inCorner()) {
|
||||
|
||||
// if hovering over the button show a background to indicate
|
||||
// that pressing a button is good
|
||||
QPainterPath path;
|
||||
path.addRoundedRect(QRectF(geometry().width()-40-ROWHEIGHT,0,
|
||||
ROWHEIGHT+40, ROWHEIGHT+40), ROWHEIGHT/5, ROWHEIGHT/5);
|
||||
painter->setPen(Qt::NoPen);
|
||||
QColor darkgray(GColor(CCARDBACKGROUND).lighter(200));
|
||||
painter->setBrush(darkgray);
|
||||
painter->drawPath(path);
|
||||
painter->fillRect(QRectF(geometry().width()-40-ROWHEIGHT, 0, ROWHEIGHT+40-(ROWHEIGHT/5), ROWHEIGHT+40), QBrush(darkgray));
|
||||
painter->fillRect(QRectF(geometry().width()-40-ROWHEIGHT, ROWHEIGHT/5, ROWHEIGHT+40, ROWHEIGHT+40-(ROWHEIGHT/5)), QBrush(darkgray));
|
||||
|
||||
// draw the config button and make it more obvious
|
||||
// when hovering over the card
|
||||
painter->drawPixmap(geometry().width()-20-(ROWHEIGHT*1), 20, ROWHEIGHT*1, ROWHEIGHT*1, accentConfig.pixmap(QSize(ROWHEIGHT*1, ROWHEIGHT*1)));
|
||||
|
||||
} else {
|
||||
|
||||
// hover on card - make it more obvious there is a config button
|
||||
painter->drawPixmap(geometry().width()-20-(ROWHEIGHT*1), 20, ROWHEIGHT*1, ROWHEIGHT*1, whiteConfig.pixmap(QSize(ROWHEIGHT*1, ROWHEIGHT*1)));
|
||||
}
|
||||
|
||||
} else painter->drawPixmap(geometry().width()-20-(ROWHEIGHT*1), 20, ROWHEIGHT*1, ROWHEIGHT*1, grayConfig.pixmap(QSize(ROWHEIGHT*1, ROWHEIGHT*1)));
|
||||
|
||||
itemPaint(painter, opt, widget);
|
||||
}
|
||||
|
||||
static bool ChartSpaceItemSort(const ChartSpaceItem* left, const ChartSpaceItem* right)
|
||||
{
|
||||
return (left->column < right->column ? true : (left->column == right->column && left->order < right->order ? true : false));
|
||||
}
|
||||
|
||||
void
|
||||
ChartSpace::updateGeometry()
|
||||
{
|
||||
|
||||
bool animated=false;
|
||||
|
||||
// prevent a memory leak
|
||||
group->stop();
|
||||
delete group;
|
||||
group = new QParallelAnimationGroup(this);
|
||||
|
||||
// order the items to their positions
|
||||
qSort(items.begin(), items.end(), ChartSpaceItemSort);
|
||||
|
||||
int y=SPACING;
|
||||
int maxy = y;
|
||||
int column=-1;
|
||||
|
||||
int x=SPACING;
|
||||
|
||||
// just set their geometry for now, no interaction
|
||||
for(int i=0; i<items.count(); i++) {
|
||||
|
||||
// don't show hidden
|
||||
if (!items[i]->isVisible()) continue;
|
||||
|
||||
// move on to next column, check if first item too
|
||||
if (items[i]->column > column) {
|
||||
|
||||
// once past the first column we need to update x
|
||||
if (column >= 0) x+= columns[column] + SPACING;
|
||||
|
||||
int diff = items[i]->column - column - 1;
|
||||
if (diff > 0) {
|
||||
|
||||
// there are empty columns so shift the cols to the right
|
||||
// to the left to fill the gap left and all the column
|
||||
// widths also need to move down too
|
||||
for(int j=items[i]->column-1; j < 8; j++) columns[j]=columns[j+1];
|
||||
for(int j=i; j<items.count();j++) items[j]->column -= diff;
|
||||
}
|
||||
y=SPACING; column = items[i]->column;
|
||||
|
||||
}
|
||||
|
||||
// set geometry
|
||||
int ty = y;
|
||||
int tx = x;
|
||||
int twidth = columns[column];
|
||||
int theight = items[i]->deep * ROWHEIGHT;
|
||||
|
||||
// make em smaller when configuring visual cue stolen from Windows Start Menu
|
||||
int add = 0; //XXX PERFORMANCE ISSSE XXX (state == DRAG) ? (ROWHEIGHT/2) : 0;
|
||||
|
||||
|
||||
// for setting the scene rectangle - but ignore a ChartSpaceItem if we are dragging it
|
||||
if (maxy < ty+theight+SPACING) maxy = ty+theight+SPACING;
|
||||
|
||||
// add to scene if new
|
||||
if (!items[i]->onscene) {
|
||||
scene->addItem(items[i]);
|
||||
items[i]->setGeometry(tx, ty, twidth, theight);
|
||||
items[i]->onscene = true;
|
||||
|
||||
} else if (items[i]->invisible == false &&
|
||||
(items[i]->geometry().x() != tx+add ||
|
||||
items[i]->geometry().y() != ty+add ||
|
||||
items[i]->geometry().width() != twidth-(add*2) ||
|
||||
items[i]->geometry().height() != theight-(add*2))) {
|
||||
|
||||
// we've got an animation to perform
|
||||
animated = true;
|
||||
|
||||
// add an animation for this movement
|
||||
QPropertyAnimation *animation = new QPropertyAnimation(items[i], "geometry");
|
||||
animation->setDuration(300);
|
||||
animation->setStartValue(items[i]->geometry());
|
||||
animation->setEndValue(QRect(tx+add,ty+add,twidth-(add*2),theight-(add*2)));
|
||||
|
||||
// when placing a little feedback helps
|
||||
if (items[i]->placing) {
|
||||
animation->setEasingCurve(QEasingCurve(QEasingCurve::OutBack));
|
||||
items[i]->placing = false;
|
||||
} else animation->setEasingCurve(QEasingCurve(QEasingCurve::OutQuint));
|
||||
|
||||
group->addAnimation(animation);
|
||||
}
|
||||
|
||||
// set spot for next tile
|
||||
y += theight + SPACING;
|
||||
}
|
||||
|
||||
// set the scene rectangle, columns start at 0
|
||||
sceneRect = QRectF(0, 0, columns[column] + x + SPACING, maxy);
|
||||
|
||||
if (animated) group->start();
|
||||
}
|
||||
|
||||
void
|
||||
ChartSpace::configChanged(qint32)
|
||||
{
|
||||
grayConfig = colouredIconFromPNG(":images/configure.png", GColor(COVERVIEWBACKGROUND).lighter(75));
|
||||
whiteConfig = colouredIconFromPNG(":images/configure.png", QColor(100,100,100));
|
||||
accentConfig = colouredIconFromPNG(":images/configure.png", QColor(150,150,150));
|
||||
|
||||
// set fonts
|
||||
bigfont.setPixelSize(pixelSizeForFont(bigfont, ROWHEIGHT *2.5f));
|
||||
titlefont.setPixelSize(pixelSizeForFont(titlefont, ROWHEIGHT)); // need a bit of space
|
||||
midfont.setPixelSize(pixelSizeForFont(midfont, ROWHEIGHT *0.8f));
|
||||
smallfont.setPixelSize(pixelSizeForFont(smallfont, ROWHEIGHT*0.7f));
|
||||
|
||||
setProperty("color", GColor(COVERVIEWBACKGROUND));
|
||||
view->setBackgroundBrush(QBrush(GColor(COVERVIEWBACKGROUND)));
|
||||
scene->setBackgroundBrush(QBrush(GColor(COVERVIEWBACKGROUND)));
|
||||
scrollbar->setStyleSheet(TabView::ourStyleSheet());
|
||||
|
||||
// text edit colors
|
||||
QPalette palette;
|
||||
palette.setColor(QPalette::Window, GColor(COVERVIEWBACKGROUND));
|
||||
palette.setColor(QPalette::Background, GColor(COVERVIEWBACKGROUND));
|
||||
|
||||
// only change base if moved away from white plots
|
||||
// which is a Mac thing
|
||||
#ifndef Q_OS_MAC
|
||||
if (GColor(COVERVIEWBACKGROUND) != Qt::white)
|
||||
#endif
|
||||
{
|
||||
//palette.setColor(QPalette::Base, GCColor::alternateColor(GColor(CTRAINPLOTBACKGROUND)));
|
||||
palette.setColor(QPalette::Base, GColor(COVERVIEWBACKGROUND));
|
||||
palette.setColor(QPalette::Window, GColor(COVERVIEWBACKGROUND));
|
||||
}
|
||||
|
||||
#ifndef Q_OS_MAC // the scrollers appear when needed on Mac, we'll keep that
|
||||
//code->setStyleSheet(TabView::ourStyleSheet());
|
||||
#endif
|
||||
|
||||
palette.setColor(QPalette::WindowText, GCColor::invertColor(GColor(COVERVIEWBACKGROUND)));
|
||||
palette.setColor(QPalette::Text, GCColor::invertColor(GColor(COVERVIEWBACKGROUND)));
|
||||
//code->setPalette(palette);
|
||||
repaint();
|
||||
}
|
||||
|
||||
void
|
||||
ChartSpace::updateView()
|
||||
{
|
||||
scene->setSceneRect(sceneRect);
|
||||
scene->update();
|
||||
|
||||
// don'r scale whilst resizing on x?
|
||||
if (scrolling || (state != YRESIZE && state != XRESIZE && state != DRAG)) {
|
||||
|
||||
// much of a resize / change ?
|
||||
double dx = fabs(viewRect.x() - sceneRect.x());
|
||||
double dy = fabs(viewRect.y() - sceneRect.y());
|
||||
double vy = fabs(viewRect.y()-double(_viewY));
|
||||
double dwidth = fabs(viewRect.width() - sceneRect.width());
|
||||
double dheight = fabs(viewRect.height() - sceneRect.height());
|
||||
|
||||
// scale immediately if not a bit change
|
||||
// otherwise it feels unresponsive
|
||||
if (viewRect.width() == 0 || (vy < 20 && dx < 20 && dy < 20 && dwidth < 20 && dheight < 20)) {
|
||||
setViewRect(sceneRect);
|
||||
} else {
|
||||
|
||||
// tempting to make this longer but feels ponderous at longer durations
|
||||
viewchange->setDuration(400);
|
||||
viewchange->setStartValue(viewRect);
|
||||
viewchange->setEndValue(sceneRect);
|
||||
viewchange->start();
|
||||
}
|
||||
}
|
||||
|
||||
if (view->sceneRect().height() >= scene->sceneRect().height()) {
|
||||
scrollbar->setEnabled(false);
|
||||
} else {
|
||||
|
||||
// now set scrollbar
|
||||
setscrollbar = true;
|
||||
scrollbar->setMinimum(0);
|
||||
scrollbar->setMaximum(scene->sceneRect().height()-view->sceneRect().height());
|
||||
scrollbar->setValue(_viewY);
|
||||
scrollbar->setPageStep(view->sceneRect().height());
|
||||
scrollbar->setEnabled(true);
|
||||
setscrollbar = false;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ChartSpace::edgeScroll(bool down)
|
||||
{
|
||||
// already scrolling, so don't move
|
||||
if (scrolling) return;
|
||||
// we basically scroll the view if the cursor is at or above
|
||||
// the top of the view, or at or below the bottom and the mouse
|
||||
// is moving away. Needs to work in normal and full screen.
|
||||
if (state == DRAG || state == YRESIZE) {
|
||||
|
||||
QPointF pos =this->mapFromGlobal(QCursor::pos());
|
||||
|
||||
if (!down && pos.y() <= 0) {
|
||||
|
||||
// at the top of the screen, go up a qtr of a screen
|
||||
scrollTo(_viewY - (view->sceneRect().height()/4));
|
||||
|
||||
} else if (down && (geometry().height()-pos.y()) <= 0) {
|
||||
|
||||
// at the bottom of the screen, go down a qtr of a screen
|
||||
scrollTo(_viewY + (view->sceneRect().height()/4));
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ChartSpace::scrollTo(int newY)
|
||||
{
|
||||
|
||||
// bound the target to the top or a screenful from the bottom, except when we're
|
||||
// resizing on Y as we are expanding the scene by increasing the size of an object
|
||||
if ((state != YRESIZE) && (newY +view->sceneRect().height()) > sceneRect.bottom())
|
||||
newY = sceneRect.bottom() - view->sceneRect().height();
|
||||
if (newY < 0)
|
||||
newY = 0;
|
||||
|
||||
if (_viewY != newY) {
|
||||
|
||||
if (abs(_viewY - newY) < 20) {
|
||||
|
||||
// for small scroll increments just do it, its tedious to wait for animations
|
||||
_viewY = newY;
|
||||
updateView();
|
||||
|
||||
} else {
|
||||
|
||||
// disable other view updates whilst scrolling
|
||||
scrolling = true;
|
||||
|
||||
// make it snappy for short distances - ponderous for drag scroll
|
||||
// and vaguely snappy for page by page scrolling
|
||||
if (state == DRAG || state == YRESIZE) scroller->setDuration(300);
|
||||
else if (abs(_viewY-newY) < 100) scroller->setDuration(150);
|
||||
else scroller->setDuration(250);
|
||||
scroller->setStartValue(_viewY);
|
||||
scroller->setEndValue(newY);
|
||||
scroller->start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ChartSpace::setViewRect(QRectF rect)
|
||||
{
|
||||
viewRect = rect;
|
||||
|
||||
// fit to scene width XXX need to fix scrollbars.
|
||||
double scale = view->frameGeometry().width() / viewRect.width();
|
||||
QRectF scaledRect(0,_viewY, viewRect.width(), view->frameGeometry().height() / scale);
|
||||
|
||||
// scale to selection
|
||||
view->scale(scale,scale);
|
||||
view->setSceneRect(scaledRect);
|
||||
view->fitInView(scaledRect, Qt::KeepAspectRatio);
|
||||
|
||||
// if we're dragging, as the view changes it can be really jarring
|
||||
// as the dragged item is not under the mouse then snaps back
|
||||
// this might need to be cleaned up as a little too much of spooky
|
||||
// action at a distance going on here !
|
||||
if (state == DRAG) {
|
||||
|
||||
// update drag point
|
||||
QPoint vpos = view->mapFromGlobal(QCursor::pos());
|
||||
QPointF pos = view->mapToScene(vpos);
|
||||
|
||||
// move the ChartSpaceItem being dragged
|
||||
stateData.drag.item->setPos(pos.x()-stateData.drag.offx, pos.y()-stateData.drag.offy);
|
||||
}
|
||||
|
||||
view->update();
|
||||
|
||||
}
|
||||
|
||||
bool
|
||||
ChartSpace::eventFilter(QObject *, QEvent *event)
|
||||
{
|
||||
if (block || (event->type() != QEvent::KeyPress && event->type() != QEvent::GraphicsSceneWheel &&
|
||||
event->type() != QEvent::GraphicsSceneMousePress && event->type() != QEvent::GraphicsSceneMouseRelease &&
|
||||
event->type() != QEvent::GraphicsSceneMouseMove)) {
|
||||
return false;
|
||||
}
|
||||
block = true;
|
||||
bool returning = false;
|
||||
|
||||
// we only filter out keyboard shortcuts for undo redo etc
|
||||
// in the qwkcode editor, anything else is of no interest.
|
||||
if (event->type() == QEvent::KeyPress) {
|
||||
|
||||
// we care about cmd / ctrl
|
||||
Qt::KeyboardModifiers kmod = static_cast<QInputEvent*>(event)->modifiers();
|
||||
bool ctrl = (kmod & Qt::ControlModifier) != 0;
|
||||
|
||||
switch(static_cast<QKeyEvent*>(event)->key()) {
|
||||
|
||||
case Qt::Key_Y:
|
||||
if (ctrl) {
|
||||
//workout->redo();
|
||||
returning = true; // we grab all key events
|
||||
}
|
||||
break;
|
||||
|
||||
case Qt::Key_Z:
|
||||
if (ctrl) {
|
||||
//workout->undo();
|
||||
returning=true;
|
||||
}
|
||||
break;
|
||||
|
||||
case Qt::Key_Home:
|
||||
scrollTo(0);
|
||||
break;
|
||||
|
||||
case Qt::Key_End:
|
||||
scrollTo(scene->sceneRect().bottom());
|
||||
break;
|
||||
|
||||
case Qt::Key_PageDown:
|
||||
scrollTo(_viewY + view->sceneRect().height());
|
||||
break;
|
||||
|
||||
case Qt::Key_PageUp:
|
||||
scrollTo(_viewY - view->sceneRect().height());
|
||||
break;
|
||||
|
||||
case Qt::Key_Down:
|
||||
scrollTo(_viewY + ROWHEIGHT);
|
||||
break;
|
||||
|
||||
case Qt::Key_Up:
|
||||
scrollTo(_viewY - ROWHEIGHT);
|
||||
break;
|
||||
}
|
||||
|
||||
} else if (event->type() == QEvent::GraphicsSceneWheel) {
|
||||
|
||||
// take it as applied
|
||||
QGraphicsSceneWheelEvent *w = static_cast<QGraphicsSceneWheelEvent*>(event);
|
||||
scrollTo(_viewY - (w->delta()*2));
|
||||
event->accept();
|
||||
returning = true;
|
||||
|
||||
} else if (event->type() == QEvent::GraphicsSceneMousePress) {
|
||||
|
||||
// we will process clicks when configuring so long as we're
|
||||
// not in the middle of something else - this is to start
|
||||
// dragging a ChartSpaceItem around
|
||||
if (state == NONE) {
|
||||
|
||||
// where am i ?
|
||||
QPointF pos = static_cast<QGraphicsSceneMouseEvent*>(event)->scenePos();
|
||||
QGraphicsItem *gritem = scene->itemAt(pos, view->transform());
|
||||
ChartSpaceItem *item = static_cast<ChartSpaceItem*>(gritem);
|
||||
|
||||
// ignore other scene elements (e.g. charts)
|
||||
if (!items.contains(item)) item=NULL;
|
||||
|
||||
// only respond to clicks not in config corner button
|
||||
if (item && ! item->inCorner()) {
|
||||
|
||||
// are we on the boundary of the ChartSpaceItem?
|
||||
double offx = pos.x()-item->geometry().x();
|
||||
double offy = pos.y()-item->geometry().y();
|
||||
|
||||
|
||||
if (item->geometry().height()-offy < 10) {
|
||||
|
||||
state = YRESIZE;
|
||||
|
||||
stateData.yresize.item = item;
|
||||
stateData.yresize.deep = item->deep;
|
||||
stateData.yresize.posy = pos.y();
|
||||
|
||||
// thanks we'll take that
|
||||
event->accept();
|
||||
returning = true;
|
||||
|
||||
} else if (item->geometry().width()-offx < 10) {
|
||||
|
||||
state = XRESIZE;
|
||||
|
||||
stateData.xresize.column = item->column;
|
||||
stateData.xresize.width = columns[item->column];
|
||||
stateData.xresize.posx = pos.x();
|
||||
|
||||
// thanks we'll take that
|
||||
event->accept();
|
||||
returning = true;
|
||||
|
||||
} else {
|
||||
|
||||
// we're grabbing a ChartSpaceItem, so lets
|
||||
// work out the offset so we can move
|
||||
// it around when we start dragging
|
||||
state = DRAG;
|
||||
item->invisible = true;
|
||||
item->setDrag(true);
|
||||
item->setZValue(100);
|
||||
|
||||
stateData.drag.item = item;
|
||||
stateData.drag.offx = offx;
|
||||
stateData.drag.offy = offy;
|
||||
stateData.drag.width = columns[item->column];
|
||||
|
||||
// thanks we'll take that
|
||||
event->accept();
|
||||
returning = true;
|
||||
|
||||
// what is the offset?
|
||||
//updateGeometry();
|
||||
scene->update();
|
||||
view->update();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} else if (event->type() == QEvent::GraphicsSceneMouseRelease) {
|
||||
|
||||
// stop dragging
|
||||
if (state == DRAG || state == YRESIZE || state == XRESIZE) {
|
||||
|
||||
// we want this one
|
||||
event->accept();
|
||||
returning = true;
|
||||
|
||||
// set back to visible if dragging
|
||||
if (state == DRAG) {
|
||||
stateData.drag.item->invisible = false;
|
||||
stateData.drag.item->setZValue(10);
|
||||
stateData.drag.item->placing = true;
|
||||
stateData.drag.item->setDrag(false);
|
||||
}
|
||||
|
||||
// end state;
|
||||
state = NONE;
|
||||
|
||||
// drop it down
|
||||
updateGeometry();
|
||||
updateView();
|
||||
}
|
||||
|
||||
} else if (event->type() == QEvent::GraphicsSceneMouseMove) {
|
||||
|
||||
// where is the mouse now?
|
||||
QPointF pos = static_cast<QGraphicsSceneMouseEvent*>(event)->scenePos();
|
||||
|
||||
// check for autoscrolling at edges
|
||||
if (state == DRAG || state == YRESIZE) edgeScroll(lasty < pos.y());
|
||||
|
||||
// remember pos
|
||||
lasty = pos.y();
|
||||
|
||||
if (state == NONE) { // hovering
|
||||
|
||||
// where am i ?
|
||||
QGraphicsItem *gritem = scene->itemAt(pos, view->transform());
|
||||
ChartSpaceItem *item = static_cast<ChartSpaceItem*>(gritem);
|
||||
|
||||
// ignore other scene elements (e.g. charts)
|
||||
if (!items.contains(item)) item=NULL;
|
||||
|
||||
if (item) {
|
||||
|
||||
// are we on the boundary of the ChartSpaceItem?
|
||||
double offx = pos.x()-item->geometry().x();
|
||||
double offy = pos.y()-item->geometry().y();
|
||||
|
||||
if (yresizecursor == false && item->geometry().height()-offy < 10) {
|
||||
|
||||
yresizecursor = true;
|
||||
setCursor(QCursor(Qt::SizeVerCursor));
|
||||
|
||||
} else if (yresizecursor == true && item->geometry().height()-offy > 10) {
|
||||
|
||||
yresizecursor = false;
|
||||
setCursor(QCursor(Qt::ArrowCursor));
|
||||
|
||||
}
|
||||
|
||||
if (xresizecursor == false && item->geometry().width()-offx < 10) {
|
||||
|
||||
xresizecursor = true;
|
||||
setCursor(QCursor(Qt::SizeHorCursor));
|
||||
|
||||
} else if (xresizecursor == true && item->geometry().width()-offx > 10) {
|
||||
|
||||
xresizecursor = false;
|
||||
setCursor(QCursor(Qt::ArrowCursor));
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
// not hovering over tile, so if still have a resize cursor
|
||||
// set it back to the normal arrow pointer
|
||||
if (yresizecursor || xresizecursor || cursor().shape() != Qt::ArrowCursor) {
|
||||
xresizecursor = yresizecursor = false;
|
||||
setCursor(QCursor(Qt::ArrowCursor));
|
||||
}
|
||||
}
|
||||
|
||||
} else if (state == DRAG && !scrolling) { // dragging?
|
||||
|
||||
// whilst mouse moves, only update geom when changed
|
||||
bool changed = false;
|
||||
|
||||
// move the ChartSpaceItem being dragged
|
||||
stateData.drag.item->setPos(pos.x()-stateData.drag.offx, pos.y()-stateData.drag.offy);
|
||||
|
||||
// should I move?
|
||||
QList<QGraphicsItem *> overlaps;
|
||||
foreach(QGraphicsItem *p, scene->items(pos))
|
||||
if(items.contains(static_cast<ChartSpaceItem*>(p)))
|
||||
overlaps << p;
|
||||
|
||||
// we always overlap with ourself, so see if more
|
||||
if (overlaps.count() > 1) {
|
||||
|
||||
ChartSpaceItem *over = static_cast<ChartSpaceItem*>(overlaps[1]);
|
||||
if (pos.y()-over->geometry().y() > over->geometry().height()/2) {
|
||||
|
||||
// place below the one its over
|
||||
stateData.drag.item->column = over->column;
|
||||
stateData.drag.item->order = over->order+1;
|
||||
for(int i=items.indexOf(over); i< items.count(); i++) {
|
||||
if (i>=0 && items[i]->column == over->column && items[i]->order > over->order && items[i] != stateData.drag.item) {
|
||||
items[i]->order += 1;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
// place above the one its over
|
||||
stateData.drag.item->column = over->column;
|
||||
stateData.drag.item->order = over->order;
|
||||
for(int i=0; i< items.count(); i++) {
|
||||
if (i>=0 && items[i]->column == over->column && items[i]->order >= (over->order) && items[i] != stateData.drag.item) {
|
||||
items[i]->order += 1;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
// columns are now variable width
|
||||
// create a new column to the right?
|
||||
int x=SPACING;
|
||||
int targetcol = -1;
|
||||
for(int i=0; i<10; i++) {
|
||||
if (pos.x() > x && pos.x() < (x+columns[i]+SPACING)) {
|
||||
targetcol = i;
|
||||
break;
|
||||
}
|
||||
x += columns[i]+SPACING;
|
||||
}
|
||||
|
||||
if (items.last()->column < 9 && targetcol < 0) {
|
||||
|
||||
// don't keep moving - if we're already alone in column 0 then no move is needed
|
||||
if (stateData.drag.item->column != 0 || (items.count()>1 && items[1]->column == 0)) {
|
||||
|
||||
// new col to left
|
||||
for(int i=0; i< items.count(); i++) items[i]->column += 1;
|
||||
stateData.drag.item->column = 0;
|
||||
stateData.drag.item->order = 0;
|
||||
|
||||
// shift columns widths to the right
|
||||
for(int i=9; i>0; i--) columns[i] = columns[i-1];
|
||||
columns[0] = stateData.drag.width;
|
||||
|
||||
changed = true;
|
||||
}
|
||||
|
||||
} else if (items.last()->column < 9 && items.last() && items.last()->column < targetcol) {
|
||||
|
||||
// new col to the right
|
||||
stateData.drag.item->column = items.last()->column + 1;
|
||||
stateData.drag.item->order = 0;
|
||||
|
||||
// make column width same as source width
|
||||
columns[stateData.drag.item->column] = stateData.drag.width;
|
||||
|
||||
changed = true;
|
||||
|
||||
} else {
|
||||
|
||||
// add to the end of the column
|
||||
int last = -1;
|
||||
for(int i=0; i<items.count() && items[i]->column <= targetcol; i++) {
|
||||
if (items[i]->column == targetcol) last=i;
|
||||
}
|
||||
|
||||
// so long as its been dragged below the last entry on the column !
|
||||
if (last >= 0 && pos.y() > items[last]->geometry().bottom()) {
|
||||
stateData.drag.item->column = targetcol;
|
||||
stateData.drag.item->order = items[last]->order+1;
|
||||
}
|
||||
|
||||
changed = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
// drop it down
|
||||
updateGeometry();
|
||||
updateView();
|
||||
}
|
||||
|
||||
} else if (state == YRESIZE) {
|
||||
|
||||
// resize in rows, so in 75px units
|
||||
int addrows = (pos.y() - stateData.yresize.posy) / ROWHEIGHT;
|
||||
int setdeep = stateData.yresize.deep + addrows;
|
||||
|
||||
//min height
|
||||
if (setdeep < 5) setdeep=5; // min of 5 rows
|
||||
|
||||
stateData.yresize.item->deep = setdeep;
|
||||
|
||||
// drop it down
|
||||
updateGeometry();
|
||||
updateView();
|
||||
|
||||
} else if ( state == XRESIZE) {
|
||||
|
||||
// multiples of 50 (smaller than margin)
|
||||
int addblocks = (pos.x() - stateData.xresize.posx) / 50;
|
||||
int setcolumn = stateData.xresize.width + (addblocks * 50);
|
||||
|
||||
// min max width
|
||||
if (setcolumn < 800) setcolumn = 800;
|
||||
if (setcolumn > 4400) setcolumn = 4400;
|
||||
|
||||
columns[stateData.xresize.column] = setcolumn;
|
||||
|
||||
// animate
|
||||
updateGeometry();
|
||||
updateView();
|
||||
}
|
||||
}
|
||||
|
||||
block = false;
|
||||
return returning;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
ChartSpaceItem::clicked()
|
||||
{
|
||||
if (isVisible()) hide();
|
||||
else show();
|
||||
|
||||
//if (brush.color() == GColor(CChartSpaceItemBACKGROUND)) brush.setColor(Qt::red);
|
||||
//else brush.setColor(GColor(CChartSpaceItemBACKGROUND));
|
||||
|
||||
update(geometry());
|
||||
}
|
||||
237
src/Gui/ChartSpace.h
Normal file
237
src/Gui/ChartSpace.h
Normal file
@@ -0,0 +1,237 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Mark Liversedge (liversedge@gmail.com)
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc., 51
|
||||
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#ifndef _GC_ChartSpace_h
|
||||
#define _GC_ChartSpace_h 1
|
||||
|
||||
// basics
|
||||
#include "GoldenCheetah.h"
|
||||
#include "Settings.h"
|
||||
#include "Units.h"
|
||||
#include "Colors.h"
|
||||
#include "Context.h"
|
||||
#include "Athlete.h"
|
||||
#include "RideItem.h"
|
||||
|
||||
// QGraphics
|
||||
#include <QGraphicsScene>
|
||||
#include <QGraphicsView>
|
||||
#include <QGraphicsWidget>
|
||||
#include <QGraphicsDropShadowEffect>
|
||||
|
||||
// qt
|
||||
#include <QtGui>
|
||||
#include <QScrollBar>
|
||||
#include <QIcon>
|
||||
#include <QTimer>
|
||||
|
||||
// geometry basics
|
||||
#define SPACING 80
|
||||
#define ROWHEIGHT 80
|
||||
|
||||
class ChartSpace;
|
||||
|
||||
// must be subclassed to add items to a ChartSpace
|
||||
class ChartSpaceItem : public QGraphicsWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
// When subclassing you must reimplement these
|
||||
virtual void itemPaint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *) =0;
|
||||
virtual void itemGeometryChanged() =0;
|
||||
virtual void setData(RideItem *item)=0;
|
||||
|
||||
// what type am I- managed by user
|
||||
int type;
|
||||
|
||||
ChartSpaceItem(ChartSpace *parent, QString name) : QGraphicsWidget(NULL),
|
||||
parent(parent), name(name),
|
||||
column(0), order(0), deep(5), onscene(false),
|
||||
placing(false), drag(false), invisible(false) {
|
||||
|
||||
setAutoFillBackground(false);
|
||||
setFlags(flags() | QGraphicsItem::ItemClipsToShape); // don't paint outside the card
|
||||
setAcceptHoverEvents(true);
|
||||
|
||||
setZValue(10);
|
||||
|
||||
// a sensible default?
|
||||
type = 0;
|
||||
delcounter=0;
|
||||
|
||||
// watch geom changes
|
||||
connect(this, SIGNAL(geometryChanged()), SLOT(geometryChanged()));
|
||||
}
|
||||
|
||||
// watch mouse enter/leave
|
||||
bool sceneEvent(QEvent *event);
|
||||
bool inCorner();
|
||||
bool underMouse();
|
||||
|
||||
// keep track of reuse of xyseries and delete to
|
||||
// try and minimise memory leak in Qt Chart
|
||||
int delcounter;
|
||||
|
||||
// when dragging around
|
||||
void setDrag(bool x);
|
||||
virtual void dragChanged(bool) {}
|
||||
|
||||
// my parent
|
||||
ChartSpace *parent;
|
||||
|
||||
// what to do if clicked XXX just a hack for now
|
||||
void clicked();
|
||||
|
||||
// name
|
||||
QString name;
|
||||
|
||||
// which column, sequence and size in rows
|
||||
int column, order, deep;
|
||||
bool onscene, placing, drag;
|
||||
bool incorner;
|
||||
bool invisible;
|
||||
|
||||
// base paint
|
||||
void paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *);
|
||||
|
||||
public slots:
|
||||
|
||||
void geometryChanged();
|
||||
};
|
||||
|
||||
class ChartSpace : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(QRectF viewRect READ getViewRect WRITE setViewRect)
|
||||
Q_PROPERTY(int viewY READ getViewY WRITE setViewY)
|
||||
|
||||
public:
|
||||
|
||||
ChartSpace(Context *context);
|
||||
|
||||
// current state for event processing
|
||||
enum { NONE, DRAG, XRESIZE, YRESIZE } state;
|
||||
|
||||
// used by children
|
||||
Context *context;
|
||||
QGraphicsView *view;
|
||||
QFont titlefont, bigfont, midfont, smallfont;
|
||||
|
||||
// to get paint device
|
||||
QGraphicsView *device() { return view; }
|
||||
const QList<ChartSpaceItem*> allItems() { return items; }
|
||||
|
||||
|
||||
public slots:
|
||||
|
||||
// ride item changed
|
||||
void rideSelected(RideItem *item);
|
||||
|
||||
// for smooth scrolling
|
||||
void setViewY(int x) { if (_viewY != x) {_viewY =x; updateView();} }
|
||||
int getViewY() const { return _viewY; }
|
||||
|
||||
// for smooth scaling
|
||||
void setViewRect(QRectF);
|
||||
QRectF getViewRect() const { return viewRect; }
|
||||
|
||||
// trap signals
|
||||
void configChanged(qint32);
|
||||
|
||||
// scale on first show
|
||||
void showEvent(QShowEvent *) { updateView(); }
|
||||
void resizeEvent(QResizeEvent *) { updateView(); }
|
||||
|
||||
// scrolling
|
||||
void edgeScroll(bool down);
|
||||
void scrollTo(int y);
|
||||
void scrollFinished() { scrolling = false; updateView(); }
|
||||
void scrollbarMoved(int x) { if (!scrolling && !setscrollbar) { setViewY(x); }}
|
||||
|
||||
// set geometry on the widgets (size and pos)
|
||||
void updateGeometry();
|
||||
|
||||
// set scale, zoom etc appropriately
|
||||
void updateView();
|
||||
|
||||
// add a ChartSpaceItem to the view
|
||||
void addItem(int row, int column, int deep, ChartSpaceItem *item);
|
||||
|
||||
protected:
|
||||
|
||||
// process events
|
||||
bool eventFilter(QObject *, QEvent *event);
|
||||
|
||||
private:
|
||||
|
||||
// gui setup
|
||||
QGraphicsScene *scene;
|
||||
QScrollBar *scrollbar;
|
||||
|
||||
// for animating transitions
|
||||
QParallelAnimationGroup *group;
|
||||
QPropertyAnimation *viewchange;
|
||||
QPropertyAnimation *scroller;
|
||||
|
||||
// scene and view
|
||||
int _viewY;
|
||||
QRectF sceneRect;
|
||||
QRectF viewRect;
|
||||
|
||||
// content
|
||||
QVector<int> columns; // column widths
|
||||
QList<ChartSpaceItem*> items; // tiles
|
||||
|
||||
// state data
|
||||
bool yresizecursor; // is the cursor set to resize?
|
||||
bool xresizecursor; // is the cursor set to resize?
|
||||
bool block; // block event processing
|
||||
bool scrolling; // scrolling the view?
|
||||
bool setscrollbar; // distinguish between user and program actions
|
||||
double lasty; // to see if the mouse is moving up or down
|
||||
|
||||
union ChartSpaceState {
|
||||
struct {
|
||||
double offx, offy; // mouse grab position on card
|
||||
ChartSpaceItem *item; // index of card in QList
|
||||
int width; // how big was I when I started dragging?
|
||||
} drag;
|
||||
|
||||
struct {
|
||||
double posy;
|
||||
int deep;
|
||||
ChartSpaceItem *item;
|
||||
} yresize;
|
||||
|
||||
struct {
|
||||
double posx;
|
||||
int width;
|
||||
int column;
|
||||
} xresize;
|
||||
|
||||
} stateData;
|
||||
|
||||
bool stale;
|
||||
bool configured;
|
||||
RideItem *current;
|
||||
};
|
||||
|
||||
#endif // _GC_ChartSpace_h
|
||||
@@ -39,6 +39,7 @@
|
||||
#include "RideEditor.h"
|
||||
#include "RideNavigator.h"
|
||||
#include "RideSummaryWindow.h"
|
||||
#include "RideMapWindow.h"
|
||||
#include "ScatterWindow.h"
|
||||
#include "SummaryWindow.h"
|
||||
#include "MetadataWindow.h"
|
||||
@@ -57,7 +58,7 @@
|
||||
#endif
|
||||
#include "PlanningWindow.h"
|
||||
#ifdef GC_HAVE_OVERVIEW
|
||||
#include "OverviewWindow.h"
|
||||
#include "Overview.h"
|
||||
#endif
|
||||
#include "UserChart.h"
|
||||
// Not until v4.0
|
||||
|
||||
@@ -632,12 +632,13 @@ greaterThan(QT_MAJOR_VERSION, 4) {
|
||||
# qt charts is officially supported from QT5.8 or higher
|
||||
# in 5.7 it is a tech preview and not always available
|
||||
greaterThan(QT_MINOR_VERSION, 7) {
|
||||
|
||||
QT += charts opengl
|
||||
|
||||
# Dashboard uses qt charts, so needs at least Qt 5.7
|
||||
DEFINES += GC_HAVE_OVERVIEW
|
||||
HEADERS += Charts/OverviewWindow.h
|
||||
SOURCES += Charts/OverviewWindow.cpp
|
||||
HEADERS += Gui/ChartSpace.h Charts/OverviewItems.h Charts/Overview.h
|
||||
SOURCES += Gui/ChartSpace.cpp Charts/OverviewItems.cpp Charts/Overview.cpp
|
||||
|
||||
# generic chart
|
||||
DEFINES += GC_HAVE_GENERIC
|
||||
|
||||
Reference in New Issue
Block a user