mirror of
https://github.com/GoldenCheetah/GoldenCheetah.git
synced 2026-02-13 16:18:42 +00:00
Workout Library Part 3 fixups
Some fixups to recent update for the workout library support; - ergDB download now updates the DB - mainwindow menu options names more consistent - sort order in the video and workout list - hooks for import/drag and drop Will now write the importer for drag-n-drop and import workouts menu option.
This commit is contained in:
@@ -17,6 +17,7 @@
|
||||
*/
|
||||
|
||||
#include "ErgDBDownloadDialog.h"
|
||||
#include "TrainDB.h"
|
||||
|
||||
ErgDBDownloadDialog::ErgDBDownloadDialog(MainWindow *main) : QDialog(main), main(main)
|
||||
{
|
||||
@@ -151,6 +152,9 @@ ErgDBDownloadDialog::downloadFiles()
|
||||
// what format to export as?
|
||||
QString type = RideFileFactory::instance().writeSuffixes().at(0);
|
||||
|
||||
// for library updating, transactional for 10x performance
|
||||
trainDB->startLUW();
|
||||
|
||||
// loop through the table and export all selected
|
||||
for(int i=0; i<files->invisibleRootItem()->childCount(); i++) {
|
||||
|
||||
@@ -158,7 +162,10 @@ ErgDBDownloadDialog::downloadFiles()
|
||||
QApplication::processEvents();
|
||||
|
||||
// did they?
|
||||
if (aborted == true) return; // user aborted!
|
||||
if (aborted == true) {
|
||||
trainDB->endLUW(); // need to commit whatever was copied.
|
||||
return; // user aborted!
|
||||
}
|
||||
|
||||
QTreeWidgetItem *current = files->invisibleRootItem()->child(i);
|
||||
|
||||
@@ -180,7 +187,6 @@ ErgDBDownloadDialog::downloadFiles()
|
||||
// open success?
|
||||
if (p->isValid()) {
|
||||
|
||||
delete p; // free memory!
|
||||
|
||||
if (QFile(filename).exists()) {
|
||||
|
||||
@@ -188,6 +194,7 @@ ErgDBDownloadDialog::downloadFiles()
|
||||
// skip existing files
|
||||
current->setText(5, "Exists already"); QApplication::processEvents();
|
||||
fails++;
|
||||
delete p; // free memory!
|
||||
continue;
|
||||
|
||||
} else {
|
||||
@@ -210,12 +217,15 @@ ErgDBDownloadDialog::downloadFiles()
|
||||
downloads++;
|
||||
current->setText(5, "Saved"); QApplication::processEvents();
|
||||
|
||||
trainDB->importWorkout(filename, p); // add to library
|
||||
|
||||
} else {
|
||||
|
||||
fails++;
|
||||
current->setText(5, "Write failed"); QApplication::processEvents();
|
||||
}
|
||||
|
||||
delete p; // free memory!
|
||||
|
||||
// couldn't parse
|
||||
} else {
|
||||
@@ -228,4 +238,6 @@ ErgDBDownloadDialog::downloadFiles()
|
||||
|
||||
}
|
||||
}
|
||||
// for library updating, transactional for 10x performance
|
||||
trainDB->endLUW();
|
||||
}
|
||||
|
||||
@@ -375,6 +375,9 @@ LibrarySearchDialog::removeDirectory()
|
||||
void
|
||||
LibrarySearchDialog::updateDB()
|
||||
{
|
||||
// wipe away all user data before updating
|
||||
trainDB->rebuildDB();
|
||||
|
||||
trainDB->startLUW();
|
||||
|
||||
// workouts
|
||||
@@ -435,6 +438,24 @@ LibrarySearch::run()
|
||||
// is a workout?
|
||||
if (findWorkout && ErgFile::isWorkout(name)) emit foundWorkout(name);
|
||||
}
|
||||
|
||||
// Now check and re-add references, if there are any
|
||||
// these are files which were drag-n-dropped into the
|
||||
// GC train window, but which were referenced not
|
||||
// copied into the workout directory.
|
||||
Library *l = Library::findLibrary("Media Library");
|
||||
if (l) {
|
||||
foreach(QString r, l->refs) {
|
||||
|
||||
if (!QFile(r).exists()) continue;
|
||||
|
||||
// is a video?
|
||||
if (findMedia && helper.isMedia(r)) emit foundVideo(r);
|
||||
// is a workout?
|
||||
if (findWorkout && ErgFile::isWorkout(r)) emit foundWorkout(r);
|
||||
}
|
||||
}
|
||||
|
||||
emit done();
|
||||
};
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ class Library
|
||||
public:
|
||||
QString name; // e.g. Media Library
|
||||
QList<QString> paths; // array of search paths for files in this library
|
||||
QList<QString> refs; // array of drag-n-dropped files referenced not copied
|
||||
|
||||
static void initialise(QDir); // init
|
||||
static Library *findLibrary(QString);
|
||||
|
||||
@@ -40,6 +40,11 @@ bool LibraryParser::endElement( const QString&, const QString&, const QString &q
|
||||
if (qName == "path") {
|
||||
library->paths.append(buffer.trimmed());
|
||||
}
|
||||
|
||||
// a reference
|
||||
if (qName == "ref") {
|
||||
library->refs.append(buffer.trimmed());
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
@@ -87,6 +92,10 @@ LibraryParser::serialize(QDir home)
|
||||
foreach(QString p, l->paths)
|
||||
out << QString("\t<path>%1</path>\n").arg(p);
|
||||
|
||||
// paths...
|
||||
foreach(QString r, l->refs)
|
||||
out << QString("\t<ref>%1</ref>\n").arg(r);
|
||||
|
||||
// end document
|
||||
out << "</library>\n";
|
||||
}
|
||||
|
||||
@@ -806,9 +806,10 @@ MainWindow::MainWindow(const QDir &home) :
|
||||
optionsMenu->addAction(tr("Get &Zeo Data..."), this,
|
||||
SLOT (downloadMeasuresFromZeo()));
|
||||
optionsMenu->addSeparator();
|
||||
optionsMenu->addAction(tr("Workout Wizard"), this, SLOT(showWorkoutWizard()));
|
||||
optionsMenu->addAction(tr("Get Workouts from ErgDB"), this, SLOT(downloadErgDB()));
|
||||
optionsMenu->addAction(tr("Manage Media/Workout Library"), this, SLOT(manageLibrary()));
|
||||
optionsMenu->addAction(tr("Create a new workout..."), this, SLOT(showWorkoutWizard()));
|
||||
optionsMenu->addAction(tr("Download workouts from ErgDB..."), this, SLOT(downloadErgDB()));
|
||||
optionsMenu->addAction(tr("Import workouts or videos..."), this, SLOT(importWorkout()));
|
||||
optionsMenu->addAction(tr("Scan disk for videos and workouts..."), this, SLOT(manageLibrary()));
|
||||
|
||||
#ifdef GC_HAVE_ICAL
|
||||
optionsMenu->addSeparator();
|
||||
@@ -1556,9 +1557,13 @@ MainWindow::dropEvent(QDropEvent *event)
|
||||
QList<QUrl> urls = event->mimeData()->urls();
|
||||
if (urls.isEmpty()) return;
|
||||
|
||||
// We have something to process then
|
||||
RideImportWizard *dialog = new RideImportWizard (&urls, home, this);
|
||||
dialog->process(); // do it!
|
||||
if (currentWindow != trainWindow) {
|
||||
// We have something to process then
|
||||
RideImportWizard *dialog = new RideImportWizard (&urls, home, this);
|
||||
dialog->process(); // do it!
|
||||
} else {
|
||||
//XXX hook for workout importer HERE
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1959,6 +1964,36 @@ MainWindow::uploadTtb()
|
||||
}
|
||||
}
|
||||
|
||||
/*----------------------------------------------------------------------
|
||||
* Import Workout from Disk
|
||||
*--------------------------------------------------------------------*/
|
||||
void
|
||||
MainWindow::importWorkout()
|
||||
{
|
||||
// go look at last place we imported workouts from...
|
||||
QVariant lastDirVar = appsettings->value(this, GC_SETTINGS_LAST_WORKOUT_PATH);
|
||||
QString lastDir = (lastDirVar != QVariant())
|
||||
? lastDirVar.toString() : QDir::homePath();
|
||||
|
||||
// anything for now, we could add filters later
|
||||
QStringList allFormats;
|
||||
allFormats << "All files (*.*)";
|
||||
QStringList fileNames = QFileDialog::getOpenFileNames(this, tr("Import from File"), lastDir,
|
||||
allFormats.join(";;"));
|
||||
|
||||
// lets process them
|
||||
if (!fileNames.isEmpty()) {
|
||||
|
||||
// save away last place we looked
|
||||
lastDir = QFileInfo(fileNames.front()).absolutePath();
|
||||
appsettings->setValue(GC_SETTINGS_LAST_WORKOUT_PATH, lastDir);
|
||||
|
||||
QStringList fileNamesCopy = fileNames; // QT doc says iterate over a copy
|
||||
|
||||
// import them via the workoutimporter
|
||||
//XXX hook for workout importer HERE
|
||||
}
|
||||
}
|
||||
/*----------------------------------------------------------------------
|
||||
* ErgDB
|
||||
*--------------------------------------------------------------------*/
|
||||
|
||||
@@ -272,6 +272,7 @@ class MainWindow : public QMainWindow
|
||||
void uploadCalendar(); // upload ride to calendar
|
||||
#endif
|
||||
void importFile();
|
||||
void importWorkout();
|
||||
void findBestIntervals();
|
||||
void addIntervals();
|
||||
void addIntervalForPowerPeaksForSecs(RideFile *ride, int windowSizeSecs, QString name);
|
||||
|
||||
@@ -55,6 +55,7 @@
|
||||
#define GC_BIO "bio"
|
||||
#define GC_AVATAR "avatar"
|
||||
#define GC_SETTINGS_LAST_IMPORT_PATH "mainwindow/lastImportPath"
|
||||
#define GC_SETTINGS_LAST_WORKOUT_PATH "mainwindow/lastWorkoutPath"
|
||||
#define GC_LAST_DOWNLOAD_DEVICE "mainwindow/lastDownloadDevice"
|
||||
#define GC_LAST_DOWNLOAD_PORT "mainwindow/lastDownloadPort"
|
||||
#define GC_CRANKLENGTH "crankLength"
|
||||
|
||||
@@ -70,6 +70,18 @@ TrainDB::initDatabase(QDir home)
|
||||
}
|
||||
}
|
||||
|
||||
// rebuild effectively drops and recreates all tables
|
||||
// but not the version table, since its about deleting
|
||||
// user data (e.g. when rescanning their hard disk)
|
||||
void
|
||||
TrainDB::rebuildDB()
|
||||
{
|
||||
dropWorkoutTable();
|
||||
createWorkoutTable();
|
||||
dropVideoTable();
|
||||
createVideoTable();
|
||||
}
|
||||
|
||||
bool TrainDB::createVideoTable()
|
||||
{
|
||||
QSqlQuery query(dbconn);
|
||||
@@ -149,12 +161,14 @@ bool TrainDB::createWorkoutTable()
|
||||
|
||||
rc = query.exec(createMetricTable);
|
||||
|
||||
// adding a space at the front of string to make manual mode always
|
||||
// appear first in a sorted list is a bit of a hack, but works ok
|
||||
QString manualErg = QString("INSERT INTO workouts (filepath, filename) values (\"//1\", \"%1\");")
|
||||
.arg(tr("Manual Erg Mode"));
|
||||
.arg(tr(" Manual Erg Mode"));
|
||||
rc = query.exec(manualErg);
|
||||
|
||||
QString manualCrs = QString("INSERT INTO workouts (filepath, filename) values (\"//2\", \"%1\");")
|
||||
.arg(tr("Manual Slope Mode"));
|
||||
.arg(tr(" Manual Slope Mode"));
|
||||
rc = query.exec(manualCrs);
|
||||
|
||||
// add row to version database
|
||||
|
||||
@@ -52,6 +52,9 @@ class TrainDB : public QObject
|
||||
bool importWorkout(QString pathname, ErgFile *ergFile);
|
||||
bool importVideo(QString pathname); //XXX simple for now
|
||||
|
||||
// drop and recreate tables
|
||||
void rebuildDB();
|
||||
|
||||
signals:
|
||||
void dataChanged();
|
||||
|
||||
|
||||
@@ -86,8 +86,13 @@ TrainTool::TrainTool(MainWindow *parent, const QDir &home) : GcWindow(parent), h
|
||||
videoModel->select();
|
||||
while (videoModel->canFetchMore(QModelIndex())) videoModel->fetchMore(QModelIndex());
|
||||
|
||||
vsortModel = new QSortFilterProxyModel(this);
|
||||
vsortModel->setSourceModel(videoModel);
|
||||
vsortModel->setDynamicSortFilter(true);
|
||||
vsortModel->sort(1, Qt::AscendingOrder); //filename order
|
||||
|
||||
mediaTree = new QTreeView;
|
||||
mediaTree->setModel(videoModel);
|
||||
mediaTree->setModel(vsortModel);
|
||||
|
||||
// hide unwanted columns and header
|
||||
for(int i=0; i<mediaTree->header()->count(); i++)
|
||||
@@ -95,7 +100,7 @@ TrainTool::TrainTool(MainWindow *parent, const QDir &home) : GcWindow(parent), h
|
||||
mediaTree->setColumnHidden(1, false); // show filename
|
||||
mediaTree->header()->hide();
|
||||
|
||||
mediaTree->setSortingEnabled(true);
|
||||
mediaTree->setSortingEnabled(false);
|
||||
mediaTree->setAlternatingRowColors(false);
|
||||
mediaTree->setEditTriggers(QAbstractItemView::NoEditTriggers); // read-only
|
||||
mediaTree->expandAll();
|
||||
@@ -132,8 +137,13 @@ TrainTool::TrainTool(MainWindow *parent, const QDir &home) : GcWindow(parent), h
|
||||
workoutModel->select();
|
||||
while (workoutModel->canFetchMore(QModelIndex())) workoutModel->fetchMore(QModelIndex());
|
||||
|
||||
sortModel = new QSortFilterProxyModel(this);
|
||||
sortModel->setSourceModel(workoutModel);
|
||||
sortModel->setDynamicSortFilter(true);
|
||||
sortModel->sort(1, Qt::AscendingOrder); //filename order
|
||||
|
||||
workoutTree = new QTreeView;
|
||||
workoutTree->setModel(workoutModel);
|
||||
workoutTree->setModel(sortModel);
|
||||
|
||||
// hide unwanted columns and header
|
||||
for(int i=0; i<workoutTree->header()->count(); i++)
|
||||
@@ -503,7 +513,8 @@ void
|
||||
TrainTool::workoutTreeWidgetSelectionChanged()
|
||||
{
|
||||
QModelIndex current = workoutTree->currentIndex();
|
||||
QString filename = workoutModel->data(workoutModel->index(current.row(), 0), Qt::DisplayRole).toString();
|
||||
QModelIndex target = sortModel->mapToSource(current);
|
||||
QString filename = workoutModel->data(workoutModel->index(target.row(), 0), Qt::DisplayRole).toString();
|
||||
|
||||
// wip away the current selected workout
|
||||
if (ergFile) {
|
||||
@@ -511,8 +522,13 @@ TrainTool::workoutTreeWidgetSelectionChanged()
|
||||
ergFile = NULL;
|
||||
}
|
||||
|
||||
if (filename == "") {
|
||||
main->notifyErgFileSelected(NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
// is it the auto mode?
|
||||
int index = current.row();
|
||||
int index = target.row();
|
||||
if (index == 0) {
|
||||
// ergo mode
|
||||
main->notifyErgFileSelected(NULL);
|
||||
@@ -585,8 +601,10 @@ TrainTool::listWorkoutFiles(const QDir &dir) const
|
||||
void
|
||||
TrainTool::mediaTreeWidgetSelectionChanged()
|
||||
{
|
||||
|
||||
QModelIndex current = mediaTree->currentIndex();
|
||||
QString filename = videoModel->data(videoModel->index(current.row(), 0), Qt::DisplayRole).toString();
|
||||
QModelIndex target = vsortModel->mapToSource(current);
|
||||
QString filename = videoModel->data(videoModel->index(target.row(), 0), Qt::DisplayRole).toString();
|
||||
main->notifyMediaSelected(filename);
|
||||
}
|
||||
|
||||
|
||||
@@ -182,6 +182,8 @@ class TrainTool : public GcWindow
|
||||
QTreeWidget *deviceTree;
|
||||
QTreeWidget *serverTree;
|
||||
QTreeView *workoutTree;
|
||||
QSortFilterProxyModel *sortModel; // sorting workout list
|
||||
QSortFilterProxyModel *vsortModel; // sorting video list
|
||||
QTreeView *mediaTree;
|
||||
|
||||
QTreeWidgetItem *allServers;
|
||||
|
||||
Reference in New Issue
Block a user