Files
GoldenCheetah/src/LTMPopup.cpp
Joern af7685a11b LTM Popup - cosmetics
... Font-Size for Header Texts increased to 10px (since the 8px font is
very ugly - at least on Windows 7) - since the Screen can be resized,
bigger header texts can still be read after resizing.
2014-06-28 13:47:59 +02:00

545 lines
18 KiB
C++

/*
* Copyright (c) 2010 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 "LTMPopup.h"
#include "MainWindow.h"
#include "Athlete.h"
LTMPopup::LTMPopup(Context *context) : QWidget(context->mainWindow), context(context)
{
// get application settings
setAutoFillBackground(false);
setContentsMargins(0,0,0,0);
setMinimumWidth(800);
QVBoxLayout *mainLayout = new QVBoxLayout(this);
mainLayout->setContentsMargins(0,0,0,0);
mainLayout->setSpacing(10); // some space between the vertical Scroll bars
setStyleSheet(QString::fromUtf8(
"border-width: 0px; \
border-color: rgba(255,255,255,0); \
color: white; \
background-color: rgba(255, 255, 255, 0); \
selection-color: rgba(255,255,255,60); \
selection-background-color: rgba(0,0,255,60);"));
// title
QFont titleFont;
titleFont.setPointSize(14);
titleFont.setBold(true);
title = new QLabel(tr("No Ride Selected"));
title->setFont(titleFont);
title->setFixedHeight(30);
mainLayout->addWidget(title);
// ride list...
rides = new QTableWidget(this);
#ifdef Q_OS_LINUX // QT 4.7 bug on Linux, selection-background-color is ignored (no transparency)
#if QT_VERSION > 0x5000
QStyle *style = QStyleFactory::create("fusion");
#else
QStyle *style = QStyleFactory::create("Cleanlooks");
#endif
rides->setStyle(style);
#endif
rides->setFrameStyle(QFrame::NoFrame);
rides->viewport()->setAutoFillBackground(false);
QString styleSheet = "::section {"
"spacing: 2px;"
"color: white;"
"background-color: rgba(255,255,255,0);"
"border: 0px;"
"margin: 0px;"
"font-size: 10px; }";
rides->horizontalHeader()->setStyleSheet(styleSheet);
rides->verticalHeader()->hide();
rides->setShowGrid(false);
rides->setSelectionMode(QAbstractItemView::SingleSelection);
rides->setSelectionBehavior(QAbstractItemView::SelectRows);
rides->viewport()->setMouseTracking(true);
rides->viewport()->installEventFilter(this);
QFont rideFont;
rideFont.setPointSize(SMALLFONT);
rides->setFont(rideFont);
mainLayout->addWidget(rides, 0, Qt::AlignRight);
// display the metrics summary here
metrics = new QTextEdit(this);
metrics->setReadOnly(true);
metrics->setFrameStyle(QFrame::NoFrame);
metrics->setFixedHeight(175);
metrics->viewport()->setAutoFillBackground(false);
QFont metricFont;
metricFont.setPointSize(SMALLFONT);
metrics->setFont(metricFont);
mainLayout->addWidget(metrics);
// display the ride notes here
notes = new QTextEdit;
notes->setReadOnly(true);
notes->setFont(metricFont);
notes->setFrameStyle(QFrame::NoFrame);
notes->viewport()->setAutoFillBackground(false);
mainLayout->addWidget(notes);
connect(rides, SIGNAL(itemSelectionChanged()), this, SLOT(rideSelected()));
}
void
LTMPopup::setTitle(QString s)
{
title->setText(s);
}
void
LTMPopup::setData(QList<SummaryMetrics>data, const RideMetric *metric, QString title)
{
// create the ride list
int count = 0;
rides->clear();
selected.clear();
// set headings
rides->setColumnCount(2);
// when
QTableWidgetItem *h = new QTableWidgetItem(tr("Date & Time"), QTableWidgetItem::Type);
rides->setHorizontalHeaderItem(0,h);
// value & process html encoding of(TM)
QTextEdit name(metric->name());
h = new QTableWidgetItem(name.toPlainText(), QTableWidgetItem::Type);
rides->setHorizontalHeaderItem(1,h);
// now add rows to the table for each entry
foreach(SummaryMetrics x, data) {
QDateTime rideDate = x.getRideDate();
// we'll select it for summary aggregation
selected << x;
// date/time
QTableWidgetItem *t = new QTableWidgetItem(rideDate.toString(tr("ddd, dd MMM yy hh:mmA")));
t->setFlags(t->flags() & (~Qt::ItemIsEditable));
t->setTextAlignment(Qt::AlignHCenter);
rides->setRowCount(count+1);
rides->setItem(count, 0, t);
rides->setRowHeight(count, 14);
// metrics
QString value = x.getStringForSymbol(metric->symbol(), context->athlete->useMetricUnits);
h = new QTableWidgetItem(value,QTableWidgetItem::Type);
h->setFlags(t->flags() & (~Qt::ItemIsEditable));
h->setTextAlignment(Qt::AlignHCenter);
rides->setItem(count, 1, h);
count++;
}
// make em all visible! - also show rides if only 1 ride selected
rides->resizeColumnsToContents();
rides->setFixedHeight((count > 10 ? 10 : count) * 14 + rides->horizontalHeader()->height());
// select the first one only if more than 1 rows exist !
if (count > 1) {
rides->setRangeSelected(QTableWidgetSelectionRange(0,0,0,1), true);
}
// show rides and metrics if at least 1 rides is selected -
// and even if only 1 Rides (since without the Ride we have not date/time)
// and the metrics for which the ride was selected
if (count == 0) {
// no ride
rides->hide();
metrics->hide();
notes->hide();
title = tr("No ride selected");
} else {
// one or more rides
rides->show();
metrics->show();
notes->show();
}
setTitle(title);
rideSelected();
}
void
LTMPopup::setData(LTMSettings &settings, QDate start, QDate end, QTime time)
{
// set the title
QString _title;
switch (settings.groupBy) {
case LTM_DAY:
_title = start.toString(tr("dddd, d MMMM yyyy"));
break;
case LTM_MONTH:
_title = start.toString(tr("MMMM yyyy"));
break;
case LTM_YEAR:
_title = start.toString("yyyy");
break;
case LTM_WEEK:
_title = QString(tr("%1 to %2"))
.arg(start.toString(tr("dd MMMM yyyy")))
.arg(end.toString(tr("dd MMMM yyyy")));
break;
case LTM_TOD:
case LTM_ALL:
_title = QString(tr("%1 to %2"))
.arg(settings.start.date().toString(tr("dd MMMM yy")))
.arg(settings.end.date().toString(tr("dd MMMM yy")));
break;
}
// add Search/Filter info to Title
if (context->isfiltered || context->ishomefiltered)
_title = tr("Search/Filter: ") + _title;
// For LTM_ALL show all Rides which are done in the active/selected Date Range
if (settings.groupBy == LTM_ALL) {
start = settings.start.date();
end = settings.end.date();
}
// For LTM_TOD show all Rides which are done in the active/selected Date Range considering the Ride Time
QTime end_time;
if (settings.groupBy == LTM_TOD) {
end_time = time.addSecs(3599); // full hour to 1 second before next (this also avoids reaching 00:00:00)
}
// create the ride list
int count = 0;
rides->clear();
selected.clear();
// set headings
rides->setColumnCount(1);
QTableWidgetItem *h = new QTableWidgetItem(tr("Date & Time"), QTableWidgetItem::Type);
rides->setHorizontalHeaderItem(0,h);
bool nonRideMetrics = false; // a non Ride specific metrics is shown on the Chart
// collect the header texts (to know the number of columns)
int column = 1;
QList<QString> headerList;
foreach(MetricDetail d, settings.metrics) {
// only add columns for ride related metrics (DB, META and BEST)
if (d.type == METRIC_DB || d.type == METRIC_META || d.type == METRIC_BEST) {
headerList << d.uname;
column++;
} else {
// there is at least one other metrics in the chart, so add info to title
nonRideMetrics = true;
}
}
// set the header texts of relevant columns exist
if (column > 1) {
rides->setColumnCount(column);
int i = 1;
foreach (QString header, headerList) {
h = new QTableWidgetItem(header,QTableWidgetItem::Type);
rides->setHorizontalHeaderItem(i++,h);
}
}
// Summary Metrics.data is always available and thus perfect find the rides eligible for the list
int i = 0;
foreach(SummaryMetrics data, (*settings.data)) {
QDateTime rideDate = data.getRideDate();
// check either RideDate (for all Date related groupBy's) or RideTime (for LTM_TOD only)
if (((settings.groupBy != LTM_TOD) && (rideDate.date() >= start && rideDate.date() <= end))
||((settings.groupBy == LTM_TOD) && (rideDate.time() >= time && rideDate.time() <= end_time))) {
// apply filters
if (context->isfiltered && !context->filters.contains(data.getFileName())) continue;
if (context->ishomefiltered && !context->homeFilters.contains(data.getFileName())) continue;
// we'll select it for summary display
selected << data;
// date/time
QTableWidgetItem *t = new QTableWidgetItem(rideDate.toString(tr("ddd, dd MMM yy hh:mmA")));
t->setFlags(t->flags() & (~Qt::ItemIsEditable));
t->setTextAlignment(Qt::AlignHCenter);
rides->setRowCount(count+1);
rides->setItem(count, 0, t);
rides->setRowHeight(count, 14);
// the column data has to be determined depending on the metrics type
int column = 1;
// per selected Ride find values for each Metrics
foreach(MetricDetail d, settings.metrics) {
QString value;
if (d.type == METRIC_DB || d.type == METRIC_META) {
value = data.getStringForSymbol(d.symbol, context->athlete->useMetricUnits);
} else if (d.type == METRIC_BEST) {
// bests are only available if a METRIC_BEST exists
SummaryMetrics bests = settings.bests->at(i);
// and are not considered as standard metrics for rounding, so do it here with precision 0
double v = bests.getForSymbol(d.bestSymbol);
value = QString("%1").arg(v, 0, 'f', 0);
} else {
// for any other type, rides have no data / and no header column is prepared
continue;
}
h = new QTableWidgetItem(value,QTableWidgetItem::Type);
h->setTextAlignment(Qt::AlignHCenter);
rides->setItem(count, column++, h);
}
count++;
}
i++;
}
// make em all visible!
rides->resizeColumnsToContents();
rides->setFixedHeight((count > 10 ? 10 : count) * 14 + rides->horizontalHeader()->height());
// select the first one only if more than 1 rows exist !
if (count > 1) {
rides->setRangeSelected(QTableWidgetSelectionRange(0,0,0,settings.metrics.count()), true);
}
// show rides and metrics if at least 1 rides is selected -
// and even if only 1 Rides (since without the Ride we have not date/time)
// and the metrics for which the ride was selected
if (count == 0) {
// no ride
rides->hide();
metrics->hide();
notes->hide();
_title = tr("No ride selected");
} else {
// one or more rides
rides->show();
metrics->show();
notes->show();
// give some extrat info
if (count > 1) _title += QString(tr(" (%1 rides)")).arg(count);
}
if (nonRideMetrics) _title += QString(tr(" / non ride-related metrics skipped"));
setTitle(_title);
rideSelected();
}
QString
LTMPopup::setSummaryHTML(SummaryMetrics &results)
{
// where we construct the text
QString summaryText("");
// main totals
static const QStringList totalColumn = QStringList()
<< "workout_time"
<< "time_riding"
<< "total_distance"
<< "total_work"
<< "skiba_wprime_exp"
<< "elevation_gain";
static const QStringList averageColumn = QStringList()
<< "average_speed"
<< "average_power"
<< "average_hr"
<< "average_cad";
static const QStringList maximumColumn = QStringList()
<< "max_speed"
<< "max_power"
<< "max_heartrate"
<< "max_cadence"
<< "skiba_wprime_max";
// user defined
QString s = appsettings->value(this, GC_SETTINGS_SUMMARY_METRICS, GC_SETTINGS_SUMMARY_METRICS_DEFAULT).toString();
// in case they were set tand then unset
if (s == "") s = GC_SETTINGS_SUMMARY_METRICS_DEFAULT;
QStringList metricColumn = s.split(",");
// foreach of the metrics get the ride value
// header of summary
summaryText = QString("<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 3.2//EN\">"
"<html>"
"<head>"
"<title></title>"
"</head>"
"<body>"
"<table border= \"0\" cellspacing=\"10\">"
"<tr>");
for (int i=0; i<4; i++) {
QString aggname;
QStringList list;
switch(i) {
case 0 : // Totals
aggname = tr("Totals");
list = totalColumn;
break;
case 1 : // Averages
aggname = tr("Averages");
list = averageColumn;
break;
case 2 : // Maximums
aggname = tr("Maximums");
list = maximumColumn;
break;
case 3 : // User defined..
aggname = tr("Metrics");
list = metricColumn;
break;
}
summaryText += QString("<td align=\"center\" width=\"25%\">"
"<table>"
"<tr>"
"<td align=\"center\" colspan=\"2\">"
"<b>%1</b>"
"</td>"
"</tr>").arg(aggname);
foreach(QString metricname, list) {
const RideMetric *metric = RideMetricFactory::instance().rideMetric(metricname);
QStringList empty; // filter list not used at present
QList<SummaryMetrics> resultList1;
resultList1 << results;
// use getAggregated even if it's only 1 file to have consistent value treatment
QString value = SummaryMetrics::getAggregated(context, metricname, resultList1, empty, false, context->athlete->useMetricUnits);
// Maximum Max and Average Average looks nasty, remove from name for display
QString s = metric ? metric->name().replace(QRegExp(tr("^(Average|Max) ")), "") : "unknown";
// don't show units for time values
if (metric && (metric->units(context->athlete->useMetricUnits) == "seconds" ||
metric->units(context->athlete->useMetricUnits) == tr("seconds") ||
metric->units(context->athlete->useMetricUnits) == "")) {
summaryText += QString("<tr><td>%1:</td><td align=\"right\"> %2</td></tr>")
.arg(s)
.arg(value);
} else {
summaryText += QString("<tr><td>%1(%2):</td><td align=\"right\"> %3</td></tr>")
.arg(s)
.arg(metric ? metric->units(context->athlete->useMetricUnits) : "unknown")
.arg(value);
}
}
summaryText += QString("</table>"
"</td>");
}
// finish off the html document
summaryText += QString("</tr>"
"</table>"
"</center>"
"</body>"
"</html>");
return summaryText;
}
void
LTMPopup::rideSelected()
{
int index = 0;
foreach (QTableWidgetItem *item, rides->selectedItems())
index = item->row();
if (selected.count() > index) {
// update summary
metrics->setText(setSummaryHTML(selected[index]));
notes->setText(selected[index].getText("Notes", ""));
}
resizeEvent(NULL);
}
void
LTMPopup::resizeEvent(QResizeEvent *)
{
int _width = width()-10;
int _firstWidth = 150;
int _scrollbarWidth = 20;
rides->setFixedWidth(_width);
rides->horizontalHeader()->resizeSection(0, _firstWidth);
// only resize if more than Date/Time column exists
if (rides->horizontalHeader()->count() > 1) {
int _sectionWidth = ((_width-_scrollbarWidth-_firstWidth) / (rides->horizontalHeader()->count()-1)) - 4;
rides->horizontalHeader()->resizeSection(0, _firstWidth);
for (int i=1; i < rides->horizontalHeader()->count(); i++)
rides->horizontalHeader()->resizeSection(i, _sectionWidth);
}
}
bool
LTMPopup::eventFilter(QObject * /*object*/, QEvent *e)
{
//if (object != (QObject *)plot()->canvas() )
//return false;
//if only 1 Ride in list, ignore any mouse activities
if (rides->rowCount() == 1) return false;
// process mouse
switch (e->type()) {
case QEvent::MouseMove:
{
int row = rides->indexAt((static_cast<QMouseEvent*>(e))->pos()).row();
if (row >= 0) {
QList<QModelIndex> selection = rides->selectionModel()->selection().indexes();
if (selection.count() == 0 || selection[0].row() != row) {
rides->clearSelection();
rides->setRangeSelected(QTableWidgetSelectionRange(row,0,row,rides->columnCount()-1), true);
}
}
return false;
}
break;
default:
return false;
}
return false;
}