/* * 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" #include "Specification.h" #include "RideCache.h" #include "RideFileCache.h" // for RideBest 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 activity 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(Specification spec, 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(RideItem *item, context->athlete->rideCache->rides()) { if (!spec.pass(item)) continue; // what rides were selected ? selected << item->fileName; // date/time QTableWidgetItem *t = new QTableWidgetItem(item->dateTime.toString(tr("ddd, dd MMM yy hh:mm"))); t->setFlags(t->flags() & (~Qt::ItemIsEditable)); t->setTextAlignment(Qt::AlignHCenter); rides->setRowCount(count+1); rides->setItem(count, 0, t); rides->setRowHeight(count, 14); // metrics double d = item->getForSymbol(metric->symbol()); const_cast(metric)->setValue(d); QString value = metric->toString(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 activity 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 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(RideItem *item, context->athlete->rideCache->rides()) { if (!settings.specification.pass(item)) continue; QDate rideDate = item->dateTime.date(); // check either RideDate (for all Date related groupBy's) or RideTime (for LTM_TOD only) if (((settings.groupBy != LTM_TOD) && (rideDate >= start && rideDate <= end)) || // group by time of day ? ((settings.groupBy == LTM_TOD) && (item->dateTime.time() >= time && item->dateTime.time() <= end_time))) { // we'll select it for summary display selected << item->fileName; // date/time QTableWidgetItem *t = new QTableWidgetItem(item->dateTime.toString(tr("ddd, dd MMM yy hh:mm"))); 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) { // get the value to show as a string value = item->getStringForSymbol(d.symbol, context->athlete->useMetricUnits); } else if (d.type == METRIC_BEST) { // bests are only available if a METRIC_BEST exists RideBest 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 activity selected"); } else { // one or more rides rides->show(); metrics->show(); notes->show(); // give some extrat info if (count > 1) _title += QString(tr(" (%1 activities)")).arg(count); } if (nonRideMetrics) _title += QString(tr(" / non activity-related metrics skipped")); setTitle(_title); rideSelected(); } QString LTMPopup::setSummaryHTML(RideItem *item) { // where we construct the text QString summaryText(""); // main totals const QStringList totalColumn = QStringList() << "workout_time" << "time_riding" << (item->isSwim ? "distance_swim" : "total_distance") << "total_work" << "skiba_wprime_exp" << "elevation_gain"; const QStringList averageColumn = QStringList() << "average_speed" << "average_power" << "average_hr" << "average_cad" << (item->isSwim ? "pace_swim" : "pace"); 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("" "" "" "" "" "" "" ""); 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(""); } // finish off the html document summaryText += QString("" "
" "" "" "" "").arg(aggname); foreach(QString metricname, list) { const RideMetric *metric = RideMetricFactory::instance().rideMetric(metricname); // use getAggregate even if it's only 1 file to have consistent value treatment double d = item->getForSymbol(metricname, true); QString value; if (metric) { const_cast(metric)->setValue(d); value = metric->toString(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("") .arg(s) .arg(value); } else { summaryText += QString("") .arg(s) .arg(metric ? metric->units(context->athlete->useMetricUnits) : "unknown") .arg(value); } } summaryText += QString("
" "%1" "
%1: %2
%1(%2): %3
" "
" "" "" ""); return summaryText; } void LTMPopup::rideSelected() { // which ride is selected int index = 0; if (rides->selectedItems().count()) index = rides->selectedItems().first()->row(); // do we have any rides and is the index within bounds if (selected.count() > index) { RideItem *have = context->athlete->rideCache->getRide(selected[index]); if (have) { // update summary metrics->setText(""); //! stop crash (?) metrics->setText(setSummaryHTML(have)); notes->setText(""); //! stop crash (?) notes->setText(have->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(e))->pos()).row(); if (row >= 0) { QList 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; }