diff --git a/src/Athlete.cpp b/src/Athlete.cpp index 915b767d3..40fc8e038 100644 --- a/src/Athlete.cpp +++ b/src/Athlete.cpp @@ -45,6 +45,8 @@ #include "LTMSettings.h" #include "Route.h" #include "RouteWindow.h" +#include "RideImportWizard.h" + #include "GcUpgrade.h" // upgrade wizard #include "GcCrashDialog.h" // recovering from a crash? @@ -501,3 +503,41 @@ Athlete::configChanged() } } } + +void +Athlete::importFilesWithoutDialog() { + + int importSettings = appsettings->cvalue(context->athlete->cyclist, GC_IMPORTSETTINGS).toInt(); // default/unset = 0 + if (importSettings == 0) return; // no autoimport requested + + QVariant importDirQV = appsettings->cvalue(context->athlete->cyclist, GC_IMPORTDIR, ""); + QString importDirectory = importDirQV.toString(); + if (importDirectory == "") return; // no implicit assumptions on the directory - only explicite is allowed + + // now get the files formats + const RideFileFactory &rff = RideFileFactory::instance(); + QStringList suffixList = rff.suffixes(); + suffixList.replaceInStrings(QRegExp("^"), "*."); + QStringList allFormats; + QFileInfoList fileInfos; + foreach(QString suffix, rff.suffixes()) + allFormats << QString("*.%1").arg(suffix); + + // and now the files for the GC formats / no SubDirs considered + QDir *importDir = new QDir (importDirectory); + if (!importDir->exists()) return; // directory might not be available (USB,..) + if (!importDir->isReadable()) return; // check if directory is readable, + + // now get the files with their full names + fileInfos = importDir->entryInfoList(allFormats, QDir::Files, QDir::NoSort); + if (!fileInfos.isEmpty()) { + QStringList fileNames; + foreach(QFileInfo f, fileInfos) { + fileNames.append(f.absoluteFilePath()); + } + RideImportWizard *import = new RideImportWizard(fileNames, context->athlete->home, context); + if (importSettings == 1) import->setDialogMode(RideImportWizard::allButDupFileErrors); + if (importSettings == 2) import->setDialogMode(RideImportWizard::allErrors); + import->process(); + } +} diff --git a/src/Athlete.h b/src/Athlete.h index a0fa60257..c5411d9a9 100644 --- a/src/Athlete.h +++ b/src/Athlete.h @@ -124,6 +124,9 @@ class Athlete : public QObject void notifySeasonsChanged() { seasonsChanged(); } void notifyNamedSearchesChanged() { namedSearchesChanged(); } + // import rides from athlete specific directory + void importFilesWithoutDialog(); + signals: void zonesChanged(); void seasonsChanged(); @@ -136,5 +139,6 @@ class Athlete : public QObject void updateRideFileIntervals(); void configChanged(); + }; #endif diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index 917adf4b1..97094b282 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -1387,6 +1387,7 @@ MainWindow::openWindow(QString name) // main window will register itself MainWindow *main = new MainWindow(home); main->show(); + main->ridesAutoImport(); } void @@ -1437,6 +1438,9 @@ MainWindow::openTab(QString name) showTabbar(true); setUpdatesEnabled(true); + + // now do the automatic ride file import + context->athlete->importFilesWithoutDialog(); } void @@ -1947,3 +1951,10 @@ MainWindow::addIntervals() currentTab->addIntervals(); } +void +MainWindow::ridesAutoImport() { + + currentTab->context->athlete->importFilesWithoutDialog(); + +} + diff --git a/src/MainWindow.h b/src/MainWindow.h index 924698cc7..5c3984287 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -189,6 +189,9 @@ class MainWindow : public QMainWindow void revertRide(); bool saveRideExitDialog(Context *); // save dirty rides on exit dialog + // autoload rides from athlete specific directory (preferences) + void ridesAutoImport(); + // save and restore state to context void saveGCState(Context *); void restoreGCState(Context *); diff --git a/src/Pages.cpp b/src/Pages.cpp index f85c9b91d..86ac00b3a 100644 --- a/src/Pages.cpp +++ b/src/Pages.cpp @@ -780,6 +780,20 @@ RiderPage::RiderPage(QWidget *parent, Context *context) : QWidget(parent), conte avatarButton->setFixedHeight(140); avatarButton->setFixedWidth(140); + QVariant importDir = appsettings->cvalue(context->athlete->cyclist, GC_IMPORTDIR, ""); + + importLabel = new QLabel(tr("Auto Import from")); + importDirectory = new QLineEdit; + importDirectory->setText(importDir.toString()); + importBrowseButton = new QPushButton(tr("Browse")); + importBrowseButton->setFixedWidth(120); + + importSetting = new QComboBox(); + importSetting->addItem("No auto import"); + importSetting->addItem("No duplicate file errors"); + importSetting->addItem("All errors"); + importSetting->setCurrentIndex(appsettings->cvalue(context->athlete->cyclist, GC_IMPORTSETTINGS).toInt()); // default/unset = 0 + Qt::Alignment alignment = Qt::AlignLeft|Qt::AlignVCenter; grid->addWidget(nicklabel, 0, 0, alignment); @@ -797,11 +811,18 @@ RiderPage::RiderPage(QWidget *parent, Context *context) : QWidget(parent), conte grid->addWidget(bio, 6, 0, 1, 4); grid->addWidget(avatarButton, 0, 2, 4, 2, Qt::AlignRight|Qt::AlignVCenter); + + grid->addWidget(importLabel, 7,0, alignment); + grid->addWidget(importDirectory, 7,1, alignment); + grid->addWidget(importBrowseButton, 7,2, alignment); + grid->addWidget(importSetting, 7,3, alignment); + all->addLayout(grid); all->addStretch(); connect (avatarButton, SIGNAL(clicked()), this, SLOT(chooseAvatar())); connect (unitCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(unitChanged(int))); + connect (importBrowseButton, SIGNAL(clicked()), this, SLOT(browseImportDir())); } void @@ -832,6 +853,15 @@ RiderPage::unitChanged(int currentIndex) } } +void +RiderPage::browseImportDir() +{ + QString dir = QFileDialog::getExistingDirectory(this, tr("Select Import Directory"), + "", QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks); + if (dir != "") importDirectory->setText(dir); //only overwrite current dir, if a new was selected +} + + void RiderPage::saveClicked() { @@ -847,6 +877,11 @@ RiderPage::saveClicked() appsettings->setCValue(context->athlete->cyclist, GC_SEX, sex->currentIndex()); appsettings->setCValue(context->athlete->cyclist, GC_BIO, bio->toPlainText()); avatar.save(context->athlete->home.absolutePath() + "/" + "avatar.png", "PNG"); + + // save the import settings + appsettings->setCValue(context->athlete->cyclist, GC_IMPORTSETTINGS, importSetting->currentIndex()); + appsettings->setCValue(context->athlete->cyclist, GC_IMPORTDIR, importDirectory->text()); + } // diff --git a/src/Pages.h b/src/Pages.h index 6057f665b..36fa15e98 100644 --- a/src/Pages.h +++ b/src/Pages.h @@ -111,6 +111,7 @@ class RiderPage : public QWidget public slots: void chooseAvatar(); void unitChanged(int currentIndex); + void browseImportDir(); private: Context *context; @@ -124,6 +125,11 @@ class RiderPage : public QWidget QTextEdit *bio; QPushButton *avatarButton; QPixmap avatar; + QLineEdit *importDirectory; + QPushButton *importBrowseButton; + QLabel *importLabel; + QComboBox *importSetting; + }; class CredentialsPage : public QScrollArea diff --git a/src/RideImportWizard.cpp b/src/RideImportWizard.cpp index 7bee20b29..0392c2977 100644 --- a/src/RideImportWizard.cpp +++ b/src/RideImportWizard.cpp @@ -36,6 +36,7 @@ // drag and drop passes urls ... convert to a list of files and call main constructor RideImportWizard::RideImportWizard(QList *urls, QDir &home, Context *context, QWidget *parent) : QDialog(parent), context(context) { + dialogMode = standardDialog; setAttribute(Qt::WA_DeleteOnClose); setWindowFlags(windowFlags() | Qt::WindowStaysOnTopHint); QList filenames; @@ -47,6 +48,7 @@ RideImportWizard::RideImportWizard(QList *urls, QDir &home, Context *conte RideImportWizard::RideImportWizard(QList files, QDir &home, Context *context, QWidget *parent) : QDialog(parent), context(context) { + dialogMode = standardDialog; init(files, home, context); } @@ -203,14 +205,23 @@ RideImportWizard::init(QList files, QDir &home, Context * /*mainWindow* tableWidget->adjustSize(); - // Refresh prior to running down the list & processing... - this->show(); } int RideImportWizard::process() { + // Refresh prior to running down the list & processing... + // do it here and not in Constructor, since "dialogMode" might have been changed + if (dialogMode == standardDialog) show(); + + // set if any error occurs in one of the stages, for the "non-Dialog" modes - to make the widget visible + // if an error in one of the steps has happened - first error is set after the first error has occured + // and the widget has been made visible (in case of noDialog mode)- from that point on, no need + // to "show()" the widget again + bool firstError = false; + bool sectionError = false; + // set progress bar limits - for each file we // will make 5 passes over the files // 1. checking it is a file ane readable @@ -244,11 +255,13 @@ RideImportWizard::process() } else { tableWidget->item(i,5)->setText(tr("Error - Unknown file type")); + sectionError = true; } } else { // Cannot open tableWidget->item(i,5)->setText(tr("Error - Not a valid file")); + sectionError = true; } progressBar->setValue(progressBar->value()+1); @@ -256,6 +269,11 @@ RideImportWizard::process() } if (aborted) { done(0); } + if ((dialogMode == allErrors || dialogMode == allButDupFileErrors) && sectionError) { + firstError = true; + show(); // make the widget visible in case an error has happened errors + sectionError = false; // reset Error flag for next section + } repaint(); QApplication::processEvents(); @@ -380,8 +398,10 @@ RideImportWizard::process() // ride != NULL but !errors.isEmpty() means they're just warnings if (errors.isEmpty()) tableWidget->item(i,5)->setText(tr("Validated")); - else + else { tableWidget->item(i,5)->setText(tr("Warning - ") + errors.join(tr(" "))); + sectionError = true; + } // Set Date and Time if (ride->startTime().isNull()) { @@ -390,6 +410,7 @@ RideImportWizard::process() blanks[i] = true; tableWidget->item(i,1)->setText(tr("")); tableWidget->item(i,2)->setText(tr("")); + sectionError = true; } else { @@ -434,11 +455,17 @@ RideImportWizard::process() } else { // nope - can't handle this file tableWidget->item(i,5)->setText(tr("Error - ") + errors.join(tr(" "))); + sectionError = true; } } progressBar->setValue(progressBar->value()+1); QApplication::processEvents(); if (aborted) { done(0); } + if ((dialogMode == allErrors || dialogMode == allButDupFileErrors) && sectionError && !firstError) { + firstError = true; + show(); // make the widget visible in case an error has happened errors + sectionError = false; // reset Error flag for next section + } this->repaint(); next:; @@ -463,6 +490,11 @@ RideImportWizard::process() progressBar->setValue(progressBar->value()+1); progressBar->repaint(); } + // dialog has to show up for this import to complete the data + if (needdates != 0 && (dialogMode == allErrors || dialogMode == allButDupFileErrors) && !firstError) { + firstError = true; + show(); // make the widget visible in case an error has happened errors + } // Wait for user to press save abortButton->setText(tr("Save")); @@ -490,6 +522,15 @@ RideImportWizard::process() } connect(tableWidget, SIGNAL(itemChanged(QTableWidgetItem *)), this, SLOT(activateSave())); + // if running in any "dialog only in error cases mode" and no Error have occured until now, + // we need to trigger the final saving step + if ((dialogMode == allErrors || dialogMode == allButDupFileErrors) && !firstError) { + abortClicked(); // simulate user input - at this point "Abort" == "Save" + // in additional simulate the "Finish" user-click + context->athlete->isclean = false; + context->athlete->metricDB->refreshMetrics(); + } + return 0; } @@ -693,6 +734,9 @@ removeDuplicate(QString filename) void RideImportWizard::abortClicked() { + // similar procedure for "non-Dialog" processing to handle errors like in "process()" here + bool sectionError = false; + bool fileExistsError = false; // if done when labelled abort we kill off this dialog QString label = abortButton->text(); @@ -792,6 +836,7 @@ RideImportWizard::abortClicked() duplicates = findDuplicates(fulltarget); if (duplicates.count() && !overwriteFiles) { tableWidget->item(i,5)->setText(tr("Error - File exists")); + fileExistsError = true; } else { // wipe away the duplicate @@ -858,13 +903,17 @@ RideImportWizard::abortClicked() if (temp.rename(fulltarget)) { tableWidget->item(i,5)->setText(tr("File Overwritten")); //no need to add since its already there! - } else + } else { tableWidget->item(i,5)->setText(tr("Error - overwrite failed")); + sectionError = true; + } } else { tableWidget->item(i,5)->setText(tr("Error - overwrite failed")); + sectionError = true; } } else { tableWidget->item(i,5)->setText(tr("Error - File exists")); + fileExistsError = true; } } else { tableWidget->item(i,5)->setText(tr("Saving file...")); @@ -879,13 +928,19 @@ RideImportWizard::abortClicked() // BUT! Some charts/windows will hava snaffled away the ridefile // pointer which is now invalid so once all the rides have been imported // we need to select the last one... see below - } else + } else { tableWidget->item(i,5)->setText(tr("Error - copy failed")); + sectionError = true; + } } } QApplication::processEvents(); if (aborted) { done(0); } progressBar->setValue(progressBar->value()+1); + if (((sectionError || fileExistsError) && (dialogMode == allErrors)) || + ((sectionError && !fileExistsError) && (dialogMode == allButDupFileErrors))) { + show(); // make the widget visible in case an error was logged + } this->repaint(); } @@ -906,6 +961,12 @@ RideImportWizard::abortClicked() aborted = false; } + +void RideImportWizard::setDialogMode(int mode) { + + dialogMode = mode; +} + // clean up files RideImportWizard::~RideImportWizard() { diff --git a/src/RideImportWizard.h b/src/RideImportWizard.h index 3e5c67631..8fd892a82 100644 --- a/src/RideImportWizard.h +++ b/src/RideImportWizard.h @@ -46,6 +46,8 @@ public: RideImportWizard(QList files, QDir &home, Context *context, QWidget *parent = 0); ~RideImportWizard(); int process(); + void setDialogMode(int); // default is fullDialog + enum DialogMode { standardDialog, allErrors, allButDupFileErrors }; signals: @@ -62,6 +64,7 @@ private: QList blanks; // record of which have a RideFileReader returned date & time QDir home; // target directory bool aborted; + int dialogMode; // see enum QLabel *phaseLabel; QTableWidget *tableWidget; QProgressBar *progressBar; diff --git a/src/Settings.h b/src/Settings.h index a1307491f..3762f4096 100644 --- a/src/Settings.h +++ b/src/Settings.h @@ -56,6 +56,8 @@ #define GC_SEX "sex" #define GC_BIO "bio" #define GC_AVATAR "avatar" +#define GC_IMPORTDIR "importDir" +#define GC_IMPORTSETTINGS "importSettings" #define GC_SETTINGS_LAST_IMPORT_PATH "mainwindow/lastImportPath" #define GC_SETTINGS_LAST_WORKOUT_PATH "mainwindow/lastWorkoutPath" #define GC_LAST_DOWNLOAD_DEVICE "mainwindow/lastDownloadDevice" diff --git a/src/main.cpp b/src/main.cpp index 9bbb3baf4..abf6c774f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -286,6 +286,7 @@ main(int argc, char *argv[]) if (home.cd(cyclist)) { MainWindow *mainWindow = new MainWindow(home); mainWindow->show(); + mainWindow->ridesAutoImport(); home.cdUp(); anyOpened = true; } @@ -314,6 +315,7 @@ main(int argc, char *argv[]) // .. and open a mainwindow MainWindow *mainWindow = new MainWindow(home); mainWindow->show(); + mainWindow->ridesAutoImport(); } ret=application->exec();