diff --git a/src/Cloud/CloudService.cpp b/src/Cloud/CloudService.cpp index 2ddd63388..4e502f43a 100644 --- a/src/Cloud/CloudService.cpp +++ b/src/Cloud/CloudService.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include "../qzip/zipwriter.h" #include "../qzip/zipreader.h" @@ -1589,6 +1590,11 @@ CloudServiceAutoDownload::autoDownload() { if (initial) { initial = false; + + // Start means we are looking for downloads to do + context->notifyAutoDownloadStart(); + + // starts a thread start(); } } @@ -1599,7 +1605,6 @@ CloudServiceAutoDownload::run() // this is a separate thread and can run in parallel with the main gui // so we can loop through services and download the data needed. // we notify the main gui via the usual signals. - context->notifyAutoDownloadStart(); // get a list of services to sync from QStringList worklist; @@ -1609,9 +1614,6 @@ CloudServiceAutoDownload::run() } } - // update progress indicator - context->notifyAutoDownloadProgress(0.0f); - // // generate a worklist to process // @@ -1709,6 +1711,9 @@ CloudServiceAutoDownload::run() double inc = 100.0f / double(downloadlist.count()); for(int i=0; inotifyAutoDownloadProgress(progress); + CloudServiceDownloadEntry download= downloadlist[i]; // we block on read completing @@ -1724,12 +1729,14 @@ CloudServiceAutoDownload::run() // block on timeout or readComplete... loop.exec(); - // notify progress - context->notifyAutoDownloadProgress(progress += inc); + // update progress + progress += inc; + + // if last one we need to signal done. + if ((i+1) == downloadlist.count()) context->notifyAutoDownloadProgress(progress); } - // all done, close the sync notification - context->notifyAutoDownloadProgress(100); + // all done, close the sync notification, regardless of if anything was downloaded context->notifyAutoDownloadEnd(); // remove providers @@ -1747,10 +1754,8 @@ CloudServiceAutoDownload::run() } void -CloudServiceAutoDownload::readComplete(QByteArray*data,QString name,QString status) +CloudServiceAutoDownload::readComplete(QByteArray*data,QString name,QString) { - qDebug()<<"received:"<athlete->addRide(fileinfo.fileName(), true); + // add to the ride list -- but don't select it + context->athlete->addRide(fileinfo.fileName(), true, false); } + + +CloudServiceAutoDownloadWidget::CloudServiceAutoDownloadWidget(Context *context,QWidget *parent) : + QWidget(parent), context(context), state(Dormant) +{ + connect(context, SIGNAL(autoDownloadStart()), this, SLOT(downloadStart())); + connect(context, SIGNAL(autoDownloadEnd()), this, SLOT(downloadFinish())); + connect(context, SIGNAL(autoDownloadProgress(double)), this, SLOT(downloadProgress(double))); + + // just a small little thing + setFixedHeight(dpiYFactor * 25); + hide(); + + // animating checking + animator= new QPropertyAnimation(this, "transition"); + animator->setStartValue(0); + animator->setEndValue(100); + animator->setDuration(1000); + animator->setEasingCurve(QEasingCurve::Linear); +} + +void +CloudServiceAutoDownloadWidget::downloadStart() +{ + state = Checking; + animator->start(); + show(); +} + +void +CloudServiceAutoDownloadWidget::downloadFinish() +{ + state = Dormant; + animator->stop(); + hide(); +} + +void +CloudServiceAutoDownloadWidget::downloadProgress(double x) +{ + state = Downloading; + animator->stop(); + show(); + progress = x; + repaint(); +} + +void +CloudServiceAutoDownloadWidget::paintEvent(QPaintEvent*) +{ + QPainter painter(this); + QBrush brush(GColor(CPLOTBACKGROUND)); + painter.fillRect(0,0,width(),height(), brush); + + QString statusstring; + switch(state) { + case Dormant: statusstring=""; break; + case Downloading: statusstring=tr("Downloading"); break; + case Checking: statusstring=tr("Checking"); break; + } + + // smallest font we can + QFont font; + QFontMetrics fm(font); + painter.setFont(font); + painter.setPen(GCColor::invertColor(GColor(CPLOTBACKGROUND))); + QRectF textbox = QRectF(0,0, fm.width(statusstring), height()); + painter.drawText(textbox, Qt::AlignVCenter | Qt::AlignCenter, statusstring); + + // rectangle + QRectF pr(textbox.width()+(5.0f*dpiXFactor), textbox.top()+(8.0f*dpiXFactor), width()-(10.0f*dpiXFactor)-textbox.width(), height()-(16*dpiXFactor)); + + // progress rect + QColor col = GColor(CPLOTMARKER); + col.setAlpha(150); + brush= QBrush(col); + + if (state == Downloading) { + QRectF bar(pr.left(), pr.top(), (pr.width() / 100.00f * progress), pr.height()); + painter.fillRect(bar, brush); + } else if (state == Checking) { + // bounce + QRectF lbar(pr.left()+ ((pr.width() *0.8f) / 100.0f * transition), pr.top(), pr.width() * 0.2f, pr.height()); + QRectF rbar(pr.left()+ (pr.width()*0.8f) - ((pr.width() *0.8f) / 100.0f * transition), pr.top(), pr.width() * 0.2f, pr.height()); + painter.fillRect(lbar, brush); + painter.fillRect(rbar, brush); + + // if we ran out of juice start again + if (transition == 100) { animator->stop(); animator->start(); } + } + + // border of progress bar + painter.drawRect(pr); +} diff --git a/src/Cloud/CloudService.h b/src/Cloud/CloudService.h index 2e1b3a23b..2fc1bbb7b 100644 --- a/src/Cloud/CloudService.h +++ b/src/Cloud/CloudService.h @@ -35,6 +35,7 @@ #include #include #include +#include #include "Context.h" #include "Athlete.h" @@ -603,4 +604,38 @@ class CloudServiceFactory { }; +class CloudServiceAutoDownloadWidget : public QWidget +{ + + Q_OBJECT + + Q_PROPERTY(int transition READ getTransition WRITE setTransition) + + public: + CloudServiceAutoDownloadWidget(Context *context,QWidget *parent); + + // transition animation 0-255 + int getTransition() const {return transition;} + void setTransition(int x) { if (transition !=x) {transition=x; update();}} + + protected: + void paintEvent(QPaintEvent*); + + public slots: + + void downloadStart(); + void downloadFinish(); + void downloadProgress(double x); + + private: + + Context *context; + enum { Checking, Downloading, Dormant } state; + double progress; + + // animating checking + QPropertyAnimation *animator; + int transition; +}; + #endif diff --git a/src/Core/Athlete.cpp b/src/Core/Athlete.cpp index 5e104877d..f77ef5c97 100644 --- a/src/Core/Athlete.cpp +++ b/src/Core/Athlete.cpp @@ -274,9 +274,9 @@ void Athlete::selectRideFile(QString fileName) } void -Athlete::addRide(QString name, bool dosignal, bool useTempActivities, bool planned) +Athlete::addRide(QString name, bool dosignal, bool select, bool useTempActivities, bool planned) { - rideCache->addRide(name, dosignal, useTempActivities, planned); + rideCache->addRide(name, dosignal, select, useTempActivities, planned); } void diff --git a/src/Core/Athlete.h b/src/Core/Athlete.h index 3d4c6b22e..8e8d37a41 100644 --- a/src/Core/Athlete.h +++ b/src/Core/Athlete.h @@ -151,7 +151,7 @@ class Athlete : public QObject // ride collection void selectRideFile(QString); - void addRide(QString name, bool bSelect=true, bool useTempActivities=false, bool planned=false); + void addRide(QString name, bool signal, bool select=true, bool useTempActivities=false, bool planned=false); void removeCurrentRide(); // zones etc diff --git a/src/Core/RideCache.cpp b/src/Core/RideCache.cpp index 5a4934650..1ff221d72 100644 --- a/src/Core/RideCache.cpp +++ b/src/Core/RideCache.cpp @@ -202,7 +202,7 @@ RideCache::itemChanged() emit itemChanged(item); // current ride changed is more relevant for the charts lets notify - // them the ride they're showing has changed + // them the ride they're showing has changed if (item == context->currentRideItem()) { context->notifyRideChanged(item); @@ -211,7 +211,7 @@ RideCache::itemChanged() // add a new ride void -RideCache::addRide(QString name, bool dosignal, bool useTempActivities, bool planned) +RideCache::addRide(QString name, bool dosignal, bool select, bool useTempActivities, bool planned) { RideItem *prior = context->ride; @@ -249,7 +249,7 @@ RideCache::addRide(QString name, bool dosignal, bool useTempActivities, bool pla model_->endReset(); } - // refresh metrics for *this ride only* + // refresh metrics for *this ride only* last->refresh(); if (dosignal) context->notifyRideAdded(last); // here so emitted BEFORE rideSelected is emitted! @@ -260,8 +260,13 @@ RideCache::addRide(QString name, bool dosignal, bool useTempActivities, bool pla if (prior) prior->close(); // notify everyone to select it - context->ride = last; - context->notifyRideSelected(last); + if (select) { + context->ride = last; + context->notifyRideSelected(last); + } else{ + // notify everyone to select the one we were already on + context->notifyRideSelected(prior); + } } void @@ -464,7 +469,7 @@ RideCache::refresh() foreach(RideItem *item, rides_) { // ok set stale so we refresh - if (item->checkStale()) + if (item->checkStale()) staleCount++; } @@ -479,7 +484,9 @@ RideCache::refresh() // nothing to do, notify its started and done immediately context->notifyRefreshStart(); - context->notifyRefreshEnd(); + + // wait five seconds, so mainwindow can get up and running... + QTimer::singleShot(5000, context, SLOT(notifyRefreshEnd())); } } @@ -570,7 +577,7 @@ RideCache::getAggregate(QString name, Specification spec, bool useMetricUnits, b } else result = metric->toString(useMetricUnits); - // 0 temp from aggregate means no values + // 0 temp from aggregate means no values if ((metric->symbol() == "average_temp" || metric->symbol() == "max_temp") && result == "0.0") result = "-"; return result; } @@ -585,7 +592,7 @@ bool rideCachesummaryBestLowerThan(const AthleteBest &s1, const AthleteBest &s2) return s1.nvalue < s2.nvalue; } -QList +QList RideCache::getBests(QString symbol, int n, Specification specification, bool useMetricUnits) { QList results; @@ -665,7 +672,7 @@ class RollingBests { returning.fill(0.0f, size); // get largest values - for(int i=0; i returning[j]) returning[j] = buffer[i].at(j); @@ -781,7 +788,7 @@ RideCache::refreshCPModelMetrics() if (add.CP && add.WPrime) add.EI = add.WPrime / add.CP ; // so long as the important model derived values are sensible ... - if (add.WPrime > 1000 && add.CP > 100) + if (add.WPrime > 1000 && add.CP > 100) context->athlete->PDEstimates_ << add; //qDebug()<code()<< "W'="<< model->WPrime() <<"CP="<< model->CP() <<"pMax="<PMax(); @@ -801,7 +808,7 @@ RideCache::refreshCPModelMetrics() if (add.CP && add.WPrime) add.EI = add.WPrime / add.CP ; // so long as the model derived values are sensible ... - if ((!model->hasWPrime() || add.WPrime > 10.0f) && + if ((!model->hasWPrime() || add.WPrime > 10.0f) && (!model->hasCP() || add.CP > 1.0f) && (!model->hasPMax() || add.PMax > 1.0f) && (!model->hasFTP() || add.FTP > 1.0f)) @@ -826,7 +833,7 @@ RideCache::refreshCPModelMetrics() emit modelProgress(0, 0); // all done } -QList +QList RideCache::getAllDates() { QList returning; @@ -836,7 +843,7 @@ RideCache::getAllDates() return returning; } -QStringList +QStringList RideCache::getAllFilenames() { QStringList returning; @@ -876,7 +883,7 @@ RideCache::getRankedValues(QString field) int count = returning.value(value,0); returning.insert(value,++count); } - } + } return returning; } diff --git a/src/Core/RideCache.h b/src/Core/RideCache.h index cff8f55e0..e3420381a 100644 --- a/src/Core/RideCache.h +++ b/src/Core/RideCache.h @@ -87,7 +87,7 @@ class RideCache : public QObject QVector&rides() { return rides_; } // add/remove a ride to the list - void addRide(QString name, bool dosignal, bool useTempActivities, bool planned); + void addRide(QString name, bool dosignal, bool select, bool useTempActivities, bool planned); void removeCurrentRide(); // export metrics in CSV format diff --git a/src/Gui/AnalysisSidebar.cpp b/src/Gui/AnalysisSidebar.cpp index 5b7c87e59..95018888c 100644 --- a/src/Gui/AnalysisSidebar.cpp +++ b/src/Gui/AnalysisSidebar.cpp @@ -49,6 +49,9 @@ AnalysisSidebar::AnalysisSidebar(Context *context) : QWidget(context->mainWindow splitter = new GcSplitter(Qt::Vertical); mainLayout->addWidget(splitter); + autowidget = new CloudServiceAutoDownloadWidget(context, this); + mainLayout->addWidget(autowidget); + // calendar calendarWidget = new GcMultiCalendar(context); calendarItem = new GcSplitterItem(tr("Calendar"), iconFromPNG(":images/sidebar/calendar.png"), this); diff --git a/src/Gui/AnalysisSidebar.h b/src/Gui/AnalysisSidebar.h index 63ee7dc46..b8e28abd4 100644 --- a/src/Gui/AnalysisSidebar.h +++ b/src/Gui/AnalysisSidebar.h @@ -27,6 +27,7 @@ #include "RideItem.h" #include "IntervalTreeView.h" #include "IntervalSummaryWindow.h" +#include "CloudService.h" #include #include @@ -104,6 +105,8 @@ class AnalysisSidebar : public QWidget IntervalTreeView *intervalTree; // the interval tree QMap trees; + + CloudServiceAutoDownloadWidget *autowidget; }; #endif // _GC_AnalysisSidebar_h diff --git a/src/Gui/ManualRideDialog.cpp b/src/Gui/ManualRideDialog.cpp index 7cf0b84ea..ccc533461 100644 --- a/src/Gui/ManualRideDialog.cpp +++ b/src/Gui/ManualRideDialog.cpp @@ -546,7 +546,7 @@ ManualRideDialog::okClicked() if (success) { // refresh metric db etc - context->athlete->addRide(basename + ".json"); + context->athlete->addRide(basename + ".json", true); accept(); } else { diff --git a/src/Gui/RideNavigator.cpp b/src/Gui/RideNavigator.cpp index 9242cad9b..f70458342 100644 --- a/src/Gui/RideNavigator.cpp +++ b/src/Gui/RideNavigator.cpp @@ -124,6 +124,7 @@ RideNavigator::RideNavigator(Context *context, bool mainwindow) : GcChartWindow( // refresh when rides added/removed connect(context, SIGNAL(rideAdded(RideItem*)), this, SLOT(refresh())); connect(context, SIGNAL(rideDeleted(RideItem*)), this, SLOT(rideDeleted(RideItem*))); + connect(context, SIGNAL(rideSelected(RideItem*)), this, SLOT(setRide(RideItem*))); // user selected a ride on the ride list, we should reflect that too.. connect(tableView, SIGNAL(rowSelected(QItemSelection)), this, SLOT(selectionChanged(QItemSelection))); @@ -949,7 +950,8 @@ RideNavigator::showColumnChooser() void RideNavigator::setRide(RideItem*rideItem) { - if (currentItem == rideItem) return; + // if we have a selection and its this one just ignore. + if (currentItem == rideItem && tableView->selectionModel()->selectedRows().count() != 0) return; for (int i=0; imodel()->rowCount(); i++) {