From fc741fb9b41bb55d061fa79be42c063b0fb4d7ae Mon Sep 17 00:00:00 2001 From: Mark Liversedge Date: Tue, 10 May 2011 18:21:08 +0100 Subject: [PATCH] Make Training View Configurable The training view (aka realtime) is now configurable allowing users to drag and drop appropriate charts and dials onto the main view. The controls for this view are static and comprise the old controls with start/stop buttons, device selections etc. I have removed deprecated code too, the following are removed from the repository; * ViewSelection * RealtimeWindow * TrainWindow * TrainTabs Fixes #290. --- INSTALL-LINUX | 23 + src/ANTlocalController.cpp | 2 +- src/ANTlocalController.h | 2 +- src/ANTplusController.cpp | 2 +- src/ANTplusController.h | 2 +- src/ComputrainerController.cpp | 3 +- src/ComputrainerController.h | 3 +- src/ConfigDialog.cpp | 1 - src/ErgFilePlot.cpp | 2 +- src/GcWindowRegistry.cpp | 8 +- src/GcWindowRegistry.h | 1 - src/HomeWindow.cpp | 10 +- src/HomeWindow.h | 2 +- src/MainWindow.cpp | 25 +- src/MainWindow.h | 4 +- src/NullController.cpp | 2 +- src/NullController.h | 4 +- src/RealtimeController.cpp | 4 +- src/RealtimeController.h | 6 +- src/RealtimeWindow.cpp | 971 -------------------------------- src/RealtimeWindow.h | 204 ------- src/SimpleNetworkController.cpp | 2 +- src/SimpleNetworkController.h | 4 +- src/TrainTabs.cpp | 29 - src/TrainTabs.h | 53 -- src/TrainTool.cpp | 671 +++++++++++++++++++++- src/TrainTool.h | 116 +++- src/TrainWindow.cpp | 45 -- src/TrainWindow.h | 52 -- src/VideoWindow.cpp | 2 +- src/ViewSelection.cpp | 62 -- src/ViewSelection.h | 50 -- src/WorkoutPlotWindow.cpp | 1 + src/application.qrc | 1 + src/src.pro | 8 - src/xml/train-layout.xml | 93 +++ 36 files changed, 907 insertions(+), 1563 deletions(-) delete mode 100644 src/RealtimeWindow.cpp delete mode 100644 src/RealtimeWindow.h delete mode 100644 src/TrainTabs.cpp delete mode 100644 src/TrainTabs.h delete mode 100644 src/TrainWindow.cpp delete mode 100644 src/TrainWindow.h delete mode 100644 src/ViewSelection.cpp delete mode 100644 src/ViewSelection.h create mode 100644 src/xml/train-layout.xml diff --git a/INSTALL-LINUX b/INSTALL-LINUX index 442b7dcdc..ec71d0273 100644 --- a/INSTALL-LINUX +++ b/INSTALL-LINUX @@ -31,6 +31,7 @@ CONTENTS - flex - bison - libical - Diary window and CalDAV support (google/mobileme calendar integration) + - libvlc - Video playback in training mode 1. BASIC INSTALLATION WITH MANDATORY DEPENDENCIES @@ -87,6 +88,13 @@ and it should read like this (take off trailing /include): BOOST_INCLUDE=$${BOOST_INSTALL) +If you are building for your local host you may find that you get better performance if +compiling with gcc -O3 (tree vectorization can have a significat impact) + +If so you might like to uncomment: + +QMAKE_CXXFLAGS += -O3 + Save and exit $ cd .. @@ -358,3 +366,18 @@ NOTE: That upload to MobileMe and Google requires a functioning https lib in QT. upon the version installed this might not be the case and will need to be built and configured -- this is beyond the scope of this walkthough. Sorry. +LIBVLC - Video playback in Realtime (Experimental) +-------------------------------------------------- + +You will need libvlc 1.1.9 or higher (1.1.8 is ok but will segv on exit) +$ sudo apt-get install libvlc-dev + +$ vi gcconfig.pri + +Comment out VLC_INSTALL and it should read: + +VLC_INSTALL = /usr/include/vlc/ + +$ make clean +$ qmake +$ make diff --git a/src/ANTlocalController.cpp b/src/ANTlocalController.cpp index 1e6d1704b..aa7e17fea 100644 --- a/src/ANTlocalController.cpp +++ b/src/ANTlocalController.cpp @@ -22,7 +22,7 @@ #include "ANT.h" #include "RealtimeData.h" -ANTlocalController::ANTlocalController(RealtimeWindow *parent, DeviceConfiguration *dc) : RealtimeController(parent, dc) +ANTlocalController::ANTlocalController(TrainTool *parent, DeviceConfiguration *dc) : RealtimeController(parent, dc) { myANTlocal = new ANT (parent, dc); } diff --git a/src/ANTlocalController.h b/src/ANTlocalController.h index b64bc40d5..5d3d1c097 100644 --- a/src/ANTlocalController.h +++ b/src/ANTlocalController.h @@ -32,7 +32,7 @@ class ANTlocalController : public RealtimeController { public: - ANTlocalController (RealtimeWindow *parent =0, DeviceConfiguration *dc =0); + ANTlocalController (TrainTool *parent =0, DeviceConfiguration *dc =0); ANT *myANTlocal; // the device itself diff --git a/src/ANTplusController.cpp b/src/ANTplusController.cpp index 5a050d47e..c257a0e3f 100644 --- a/src/ANTplusController.cpp +++ b/src/ANTplusController.cpp @@ -21,7 +21,7 @@ #include "QuarqdClient.h" #include "RealtimeData.h" -ANTplusController::ANTplusController(RealtimeWindow *parent, DeviceConfiguration *dc) : RealtimeController(parent, dc) +ANTplusController::ANTplusController(TrainTool *parent, DeviceConfiguration *dc) : RealtimeController(parent, dc) { myANTplus = new QuarqdClient (parent, dc); } diff --git a/src/ANTplusController.h b/src/ANTplusController.h index 811063b37..7a7897b63 100644 --- a/src/ANTplusController.h +++ b/src/ANTplusController.h @@ -32,7 +32,7 @@ class ANTplusController : public RealtimeController { public: - ANTplusController (RealtimeWindow *parent =0, DeviceConfiguration *dc =0); + ANTplusController (TrainTool *parent =0, DeviceConfiguration *dc =0); QuarqdClient *myANTplus; // the device itself diff --git a/src/ComputrainerController.cpp b/src/ComputrainerController.cpp index 3c9393790..3362387e7 100644 --- a/src/ComputrainerController.cpp +++ b/src/ComputrainerController.cpp @@ -20,7 +20,7 @@ #include "Computrainer.h" #include "RealtimeData.h" -ComputrainerController::ComputrainerController(RealtimeWindow *parent, DeviceConfiguration *dc) : RealtimeController(parent, dc) +ComputrainerController::ComputrainerController(TrainTool *parent, DeviceConfiguration *dc) : RealtimeController(parent, dc) { myComputrainer = new Computrainer (parent, dc->portSpec); } @@ -77,7 +77,6 @@ ComputrainerController::getRealtimeData(RealtimeData &rtData) if(!myComputrainer->isRunning()) { - parent->Stop(); QMessageBox msgBox; msgBox.setText("Cannot Connect to Computrainer"); msgBox.setIcon(QMessageBox::Critical); diff --git a/src/ComputrainerController.h b/src/ComputrainerController.h index 483b7e3b8..29829ae72 100644 --- a/src/ComputrainerController.h +++ b/src/ComputrainerController.h @@ -22,7 +22,6 @@ // Abstract base class for Realtime device controllers - #ifndef _GC_ComputrainerController_h #define _GC_ComputrainerController_h 1 @@ -30,7 +29,7 @@ class ComputrainerController : public RealtimeController { public: - ComputrainerController (RealtimeWindow *, DeviceConfiguration *); + ComputrainerController (TrainTool *, DeviceConfiguration *); Computrainer *myComputrainer; // the device itself diff --git a/src/ConfigDialog.cpp b/src/ConfigDialog.cpp index 0fbc1f892..64b370daf 100644 --- a/src/ConfigDialog.cpp +++ b/src/ConfigDialog.cpp @@ -4,7 +4,6 @@ #include "MainWindow.h" #include "ConfigDialog.h" -#include "RealtimeWindow.h" #include "Pages.h" #include "Settings.h" #include "Zones.h" diff --git a/src/ErgFilePlot.cpp b/src/ErgFilePlot.cpp index b7b7cbacf..a20b0c838 100644 --- a/src/ErgFilePlot.cpp +++ b/src/ErgFilePlot.cpp @@ -82,7 +82,7 @@ ErgFilePlot::setData(ErgFile *ergfile) // set up again ergFile = ergfile; - setTitle(ergFile->Name); + //setTitle(ergFile->Name); courseData = &ergfile->Points; MaxWatts = ergfile->MaxWatts; diff --git a/src/GcWindowRegistry.cpp b/src/GcWindowRegistry.cpp index 5c43a076b..4a3ce1e1c 100644 --- a/src/GcWindowRegistry.cpp +++ b/src/GcWindowRegistry.cpp @@ -39,13 +39,11 @@ #include "PfPvWindow.h" #include "HrPwWindow.h" #include "RaceWindow.h" // XXX not done -#include "RealtimeWindow.h" #include "RideEditor.h" #include "RideSummaryWindow.h" #include "ScatterWindow.h" #include "SummaryWindow.h" #include "MetadataWindow.h" -#include "TrainWindow.h" // XXX not done #include "TreeMapWindow.h" #include "WeeklySummaryWindow.h" #include "DialWindow.h" @@ -65,16 +63,15 @@ GcWindowRegistry GcWindows[] = { { "Performance Manager", GcWindowTypes::PerformanceManager }, { "PfPv", GcWindowTypes::PfPv }, { "HrPw", GcWindowTypes::HrPw }, - { "Training", GcWindowTypes::Training }, { "Ride Editor", GcWindowTypes::RideEditor }, { "Ride Summary", GcWindowTypes::RideSummary }, - { "Scatter", GcWindowTypes::Scatter }, + { "Metadata Fields", GcWindowTypes::MetadataWindow }, { "Ride Summary & Fields", GcWindowTypes::Summary }, + { "Scatter", GcWindowTypes::Scatter }, { "Treemap", GcWindowTypes::TreeMap }, { "Weekly Summary", GcWindowTypes::WeeklySummary }, { "Video Player", GcWindowTypes::VideoPlayer }, { "Realtime Dial", GcWindowTypes::DialWindow }, - { "Metadata Fields", GcWindowTypes::MetadataWindow }, { "Realtime Plot", GcWindowTypes::RealtimePlot }, { "Workout Plot", GcWindowTypes::WorkoutPlot }, { "", GcWindowTypes::None }}; @@ -105,7 +102,6 @@ GcWindowRegistry::newGcWindow(GcWinID id, MainWindow *main) //XXX mainWindow wil case GcWindowTypes::PerformanceManager: returning = new PerformanceManagerWindow(main); break; case GcWindowTypes::PfPv: returning = new PfPvWindow(main); break; case GcWindowTypes::HrPw: returning = new HrPwWindow(main); break; - case GcWindowTypes::Training: returning = new TrainWindow(main, main->home); break; case GcWindowTypes::RideEditor: returning = new RideEditor(main); break; case GcWindowTypes::RideSummary: returning = new RideSummaryWindow(main); break; case GcWindowTypes::Scatter: returning = new ScatterWindow(main, main->home); break; diff --git a/src/GcWindowRegistry.h b/src/GcWindowRegistry.h index 34536edba..f34c525bb 100644 --- a/src/GcWindowRegistry.h +++ b/src/GcWindowRegistry.h @@ -38,7 +38,6 @@ enum gcwinid { PerformanceManager =9, PfPv =10, Race =11, - Training =12, RideEditor =13, RideSummary =14, Scatter =15, diff --git a/src/HomeWindow.cpp b/src/HomeWindow.cpp index 05c750825..911ca1f02 100644 --- a/src/HomeWindow.cpp +++ b/src/HomeWindow.cpp @@ -20,7 +20,7 @@ #include "HomeWindow.h" #include "LTMSettings.h" -HomeWindow::HomeWindow(MainWindow *mainWindow, QString name) : +HomeWindow::HomeWindow(MainWindow *mainWindow, QString name, QString windowtitle) : GcWindow(mainWindow), mainWindow(mainWindow), name(name), active(false), clicked(NULL), dropPending(false), chartCursor(-2), loaded(false) { @@ -36,7 +36,7 @@ HomeWindow::HomeWindow(MainWindow *mainWindow, QString name) : bigandbold.setWeight(QFont::Bold); QHBoxLayout *titleBar = new QHBoxLayout; - title = new QLabel(" Home", this); + title = new QLabel(windowtitle, this); title->setFont(bigandbold); QPalette mypalette; mypalette.setColor(title->foregroundRole(), Qt::white); @@ -1105,6 +1105,12 @@ HomeWindow::restoreState() { // restore window state QString filename = mainWindow->home.absolutePath() + "/" + name + "-layout.xml"; + QFileInfo finfo(filename); + + // use a default if not there + if (!finfo.exists()) filename = QString(":xml/%1-layout.xml").arg(name); + + // now go read... QFile file(filename); // setup the handler diff --git a/src/HomeWindow.h b/src/HomeWindow.h index c8bd104e1..f3b2de311 100644 --- a/src/HomeWindow.h +++ b/src/HomeWindow.h @@ -39,7 +39,7 @@ class HomeWindow : public GcWindow public: - HomeWindow(MainWindow *, QString name); + HomeWindow(MainWindow *, QString name, QString title); ~HomeWindow(); //int view() const { return viewMode->currentIndex(); } diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index f7056f8fa..7a277379b 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -43,7 +43,6 @@ #include "ManualRideDialog.h" #include "HistogramWindow.h" #include "ModelWindow.h" -#include "RealtimeWindow.h" #include "RideItem.h" #include "IntervalItem.h" #include "IntervalSummaryWindow.h" @@ -79,7 +78,6 @@ #include "MetricAggregator.h" #include "SplitRideDialog.h" #include "PerformanceManagerWindow.h" -#include "TrainWindow.h" #include "TreeMapWindow.h" #include "TwitterDialog.h" #include "WithingsDownload.h" @@ -193,9 +191,9 @@ MainWindow::MainWindow(const QDir &home) : toolbar->setFloatable(false); toolbar->setIconSize(QSize(32,32)); #ifndef Q_OS_MAC - //toolbar->setAutoFillBackground(false); //toolbar->setStyleSheet("background-image: url(\":/images/aluLight.jpg\"); border: 0px;"); toolbar->setContentsMargins(0,0,0,0); + toolbar->setAutoFillBackground(true); #else QIcon tickIcon(":images/toolbar/main/tick.png"); QPushButton *showControls = new QPushButton(tickIcon, "", this); @@ -382,12 +380,13 @@ MainWindow::MainWindow(const QDir &home) : homeControls->setContentsMargins(0,0,0,0); masterControls->addWidget(homeControls); - homeWindow = new HomeWindow(this, "home"); + homeWindow = new HomeWindow(this, "home", "Home"); homeControls->addWidget(homeWindow->controls()); homeControls->setCurrentIndex(0); + // setup trainWindow - trainWindow = new TrainWindow(this, home); - trainControls->addWidget(trainWindow->controls()); + trainWindow = new HomeWindow(this, "train", "Training"); + trainControls->addWidget(new TrainTool(this, this->home)); // ToolBox has one thing at the mo... will change soon toolBox = new QToolBox(this); @@ -754,18 +753,6 @@ MainWindow::tabViewTriggered(bool) appsettings->setValue(GC_TABS_TO_HIDE, tabsToHide.join(",")); } -void -MainWindow::selectView(int view) -{ - if (view == VIEW_ANALYSIS) - views->setCurrentIndex(0); // set stacked widget to Analysis - else if (view == VIEW_TRAIN) - views->setCurrentIndex(1); // set stacked widget to Train - - // notify with a signal - viewChanged(view); -} - void MainWindow::dragEnterEvent(QDragEnterEvent *event) { @@ -1509,6 +1496,7 @@ MainWindow::closeEvent(QCloseEvent* event) // save the state of all the pages homeWindow->saveState(); + trainWindow->saveState(); // clear the clipboard if neccessary QApplication::clipboard()->setText(""); @@ -1881,6 +1869,7 @@ MainWindow::selectTrain() { masterControls->setCurrentIndex(1); views->setCurrentIndex(1); + trainWindow->selected(); // tell it! } void diff --git a/src/MainWindow.h b/src/MainWindow.h index 682a1588f..4f986a1e1 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -152,7 +152,6 @@ class MainWindow : public QMainWindow void zonesChanged(); void seasonsChanged(); void configChanged(); - void viewChanged(int); void rideAdded(RideItem *); void rideDeleted(RideItem *); void rideDirty(); @@ -261,7 +260,6 @@ class MainWindow : public QMainWindow IntervalItem *activeInterval; // currently active for context menu popup RideItem *activeRide; // currently active for context menu popup - ViewSelection *viewSelection; QStackedWidget *views; // each view has its own controls XXX more to come @@ -286,7 +284,7 @@ class MainWindow : public QMainWindow SummaryWindow *summaryWindow; DiaryWindow *diaryWindow; HomeWindow *homeWindow; - TrainWindow *trainWindow; + HomeWindow *trainWindow; AllPlotWindow *allPlotWindow; HistogramWindow *histogramWindow; WeeklySummaryWindow *weeklySummaryWindow; diff --git a/src/NullController.cpp b/src/NullController.cpp index c4e44e70d..2582391da 100644 --- a/src/NullController.cpp +++ b/src/NullController.cpp @@ -23,7 +23,7 @@ #include "RaceDispatcher.h" #include "RealtimeData.h" -NullController::NullController(RealtimeWindow *parent, +NullController::NullController(TrainTool *parent, DeviceConfiguration *) : RealtimeController(parent), parent(parent), load(100) { diff --git a/src/NullController.h b/src/NullController.h index e8872ae08..659b0f267 100644 --- a/src/NullController.h +++ b/src/NullController.h @@ -34,11 +34,11 @@ class NullController : public RealtimeController { public: - RealtimeWindow *parent; + TrainTool *parent; // hostname and port are the hostname/port of the server to which // this NullControlller should connect. - NullController(RealtimeWindow *parent, + NullController(TrainTool *parent, DeviceConfiguration *dc); ~NullController() { } diff --git a/src/RealtimeController.cpp b/src/RealtimeController.cpp index 58f212103..804efe6f6 100644 --- a/src/RealtimeController.cpp +++ b/src/RealtimeController.cpp @@ -17,13 +17,13 @@ */ #include "RealtimeController.h" -#include "RealtimeWindow.h" +#include "TrainTool.h" #include "RealtimeData.h" #include "Units.h" // Abstract base class for Realtime device controllers -RealtimeController::RealtimeController(RealtimeWindow *parent, DeviceConfiguration *dc) : parent(parent), dc(dc) +RealtimeController::RealtimeController(TrainTool *parent, DeviceConfiguration *dc) : parent(parent), dc(dc) { if (dc != NULL) { diff --git a/src/RealtimeController.h b/src/RealtimeController.h index 00a8d5b9b..fe7657ae6 100644 --- a/src/RealtimeController.h +++ b/src/RealtimeController.h @@ -19,7 +19,7 @@ // Abstract base class for Realtime device controllers #include "RealtimeData.h" -#include "RealtimeWindow.h" +#include "TrainTool.h" #ifndef _GC_RealtimeController_h #define _GC_RealtimeController_h 1 @@ -31,9 +31,9 @@ class RealtimeController { public: - RealtimeWindow *parent; // for push devices + TrainTool *parent; // for push devices - RealtimeController (RealtimeWindow *parent, DeviceConfiguration *dc = 0); + RealtimeController (TrainTool *parent, DeviceConfiguration *dc = 0); virtual ~RealtimeController() {} virtual int start(); diff --git a/src/RealtimeWindow.cpp b/src/RealtimeWindow.cpp deleted file mode 100644 index 982c5df7b..000000000 --- a/src/RealtimeWindow.cpp +++ /dev/null @@ -1,971 +0,0 @@ -/* - * Copyright (c) 2009 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 "MainWindow.h" -#include "RealtimeWindow.h" -#include "RealtimeData.h" -#include "RealtimePlot.h" -#include "math.h" // for round() -#include "Units.h" // for MILES_PER_KM -#include - -// user selections for device/server/workout here -#include "TrainTool.h" - -// Three current realtime device types supported are: -#include "RealtimeController.h" -#include "ComputrainerController.h" -#include "ANTplusController.h" -#include "ANTlocalController.h" -#include "NullController.h" -#include "ErgFile.h" - -// And connect to server is via a GoldenServer object -#include "GoldenClient.h" - - -void -RealtimeWindow::configUpdate() -{ - - FTP=285; // default to 285 if zones are not set - int range = main->zones()->whichRange(QDate::currentDate()); - if (range != -1) FTP = main->zones()->getCP(range); - - // metric or imperial changed? - QVariant unit = appsettings->value(this, GC_UNIT); - useMetricUnits = (unit.toString() == "Metric"); - - // set labels accordingly - distanceLabel->setText(useMetricUnits ? tr("Distance (KM)") : tr("Distance (Miles)")); - speedLabel->setText(useMetricUnits ? tr("KPH") : tr("MPH")); - avgspeedLabel->setText(useMetricUnits ? tr("Avg KPH") : tr("Avg MPH")); - - // devices - DeviceConfigurations all; - Devices.clear(); - Devices = all.getList(); -} - -RealtimeWindow::RealtimeWindow(MainWindow *parent, TrainTool *trainTool, const QDir &home) : GcWindow(parent) -{ - - setInstanceName("Solo Ride Window"); - setControls(NULL); - - // set home - this->home = home; - this->trainTool = trainTool; - main = parent; - deviceController = NULL; - streamController = NULL; - ergFile = NULL; - - // metric or imperial? - QVariant unit = appsettings->value(this, GC_UNIT); - useMetricUnits = (unit.toString() == "Metric"); - - // main layout for the window - main_layout = new QVBoxLayout(this); - timer_layout = new QGridLayout(); - - // BUTTONS AND LCDS - button_layout = new QHBoxLayout(); - option_layout = new QHBoxLayout(); - controls_layout = new QVBoxLayout(); - - // handle config changes - connect(main, SIGNAL(configChanged()), this, SLOT(configUpdate())); - - connect(trainTool, SIGNAL(workoutSelected()), this, SLOT(SelectWorkout())); - - // connect train tool buttons! - connect(trainTool, SIGNAL(start()), this, SLOT(Start())); - connect(trainTool, SIGNAL(pause()), this, SLOT(Pause())); - connect(trainTool, SIGNAL(stop()), this, SLOT(Stop())); - - powerLabel = new QLabel(tr("WATTS"), this); - powerLabel->setAlignment(Qt::AlignHCenter | Qt::AlignBottom); - heartrateLabel = new QLabel(tr("BPM"), this); - heartrateLabel->setAlignment(Qt::AlignHCenter | Qt::AlignBottom); - speedLabel = new QLabel(useMetricUnits ? tr("KPH") : tr("MPH"), this); - speedLabel->setAlignment(Qt::AlignHCenter | Qt::AlignBottom); - cadenceLabel = new QLabel(tr("RPM"), this); - cadenceLabel->setAlignment(Qt::AlignHCenter | Qt::AlignBottom); - lapLabel = new QLabel(tr("Lap/Interval"), this); - lapLabel->setAlignment(Qt::AlignHCenter | Qt::AlignBottom); - loadLabel = new QLabel(tr("Load WATTS"), this); - loadLabel->setAlignment(Qt::AlignHCenter | Qt::AlignBottom); - distanceLabel = new QLabel(useMetricUnits ? tr("Distance (KM)") : tr("Distance (Miles)"), this); - distanceLabel->setAlignment(Qt::AlignHCenter | Qt::AlignBottom); - kjouleLabel = new QLabel(tr("kJoules"),this); - kjouleLabel->setAlignment(Qt::AlignHCenter | Qt::AlignBottom); - - - avgpowerLabel = new QLabel(tr("Avg WATTS"), this); - avgpowerLabel->setAlignment(Qt::AlignHCenter | Qt::AlignBottom); - avgheartrateLabel = new QLabel(tr("Avg BPM"), this); - avgheartrateLabel->setAlignment(Qt::AlignHCenter | Qt::AlignBottom); - avgspeedLabel = new QLabel(useMetricUnits ? tr("Avg KPH") : tr("Avg MPH"), this); - avgspeedLabel->setAlignment(Qt::AlignHCenter | Qt::AlignBottom); - avgcadenceLabel = new QLabel(tr("Avg RPM"), this); - avgcadenceLabel->setAlignment(Qt::AlignHCenter | Qt::AlignBottom); - xpowerLabel = new QLabel(tr("xPower"), this); - xpowerLabel->setAlignment(Qt::AlignHCenter | Qt::AlignBottom); - bikescoreLabel = new QLabel(tr("BikeScore"),this); - bikescoreLabel->setAlignment(Qt::AlignHCenter | Qt::AlignBottom); - - laptimeLabel = new QLabel(tr("LAP TIME"), this); - laptimeLabel->setAlignment(Qt::AlignHCenter | Qt::AlignBottom); - timeLabel = new QLabel(tr("TIME"), this); - timeLabel->setAlignment(Qt::AlignHCenter | Qt::AlignBottom); - - powerLCD = new QLCDNumber(this); powerLCD->setSegmentStyle(QLCDNumber::Filled); - heartrateLCD = new QLCDNumber(this); heartrateLCD->setSegmentStyle(QLCDNumber::Filled); - speedLCD = new QLCDNumber(this); speedLCD->setSegmentStyle(QLCDNumber::Filled); - cadenceLCD = new QLCDNumber(this); cadenceLCD->setSegmentStyle(QLCDNumber::Filled); - lapLCD = new QLCDNumber(this); lapLCD->setSegmentStyle(QLCDNumber::Filled); - loadLCD = new QLCDNumber(this); loadLCD->setSegmentStyle(QLCDNumber::Filled); - distanceLCD = new QLCDNumber(this); distanceLCD->setSegmentStyle(QLCDNumber::Filled); - distanceLCD->setNumDigits(9); // just to be same size as timers! - kjouleLCD = new QLCDNumber(this); distanceLCD->setSegmentStyle(QLCDNumber::Filled); - bikescoreLCD = new QLCDNumber(this); distanceLCD->setSegmentStyle(QLCDNumber::Filled); - - avgpowerLCD = new QLCDNumber(this); avgpowerLCD->setSegmentStyle(QLCDNumber::Filled); - avgheartrateLCD = new QLCDNumber(this); avgheartrateLCD->setSegmentStyle(QLCDNumber::Filled); - avgspeedLCD = new QLCDNumber(this); avgspeedLCD->setSegmentStyle(QLCDNumber::Filled); - avgcadenceLCD = new QLCDNumber(this); avgcadenceLCD->setSegmentStyle(QLCDNumber::Filled); - xpowerLCD = new QLCDNumber(this); xpowerLCD->setSegmentStyle(QLCDNumber::Filled); - - laptimeLCD = new QLCDNumber(this); laptimeLCD->setSegmentStyle(QLCDNumber::Filled); - laptimeLCD->setNumDigits(9); - timeLCD = new QLCDNumber(this); timeLCD->setSegmentStyle(QLCDNumber::Filled); - timeLCD->setNumDigits(9); - - // 4 x 6 grid. - int row = 0; - int colm = 0; - gridLayout = new QGridLayout(); - - gridLayout->addWidget(powerLabel, row, colm); - gridLayout->addWidget(powerLCD, row+1, colm); - - colm++; - gridLayout->addWidget(cadenceLabel, row, colm); - gridLayout->addWidget(cadenceLCD, row+1, colm); - - colm++; - gridLayout->addWidget(heartrateLabel, row, colm); - gridLayout->addWidget(heartrateLCD, row+1, colm); - - colm++; - gridLayout->addWidget(speedLabel, row, colm); - gridLayout->addWidget(speedLCD, row+1, colm); - - colm++; - gridLayout->addWidget(lapLabel, row, colm); - gridLayout->addWidget(lapLCD, row+1, colm); - - colm++; - gridLayout->addWidget(kjouleLabel,row,colm); - gridLayout->addWidget(kjouleLCD, row +1,colm); - - // end of first row of data - colm = 0; - row += 2; - gridLayout->addWidget(avgpowerLabel, row, colm); - gridLayout->addWidget(avgpowerLCD, row+1, colm); - - colm++; - gridLayout->addWidget(avgcadenceLabel, row, colm); - gridLayout->addWidget(avgcadenceLCD, row+1, colm); - - colm++; - gridLayout->addWidget(avgheartrateLabel, row, colm); - gridLayout->addWidget(avgheartrateLCD, row+1, colm); - - colm++; - gridLayout->addWidget(avgspeedLabel, row, colm); - gridLayout->addWidget(avgspeedLCD, row+1, colm); - - colm++; - gridLayout->addWidget(xpowerLabel, row, colm); - gridLayout->addWidget(xpowerLCD, row+1, colm); - - colm++; - gridLayout->addWidget(bikescoreLabel, row, colm); - gridLayout->addWidget(bikescoreLCD, row+1, colm); - - colm++; - gridLayout->addWidget(loadLabel, row, colm); - gridLayout->addWidget(loadLCD, row+1, colm); - - gridLayout->setRowStretch(1, 3); - gridLayout->setRowStretch(3, 3); - - // timers etc - timer_layout->addWidget(timeLabel, 0, 0); - timer_layout->addWidget(distanceLabel, 0, 1); - timer_layout->addWidget(laptimeLabel, 0, 2); - timer_layout->addWidget(timeLCD, 1, 0); - timer_layout->addWidget(distanceLCD, 1, 1); - timer_layout->addWidget(laptimeLCD, 1, 2); - timer_layout->setRowStretch(0, 1); - timer_layout->setRowStretch(1, 4); - - // REALTIME PLOT - rtPlot = new RealtimePlot(); - connect(main, SIGNAL(configChanged()), rtPlot, SLOT(configChanged())); - - // COURSE PLOT - ergPlot = new ErgFilePlot(0); - ergPlot->setVisible(false); - - // LAYOUT - - main_layout->addWidget(ergPlot); - main_layout->addItem(timer_layout); - main_layout->addItem(gridLayout); - main_layout->addWidget(rtPlot); - - displaymode=2; - main_layout->setStretch(0,1); - main_layout->setStretch(1,1); - main_layout->setStretch(2,2); - main_layout->setStretch(3, displaymode); - - - // now the GUI is setup lets sort our control variables - gui_timer = new QTimer(this); - disk_timer = new QTimer(this); - stream_timer = new QTimer(this); - load_timer = new QTimer(this); - metrics_timer = new QTimer(this); - - session_time = QTime(); - session_elapsed_msec = 0; - lap_time = QTime(); - lap_elapsed_msec = 0; - - recordFile = NULL; - status = 0; - status |= RT_MODE_ERGO; // ergo mode by default - displayWorkoutLap = displayLap = 0; - pwrcount = 0; - cadcount = 0; - hrcount = 0; - spdcount = 0; - lodcount = 0; - load_msecs = total_msecs = lap_msecs = 0; - displayWorkoutDistance = displayDistance = displayPower = displayHeartRate = - displaySpeed = displayCadence = displayGradient = displayLoad = 0; - avgPower= avgHeartRate= avgSpeed= avgCadence= avgLoad= 0; - - rideFile = boost::shared_ptr(new RideFile(QDateTime::currentDateTime(),1)); - - connect(gui_timer, SIGNAL(timeout()), this, SLOT(guiUpdate())); - connect(disk_timer, SIGNAL(timeout()), this, SLOT(diskUpdate())); - connect(stream_timer, SIGNAL(timeout()), this, SLOT(streamUpdate())); - connect(load_timer, SIGNAL(timeout()), this, SLOT(loadUpdate())); - connect(metrics_timer, SIGNAL(timeout()), this, SLOT(metricsUpdate())); - - - configUpdate(); // apply current config - - rtPlot->replot(); -} - -void RealtimeWindow::setDeviceController() -{ - int deviceno = trainTool->selectedDeviceNumber(); - - if (deviceno == -1) // not selected, maybe they are spectating - return; - - // zap the current one - if (deviceController != NULL) { - delete deviceController; - deviceController = NULL; - } - - if (Devices.count() > 0) { - DeviceConfiguration temp = Devices.at(deviceno); - if (Devices.at(deviceno).type == DEV_ANTPLUS) { - deviceController = new ANTplusController(this, &temp); - } else if (Devices.at(deviceno).type == DEV_CT) { - deviceController = new ComputrainerController(this, &temp); - } else if (Devices.at(deviceno).type == DEV_NULL) { - deviceController = new NullController(this, &temp); - } else if (Devices.at(deviceno).type == DEV_ANTLOCAL) { - deviceController = new ANTlocalController(this, &temp); - } - } - -} - -// open a connection to the GoldenServer via a GoldenClient -void RealtimeWindow::setStreamController() -{ - int deviceno = trainTool->selectedServerNumber(); - - if (deviceno == -1) return; - - // zap the current one - if (streamController != NULL) { - delete streamController; - streamController = NULL; - } - - if (Devices.count() > 0) { - DeviceConfiguration config = Devices.at(deviceno); - streamController = new GoldenClient; - - // connect - QStringList speclist = config.portSpec.split(":", QString::SkipEmptyParts); - bool rc = streamController->connect(speclist[0], // host - speclist[1].toInt(), // port - "9cf638294030cea7b1590a4ca32e7f58", // raceid - appsettings->cvalue(main->cyclist, GC_NICKNAME).toString(), // name - FTP, // CP60 - appsettings->cvalue(main->cyclist, GC_WEIGHT).toDouble()); // weight - - // no connection - if (rc == false) { - streamController->closeAndExit(); - streamController = NULL; - status &= ~RT_STREAMING; - QMessageBox msgBox; - msgBox.setText(QString(tr("Cannot Connect to Server %1 on port %2").arg(speclist[0]).arg(speclist[1]))); - msgBox.setIcon(QMessageBox::Critical); - msgBox.exec(); - } - } -} - -void RealtimeWindow::Start() // when start button is pressed -{ - - if (status&RT_RUNNING) { - newLap(); - } else { - - // open the controller if it is selected - setDeviceController(); - if (deviceController == NULL) return; - else deviceController->start(); // start device - - // we're away! - status |=RT_RUNNING; - - // should we be streaming too? - setStreamController(); - if (streamController != NULL) status |= RT_STREAMING; - - trainTool->setStartText(tr("Lap/Interval")); - - load_period.restart(); - session_time.start(); - session_elapsed_msec = 0; - lap_time.start(); - lap_elapsed_msec = 0; - - if (status & RT_WORKOUT) { - load_timer->start(LOADRATE); // start recording - } - - if (trainTool->recordSelector->isChecked()) { - status |= RT_RECORDING; - } - - if (status & RT_RECORDING) { - QDateTime now = QDateTime::currentDateTime(); - - // setup file - QString filename = now.toString(QString("yyyy_MM_dd_hh_mm_ss")) + QString(".csv"); - - QString fulltarget = home.absolutePath() + "/" + filename; - if (recordFile) delete recordFile; - recordFile = new QFile(fulltarget); - if (!recordFile->open(QFile::WriteOnly | QFile::Truncate)) { - status &= ~RT_RECORDING; - } else { - - // CSV File header - - QTextStream recordFileStream(recordFile); - recordFileStream << "Minutes,Torq (N-m),Km/h,Watts,Km,Cadence,Hrate,ID,Altitude (m)\n"; - disk_timer->start(SAMPLERATE); // start screen - } - } - - // create a new rideFile - rideFile = boost::shared_ptr(new RideFile(QDateTime::currentDateTime(),1)); - - - // stream - if (status & RT_STREAMING) { - stream_timer->start(STREAMRATE); - } - - gui_timer->start(REFRESHRATE); // start recording - metrics_timer->start(METRICSRATE); - - } -} - -void RealtimeWindow::Pause() // pause capture to recalibrate -{ - if (deviceController == NULL) return; - - // we're not running fool! - if ((status&RT_RUNNING) == 0) return; - - if (status&RT_PAUSED) { - session_time.start(); - lap_time.start(); - status &=~RT_PAUSED; - deviceController->restart(); - trainTool->setPauseText(tr("Pause")); - gui_timer->start(REFRESHRATE); - metrics_timer->start(METRICSRATE); - if (status & RT_STREAMING) stream_timer->start(STREAMRATE); - if (status & RT_RECORDING) disk_timer->start(SAMPLERATE); - load_period.restart(); - if (status & RT_WORKOUT) load_timer->start(LOADRATE); - } else { - session_elapsed_msec += session_time.elapsed(); - lap_elapsed_msec += lap_time.elapsed(); - deviceController->pause(); - trainTool->setPauseText(tr("Un-Pause")); - status |=RT_PAUSED; - gui_timer->stop(); - metrics_timer->stop(); - if (status & RT_STREAMING) stream_timer->stop(); - if (status & RT_RECORDING) disk_timer->stop(); - if (status & RT_WORKOUT) load_timer->stop(); - load_msecs += load_period.restart(); - } -} - -void RealtimeWindow::Stop(int deviceStatus) // when stop button is pressed -{ - if (deviceController == NULL) return; - - if ((status&RT_RUNNING) == 0) return; - - status &= ~RT_RUNNING; - trainTool->setStartText(tr("Start")); - - // wipe connection - deviceController->stop(); - delete deviceController; - deviceController = NULL; - - gui_timer->stop(); - metrics_timer->stop(); - - QDateTime now = QDateTime::currentDateTime(); - - if (status & RT_RECORDING) { - disk_timer->stop(); - - // close and reset File - recordFile->close(); - - if(deviceStatus == DEVICE_ERROR) - { - recordFile->remove(); - } - else { - // add to the view - using basename ONLY - QString name; - name = recordFile->fileName(); - main->addRide(QFileInfo(name).fileName(), true); - } - } - - if (status & RT_STREAMING) { - stream_timer->stop(); - streamController->closeAndExit(); - delete streamController; - streamController = NULL; - } - - if (status & RT_WORKOUT) { - load_timer->stop(); - load_msecs = 0; - main->notifySetNow(load_msecs); - ergPlot->setNow(load_msecs); - ergPlot->replot(); - } - - // Re-enable gui elements - //recordSelector->setEnabled(true); - - // reset counters etc - pwrcount = 0; - cadcount = 0; - hrcount = 0; - spdcount = 0; - lodcount = 0; - displayWorkoutLap = displayLap =0; - session_elapsed_msec = 0; - session_time.restart(); - lap_elapsed_msec = 0; - lap_time.restart(); - avgPower= avgHeartRate= avgSpeed= avgCadence= avgLoad= 0; - displayWorkoutDistance = displayDistance = 0; - guiUpdate(); - - return; -} - - -// Called by push devices (e.g. ANT+) -void RealtimeWindow::updateData(RealtimeData &rtData) -{ - displayPower = rtData.getWatts(); - displayCadence = rtData.getCadence(); - displayHeartRate = rtData.getHr(); - displaySpeed = rtData.getSpeed(); - displayLoad = rtData.getLoad(); - // Gradient not supported - return; -} - -//---------------------------------------------------------------------- -// SCREEN UPDATE FUNCTIONS -//---------------------------------------------------------------------- - -void RealtimeWindow::guiUpdate() // refreshes the telemetry -{ - RealtimeData rtData; - - if (deviceController == NULL) return; - - // get latest telemetry from device (if it is a pull device e.g. Computrainer // - if (status&RT_RUNNING && deviceController->doesPull() == true) { - - deviceController->getRealtimeData(rtData); - - // Distance assumes current speed for the last second. from km/h to km/sec - displayDistance += displaySpeed / (5 * 3600); // XXX assumes 200ms refreshrate - displayWorkoutDistance += displaySpeed / (5 * 3600); // XXX assumes 200ms refreshrate - rtData.setDistance(displayDistance); - - // time - total_msecs = session_elapsed_msec + session_time.elapsed(); - lap_msecs = lap_elapsed_msec + lap_time.elapsed(); - rtData.setMsecs(total_msecs); - rtData.setLapMsecs(lap_msecs); - - // metrics - rtData.setJoules(kjoules); - rtData.setBikeScore(bikescore); - rtData.setXPower(xpower); - - // update those LCDs! - timeLCD->display(QString("%1:%2:%3.%4").arg(total_msecs/3600000) - .arg((total_msecs%3600000)/60000,2,10,QLatin1Char('0')) - .arg((total_msecs%60000)/1000,2,10,QLatin1Char('0')) - .arg((total_msecs%1000)/100)); - - laptimeLCD->display(QString("%1:%2:%3.%4").arg(lap_msecs/3600000,2) - .arg((lap_msecs%3600000)/60000,2,10,QLatin1Char('0')) - .arg((lap_msecs%60000)/1000,2,10,QLatin1Char('0')) - .arg((lap_msecs%1000)/100)); - - // local stuff ... - displayPower = rtData.getWatts(); - displayCadence = rtData.getCadence(); - displayHeartRate = rtData.getHr(); - displaySpeed = rtData.getSpeed(); - displayLoad = rtData.getLoad(); - - // go update the displays... - main->notifyTelemetryUpdate(rtData); // signal everyone to update telemetry - } - - // Cadence, HR and Power needs to be rounded to 0 decimal places - powerLCD->display(round(displayPower)); - double val = round(displaySpeed * (useMetricUnits ? 1.0 : MILES_PER_KM) * 10.00)/10.00; - speedLCD->display(QString::number(val, 'f', 1)); // always show 1 decimal point - cadenceLCD->display(round(displayCadence)); - heartrateLCD->display(round(displayHeartRate)); - lapLCD->display(displayWorkoutLap+displayLap); - - // load or gradient depending on mode we are running - if (status&RT_MODE_ERGO) - loadLCD->display(displayLoad); - else - { - val = round(displayGradient*10)/10.00; - loadLCD->display(QString::number(val, 'f', 1)); // always show 1 decimal point - } - - // distance - val = round(displayDistance*(useMetricUnits ? 1.0 : MILES_PER_KM) *10.00) /10.00; - distanceLCD->display(QString::number(val, 'f', 1)); // always show 1 decimal point - - // NZ Averages..... - if (displayPower) { //NZAP is bogus - make it configurable!!! - pwrcount++; if (pwrcount ==1) avgPower = displayPower; - avgPower = ((avgPower * (double)pwrcount) + displayPower) /(double) (pwrcount+1); - kjoules = avgPower * total_msecs /(1000*1000); - } - if (displayCadence) { - cadcount++; if (cadcount ==1) avgCadence = displayCadence; - avgCadence = ((avgCadence * (double)cadcount) + displayCadence) /(double) (cadcount+1); - } - if (displayHeartRate) { - hrcount++; if (hrcount ==1) avgHeartRate = displayHeartRate; - avgHeartRate = ((avgHeartRate * (double)hrcount) + displayHeartRate) /(double) (hrcount+1); - } - if (displaySpeed) { - spdcount++; if (spdcount ==1) avgSpeed = displaySpeed; - avgSpeed = ((avgSpeed * (double)spdcount) + displaySpeed) /(double) (spdcount+1); - } -#if 0 - if (displayLoad && status&RT_MODE_ERGO) { - lodcount++; if (lodcount ==1) avgLoad = displayLoad; - avgLoad = ((avgLoad * (double)lodcount) + displayLoad) /(double) (lodcount+1); - //avgloadLCD->display((int)avgLoad); - } -#endif - if (status&RT_MODE_SPIN) { - grdcount++; if (grdcount ==1) avgGradient = displayGradient; - avgGradient = ((avgGradient * (double)grdcount) + displayGradient) /(double) (grdcount+1); - // avgloadLCD->display((int)avgGradient); - } - - avgpowerLCD->display((int)avgPower); - val = round(avgSpeed * (useMetricUnits ? 1.0 : MILES_PER_KM) * 10.00)/10.00; - avgspeedLCD->display(QString::number(val, 'f', 1)); // always show 1 decimal point - avgcadenceLCD->display((int)avgCadence); - avgheartrateLCD->display((int)avgHeartRate); - kjouleLCD->display(round(kjoules)); - bikescoreLCD->display(round(bikescore)); - xpowerLCD->display(round(xpower)); - - // now that plot.... - rtPlot->pwrData.addData(displayPower); // add new data point - rtPlot->cadData.addData(displayCadence); // add new data point - rtPlot->spdData.addData(displaySpeed); // add new data point - rtPlot->hrData.addData(displayHeartRate); // add new data point - //rtPlot->lodData.addData(displayLoad); // add new Load point - rtPlot->replot(); // redraw - - this->update(); - -} - -// can be called from the controller - when user presses "Lap" button -void RealtimeWindow::newLap() -{ - displayLap++; - - pwrcount = 0; - cadcount = 0; - hrcount = 0; - spdcount = 0; - - lap_time.restart(); - lap_elapsed_msec = 0; - - // set avg to current values to ensure averages represent from now onwards - // and not from beginning of workout - avgPower = displayPower; - avgCadence = displayCadence; - avgHeartRate = displayHeartRate; - avgSpeed = displaySpeed; - avgLoad = displayLoad; -} - -// can be called from the controller -void RealtimeWindow::nextDisplayMode() -{ - if (displaymode == 8) displaymode=2; - else displaymode += 1; - main_layout->setStretch(3,displaymode); -} - -void RealtimeWindow::warnnoConfig() -{ - QMessageBox::warning(this, tr("No Devices Configured"), "Please configure a device in Preferences."); -} - -//---------------------------------------------------------------------- -// STREAMING FUNCTION -//---------------------------------------------------------------------- -#if 0 -RealtimeWindow::SelectStream(int index) -{ - - if (index > 0) { - status |= RT_STREAMING; - setStreamController(); - } else { - status &= ~RT_STREAMING; - } -} -#endif - -void -RealtimeWindow::streamUpdate() -{ - // send over the wire... - if (streamController) { - - // send my data - streamController->sendTelemetry(displayPower, - displayCadence, - displayDistance, - displayHeartRate, - displaySpeed); - - // get standings for everyone else - RaceStatus current = streamController->getStandings(); - - // send out to all the widgets... - trainTool->notifyRaceStandings(current); - - // has the race finished? - if (current.race_finished == true) { - Stop(); // all over dude - QMessageBox msgBox; - msgBox.setText(tr("Race Over!")); - msgBox.setIcon(QMessageBox::Information); - msgBox.exec(); - } - } -} - -//---------------------------------------------------------------------- -// DISK UPDATE FUNCTIONS -//---------------------------------------------------------------------- -void RealtimeWindow::diskUpdate() -{ - double Minutes; - - long Torq = 0, Altitude = 0; - QTextStream recordFileStream(recordFile); - - // convert from milliseconds to minutes - total_msecs = session_elapsed_msec + session_time.elapsed(); - Minutes = total_msecs; - Minutes /= 1000.00; - Minutes *= (1.0/60); - - // PowerAgent Format "Minutes,Torq (N-m),Km/h,Watts,Km,Cadence,Hrate,ID,Altitude (m)" - recordFileStream << Minutes - << "," << Torq - << "," << displaySpeed - << "," << displayPower - << "," << displayDistance - << "," << displayCadence - << "," << displayHeartRate - << "," << (displayLap + displayWorkoutLap) - << "," << Altitude - << "," << "\n"; - - rideFile->appendPoint(total_msecs/1000,displayCadence,displayHeartRate,displayDistance,displaySpeed,0, - displayPower,Altitude,0,0,0,displayLap + displayWorkoutLap); -} - -void RealtimeWindow::metricsUpdate() -{ - // calculate bike score, xpower - const RideMetricFactory &factory = RideMetricFactory::instance(); - const RideMetric *rm = factory.rideMetric("skiba_xpower"); - - QStringList metrics; - metrics.append("skiba_bike_score"); - metrics.append("skiba_xpower"); - QHash results = rm->computeMetrics( - this->main,&*rideFile,this->main->zones(),this->main->hrZones(),metrics); - bikescore = results["skiba_bike_score"]->value(true); - xpower = results["skiba_xpower"]->value(true); -} - -//---------------------------------------------------------------------- -// WORKOUT MODE -//---------------------------------------------------------------------- - -void -RealtimeWindow::SelectWorkout() -{ - int mode; - - // wip away the current selected workout - if (ergFile) { - delete ergFile; - ergFile = NULL; - } - - // which one is selected? - if (trainTool->currentWorkout() == NULL || trainTool->currentWorkout()->type() != WORKOUT_TYPE) return; - - // is it the auto mode? - int index = trainTool->workoutItems()->indexOfChild((QTreeWidgetItem *)trainTool->currentWorkout()); - if (index == 0) { - // ergo mode - mode = ERG; - status &= ~RT_WORKOUT; - ergPlot->setVisible(false); - } else if (index == 1) { - // slope mode - mode = CRS; - status &= ~RT_WORKOUT; - ergPlot->setVisible(false); - } else { - // workout mode - QVariant workoutDir = appsettings->value(this, GC_WORKOUTDIR); - QString fileName = workoutDir.toString() + "/" + trainTool->currentWorkout()->text(0); // filename - - // Get users CP for relative watts calculations - QDate today = QDate::currentDate(); - - ergFile = new ErgFile(fileName, mode, FTP); - if (ergFile->isValid()) { - - status |= RT_WORKOUT; - - // success! we have a load file - // setup the course profile in the - // display! - main->notifyErgFileSelected(ergFile); - ergPlot->setData(ergFile); - ergPlot->setVisible(true); - ergPlot->replot(); - } - } - - // set the device to the right mode - if (mode == ERG || mode == MRC) { - status |= RT_MODE_ERGO; - status &= ~RT_MODE_SPIN; - if (deviceController != NULL) deviceController->setMode(RT_MODE_ERGO); - // set the labels on the gui - loadLabel->setText("Load WATTS"); - } else { // SLOPE MODE - status |= RT_MODE_SPIN; - status &= ~RT_MODE_ERGO; - if (deviceController != NULL) deviceController->setMode(RT_MODE_SPIN); - // set the labels on the gui - loadLabel->setText("Gradient PERCENT"); - } -} - -void RealtimeWindow::loadUpdate() -{ - long load; - double gradient; - // the period between loadUpdate calls is not constant, and not exactly LOADRATE, - // therefore, use a QTime timer to measure the load period - load_msecs += load_period.restart(); - - if (deviceController == NULL) return; - - if (status&RT_MODE_ERGO) { - load = ergFile->wattsAt(load_msecs, displayWorkoutLap); - - // we got to the end! - if (load == -100) { - Stop(DEVICE_OK); - } else { - displayLoad = load; - deviceController->setLoad(displayLoad); - main->notifySetNow(load_msecs); - ergPlot->setNow(load_msecs); - } - } else { - gradient = ergFile->gradientAt(displayWorkoutDistance*1000, displayWorkoutLap); - - // we got to the end! - if (gradient == -100) { - Stop(DEVICE_OK); - } else { - displayGradient = gradient; - deviceController->setGradient(displayGradient); - main->notifySetNow(displayWorkoutDistance * 1000); - ergPlot->setNow(displayWorkoutDistance * 1000); // now is in meters we keep it in kilometers - } - } -} - -void RealtimeWindow::FFwd() -{ - if (status&RT_MODE_ERGO) load_msecs += 10000; // jump forward 10 seconds - else displayWorkoutDistance += 1; // jump forward a kilometer in the workout -} - -void RealtimeWindow::Rewind() -{ - if (status&RT_MODE_ERGO) { - load_msecs -=10000; // jump back 10 seconds - if (load_msecs < 0) load_msecs = 0; - } else { - displayWorkoutDistance -=1; // jump back a kilometer - if (displayWorkoutDistance < 0) displayWorkoutDistance = 0; - } -} - - -// jump to next Lap marker (if there is one?) -void RealtimeWindow::FFwdLap() -{ - double lapmarker; - - if (status&RT_MODE_ERGO) { - lapmarker = ergFile->nextLap(load_msecs); - if (lapmarker != -1) load_msecs = lapmarker; // jump forward to lapmarker - } else { - lapmarker = ergFile->nextLap(displayWorkoutDistance*1000); - if (lapmarker != -1) displayWorkoutDistance = lapmarker/1000; // jump forward to lapmarker - } -} - -// higher load/gradient -void RealtimeWindow::Higher() -{ - if (deviceController == NULL) return; - - if (status&RT_MODE_ERGO) displayLoad += 5; - else displayGradient += 0.1; - - if (displayLoad >1500) displayLoad = 1500; - if (displayGradient >15) displayGradient = 15; - - if (status&RT_MODE_ERGO) deviceController->setLoad(displayLoad); - else deviceController->setGradient(displayGradient); -} - -// higher load/gradient -void RealtimeWindow::Lower() -{ - if (deviceController == NULL) return; - - if (status&RT_MODE_ERGO) displayLoad -= 5; - else displayGradient -= 0.1; - - if (displayLoad <0) displayLoad = 0; - if (displayGradient <-10) displayGradient = -10; - - if (status&RT_MODE_ERGO) deviceController->setLoad(displayLoad); - else deviceController->setGradient(displayGradient); -} diff --git a/src/RealtimeWindow.h b/src/RealtimeWindow.h deleted file mode 100644 index a22960cc1..000000000 --- a/src/RealtimeWindow.h +++ /dev/null @@ -1,204 +0,0 @@ -/* - * Copyright (c) 2009 Sean C. Rhea (srhea@srhea.net) - * - * 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_RealtimeWindow_h -#define _GC_RealtimeWindow_h 1 -#include "GoldenCheetah.h" - -#include -#include -#include "MainWindow.h" -#include "DeviceConfiguration.h" -#include "DeviceTypes.h" -#include "ErgFile.h" -#include "ErgFilePlot.h" -#include "GoldenClient.h" - -class TrainTool; -class TrainTabs; - -// Status settings -#define RT_MODE_ERGO 0x0001 // load generation modes -#define RT_MODE_SPIN 0x0002 // spinscan like modes -#define RT_RUNNING 0x0100 // is running now -#define RT_PAUSED 0x0200 // is paused -#define RT_RECORDING 0x0400 // is recording to disk -#define RT_WORKOUT 0x0800 // is running a workout -#define RT_STREAMING 0x1000 // is streaming to a remote peer - -#define REFRESHRATE 200 // screen refresh in milliseconds -#define STREAMRATE 200 // rate at which we stream updates to remote peer -#define SAMPLERATE 1000 // disk update in milliseconds -#define LOADRATE 1000 // rate at which load is adjusted -#define METRICSRATE 1000 // rate the metrics are updated - - -class RealtimeController; -class RealtimePlot; -class RealtimeData; - -class RealtimeWindow : public GcWindow -{ - Q_OBJECT - G_OBJECT - - - public: - - RealtimeController *deviceController; // read from - GoldenClient *streamController; // send out to - - RealtimeWindow(MainWindow *, TrainTool *, const QDir &); - - void updateData(RealtimeData &); // to update telemetry by push devices - void newLap(); // start new Lap! - void nextDisplayMode(); // show next display mode - void setDeviceController(); // based upon selected device - void setStreamController(); // based upon selected device - - public slots: - - void Start(); // when start button is pressed - void Pause(); // when Paude is pressed - void Stop(int status=0); // when stop button is pressed - - void FFwd(); // jump forward when in a workout - void Rewind(); // jump backwards when in a workout - void FFwdLap(); // jump forward to next Lap marker - void Higher(); // set load/gradient higher - void Lower(); // set load/gradient higher - - void SelectWorkout(); - - // Timed actions - void guiUpdate(); // refreshes the telemetry - void diskUpdate(); // writes to CSV file - void streamUpdate(); // writes to remote Peer - void loadUpdate(); // sets Load on CT like devices - void metricsUpdate(); // calculates the metrics - - // Handle config updates - void configUpdate(); // called when config changes - - // When no config has been setup - void warnnoConfig(); - - protected: - - - // passed from MainWindow - QDir home; - MainWindow *main; - TrainTool *trainTool; - int FTP; // current FTP - - QList Devices; - bool useMetricUnits; - - // updated with a RealtimeData object either from - // update() - from a push device (quarqd ANT+) - // Device->getRealtimeData() - from a pull device (Computrainer) - double displayPower, displayHeartRate, displayCadence, - displayLoad, displayGradient, displaySpeed; - double displayDistance, displayWorkoutDistance; - int displayLap; // user increment for Lap - int displayWorkoutLap; // which Lap in the workout are we at? - double kjoules; - double bikescore; - double xpower; - - // for non-zero average calcs - int pwrcount, cadcount, hrcount, spdcount, lodcount, grdcount; // for NZ average calc - int status; - int displaymode; - - QFile *recordFile; // where we record! - ErgFile *ergFile; // workout file - boost::shared_ptr rideFile; // keeps track of the workout to figure out BikeScore - - long total_msecs, - lap_msecs, - load_msecs; - QTime load_period; - - uint session_elapsed_msec, lap_elapsed_msec; - QTime session_time, lap_time; - - // GUI WIDGETS - // layout - RealtimePlot *rtPlot; - ErgFilePlot *ergPlot; - - QVBoxLayout *main_layout; - - // labels - QLabel *powerLabel, - *heartrateLabel, - *speedLabel, - *cadenceLabel, - *loadLabel, - *lapLabel, - *laptimeLabel, - *timeLabel, - *distanceLabel, - *kjouleLabel, - *bikescoreLabel, - *xpowerLabel; - - double avgPower, avgHeartRate, avgSpeed, avgCadence, avgLoad, avgGradient; - QLabel *avgpowerLabel, - *avgheartrateLabel, - *avgspeedLabel, - *avgcadenceLabel; - - QHBoxLayout *button_layout, - *option_layout; - QGridLayout *timer_layout; - QVBoxLayout *controls_layout; - - QGridLayout *gridLayout; - - // the LCDs - QLCDNumber *powerLCD, - *heartrateLCD, - *speedLCD, - *cadenceLCD, - *loadLCD, - *lapLCD, - *laptimeLCD, - *timeLCD, - *distanceLCD, - *kjouleLCD, - *bikescoreLCD, - *xpowerLCD; - - QLCDNumber *avgpowerLCD, - *avgheartrateLCD, - *avgspeedLCD, - *avgcadenceLCD; - - QTimer *gui_timer, // refresh the gui - *stream_timer, // send telemetry to server - *load_timer, // change the load on the device - *disk_timer, // write to .CSV file - *metrics_timer; // computational intense metrics - -}; - -#endif // _GC_RealtimeWindow_h - diff --git a/src/SimpleNetworkController.cpp b/src/SimpleNetworkController.cpp index 32173088e..d644142c9 100644 --- a/src/SimpleNetworkController.cpp +++ b/src/SimpleNetworkController.cpp @@ -22,7 +22,7 @@ #include "SimpleNetworkController.h" #include "RealtimeData.h" -SimpleNetworkController::SimpleNetworkController(RealtimeWindow *parent, +SimpleNetworkController::SimpleNetworkController(TrainTool *parent, DeviceConfiguration *dc) : RealtimeController(parent), parent(parent), client(parent, dc), state(DISCONNECTED) { diff --git a/src/SimpleNetworkController.h b/src/SimpleNetworkController.h index 45bb078ee..307dcadf5 100644 --- a/src/SimpleNetworkController.h +++ b/src/SimpleNetworkController.h @@ -44,11 +44,11 @@ class SimpleNetworkController : public RealtimeController { public: - RealtimeWindow *parent; + TrainTool *parent; // hostname and port are the hostname/port of the server to which // this SimpleNetworkControlller should connect. - SimpleNetworkController(RealtimeWindow *parent, + SimpleNetworkController(TrainTool *parent, DeviceConfiguration *dc); ~SimpleNetworkController() { } diff --git a/src/TrainTabs.cpp b/src/TrainTabs.cpp deleted file mode 100644 index 75c815cba..000000000 --- a/src/TrainTabs.cpp +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (c) 2009 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 "TrainTabs.h" -#include "TrainTool.h" -#include "RealtimeWindow.h" - -TrainTabs::TrainTabs(MainWindow *parent, TrainTool *trainTool, const QDir &home) : -trainTool(trainTool), home(home), main(parent) -{ - setContentsMargins(0,0,0,0); - realtimeWindow = new RealtimeWindow(parent, trainTool, home); - addTab(realtimeWindow, tr("Solo Ride")); -}; diff --git a/src/TrainTabs.h b/src/TrainTabs.h deleted file mode 100644 index 52ad4950a..000000000 --- a/src/TrainTabs.h +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (c) 2000 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_TrainTabs_h -#define _GC_TrainTabs_h 1 -#include "GoldenCheetah.h" - -#include -#include - -#include "MainWindow.h" -#include "TrainTool.h" -#include "RealtimeWindow.h" -#include "RaceWindow.h" -#include "MultiWindow.h" - -#ifdef GC_HAVE_PHONON -#include "VideoWindow.h" -#endif - -class TrainTabs : public QTabWidget -{ - Q_OBJECT - G_OBJECT - - - public: - TrainTabs(MainWindow *parent, TrainTool *trainTool, const QDir &home); - - private: - RealtimeWindow *realtimeWindow; - - TrainTool *trainTool; - const QDir home; - const MainWindow *main; -}; - -#endif // _GC_TrainTabs_h diff --git a/src/TrainTool.cpp b/src/TrainTool.cpp index f5c559306..b581f4a97 100644 --- a/src/TrainTool.cpp +++ b/src/TrainTool.cpp @@ -18,7 +18,6 @@ #include "TrainTool.h" #include "MainWindow.h" -#include "ViewSelection.h" #include "Settings.h" #include "Units.h" #include "DeviceTypes.h" @@ -28,6 +27,13 @@ #include #include +// Three current realtime device types supported are: +#include "RealtimeController.h" +#include "ComputrainerController.h" +#include "ANTplusController.h" +#include "ANTlocalController.h" +#include "NullController.h" + TrainTool::TrainTool(MainWindow *parent, const QDir &home) : QWidget(parent), home(home), main(parent) { @@ -73,8 +79,6 @@ TrainTool::TrainTool(MainWindow *parent, const QDir &home) : QWidget(parent), ho allWorkouts->setText(0, tr("Workout Library")); workoutTree->expandItem(allWorkouts); - configChanged(); // will reset the workout tree - buttonPanel = new QFrame; buttonPanel->setLineWidth(1); buttonPanel->setFrameStyle(QFrame::Box | QFrame::Raised); @@ -114,14 +118,11 @@ TrainTool::TrainTool(MainWindow *parent, const QDir &home) : QWidget(parent), ho trainSplitter->addWidget(workoutTree); trainSplitter->setCollapsible(3, true); - connect(serverTree,SIGNAL(itemSelectionChanged()), - this, SLOT(serverTreeWidgetSelectionChanged())); - connect(deviceTree,SIGNAL(itemSelectionChanged()), - this, SLOT(deviceTreeWidgetSelectionChanged())); - connect(workoutTree,SIGNAL(itemSelectionChanged()), - this, SLOT(workoutTreeWidgetSelectionChanged())); - connect(main, SIGNAL(configChanged()), - this, SLOT(configChanged())); + // handle config changes + connect(serverTree,SIGNAL(itemSelectionChanged()), this, SLOT(serverTreeWidgetSelectionChanged())); + connect(deviceTree,SIGNAL(itemSelectionChanged()), this, SLOT(deviceTreeWidgetSelectionChanged())); + connect(workoutTree,SIGNAL(itemSelectionChanged()), this, SLOT(workoutTreeWidgetSelectionChanged())); + connect(main, SIGNAL(configChanged()), this, SLOT(configChanged())); // connect train tool buttons! connect(startButton, SIGNAL(clicked()), this, SLOT(Start())); @@ -135,6 +136,52 @@ TrainTool::TrainTool(MainWindow *parent, const QDir &home) : QWidget(parent), ho connect(&*watcher,SIGNAL(directoryChanged(QString)),this,SLOT(configChanged())); connect(&*watcher,SIGNAL(fileChanged(QString)),this,SLOT(configChanged())); + + // set home + main = parent; + deviceController = NULL; + streamController = NULL; + ergFile = NULL; + + // metric or imperial? + QVariant unit = appsettings->value(this, GC_UNIT); + useMetricUnits = (unit.toString() == "Metric"); + + // now the GUI is setup lets sort our control variables + gui_timer = new QTimer(this); + disk_timer = new QTimer(this); + stream_timer = new QTimer(this); + load_timer = new QTimer(this); + metrics_timer = new QTimer(this); + + session_time = QTime(); + session_elapsed_msec = 0; + lap_time = QTime(); + lap_elapsed_msec = 0; + + recordFile = NULL; + status = 0; + status |= RT_MODE_ERGO; // ergo mode by default + displayWorkoutLap = displayLap = 0; + pwrcount = 0; + cadcount = 0; + hrcount = 0; + spdcount = 0; + lodcount = 0; + load_msecs = total_msecs = lap_msecs = 0; + displayWorkoutDistance = displayDistance = displayPower = displayHeartRate = + displaySpeed = displayCadence = displayGradient = displayLoad = 0; + + rideFile = boost::shared_ptr(new RideFile(QDateTime::currentDateTime(),1)); + + connect(gui_timer, SIGNAL(timeout()), this, SLOT(guiUpdate())); + connect(disk_timer, SIGNAL(timeout()), this, SLOT(diskUpdate())); + connect(stream_timer, SIGNAL(timeout()), this, SLOT(streamUpdate())); + connect(load_timer, SIGNAL(timeout()), this, SLOT(loadUpdate())); + connect(metrics_timer, SIGNAL(timeout()), this, SLOT(metricsUpdate())); + + configChanged(); // will reset the workout tree + } void @@ -149,7 +196,7 @@ TrainTool::configChanged() for (int i=0; i Devices; + Devices.clear(); Devices = all.getList(); for (int i=0; isetText(0, name); } + + // Athlete + FTP=285; // default to 285 if zones are not set + int range = main->zones()->whichRange(QDate::currentDate()); + if (range != -1) FTP = main->zones()->getCP(range); + + // metric or imperial changed? + QVariant unit = appsettings->value(this, GC_UNIT); + useMetricUnits = (unit.toString() == "Metric"); } /*---------------------------------------------------------------------- * Buttons! *----------------------------------------------------------------------*/ -void -TrainTool::Start() -{ - start(); -} - -void -TrainTool::Stop() -{ - stop(); -} - -void -TrainTool::Pause() -{ - pause(); -} void TrainTool::setStartText(QString string) @@ -270,7 +309,60 @@ TrainTool::workoutTreeWidgetSelectionChanged() else workout = which; } - workoutSelected(); + + int mode; + + // wip away the current selected workout + if (ergFile) { + delete ergFile; + ergFile = NULL; + } + + // which one is selected? + if (currentWorkout() == NULL || currentWorkout()->type() != WORKOUT_TYPE) return; + + // is it the auto mode? + int index = workoutItems()->indexOfChild((QTreeWidgetItem *)currentWorkout()); + if (index == 0) { + // ergo mode + mode = ERG; + status &= ~RT_WORKOUT; + //ergPlot->setVisible(false); + } else if (index == 1) { + // slope mode + mode = CRS; + status &= ~RT_WORKOUT; + //ergPlot->setVisible(false); + } else { + // workout mode + QVariant workoutDir = appsettings->value(this, GC_WORKOUTDIR); + QString fileName = workoutDir.toString() + "/" + currentWorkout()->text(0); // filename + + // Get users CP for relative watts calculations + QDate today = QDate::currentDate(); + + ergFile = new ErgFile(fileName, mode, FTP); + if (ergFile->isValid()) { + + status |= RT_WORKOUT; + + // success! we have a load file + // setup the course profile in the + // display! + main->notifyErgFileSelected(ergFile); + } + } + + // set the device to the right mode + if (mode == ERG || mode == MRC) { + status |= RT_MODE_ERGO; + status &= ~RT_MODE_SPIN; + if (deviceController != NULL) deviceController->setMode(RT_MODE_ERGO); + } else { // SLOPE MODE + status |= RT_MODE_SPIN; + status &= ~RT_MODE_ERGO; + if (deviceController != NULL) deviceController->setMode(RT_MODE_SPIN); + } } QStringList @@ -283,3 +375,522 @@ TrainTool::listWorkoutFiles(const QDir &dir) const return dir.entryList(filters, QDir::Files, QDir::Name); } + +/*-------------------------------------------------------------------------------- + * Was realtime window, now local and manages controller and chart updates etc + *------------------------------------------------------------------------------*/ + +void TrainTool::setDeviceController() +{ + int deviceno = selectedDeviceNumber(); + + if (deviceno == -1) // not selected, maybe they are spectating + return; + + // zap the current one + if (deviceController != NULL) { + delete deviceController; + deviceController = NULL; + } + + if (Devices.count() > 0) { + DeviceConfiguration temp = Devices.at(deviceno); + if (Devices.at(deviceno).type == DEV_ANTPLUS) { + deviceController = new ANTplusController(this, &temp); + } else if (Devices.at(deviceno).type == DEV_CT) { + deviceController = new ComputrainerController(this, &temp); + } else if (Devices.at(deviceno).type == DEV_NULL) { + deviceController = new NullController(this, &temp); + } else if (Devices.at(deviceno).type == DEV_ANTLOCAL) { + deviceController = new ANTlocalController(this, &temp); + } + } +} + +// open a connection to the GoldenServer via a GoldenClient +void TrainTool::setStreamController() +{ + int deviceno = selectedServerNumber(); + + if (deviceno == -1) return; + + // zap the current one + if (streamController != NULL) { + delete streamController; + streamController = NULL; + } + + if (Devices.count() > 0) { + DeviceConfiguration config = Devices.at(deviceno); + streamController = new GoldenClient; + + // connect + QStringList speclist = config.portSpec.split(":", QString::SkipEmptyParts); + bool rc = streamController->connect(speclist[0], // host + speclist[1].toInt(), // port + "9cf638294030cea7b1590a4ca32e7f58", // raceid + appsettings->cvalue(main->cyclist, GC_NICKNAME).toString(), // name + FTP, // CP60 + appsettings->cvalue(main->cyclist, GC_WEIGHT).toDouble()); // weight + + // no connection + if (rc == false) { + streamController->closeAndExit(); + streamController = NULL; + status &= ~RT_STREAMING; + QMessageBox msgBox; + msgBox.setText(QString(tr("Cannot Connect to Server %1 on port %2").arg(speclist[0]).arg(speclist[1]))); + msgBox.setIcon(QMessageBox::Critical); + msgBox.exec(); + } + } +} + +void TrainTool::Start() // when start button is pressed +{ + if (status&RT_RUNNING) { + newLap(); + } else { + + // open the controller if it is selected + setDeviceController(); + if (deviceController == NULL) return; + else deviceController->start(); // start device + + // we're away! + status |=RT_RUNNING; + + // should we be streaming too? + setStreamController(); + if (streamController != NULL) status |= RT_STREAMING; + + setStartText(tr("Lap/Interval")); + + load_period.restart(); + session_time.start(); + session_elapsed_msec = 0; + lap_time.start(); + lap_elapsed_msec = 0; + + if (status & RT_WORKOUT) { + load_timer->start(LOADRATE); // start recording + } + + if (recordSelector->isChecked()) { + status |= RT_RECORDING; + } + + if (status & RT_RECORDING) { + QDateTime now = QDateTime::currentDateTime(); + + // setup file + QString filename = now.toString(QString("yyyy_MM_dd_hh_mm_ss")) + QString(".csv"); + + QString fulltarget = home.absolutePath() + "/" + filename; + if (recordFile) delete recordFile; + recordFile = new QFile(fulltarget); + if (!recordFile->open(QFile::WriteOnly | QFile::Truncate)) { + status &= ~RT_RECORDING; + } else { + + // CSV File header + + QTextStream recordFileStream(recordFile); + recordFileStream << "Minutes,Torq (N-m),Km/h,Watts,Km,Cadence,Hrate,ID,Altitude (m)\n"; + disk_timer->start(SAMPLERATE); // start screen + } + } + + // create a new rideFile + rideFile = boost::shared_ptr(new RideFile(QDateTime::currentDateTime(),1)); + + + // stream + if (status & RT_STREAMING) { + stream_timer->start(STREAMRATE); + } + + gui_timer->start(REFRESHRATE); // start recording + metrics_timer->start(METRICSRATE); + + } +} + +void TrainTool::Pause() // pause capture to recalibrate +{ + if (deviceController == NULL) return; + + // we're not running fool! + if ((status&RT_RUNNING) == 0) return; + + if (status&RT_PAUSED) { + session_time.start(); + lap_time.start(); + status &=~RT_PAUSED; + deviceController->restart(); + setPauseText(tr("Pause")); + gui_timer->start(REFRESHRATE); + metrics_timer->start(METRICSRATE); + if (status & RT_STREAMING) stream_timer->start(STREAMRATE); + if (status & RT_RECORDING) disk_timer->start(SAMPLERATE); + load_period.restart(); + if (status & RT_WORKOUT) load_timer->start(LOADRATE); + } else { + session_elapsed_msec += session_time.elapsed(); + lap_elapsed_msec += lap_time.elapsed(); + deviceController->pause(); + setPauseText(tr("Un-Pause")); + status |=RT_PAUSED; + gui_timer->stop(); + metrics_timer->stop(); + if (status & RT_STREAMING) stream_timer->stop(); + if (status & RT_RECORDING) disk_timer->stop(); + if (status & RT_WORKOUT) load_timer->stop(); + load_msecs += load_period.restart(); + } +} + +void TrainTool::Stop(int deviceStatus) // when stop button is pressed +{ + if (deviceController == NULL) return; + + if ((status&RT_RUNNING) == 0) return; + + status &= ~RT_RUNNING; + setStartText(tr("Start")); + + // wipe connection + deviceController->stop(); + delete deviceController; + deviceController = NULL; + + gui_timer->stop(); + metrics_timer->stop(); + + QDateTime now = QDateTime::currentDateTime(); + + if (status & RT_RECORDING) { + disk_timer->stop(); + + // close and reset File + recordFile->close(); + + if(deviceStatus == DEVICE_ERROR) + { + recordFile->remove(); + } + else { + // add to the view - using basename ONLY + QString name; + name = recordFile->fileName(); + main->addRide(QFileInfo(name).fileName(), true); + } + } + + if (status & RT_STREAMING) { + stream_timer->stop(); + streamController->closeAndExit(); + delete streamController; + streamController = NULL; + } + + if (status & RT_WORKOUT) { + load_timer->stop(); + load_msecs = 0; + main->notifySetNow(load_msecs); + } + + // Re-enable gui elements + //recordSelector->setEnabled(true); + + // reset counters etc + pwrcount = 0; + cadcount = 0; + hrcount = 0; + spdcount = 0; + lodcount = 0; + displayWorkoutLap = displayLap =0; + session_elapsed_msec = 0; + session_time.restart(); + lap_elapsed_msec = 0; + lap_time.restart(); + displayWorkoutDistance = displayDistance = 0; + guiUpdate(); + + return; +} + + +// Called by push devices (e.g. ANT+) +void TrainTool::updateData(RealtimeData &rtData) +{ + displayPower = rtData.getWatts(); + displayCadence = rtData.getCadence(); + displayHeartRate = rtData.getHr(); + displaySpeed = rtData.getSpeed(); + displayLoad = rtData.getLoad(); + // Gradient not supported + return; +} + +//---------------------------------------------------------------------- +// SCREEN UPDATE FUNCTIONS +//---------------------------------------------------------------------- + +void TrainTool::guiUpdate() // refreshes the telemetry +{ + RealtimeData rtData; + + if (deviceController == NULL) return; + + // get latest telemetry from device (if it is a pull device e.g. Computrainer // + if (status&RT_RUNNING && deviceController->doesPull() == true) { + + deviceController->getRealtimeData(rtData); + + // Distance assumes current speed for the last second. from km/h to km/sec + displayDistance += displaySpeed / (5 * 3600); // XXX assumes 200ms refreshrate + displayWorkoutDistance += displaySpeed / (5 * 3600); // XXX assumes 200ms refreshrate + rtData.setDistance(displayDistance); + + // time + total_msecs = session_elapsed_msec + session_time.elapsed(); + lap_msecs = lap_elapsed_msec + lap_time.elapsed(); + rtData.setMsecs(total_msecs); + rtData.setLapMsecs(lap_msecs); + + // metrics + rtData.setJoules(kjoules); + rtData.setBikeScore(bikescore); + rtData.setXPower(xpower); + + // local stuff ... + displayPower = rtData.getWatts(); + displayCadence = rtData.getCadence(); + displayHeartRate = rtData.getHr(); + displaySpeed = rtData.getSpeed(); + displayLoad = rtData.getLoad(); + + // go update the displays... + main->notifyTelemetryUpdate(rtData); // signal everyone to update telemetry + } +} + +// can be called from the controller - when user presses "Lap" button +void TrainTool::newLap() +{ + displayLap++; + + pwrcount = 0; + cadcount = 0; + hrcount = 0; + spdcount = 0; + + lap_time.restart(); + lap_elapsed_msec = 0; + +} + +// can be called from the controller +void TrainTool::nextDisplayMode() +{ +} + +void TrainTool::warnnoConfig() +{ + QMessageBox::warning(this, tr("No Devices Configured"), "Please configure a device in Preferences."); +} + +//---------------------------------------------------------------------- +// STREAMING FUNCTION +//---------------------------------------------------------------------- +#if 0 +TrainTool::SelectStream(int index) +{ + + if (index > 0) { + status |= RT_STREAMING; + setStreamController(); + } else { + status &= ~RT_STREAMING; + } +} +#endif + +void +TrainTool::streamUpdate() +{ + // send over the wire... + if (streamController) { + + // send my data + streamController->sendTelemetry(displayPower, + displayCadence, + displayDistance, + displayHeartRate, + displaySpeed); + + // get standings for everyone else + RaceStatus current = streamController->getStandings(); + + // send out to all the widgets... + notifyRaceStandings(current); + + // has the race finished? + if (current.race_finished == true) { + Stop(0); // all over dude + QMessageBox msgBox; + msgBox.setText(tr("Race Over!")); + msgBox.setIcon(QMessageBox::Information); + msgBox.exec(); + } + } +} + +//---------------------------------------------------------------------- +// DISK UPDATE FUNCTIONS +//---------------------------------------------------------------------- +void TrainTool::diskUpdate() +{ + double Minutes; + + long Torq = 0, Altitude = 0; + QTextStream recordFileStream(recordFile); + + // convert from milliseconds to minutes + total_msecs = session_elapsed_msec + session_time.elapsed(); + Minutes = total_msecs; + Minutes /= 1000.00; + Minutes *= (1.0/60); + + // PowerAgent Format "Minutes,Torq (N-m),Km/h,Watts,Km,Cadence,Hrate,ID,Altitude (m)" + recordFileStream << Minutes + << "," << Torq + << "," << displaySpeed + << "," << displayPower + << "," << displayDistance + << "," << displayCadence + << "," << displayHeartRate + << "," << (displayLap + displayWorkoutLap) + << "," << Altitude + << "," << "\n"; + + rideFile->appendPoint(total_msecs/1000,displayCadence,displayHeartRate,displayDistance,displaySpeed,0, + displayPower,Altitude,0,0,0,displayLap + displayWorkoutLap); +} + +void TrainTool::metricsUpdate() +{ + // calculate bike score, xpower + const RideMetricFactory &factory = RideMetricFactory::instance(); + const RideMetric *rm = factory.rideMetric("skiba_xpower"); + + QStringList metrics; + metrics.append("skiba_bike_score"); + metrics.append("skiba_xpower"); + QHash results = rm->computeMetrics( + this->main,&*rideFile,this->main->zones(),this->main->hrZones(),metrics); + bikescore = results["skiba_bike_score"]->value(true); + xpower = results["skiba_xpower"]->value(true); +} + +//---------------------------------------------------------------------- +// WORKOUT MODE +//---------------------------------------------------------------------- + +void TrainTool::loadUpdate() +{ + long load; + double gradient; + // the period between loadUpdate calls is not constant, and not exactly LOADRATE, + // therefore, use a QTime timer to measure the load period + load_msecs += load_period.restart(); + + if (deviceController == NULL) return; + + if (status&RT_MODE_ERGO) { + load = ergFile->wattsAt(load_msecs, displayWorkoutLap); + + // we got to the end! + if (load == -100) { + Stop(DEVICE_OK); + } else { + displayLoad = load; + deviceController->setLoad(displayLoad); + main->notifySetNow(load_msecs); + } + } else { + gradient = ergFile->gradientAt(displayWorkoutDistance*1000, displayWorkoutLap); + + // we got to the end! + if (gradient == -100) { + Stop(DEVICE_OK); + } else { + displayGradient = gradient; + deviceController->setGradient(displayGradient); + main->notifySetNow(displayWorkoutDistance * 1000); + } + } +} + +void TrainTool::FFwd() +{ + if (status&RT_MODE_ERGO) load_msecs += 10000; // jump forward 10 seconds + else displayWorkoutDistance += 1; // jump forward a kilometer in the workout +} + +void TrainTool::Rewind() +{ + if (status&RT_MODE_ERGO) { + load_msecs -=10000; // jump back 10 seconds + if (load_msecs < 0) load_msecs = 0; + } else { + displayWorkoutDistance -=1; // jump back a kilometer + if (displayWorkoutDistance < 0) displayWorkoutDistance = 0; + } +} + + +// jump to next Lap marker (if there is one?) +void TrainTool::FFwdLap() +{ + double lapmarker; + + if (status&RT_MODE_ERGO) { + lapmarker = ergFile->nextLap(load_msecs); + if (lapmarker != -1) load_msecs = lapmarker; // jump forward to lapmarker + } else { + lapmarker = ergFile->nextLap(displayWorkoutDistance*1000); + if (lapmarker != -1) displayWorkoutDistance = lapmarker/1000; // jump forward to lapmarker + } +} + +// higher load/gradient +void TrainTool::Higher() +{ + if (deviceController == NULL) return; + + if (status&RT_MODE_ERGO) displayLoad += 5; + else displayGradient += 0.1; + + if (displayLoad >1500) displayLoad = 1500; + if (displayGradient >15) displayGradient = 15; + + if (status&RT_MODE_ERGO) deviceController->setLoad(displayLoad); + else deviceController->setGradient(displayGradient); +} + +// higher load/gradient +void TrainTool::Lower() +{ + if (deviceController == NULL) return; + + if (status&RT_MODE_ERGO) displayLoad -= 5; + else displayGradient -= 0.1; + + if (displayLoad <0) displayLoad = 0; + if (displayGradient <-10) displayGradient = -10; + + if (status&RT_MODE_ERGO) deviceController->setLoad(displayLoad); + else deviceController->setGradient(displayGradient); +} diff --git a/src/TrainTool.h b/src/TrainTool.h index e5952a9c0..0040d8336 100644 --- a/src/TrainTool.h +++ b/src/TrainTool.h @@ -21,27 +21,64 @@ #include "GoldenCheetah.h" #include "MainWindow.h" -#include "ViewSelection.h" #include "GoldenClient.h" +#include "RealtimeData.h" +#include "RealtimePlot.h" +#include "DeviceConfiguration.h" +#include "DeviceTypes.h" +#include "ErgFile.h" +#include "ErgFilePlot.h" + +// standard stuff #include #include +#include "math.h" // for round() +#include "Units.h" // for MILES_PER_KM +#include +// Status settings +#define RT_MODE_ERGO 0x0001 // load generation modes +#define RT_MODE_SPIN 0x0002 // spinscan like modes +#define RT_RUNNING 0x0100 // is running now +#define RT_PAUSED 0x0200 // is paused +#define RT_RECORDING 0x0400 // is recording to disk +#define RT_WORKOUT 0x0800 // is running a workout +#define RT_STREAMING 0x1000 // is streaming to a remote peer + +// msecs constants for timers +#define REFRESHRATE 200 // screen refresh in milliseconds +#define STREAMRATE 200 // rate at which we stream updates to remote peer +#define SAMPLERATE 1000 // disk update in milliseconds +#define LOADRATE 1000 // rate at which load is adjusted +#define METRICSRATE 1000 // rate the metrics are updated + +// device treeview node types #define HEAD_TYPE 6666 #define SERVER_TYPE 5555 #define WORKOUT_TYPE 4444 #define DEVICE_TYPE 0 +class RealtimeController; +class ComputrainerController; +class ANTplusController; +class ANTlocalController; +class NullController; +class RealtimePlot; +class RealtimeData; + class TrainTool : public QWidget { Q_OBJECT G_OBJECT - public: TrainTool(MainWindow *parent, const QDir &home); QStringList listWorkoutFiles(const QDir &) const; + RealtimeController *deviceController; // read from + GoldenClient *streamController; // send out to + const QTreeWidgetItem *currentWorkout() { return workout; } const QTreeWidgetItem *workoutItems() { return allWorkouts; } const QTreeWidgetItem *currentServer() { return server; } @@ -57,13 +94,21 @@ class TrainTool : public QWidget // notify widgets of race update void notifyRaceStandings(RaceStatus x) { raceStandings(x); } + // was realtimewindow,merged into tool + // update charts/dials and manage controller + void updateData(RealtimeData &); // to update telemetry by push devices + void newLap(); // start new Lap! + void nextDisplayMode(); // show next display mode + void setDeviceController(); // based upon selected device + void setStreamController(); // based upon selected device + // this QTabWidget *trainTabs; + signals: void deviceSelected(); void serverSelected(); - void workoutSelected(); void raceStandings(RaceStatus); void start(); void pause(); @@ -74,9 +119,28 @@ class TrainTool : public QWidget void deviceTreeWidgetSelectionChanged(); void workoutTreeWidgetSelectionChanged(); void configChanged(); - void Start(); - void Pause(); - void Stop(); + + public slots: + + void Start(); // when start button is pressed + void Pause(); // when Paude is pressed + void Stop(int status=0); // when controller wants to stop + + void FFwd(); // jump forward when in a workout + void Rewind(); // jump backwards when in a workout + void FFwdLap(); // jump forward to next Lap marker + void Higher(); // set load/gradient higher + void Lower(); // set load/gradient higher + + // Timed actions + void guiUpdate(); // refreshes the telemetry + void diskUpdate(); // writes to CSV file + void streamUpdate(); // writes to remote Peer + void loadUpdate(); // sets Load on CT like devices + void metricsUpdate(); // calculates the metrics + + // When no config has been setup + void warnnoConfig(); private: @@ -100,6 +164,46 @@ class TrainTool : public QWidget *pauseButton, *stopButton; + int FTP; // current FTP + + QList Devices; + bool useMetricUnits; + + // updated with a RealtimeData object either from + // update() - from a push device (quarqd ANT+) + // Device->getRealtimeData() - from a pull device (Computrainer) + double displayPower, displayHeartRate, displayCadence, + displayLoad, displayGradient, displaySpeed; + double displayDistance, displayWorkoutDistance; + int displayLap; // user increment for Lap + int displayWorkoutLap; // which Lap in the workout are we at? + double kjoules; + double bikescore; + double xpower; + + // for non-zero average calcs + int pwrcount, cadcount, hrcount, spdcount, lodcount, grdcount; // for NZ average calc + int status; + int displaymode; + + QFile *recordFile; // where we record! + ErgFile *ergFile; // workout file + boost::shared_ptr rideFile; // keeps track of the workout to figure out BikeScore + + long total_msecs, + lap_msecs, + load_msecs; + QTime load_period; + + uint session_elapsed_msec, lap_elapsed_msec; + QTime session_time, lap_time; + + QTimer *gui_timer, // refresh the gui + *stream_timer, // send telemetry to server + *load_timer, // change the load on the device + *disk_timer, // write to .CSV file + *metrics_timer; // computational intense metrics + public: // everyone else wants this QCheckBox *recordSelector; diff --git a/src/TrainWindow.cpp b/src/TrainWindow.cpp deleted file mode 100644 index 6d33b51ad..000000000 --- a/src/TrainWindow.cpp +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (c) 2009 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 "TrainWindow.h" -#include "TrainTool.h" -#include "TrainTabs.h" -#include "ViewSelection.h" -#include "RaceDispatcher.h" -#include "MainWindow.h" -#include "Settings.h" -#include "Units.h" -#include -#include - -TrainWindow::TrainWindow(MainWindow *parent, const QDir &home) : GcWindow(parent), home(home), main(parent) -{ - QVBoxLayout *mainLayout = new QVBoxLayout; - setLayout(mainLayout); - - // RaceDispatcher first -- could be connected by lots of different widgets - dispatcher = new RaceDispatcher(this); - - // LeftSide - trainTool = new TrainTool(parent, home); - setControls(trainTool); - - // Right side - trainTabs = new TrainTabs(parent, trainTool, home); - mainLayout->addWidget(trainTabs); -} diff --git a/src/TrainWindow.h b/src/TrainWindow.h deleted file mode 100644 index 156e45cec..000000000 --- a/src/TrainWindow.h +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (c) 2000 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_TrainWindow_h -#define _GC_TrainWindow_h 1 -#include "GoldenCheetah.h" - -#include -#include - -#include "MainWindow.h" -#include "TrainTool.h" -#include "TrainTabs.h" -#include "RaceDispatcher.h" - -#include "RealtimeWindow.h" - -class TrainWindow : public GcWindow -{ - Q_OBJECT - G_OBJECT - - public: - TrainWindow(MainWindow *parent, const QDir &home); - - public slots: - - private: - const QDir home; - const MainWindow *main; - - TrainTool *trainTool; - TrainTabs *trainTabs; - RaceDispatcher *dispatcher; -}; - -#endif // _GC_TrainWindow_h diff --git a/src/VideoWindow.cpp b/src/VideoWindow.cpp index 15a174b9f..a65a91efe 100644 --- a/src/VideoWindow.cpp +++ b/src/VideoWindow.cpp @@ -46,7 +46,7 @@ GcWindow(parent), home(home), main(parent) /* Create a new item */ // XXX need to add controls - not everyone is going to want to play a video from // my desktop!!! - m = libvlc_media_new_path(inst, "/home/markl/Desktop/My Documents/fightclub.mp4"); + m = libvlc_media_new_path(inst, "/home/markl/Videos/fightclub.mp4"); //vlc_exceptions(&exceptions); /* Create a media player playing environement */ diff --git a/src/ViewSelection.cpp b/src/ViewSelection.cpp deleted file mode 100644 index 867bb264e..000000000 --- a/src/ViewSelection.cpp +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (c) 2009 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 "ViewSelection.h" -#include "TrainTool.h" -#include "TrainTabs.h" -#include "MainWindow.h" -#include "Settings.h" -#include "Units.h" -#include -#include -#include -#include - -ViewSelection::ViewSelection(const MainWindow *parent, const int homeView) : -QComboBox((QWidget *)parent), -main(parent), -homeView(homeView) -{ - // don't make a fat ugly box... - setFixedHeight(((double)(QFontMetrics(font()).height()) * 1.6)); - - // set drop-down list - addItem(tr("Ride Analysis View"), VIEW_ANALYSIS); - addItem(tr("Train and Racing View"), VIEW_TRAIN); - - // Default to home view - if (homeView == VIEW_ANALYSIS) setCurrentIndex(0); - if (homeView == VIEW_TRAIN) setCurrentIndex(1); - - // set to this view - connect(this, SIGNAL(currentIndexChanged(int)), this, SLOT(changeView(int))); -} - -void -ViewSelection::changeView(int index) -{ - int view = itemData(index).toInt(); - - if (view != homeView) { - ((MainWindow *)main)->selectView(view); - } - - // now reset back to the homeView! - if (homeView == VIEW_ANALYSIS) setCurrentIndex(0); - if (homeView == VIEW_TRAIN) setCurrentIndex(1); -} diff --git a/src/ViewSelection.h b/src/ViewSelection.h deleted file mode 100644 index c85010044..000000000 --- a/src/ViewSelection.h +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (c) 2000 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_ViewSelection_h -#define _GC_ViewSelection_h 1 -#include "GoldenCheetah.h" - -#include -#include - -#include "MainWindow.h" -#include "TrainTool.h" -#include "RealtimeWindow.h" - -#define VIEW_ANALYSIS 1 -#define VIEW_TRAIN 2 - -class ViewSelection : public QComboBox -{ - Q_OBJECT - G_OBJECT - - - public: - ViewSelection(const MainWindow *parent, const int homeView); - - public slots: - void changeView(int); - - private: - const MainWindow *main; - const int homeView; -}; - -#endif // _GC_ViewSelection_h diff --git a/src/WorkoutPlotWindow.cpp b/src/WorkoutPlotWindow.cpp index 353490a51..6f9097b9a 100644 --- a/src/WorkoutPlotWindow.cpp +++ b/src/WorkoutPlotWindow.cpp @@ -27,6 +27,7 @@ WorkoutPlotWindow::WorkoutPlotWindow(MainWindow *mainWindow) : setControls(NULL); QVBoxLayout *layout = new QVBoxLayout(this); + layout->setSpacing(0); ergPlot = new ErgFilePlot(0); layout->addWidget(ergPlot); diff --git a/src/application.qrc b/src/application.qrc index 1cd5c56c0..587c5cc9e 100644 --- a/src/application.qrc +++ b/src/application.qrc @@ -35,6 +35,7 @@ xml/charts.xml xml/metadata.xml xml/measures.xml + xml/train-layout.xml html/ltm-summary.html images/toolbar/close-icon.png images/toolbar/save.png diff --git a/src/src.pro b/src/src.pro index d6421be4f..e26967b0a 100644 --- a/src/src.pro +++ b/src/src.pro @@ -244,7 +244,6 @@ HEADERS += \ RaceDispatcher.h \ RealtimeData.h \ RealtimePlotWindow.h \ - RealtimeWindow.h \ RealtimeController.h \ ComputrainerController.h \ RealtimePlot.h \ @@ -283,13 +282,10 @@ HEADERS += \ TxtRideFile.h \ TimeUtils.h \ ToolsDialog.h \ - TrainTabs.h \ TrainTool.h \ - TrainWindow.h \ TreeMapWindow.h \ TreeMapPlot.h \ Units.h \ - ViewSelection.h \ WeeklySummaryWindow.h \ WeeklyViewItemDelegate.h \ WithingsDownload.h \ @@ -406,7 +402,6 @@ SOURCES += \ RealtimeData.cpp \ RealtimeController.cpp \ ComputrainerController.cpp \ - RealtimeWindow.cpp \ RealtimePlot.cpp \ RealtimePlotWindow.cpp \ RideCalendar.cpp \ @@ -445,13 +440,10 @@ SOURCES += \ TimeInZone.cpp \ TimeUtils.cpp \ ToolsDialog.cpp \ - TrainTabs.cpp \ TrainTool.cpp \ - TrainWindow.cpp \ TreeMapWindow.cpp \ TreeMapPlot.cpp \ TRIMPPoints.cpp \ - ViewSelection.cpp \ WattsPerKilogram.cpp \ WithingsDownload.cpp \ WeeklySummaryWindow.cpp \ diff --git a/src/xml/train-layout.xml b/src/xml/train-layout.xml new file mode 100644 index 000000000..55dba17dc --- /dev/null +++ b/src/xml/train-layout.xml @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +