Files
GoldenCheetah/src/RideImportWizard.cpp
Mark Liversedge 7a0634cdda Missed off previous commit
An errant git rebase lost these changes, this is
to support the GTC export file support and updates
the Json parser to use the new call semantics and
gets the Import wizard to clear up temporary files
when it closes.
2011-08-05 21:37:28 +01:00

998 lines
37 KiB
C++

/*
* Copyright (c) 2006 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
*/
#include <assert.h>
#include <QDebug>
#include "RideItem.h"
#include "RideFile.h"
#include "RideImportWizard.h"
#include "MainWindow.h"
#include "QuarqRideFile.h"
#include <QWaitCondition>
#include "Settings.h"
#include "Units.h"
#include "GcRideFile.h"
#include "JsonRideFile.h"
#include "TcxRideFile.h"
// drag and drop passes urls ... convert to a list of files and call main constructor
RideImportWizard::RideImportWizard(QList<QUrl> *urls, QDir &home, MainWindow *main, QWidget *parent) : QDialog(parent), mainWindow(main)
{
setAttribute(Qt::WA_DeleteOnClose);
setWindowFlags(windowFlags() | Qt::WindowStaysOnTopHint);
QList<QString> filenames;
for (int i=0; i<urls->count(); i++)
filenames.append(QFileInfo(urls->value(i).toLocalFile()).absoluteFilePath());
init(filenames, home, mainWindow);
filenames.clear();
}
RideImportWizard::RideImportWizard(QList<QString> files, QDir &home, MainWindow *main, QWidget *parent) : QDialog(parent), mainWindow(main)
{
init(files, home, mainWindow);
}
void
RideImportWizard::init(QList<QString> files, QDir &home, MainWindow * /*mainWindow*/)
{
// initialise dialog box
tableWidget = new QTableWidget(files.count(), 6, this);
tableWidget->setItemDelegate(new RideDelegate(1)); // use a delegate for column 1 date
tableWidget->verticalHeader()->setDefaultSectionSize(20);
phaseLabel = new QLabel;
progressBar = new QProgressBar();
todayButton = new QComboBox();
todayButton->addItem(tr("Select Date..."));
todayButton->addItem(tr("Today"));
todayButton->addItem(tr("Last Monday"));
todayButton->addItem(tr("Last Tuesday"));
todayButton->addItem(tr("Last Wednesday"));
todayButton->addItem(tr("Last Thursday"));
todayButton->addItem(tr("Last Friday"));
todayButton->addItem(tr("Last Saturday"));
todayButton->addItem(tr("Last Sunday"));
todayButton->addItem(tr("Choose Date"));
cancelButton = new QPushButton(tr("Cancel"));
abortButton = new QPushButton(tr("Abort"));
overFiles = new QCheckBox(tr("Overwrite Existing Files"));
// initially the cancel, overwrite and today widgets are hidden
// they only appear whilst we are asking the user for dates
cancelButton->setHidden(true);
todayButton->setHidden(true);
overFiles->setHidden(true);
overwriteFiles = false;
aborted = false;
// NOTE: abort button morphs into save and finish button later
connect(abortButton, SIGNAL(clicked()), this, SLOT(abortClicked()));
// only used when editing dates
connect(todayButton, SIGNAL(activated(int)), this, SLOT(todayClicked(int)));
connect(cancelButton, SIGNAL(clicked()), this, SLOT(cancelClicked()));
connect(overFiles, SIGNAL(clicked()), this, SLOT(overClicked()));
// title & headings
setWindowTitle(tr("Import Ride Files"));
QTableWidgetItem *filenameHeading = new QTableWidgetItem;
filenameHeading->setText(tr("Filename"));
tableWidget->setHorizontalHeaderItem(0, filenameHeading);
QTableWidgetItem *dateHeading = new QTableWidgetItem;
dateHeading->setText(tr("Date"));
tableWidget->setHorizontalHeaderItem(1, dateHeading);
QTableWidgetItem *timeHeading = new QTableWidgetItem;
timeHeading->setText(tr("Time"));
tableWidget->setHorizontalHeaderItem(2, timeHeading);
QTableWidgetItem *durationHeading = new QTableWidgetItem;
durationHeading->setText(tr("Duration"));
tableWidget->setHorizontalHeaderItem(3, durationHeading);
QTableWidgetItem *distanceHeading = new QTableWidgetItem;
distanceHeading->setText(tr("Distance"));
tableWidget->setHorizontalHeaderItem(4, distanceHeading);
QTableWidgetItem *statusHeading = new QTableWidgetItem;
statusHeading->setText(tr("Import Status"));
tableWidget->setHorizontalHeaderItem(5, statusHeading);
// save target dir
this->home = home;
// Fill in the filenames and all the textItems
for (int i=0; i < files.count(); i++) {
QTableWidgetItem *t;
filenames.append(QFileInfo(files[i]).absoluteFilePath());
blanks.append(true); // by default editable
// Filename
t = new QTableWidgetItem();
t->setText(QFileInfo(files[i]).fileName());
t->setFlags(t->flags() & (~Qt::ItemIsEditable));
tableWidget->setItem(i,0,t);
// Date
t = new QTableWidgetItem();
t->setText(tr(""));
t->setFlags(t->flags() | Qt::ItemIsEditable);
t->setBackgroundColor(Qt::red);
tableWidget->setItem(i,1,t);
// Time
t = new QTableWidgetItem();
t->setText(tr(""));
t->setFlags(t->flags() | Qt::ItemIsEditable);
tableWidget->setItem(i,2,t);
// Duration
t = new QTableWidgetItem();
t->setText(tr(""));
t->setFlags(t->flags() & (~Qt::ItemIsEditable));
tableWidget->setItem(i,3,t);
// Distance
t = new QTableWidgetItem();
t->setText(tr(""));
t->setFlags(t->flags() & (~Qt::ItemIsEditable));
tableWidget->setItem(i,4,t);
// Import Status
t = new QTableWidgetItem();
t->setText(tr(""));
t->setFlags(t->flags() & (~Qt::ItemIsEditable));
tableWidget->setItem(i,5,t);
}
// put into our dialog box
// layout
QHBoxLayout *buttons = new QHBoxLayout;
buttons->addWidget(phaseLabel);
buttons->addStretch();
buttons->addWidget(todayButton);
buttons->addStretch();
buttons->addWidget(overFiles);
buttons->addWidget(cancelButton);
buttons->addWidget(abortButton);
QVBoxLayout *contents = new QVBoxLayout(this);
contents->addWidget(tableWidget);
contents->addWidget(progressBar);
contents->addLayout(buttons);
setLayout(contents);
// adjust all the sizes to look tidy
tableWidget->setColumnWidth(0, 200); // filename
tableWidget->setColumnWidth(1, 120); // date
tableWidget->setColumnWidth(2, 120); // time
tableWidget->setColumnWidth(3, 100); // duration
tableWidget->setColumnWidth(4, 70); // distance
tableWidget->setColumnWidth(5, 250); // status
// max height for 16 items and a scrollbar on right if > 16 items
// for some reason the window is wider for 10-16 items too.
// someone that understands width/geometry and layouts better
// than me should clean up the logic here, no doubt using a
// single call to geometry/hint. But for now it looks good
// on Leopard, no doubt not so good on Windows
resize(920 +
((files.count() > 16 ? 24 : 0) +
(files.count() > 9 && files.count() < 17) ? 8 : 0),
118 + (files.count() > 16 ? 17*20 : (files.count()+1) * 20));
tableWidget->adjustSize();
// Refresh prior to running down the list & processing...
this->show();
}
int
RideImportWizard::process()
{
// set progress bar limits - for each file we
// will make 5 passes over the files
// 1. checking it is a file ane readable
// 2. parsing it with the RideFileReader
// 3. [optional] collect date/time information from user
// 4. copy file into Library
// 5. Process for CPI (not implemented yet)
// So, therefore the progress bar runs from 0 to files*4. (since step 5 is not implemented yet)
progressBar->setMinimum(0);
progressBar->setMaximum(filenames.count()*4);
// Pass one - Is it valid?
phaseLabel->setText(tr("Step 1 of 4: Check file permissions"));
for (int i=0; i < filenames.count(); i++) {
// get fullpath name for processing
QFileInfo thisfile(filenames[i]);
if (thisfile.exists() && thisfile.isFile() && thisfile.isReadable()) {
// is it one we understand ?
QStringList suffixList = RideFileFactory::instance().suffixes();
QRegExp suffixes(QString("^(%1)$").arg(suffixList.join("|")));
suffixes.setCaseSensitivity(Qt::CaseInsensitive);
if (suffixes.exactMatch(thisfile.suffix())) {
// Woot. We know how to parse this baby
tableWidget->item(i,5)->setText(tr("Queued"));
} else {
tableWidget->item(i,5)->setText(tr("Error - Unknown file type"));
}
} else {
// Cannot open
tableWidget->item(i,5)->setText(tr("Error - Not a valid file"));
}
progressBar->setValue(progressBar->value()+1);
}
QApplication::processEvents();
if (aborted) { done(0); }
repaint();
// Pass 2 - Read in with the relevant RideFileReader method
phaseLabel->setText(tr("Step 2 of 4: Validating Files"));
for (int i=0; i< filenames.count(); i++) {
// does the status say Queued?
if (!tableWidget->item(i,5)->text().startsWith(tr("Error"))) {
QStringList errors;
QFile thisfile(filenames[i]);
tableWidget->item(i,5)->setText(tr("Parsing..."));
tableWidget->setCurrentCell(i,5);
QApplication::processEvents();
if (aborted) { done(0); }
this->repaint();
QList<RideFile*> rides;
boost::scoped_ptr<RideFile> ride(RideFileFactory::instance().openRideFile(mainWindow, thisfile, errors, &rides));
// is this an archive of files?
if (rides.count() > 1) {
int here = i;
// remove current filename from state arrays and tableview
filenames.removeAt(here);
blanks.removeAt(here);
tableWidget->removeRow(here);
// resize dialog according to the number of rows we expect
int willhave = filenames.count() + rides.count();
resize(920 + ((willhave > 16 ? 24 : 0) +
(willhave > 9 && willhave < 17) ? 8 : 0),
118 + (willhave > 16 ? 17*20 : (willhave+1) * 20));
// ok so create a temporary file and add to the tableWidget
int counter = 0;
foreach(RideFile *extracted, rides) {
// write as a temporary file, using the original
// filename with "-n" appended
QString fulltarget = QDir::tempPath() + "/" + QFileInfo(thisfile).baseName() + QString("-%1.tcx").arg(counter+1);
TcxFileReader reader;
QFile target(fulltarget);
reader.writeRideFile(mainWindow, mainWindow->cyclist, extracted, target);
deleteMe.append(fulltarget);
// now add each temporary file ...
filenames.insert(here, fulltarget);
blanks.insert(here, true); // by default editable
tableWidget->insertRow(here+counter);
QTableWidgetItem *t;
// Filename
t = new QTableWidgetItem();
t->setText(fulltarget);
t->setFlags(t->flags() & (~Qt::ItemIsEditable));
tableWidget->setItem(here+counter,0,t);
// Date
t = new QTableWidgetItem();
t->setText(tr(""));
t->setFlags(t->flags() | Qt::ItemIsEditable);
t->setBackgroundColor(Qt::red);
tableWidget->setItem(here+counter,1,t);
// Time
t = new QTableWidgetItem();
t->setText(tr(""));
t->setFlags(t->flags() | Qt::ItemIsEditable);
tableWidget->setItem(here+counter,2,t);
// Duration
t = new QTableWidgetItem();
t->setText(tr(""));
t->setFlags(t->flags() & (~Qt::ItemIsEditable));
tableWidget->setItem(here+counter,3,t);
// Distance
t = new QTableWidgetItem();
t->setText(tr(""));
t->setFlags(t->flags() & (~Qt::ItemIsEditable));
tableWidget->setItem(here+counter,4,t);
// Import Status
t = new QTableWidgetItem();
t->setText(tr(""));
t->setFlags(t->flags() & (~Qt::ItemIsEditable));
tableWidget->setItem(here+counter,5,t);
counter++;
tableWidget->adjustSize();
QApplication::processEvents();
}
// progress bar needs to adjust...
progressBar->setMaximum(filenames.count()*4);
// then go back one and re-parse from there
rides.clear();
i--;
goto next; // buttugly I know, but count em across 100,000 lines of code
}
// did it parse ok?
if (ride) {
// ride != NULL but !errors.isEmpty() means they're just warnings
if (errors.isEmpty())
tableWidget->item(i,5)->setText(tr("Validated"));
else
tableWidget->item(i,5)->setText(tr("Warning - ") + errors.join(tr(" ")));
// Set Date and Time
if (ride->startTime().isNull()) {
// Poo. The user needs to supply the date/time for this ride
blanks[i] = true;
tableWidget->item(i,1)->setText(tr(""));
tableWidget->item(i,2)->setText(tr(""));
} else {
// Cool, the date and time was extrcted from the source file
blanks[i] = false;
tableWidget->item(i,1)->setText(ride->startTime().toString(tr("dd MMM yyyy")));
tableWidget->item(i,2)->setText(ride->startTime().toString(tr("hh:mm:ss ap")));
}
tableWidget->item(i,1)->setTextAlignment(Qt::AlignRight); // put in the middle
tableWidget->item(i,2)->setTextAlignment(Qt::AlignRight); // put in the middle
QVariant unit = appsettings->value(this, GC_UNIT);
bool metric = unit.toString() == "Metric";
// time and distance from tags (.gc files)
QMap<QString,QString> lookup;
lookup = ride->metricOverrides.value("total_distance");
double km = lookup.value("value", "0.0").toDouble();
lookup = ride->metricOverrides.value("workout_time");
int secs = lookup.value("value", "0.0").toDouble();
// show duration by looking at last data point
if (!ride->dataPoints().isEmpty() && ride->dataPoints().last() != NULL) {
if (!secs) secs = ride->dataPoints().last()->secs;
if (!km) km = ride->dataPoints().last()->km;
}
QChar zero = QLatin1Char ( '0' );
QString time = QString("%1:%2:%3").arg(secs/3600,2,10,zero)
.arg(secs%3600/60,2,10,zero)
.arg(secs%60,2,10,zero);
tableWidget->item(i,3)->setText(time);
tableWidget->item(i,3)->setTextAlignment(Qt::AlignHCenter); // put in the middle
// show distance by looking at last data point
QString dist = metric
? QString ("%1 km").arg(km, 0, 'f', 1)
: QString ("%1 mi").arg(km * MILES_PER_KM, 0, 'f', 1);
tableWidget->item(i,4)->setText(dist);
tableWidget->item(i,4)->setTextAlignment(Qt::AlignRight); // put in the middle
} else {
// nope - can't handle this file
tableWidget->item(i,5)->setText(tr("Error - ") + errors.join(tr(" ")));
}
}
progressBar->setValue(progressBar->value()+1);
QApplication::processEvents();
if (aborted) { done(0); }
this->repaint();
next:;
}
// Pass 3 - get missing date and times for imported files
// Actually allow us to edit date on ANY ride, we
// make sure that the ride date/time is set from
// the filename and never from the ride data
phaseLabel->setText(tr("Step 3 of 4: Confirm Date and Time"));
int needdates=0;
for (int i=0; i<filenames.count(); i++) {
// ignore errors
QTableWidgetItem *t = tableWidget->item(i,5);
if (t->text().startsWith(tr("Error"))) continue;
if (blanks[i]) needdates++; // count the blanks tho -- these MUST be edited
// does nothing for the moment
progressBar->setValue(progressBar->value()+1);
progressBar->repaint();
}
// Wait for user to press save
abortButton->setText(tr("Save"));
aborted = false;
cancelButton->setHidden(false);
todayButton->setHidden(false);
overFiles->setHidden(false);
if (needdates == 0) {
// no need to wait for the user to input dates
// nd press save if all the dates are set
// (i.e. they got set by the file importers already)
// do nothing for now since we need to confirm dates
// and confirm overwrite files rather than importing
// without user intervention
abortButton->setDisabled(false);
// abortClicked();
} else {
// de-activate Save button until the dates and times are sorted
abortButton->setDisabled(true);
}
connect(tableWidget, SIGNAL(itemChanged(QTableWidgetItem *)), this, SLOT(activateSave()));
return 0;
}
void
RideImportWizard::overClicked()
{
overwriteFiles = overFiles->isChecked();
}
void
RideImportWizard::activateSave()
{
for (int i=0; i<filenames.count(); i++) {
// ignore errors
QTableWidgetItem *t = tableWidget->item(i,5);
if (t->text().startsWith(tr("Error"))) continue;
// date needed?
t = tableWidget->item(i,1);
if (t->text() == "") return;
// time needed?
t = tableWidget->item(i,2);
if (t->text() == "") return;
}
// if you got here then all entries that need a date have a date
abortButton->setDisabled(false);
}
// the code here is tedious. checking for conditions about total ride time
// and if it fits in a day, then distributing them over the day, but
// only if the time is not set, and then if its being set to today
// then start must be before the current time cause you can't log
// data for future rides ... and so on.
//
// but then this feature is gonna save a lot of typing over time ...
//
void
RideImportWizard::todayClicked(int index)
{
QDate selectedDate; // the date we're gonna apply to the row(s) highlighted
// set the index back to 0, so we can select again
todayButton->setCurrentIndex(0);
// 0 = nothing selected, 1 = Today - 2 = last monday thru to 8 = last sunday
if (index == 0) { // no selection
return;
} else if (index == 1) { // today
selectedDate = QDate().currentDate();
} else if (index == 9) { // other date - set focus on first highlighted date
for (int i=0; i<filenames.count(); i++) {
if (tableWidget->item(i,0)->isSelected() ||
tableWidget->item(i,1)->isSelected() ||
tableWidget->item(i,2)->isSelected() ||
tableWidget->item(i,3)->isSelected() ||
tableWidget->item(i,4)->isSelected() ||
tableWidget->item(i,5)->isSelected()) {
tableWidget->editItem(tableWidget->item(i,1));
return;
}
}
return;
} else { // last mon/tue/wed/thu/fri/sat/sun
int daysago = (QDate().currentDate().dayOfWeek() - index + 8) % 7;
if (!daysago) daysago=7; // e.g.last wednesday is 7 days ago if today is wednesday
selectedDate = QDate().fromJulianDay(QDate().currentDate().toJulianDay()-daysago);
}
// Only apply to selected date - set time to current time - ride duration
// pretty daft but at least it sets it to something, anything is gonna be random
int countselected = 0;
int totalduration = 0;
for (int i=0; i< filenames.count(); i++) {
if (tableWidget->item(i,0)->isSelected() ||
tableWidget->item(i,1)->isSelected() ||
tableWidget->item(i,2)->isSelected() ||
tableWidget->item(i,3)->isSelected() ||
tableWidget->item(i,4)->isSelected() ||
tableWidget->item(i,5)->isSelected()) {
countselected++;
QTime duration = QTime().fromString(tableWidget->item(i,3)->text(), tr("hh:mm:ss"));
totalduration += duration.hour() * 3600 +
duration.minute() * 60 +
duration.second();
}
}
// More than a days worth of rides so can't squeeze into a single day!
if (totalduration > (24 * 3600)) {
QMessageBox::warning ( this, tr ( "Invalid Selection" ), tr ( "More than 24hrs of rides to fit into a day" ));
return;
}
// if it is today then start from now - total rides duration
// if that goes negative then just do the std thing
int rstart=0;
if (index == 1) {
QTime timenow = QTime().currentTime();
int now = timenow.hour() * 3600 +
timenow.minute() * 60 +
timenow.second();
rstart = now - totalduration;
}
if (rstart <= 0) { // i.e. still not set properly...
// if it is not being set to "today" then spread them across the
// day back to back with equal time before and after noon.
rstart = ((24*3600) - totalduration) /2;
}
// zip through the rows and where highlighted set the date
// if the start time is not set set it to rstart and increment
// by the duration of the ride.
for (int i=0; i< filenames.count(); i++) {
if (tableWidget->item(i,0)->isSelected() ||
tableWidget->item(i,1)->isSelected() ||
tableWidget->item(i,2)->isSelected() ||
tableWidget->item(i,3)->isSelected() ||
tableWidget->item(i,4)->isSelected() ||
tableWidget->item(i,5)->isSelected()) {
// set the date to date selected
tableWidget->item(i,1)->setText(selectedDate.toString(tr("dd MMM yyyy")));
// look at rides with missing start time - we need to populate those
// ride duration
QTime duration = QTime().fromString(tableWidget->item(i,3)->text(), tr("hh:mm:ss"));
// ride start time
QTime time(rstart/3600, rstart%3600/60, rstart%60);
tableWidget->item(i,2)->setText(time.toString(tr("hh:mm:ss a")));
rstart += duration.hour() * 3600 +
duration.minute() * 60 +
duration.second();
}
}
// phew! - repaint!
QApplication::processEvents();
tableWidget->repaint();
}
void
RideImportWizard::cancelClicked()
{
done(0); // you are the weakest link, goodbye.
}
// info structure used by cpi updater
struct cpi_file_info {
QString file, inname, outname;
};
void
RideImportWizard::abortClicked()
{
// if done when labelled abort we kill off this dialog
QString label = abortButton->text();
if (label == tr("Abort")) {
aborted=true; // terminated. I'll be back.
return;
}
if (label == tr("Finish")) {
// phew. our work is done.
done(0);
return;
}
int needdates=0;
for (int i=0; i<filenames.count(); i++) {
QTableWidgetItem *t = tableWidget->item(i,5);
if (t->text().startsWith(tr("Error"))) continue;
// date needed?
t = tableWidget->item(i,1);
if (t->text() == "") {
needdates++;
}
t->setFlags(t->flags() | (Qt::ItemIsEditable));
// time needed?
t = tableWidget->item(i,2);
if (t->text() == "") {
needdates++;
}
t->setFlags(t->flags() | (Qt::ItemIsEditable));
}
if (needdates) return; // no dice dude, we need those dates filled in!
// if done when labelled save we copy the files and run the cpi calculator
phaseLabel->setText(tr("Step 4 of 4: Save to Library"));
abortButton->setText(tr("Abort"));
aborted = false;
cancelButton->setHidden(true);
todayButton->setHidden(true);
overFiles->setHidden(true);
// now set this fields uneditable again ... yeesh.
for (int i=0; i <filenames.count(); i++) {
QTableWidgetItem *t = tableWidget->item(i,1);
t->setFlags(t->flags() & (~Qt::ItemIsEditable));
t = tableWidget->item(i,2);
t->setFlags(t->flags() & (~Qt::ItemIsEditable));
}
QChar zero = QLatin1Char ( '0' );
for (int i=0; i< filenames.count(); i++) {
if (tableWidget->item(i,5)->text().startsWith(tr("Error"))) continue; // skip error
tableWidget->item(i,5)->setText(tr("Saving..."));
tableWidget->setCurrentCell(i,5);
QApplication::processEvents();
if (aborted) { done(0); }
this->repaint();
// Setup the ridetime as a QDateTime
QDateTime ridedatetime = QDateTime(QDate().fromString(tableWidget->item(i,1)->text(), tr("dd MMM yyyy")),
QTime().fromString(tableWidget->item(i,2)->text(), tr("hh:mm:ss a")));
QString suffix = QFileInfo(filenames[i]).suffix();
QString targetnosuffix = QString ( "%1_%2_%3_%4_%5_%6" )
.arg ( ridedatetime.date().year(), 4, 10, zero )
.arg ( ridedatetime.date().month(), 2, 10, zero )
.arg ( ridedatetime.date().day(), 2, 10, zero )
.arg ( ridedatetime.time().hour(), 2, 10, zero )
.arg ( ridedatetime.time().minute(), 2, 10, zero )
.arg ( ridedatetime.time().second(), 2, 10, zero );
QString target = QString ("%1.%2" )
.arg ( targetnosuffix )
.arg ( suffix );
QString fulltarget = home.absolutePath() + "/" + target;
// if its a gc file we need to parse and serialize
// using the ridedatetime and target filename
if (filenames[i].endsWith(".gc", Qt::CaseInsensitive) ||
filenames[i].endsWith(",json", Qt::CaseInsensitive)) {
bool existed;
if ((existed=QFileInfo(fulltarget).exists()) && !overwriteFiles) {
tableWidget->item(i,5)->setText(tr("Error - File exists"));
} else {
// read the file (again)
QStringList errors;
QFile thisfile(filenames[i]);
RideFile *ride(RideFileFactory::instance().openRideFile(mainWindow, thisfile, errors));
// update ridedatetime
ride->setStartTime(ridedatetime);
// serialize
if (filenames[i].endsWith(".gc")) {
GcFileReader reader;
QFile target(fulltarget);
reader.writeRideFile(ride, target);
} else {
JsonFileReader reader;
QFile target(fulltarget);
reader.writeRideFile(ride, target);
}
// clear
delete ride;
if (existed) {
tableWidget->item(i,5)->setText(tr("File Overwritten"));
} else {
tableWidget->item(i,5)->setText(tr("File Saved"));
mainWindow->addRide(QFileInfo(fulltarget).fileName(), true);
}
}
} else {
// for native file formats the filename IS the ride date time so
// no need to write -- we just copy
// so now we have sourcefile in 'filenames[i]' and target file name in 'target'
if (!fulltarget.compare(filenames[i])) { // they are the same file! so skip copy
tableWidget->item(i,5)->setText(tr("Error - Source is Target"));
} else if (QFileInfo(fulltarget).exists()) {
if (overwriteFiles) {
tableWidget->item(i,5)->setText(tr("Overwriting file..."));
QFile source(filenames[i]);
QString fulltargettmp(home.absolutePath() + tr("/") + targetnosuffix + tr(".tmp"));
if (source.copy(fulltargettmp)) {
// tmp version saved now zap original
QFile original(fulltarget);
original.remove(); // zap!
// mv tmp to target
QFile temp(fulltargettmp);
if (temp.rename(fulltarget)) {
tableWidget->item(i,5)->setText(tr("File Overwritten"));
//no need to add since its already there!
} else
tableWidget->item(i,5)->setText(tr("Error - overwrite failed"));
} else {
tableWidget->item(i,5)->setText(tr("Error - overwrite failed"));
}
} else {
tableWidget->item(i,5)->setText(tr("Error - File exists"));
}
} else {
tableWidget->item(i,5)->setText(tr("Saving file..."));
QFile source(filenames[i]);
if (source.copy(fulltarget)) {
tableWidget->item(i,5)->setText(tr("File Saved"));
mainWindow->addRide(QFileInfo(fulltarget).fileName(), true); // add to tree view
// free immediately otherwise all imported rides are cached
// and with large imports this can lead to memory exhaustion
// 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
mainWindow->rideItem()->freeMemory();
} else
tableWidget->item(i,5)->setText(tr("Error - copy failed"));
}
}
QApplication::processEvents();
if (aborted) { done(0); }
progressBar->setValue(progressBar->value()+1);
this->repaint();
}
#if 0 // NOT UNTIL CPINTPLOT.CPP IS REFACTORED TO SEPERATE CPI FILES MAINTENANCE FROM CP PLOT CODE
// if done when labelled save we copy the files and run the cpi calculator
phaseLabel->setText(tr("Step 5 of 5: Calculating Critical Powers"));
abortButton->setText(tr("Abort"));
aborted = false;
for (int i=0; i< filenames.count(); i++) {
if (!tableWidget->item(i,5)->text().startsWith(tr("Error"))) {
tableWidget->item(i,5)->setText(tr("Calculating..."));
tableWidget->setCurrentCell(i,5);
QApplication::processEvents();
if (aborted) { done(0); }
this->repaint();
// calculated
// change status
tableWidget->item(i,5)->setText(tr("Completed."));
}
QApplication::processEvents();
if (aborted) { done(0); }
progressBar->setValue(progressBar->value()+1);
this->repaint();
}
#endif // not until CPINTPLOT IS REFACTORED
// how did we get on in the end then ...
int completed = 0;
for (int i=0; i< filenames.count(); i++)
if (!tableWidget->item(i,5)->text().startsWith(tr("Error"))) {
completed++;
}
tableWidget->setSortingEnabled(true); // so you can browse through errors etc
QString donemessage = QString(tr("Import Complete. %1 of %2 successful."))
.arg(completed, 1, 10, zero)
.arg(filenames.count(), 1, 10, zero);
progressBar->setValue(progressBar->maximum());
phaseLabel->setText(donemessage);
abortButton->setText(tr("Finish"));
aborted = false;
}
// clean up files
RideImportWizard::~RideImportWizard()
{
foreach(QString name, deleteMe) QFile(name).remove();
}
// Below is the code to implement a custom ItemDelegate to support
// editing of the date and time of the Ride inside a QTableWidget
// the ItemDelegate is registered for every cell in the table
// and only kicks into life for the columns in which the date and
// time are stored, and only when the DateTime for that row is 0.
// construct the delegate - save away the column containing the date
// bearing in mind the time is in the next
// column.
RideDelegate::RideDelegate(int dateColumn, QObject *parent) : QItemDelegate(parent)
{
this->dateColumn = dateColumn;
}
// paint the cells for date and time - pass through for all the
// other cells. Uses the model data to display.
void RideDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
if (index.column() == dateColumn) {
QString value = index.model()->data(index, Qt::DisplayRole).toString();
// display with angles to show its doing something
QString text = QString("%1").arg(value);
QStyleOptionViewItem myOption = option;
myOption.displayAlignment = Qt::AlignRight | Qt::AlignVCenter;
drawDisplay(painter, myOption, myOption.rect, text);
drawFocus(painter, myOption, myOption.rect);
} else if (index.column() == dateColumn+1) {
QString value = index.model()->data(index, Qt::DisplayRole).toString();
// display with angles to show its doing something
QString text = QString("%1").arg(value);
QStyleOptionViewItem myOption = option;
myOption.displayAlignment = Qt::AlignRight | Qt::AlignVCenter;
drawDisplay(painter, myOption, myOption.rect, text);
drawFocus(painter, myOption, myOption.rect);
} else {
QItemDelegate::paint(painter, option, index);
}
}
// setup editor for edit of field!!
QWidget *RideDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
if (index.column() == dateColumn) {
// edit that date!
QDateEdit *dateEdit = new QDateEdit(parent);
dateEdit->setDisplayFormat(tr("dd MMM yyyy"));
connect(dateEdit, SIGNAL(editingFinished()), this, SLOT(commitAndCloseDateEditor()));
return dateEdit;
} else if (index.column() == dateColumn+1) {
// edit that time
QTimeEdit *timeEdit = new QTimeEdit(parent);
timeEdit->setDisplayFormat("hh:mm:ss a");
connect(timeEdit, SIGNAL(editingFinished()), this, SLOT(commitAndCloseTimeEditor()));
return timeEdit;
} else {
return QItemDelegate::createEditor(parent, option, index);
}
}
// user hit tab or return so save away the data to our model
void RideDelegate::commitAndCloseDateEditor()
{
QDateEdit *editor = qobject_cast<QDateEdit *>(sender());
emit commitData(editor);
emit closeEditor(editor);
}
// user hit tab or return so save away the data to our model
void RideDelegate::commitAndCloseTimeEditor()
{
QTimeEdit *editor = qobject_cast<QTimeEdit *>(sender());
emit commitData(editor);
emit closeEditor(editor);
}
// We don't set anything because the data is saved within the view not the model!
void RideDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
{
// stored as text field
if (index.column() == dateColumn) {
QDateEdit *dateEdit = qobject_cast<QDateEdit *>(editor);
QDate date = QDate().fromString(index.model()->data(index, Qt::DisplayRole).toString(), tr("dd MMM yyyy"));
dateEdit->setDate(date);
} else if (index.column() == dateColumn+1) {
QTimeEdit *timeEdit = qobject_cast<QTimeEdit *>(editor);
QTime time = QTime().fromString(index.model()->data(index, Qt::DisplayRole).toString(), "hh:mm:ss a");;
timeEdit->setTime(time);
}
}
// We don't set anything because the data is saved within the view not the model!
void RideDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
{
// stored as text field
if (index.column() == dateColumn) {
QDateEdit *dateEdit = qobject_cast<QDateEdit *>(editor);
QString value = dateEdit->date().toString(tr("dd MMM yyyy"));
// Place in the view
model->setData(index, value, Qt::DisplayRole);
} else if (index.column() == dateColumn+1) {
QTimeEdit *timeEdit = qobject_cast<QTimeEdit *>(editor);
QString value = timeEdit->time().toString("hh:mm:ss a");
model->setData(index, value, Qt::DisplayRole);
}
}