From 6f116ab07db0aaea7fc6909c8866f4a58db5a424 Mon Sep 17 00:00:00 2001 From: Mark Liversedge Date: Wed, 12 Oct 2011 18:00:58 +0100 Subject: [PATCH] File Export (part 2 of 2) Added a function for Batch Export of current activity history. The user can select files to export, the target directory and format to use. This completes the updates to improve export functionality. Fixes #476. --- src/BatchExportDialog.cpp | 250 ++++++++++++++++++++++++++++++++++++++ src/BatchExportDialog.h | 80 ++++++++++++ src/GcRideFile.cpp | 2 +- src/JsonRideFile.y | 2 +- src/KmlRideFile.cpp | 2 +- src/MainWindow.cpp | 8 +- src/TcxRideFile.cpp | 2 +- src/src.pro | 2 + 8 files changed, 341 insertions(+), 7 deletions(-) create mode 100644 src/BatchExportDialog.cpp create mode 100644 src/BatchExportDialog.h diff --git a/src/BatchExportDialog.cpp b/src/BatchExportDialog.cpp new file mode 100644 index 000000000..f67aa3189 --- /dev/null +++ b/src/BatchExportDialog.cpp @@ -0,0 +1,250 @@ + +/* + * Copyright (c) 2011 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 "BatchExportDialog.h" + +BatchExportDialog::BatchExportDialog(MainWindow *main) : QDialog(main), main(main) +{ + setAttribute(Qt::WA_DeleteOnClose); + setWindowFlags(windowFlags() | Qt::WindowStaysOnTopHint); + setWindowTitle(tr("Activity Batch Export")); + + // make the dialog a resonable size + setMinimumWidth(550); + setMinimumHeight(400); + + QVBoxLayout *layout = new QVBoxLayout; + setLayout(layout); + + files = new QTreeWidget; + files->headerItem()->setText(0, tr("")); + files->headerItem()->setText(1, tr("Filename")); + files->headerItem()->setText(2, tr("Date")); + files->headerItem()->setText(3, tr("Time")); + files->headerItem()->setText(4, tr("Action")); + + files->setColumnCount(5); + files->setColumnWidth(0, 30); // selector + files->setColumnWidth(1, 190); // filename + files->setColumnWidth(2, 95); // date + files->setColumnWidth(3, 90); // time + files->setSelectionMode(QAbstractItemView::SingleSelection); + files->setEditTriggers(QAbstractItemView::SelectedClicked); // allow edit + files->setUniformRowHeights(true); + files->setIndentation(0); + + // populate with each ride in the ridelist + const QTreeWidgetItem *allRides = main->allRideItems(); + + for (int i=0; ichildCount(); i++) { + + RideItem *rideItem = static_cast(allRides->child(i)); + + QTreeWidgetItem *add = new QTreeWidgetItem(files->invisibleRootItem()); + add->setFlags(add->flags() | Qt::ItemIsEditable); + + // selector + QCheckBox *checkBox = new QCheckBox("", this); + checkBox->setChecked(true); + files->setItemWidget(add, 0, checkBox); + + // we will wipe the original file + add->setText(1, rideItem->fileName); + add->setText(2, rideItem->dateTime.toString(tr("dd MMM yyyy"))); + add->setText(3, rideItem->dateTime.toString(tr("hh:mm:ss ap"))); + + // interval action + add->setText(4, "Export"); + } + + // format and directory + QGridLayout *grid = new QGridLayout; + formatLabel = new QLabel("Export as", this); + format = new QComboBox(this); + + const RideFileFactory &rff = RideFileFactory::instance(); + foreach(QString suffix, rff.writeSuffixes()) format->addItem(rff.description(suffix)); + + selectDir = new QPushButton("Browse", this); + dirLabel = new QLabel ("Export to", this); + dirName = new QLabel(QDir::home().absolutePath(), this); + all = new QCheckBox("check/uncheck all", this); + all->setChecked(true); + + grid->addWidget(formatLabel, 0,0, Qt::AlignLeft); + grid->addWidget(format, 0,1, Qt::AlignLeft); + grid->addWidget(dirLabel, 1,0, Qt::AlignLeft); + grid->addWidget(dirName, 1,1, Qt::AlignLeft); + grid->addWidget(selectDir, 1,2, Qt::AlignLeft); + grid->addWidget(all, 2,0, Qt::AlignLeft); + grid->setColumnStretch(0, 1); + grid->setColumnStretch(1, 10); + + // buttons + QHBoxLayout *buttons = new QHBoxLayout; + status = new QLabel("", this); + status->hide(); + overwrite = new QCheckBox("Overwrite existing files", this); + cancel = new QPushButton("Cancel", this); + ok = new QPushButton("Export", this); + buttons->addWidget(overwrite); + buttons->addWidget(status); + buttons->addStretch(); + buttons->addWidget(cancel); + buttons->addWidget(ok); + + layout->addLayout(grid); + layout->addWidget(files); + layout->addLayout(buttons); + + exports = fails = 0; + + // connect signals and slots up.. + connect(selectDir, SIGNAL(clicked()), this, SLOT(selectClicked())); + connect(ok, SIGNAL(clicked()), this, SLOT(okClicked())); + connect(all, SIGNAL(stateChanged(int)), this, SLOT(allClicked())); + connect(cancel, SIGNAL(clicked()), this, SLOT(cancelClicked())); +} + +void +BatchExportDialog::selectClicked() +{ + QString dir = QFileDialog::getExistingDirectory(this, tr("Select Target Directory"), + dirName->text(), + QFileDialog::ShowDirsOnly + | QFileDialog::DontResolveSymlinks); + if (dir!="") dirName->setText(dir); + return; +} + +void +BatchExportDialog::allClicked() +{ + // set/uncheck all rides according to the "all" + bool checked = all->isChecked(); + + for(int i=0; iinvisibleRootItem()->childCount(); i++) { + QTreeWidgetItem *current = files->invisibleRootItem()->child(i); + static_cast(files->itemWidget(current,0))->setChecked(checked); + } +} + +void +BatchExportDialog::okClicked() +{ + if (ok->text() == "Export") { + aborted = false; + + overwrite->hide(); + status->setText("Exporting..."); + status->show(); + cancel->hide(); + ok->setText("Abort"); + exportFiles(); + status->setText(QString("%1 activities exported, %2 failed or skipped.").arg(exports).arg(fails)); + ok->setText("Finish"); + + } else if (ok->text() == "Abort") { + aborted = true; + } else if (ok->text() == "Finish") { + accept(); // our work is done! + } +} + +void +BatchExportDialog::cancelClicked() +{ + reject(); +} + +void +BatchExportDialog::exportFiles() +{ + // what format to export as? + QString type = RideFileFactory::instance().writeSuffixes().at(format->currentIndex()); + + // loop through the table and export all selected + for(int i=0; iinvisibleRootItem()->childCount(); i++) { + + // give user a chance to abort.. + QApplication::processEvents(); + + // did they? + if (aborted == true) return; // user aborted! + + QTreeWidgetItem *current = files->invisibleRootItem()->child(i); + + // is it selected + if (static_cast(files->itemWidget(current,0))->isChecked()) { + + files->setCurrentItem(current); QApplication::processEvents(); + + QString filename = dirName->text() + "/" + QFileInfo(current->text(1)).baseName() + "." + type; + + if (QFile(filename).exists()) { + if (overwrite->isChecked() == false) { + // skip existing files + current->setText(4, "Exists - not exported"); QApplication::processEvents(); + fails++; + continue; + + } else { + + // remove existing + QFile(filename).remove(); + current->setText(4, "Removing..."); QApplication::processEvents(); + } + + } + // this one then + current->setText(4, "Reading..."); QApplication::processEvents(); + + // open it.. + QStringList errors; + QList rides; + QFile thisfile(QString(main->home.absolutePath()+"/"+current->text(1))); + RideFile *ride = RideFileFactory::instance().openRideFile(main, thisfile, errors, &rides); + + // open success? + if (ride) { + + + current->setText(4, "Writing..."); QApplication::processEvents(); + QFile out(filename); + bool success = RideFileFactory::instance().writeRideFile(main, ride, out, type); + + if (success) { + exports++; + current->setText(4, "Exported"); QApplication::processEvents(); + } else { + fails++; + current->setText(4, "Write failed"); QApplication::processEvents(); + } + + delete ride; // free memory! + + // open failed + } else { + + current->setText(4, "Read error"); QApplication::processEvents(); + + } + } + } +} diff --git a/src/BatchExportDialog.h b/src/BatchExportDialog.h new file mode 100644 index 000000000..c3c958988 --- /dev/null +++ b/src/BatchExportDialog.h @@ -0,0 +1,80 @@ + +/* + * Copyright (c) 2011 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 _BatchExportDialog_h +#define _BatchExportDialog_h +#include "GoldenCheetah.h" +#include "MainWindow.h" +#include "Settings.h" +#include "Units.h" + +#include "RideItem.h" +#include "RideFile.h" + +#include +#include +#include +#include +#include +#include +#include + +// Dialog class to show filenames, import progress and to capture user input +// of ride date and time + +class BatchExportDialog : public QDialog +{ + Q_OBJECT + G_OBJECT + + +public: + BatchExportDialog(MainWindow *main); + + QTreeWidget *files; // choose files to export + +signals: + +private slots: + void cancelClicked(); + void okClicked(); + void selectClicked(); + void exportFiles(); + void allClicked(); + +private: + MainWindow *main; + bool aborted; + + QCheckBox *all; + + QComboBox *format; + QLabel *formatLabel; + + QPushButton *selectDir; + QLabel *dirLabel, *dirName; + + QCheckBox *overwrite; + QPushButton *cancel, *ok; + + int exports, fails; + QLabel *status; +}; +#endif // _BatchExportDialog_h + diff --git a/src/GcRideFile.cpp b/src/GcRideFile.cpp index 465184eb0..5e1c11ff0 100644 --- a/src/GcRideFile.cpp +++ b/src/GcRideFile.cpp @@ -28,7 +28,7 @@ static int gcFileReaderRegistered = RideFileFactory::instance().registerReader( - "gc", "GoldenCheetah XML Format", new GcFileReader()); + "gc", "GoldenCheetah XML", new GcFileReader()); RideFile * GcFileReader::openRideFile(QFile &file, QStringList &errors, QList*) const diff --git a/src/JsonRideFile.y b/src/JsonRideFile.y index 2c566f13f..585de63d8 100644 --- a/src/JsonRideFile.y +++ b/src/JsonRideFile.y @@ -226,7 +226,7 @@ string: STRING { JsonString = unprotect(JsonRideFiletex static int jsonFileReaderRegistered = RideFileFactory::instance().registerReader( - "json", "GoldenCheetah Json Format", new JsonFileReader()); + "json", "GoldenCheetah Json", new JsonFileReader()); RideFile * JsonFileReader::openRideFile(QFile &file, QStringList &errors, QList*) const diff --git a/src/KmlRideFile.cpp b/src/KmlRideFile.cpp index 87286f15a..234b4f81e 100644 --- a/src/KmlRideFile.cpp +++ b/src/KmlRideFile.cpp @@ -62,7 +62,7 @@ using kmldom::StyleMapPtr; static int kmlFileReaderRegistered = RideFileFactory::instance().registerReader( - "kml", "Google Earth KML Format", new KmlFileReader()); + "kml", "Google Earth KML", new KmlFileReader()); // // Utility functions // diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index 31ed4e44e..dd9d5e263 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -52,6 +52,7 @@ #include "ToolsDialog.h" #include "MetricAggregator.h" #include "SplitActivityWizard.h" +#include "BatchExportDialog.h" #include "TwitterDialog.h" #include "WithingsDownload.h" #include "CalendarDownload.h" @@ -440,8 +441,8 @@ MainWindow::MainWindow(const QDir &home) : rideMenu->addAction(tr("&Import from file..."), this, SLOT (importFile()), tr ("Ctrl+I")); rideMenu->addAction(tr("&Manual activity entry..."), this, SLOT(manualRide()), tr("Ctrl+M")); rideMenu->addSeparator (); - rideMenu->addAction(tr("&Export ..."), this, SLOT(exportRide()), tr("Ctrl+E")); - rideMenu->addAction(tr("&Batch export ..."), this, SLOT(exportBatch()), tr("Ctrl+B")); + rideMenu->addAction(tr("&Export..."), this, SLOT(exportRide()), tr("Ctrl+E")); + rideMenu->addAction(tr("&Batch export..."), this, SLOT(exportBatch()), tr("Ctrl+B")); rideMenu->addAction(tr("Export Metrics as CSV..."), this, SLOT(exportMetrics()), tr("")); #ifdef GC_HAVE_SOAP rideMenu->addSeparator (); @@ -1090,7 +1091,8 @@ MainWindow::currentRide() void MainWindow::exportBatch() { - // XXX todo + BatchExportDialog *d = new BatchExportDialog(this); + d->exec(); } void diff --git a/src/TcxRideFile.cpp b/src/TcxRideFile.cpp index 2c8e6ffce..945dfc591 100644 --- a/src/TcxRideFile.cpp +++ b/src/TcxRideFile.cpp @@ -30,7 +30,7 @@ static int tcxFileReaderRegistered = RideFileFactory::instance().registerReader( - "tcx", "Garmin Training Centre", new TcxFileReader()); + "tcx", "Garmin Training Centre TCX", new TcxFileReader()); RideFile *TcxFileReader::openRideFile(QFile &file, QStringList &errors, QList*list) const { diff --git a/src/src.pro b/src/src.pro index 4ba29e669..64ee02849 100644 --- a/src/src.pro +++ b/src/src.pro @@ -163,6 +163,7 @@ HEADERS += \ ANTMessages.h \ ANTlocalController.h \ ANTplusController.h \ + BatchExportDialog.h \ BestIntervalDialog.h \ BinRideFile.h \ BingMap.h \ @@ -325,6 +326,7 @@ SOURCES += \ ANTlocalController.cpp \ ANTplusController.cpp \ BasicRideMetrics.cpp \ + BatchExportDialog.cpp \ BestIntervalDialog.cpp \ BikeScore.cpp \ BinRideFile.cpp \