/* * 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 "TreeMapWindow.h" #include "LTMTool.h" #include "TreeMapPlot.h" #include "LTMSettings.h" #include "Context.h" #include "Context.h" #include "Athlete.h" #include "SummaryMetrics.h" #include "Settings.h" #include "math.h" #include "Units.h" // for MILES_PER_KM #include #include #include #include #include #include TreeMapWindow::TreeMapWindow(Context *context) : GcWindow(context), context(context), active(false), dirty(true), useCustom(false), useToToday(false) { // the plot mainLayout = new QVBoxLayout; ltmPlot = new TreeMapPlot(this, context); mainLayout->addWidget(ltmPlot); mainLayout->setSpacing(0); mainLayout->setContentsMargins(0,0,0,0); setLayout(mainLayout); // the controls QWidget *c = new QWidget; QFormLayout *cl = new QFormLayout(c); setControls(c); // read metadata.xml QString filename = context->athlete->home.absolutePath()+"/metadata.xml"; QString colorfield; if (!QFile(filename).exists()) filename = ":/xml/metadata.xml"; RideMetadata::readXML(filename, keywordDefinitions, fieldDefinitions, colorfield); //title = new QLabel(this); //QFont font; //font.setPointSize(14); //font.setWeight(QFont::Bold); //title->setFont(font); //title->setFixedHeight(20); //title->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter); // widgets // setup the popup widget popup = new GcPane(); ltmPopup = new LTMPopup(context); QVBoxLayout *popupLayout = new QVBoxLayout(); popupLayout->addWidget(ltmPopup); popup->setLayout(popupLayout); // controls field1 = new QComboBox(this); addTextFields(field1); field2 = new QComboBox(this); addTextFields(field2); cl->addRow(new QLabel(tr("First")), field1); cl->addRow(new QLabel(tr("Second")), field2); cl->addRow(new QLabel("")); // spacing // metric selector .. just ride metrics metricTree = new QTreeWidget; #ifdef Q_OS_MAC metricTree->setAttribute(Qt::WA_MacShowFocusRect, 0); #endif metricTree->setColumnCount(1); metricTree->setSelectionMode(QAbstractItemView::SingleSelection); metricTree->header()->hide(); //metricTree->setFrameStyle(QFrame::NoFrame); //metricTree->setAlternatingRowColors (true); metricTree->setIndentation(5); allMetrics = new QTreeWidgetItem(metricTree, ROOT_TYPE); allMetrics->setText(0, tr("Metric")); metricTree->setContextMenuPolicy(Qt::CustomContextMenu); // initialise the metrics catalogue and user selector const RideMetricFactory &factory = RideMetricFactory::instance(); for (int i = 0; i < factory.metricCount(); ++i) { QTreeWidgetItem *add; add = new QTreeWidgetItem(allMetrics, METRIC_TYPE); // I didn't define this API with name referring to a symbol in the factory // I know it is confusing, but changing it will mean changing it absolutely // everywhere. Just remember - in the factory "name" refers to symbol and // if you want the user friendly metric description you get it via the metric QTextEdit title(factory.rideMetric(factory.metricName(i))->name()); // to handle HTML add->setText(0, title.toPlainText()); // long name add->setText(1, factory.metricName(i)); // symbol (hidden) // by default use workout_time if (factory.metricName(i) == "workout_time") allMetrics->child(i)->setSelected(true); } metricTree->expandItem(allMetrics); cl->addRow(new QLabel(tr("Metric")), metricTree); dateSetting = new DateSettingsEdit(this); cl->addRow(new QLabel("")); // spacing cl->addRow(new QLabel(tr("Date range")), dateSetting); cl->addRow(new QLabel("")); // spacing // chart settings changed connect(this, SIGNAL(dateRangeChanged(DateRange)), this, SLOT(dateRangeChanged(DateRange))); connect(metricTree,SIGNAL(itemSelectionChanged()), this, SLOT(metricTreeWidgetSelectionChanged())); connect(field1, SIGNAL(currentIndexChanged(int)), this, SLOT(fieldSelected(int))); connect(field2, SIGNAL(currentIndexChanged(int)), this, SLOT(fieldSelected(int))); // config changes or ride file activities cause a redraw/refresh (but only if active) connect(this, SIGNAL(rideItemChanged(RideItem*)), this, SLOT(rideSelected())); connect(context, SIGNAL(rideAdded(RideItem*)), this, SLOT(refresh(void))); connect(context, SIGNAL(rideDeleted(RideItem*)), this, SLOT(refresh(void))); connect(context, SIGNAL(filterChanged()), this, SLOT(refresh(void))); connect(context, SIGNAL(homeFilterChanged()), this, SLOT(refresh(void))); connect(context, SIGNAL(configChanged()), this, SLOT(refresh())); // user clicked on a cell in the plot connect(ltmPlot, SIGNAL(clicked(QString,QString)), this, SLOT(cellClicked(QString,QString))); // date settings connect(dateSetting, SIGNAL(useCustomRange(DateRange)), this, SLOT(useCustomRange(DateRange))); connect(dateSetting, SIGNAL(useThruToday()), this, SLOT(useThruToday())); connect(dateSetting, SIGNAL(useStandardRange()), this, SLOT(useStandardRange())); // lets refresh / setup state refresh(); } TreeMapWindow::~TreeMapWindow() { delete popup; } void TreeMapWindow::rideSelected() { } void TreeMapWindow::refreshPlot() { ltmPlot->setData(&settings); } void TreeMapWindow::useCustomRange(DateRange range) { // plot using the supplied range useCustom = true; useToToday = false; custom = range; dateRangeChanged(custom); } void TreeMapWindow::useStandardRange() { useToToday = useCustom = false; dateRangeChanged(myDateRange); } void TreeMapWindow::useThruToday() { // plot using the supplied range useCustom = false; useToToday = true; custom = myDateRange; if (custom.to > QDate::currentDate()) custom.to = QDate::currentDate(); dateRangeChanged(custom); } // total redraw, reread data etc void TreeMapWindow::refresh() { if (!amVisible()) return; setProperty("color", GColor(CPLOTBACKGROUND)); // refresh for changes to ridefiles / zones if (active == false) { // setup settings to current user selection foreach(QTreeWidgetItem *metric, metricTree->selectedItems()) { if (metric->type() != ROOT_TYPE) { QString symbol = metric->text(1); settings.symbol = symbol; } } if (useCustom) { settings.from = custom.from; settings.to = custom.to; } else if (useToToday) { QDate today = QDate::currentDate(); settings.from = myDateRange.from; settings.to = myDateRange.to > today ? today : myDateRange.to; } else { settings.from = myDateRange.from; settings.to = myDateRange.to; } SpecialFields sp; settings.field1 = sp.internalName(field1->currentText()); settings.field2 = sp.internalName(field2->currentText()); settings.data = &results; // get the data results.clear(); // clear any old data results = context->athlete->metricDB->getAllMetricsFor(QDateTime(settings.from, QTime(0,0,0)), QDateTime(settings.to, QTime(0,0,0))); refreshPlot(); } repaint(); // get title repainted } void TreeMapWindow::metricTreeWidgetSelectionChanged() { refresh(); } void TreeMapWindow::dateRangeChanged(DateRange) { refresh(); } void TreeMapWindow::fieldSelected(int) { refresh(); } void TreeMapWindow::cellClicked(QString f1, QString f2) { QList cell; // create a list of activities in this cell int count = 0; foreach(SummaryMetrics x, results) { // text may either not exists, then "unknown" or just be "" but f1, f2 don't know "" QString x1 = x.getText(settings.field1, tr("(unknown)")); QString x2 = x.getText(settings.field2, tr("(unknown)")); if (x1 == "") x1 = tr("(unknown)"); if (x2 == "") x2 = tr("(unknown)"); // now we can compare and append if (x1 == f1 && x2 == f2) { cell.append(x); count++; } } const RideMetricFactory &factory = RideMetricFactory::instance(); const RideMetric *metric = factory.rideMetric(settings.symbol); ltmPopup->setData(cell, metric, QString(tr("%1 ride%2")).arg(count).arg(count == 1 ? "" : tr("s"))); popup->show(); } void TreeMapWindow::addTextFields(QComboBox *combo) { combo->addItem(tr("None")); // if "None" is changed to not being first any more, adjust public methods f1,f2,setf1,setf2 SpecialFields sp; foreach (FieldDefinition x, fieldDefinitions) { if (x.type < 4) combo->addItem(sp.displayName(x.name)); } }