Ride editor and tools

A new tab 'Editor' for manually editing ride file data points and
associated menu options under 'Tools' for fixing spikes, gaps, GPS
errors and adjusting torque values. A revert to saved ride option
is also included to 'undo' all changes.

The ride editor supports undo/redo as well as cut and paste and
"paste special" (to append points or swap columns/overwrite
selected data series). The editor also supports search and will
automatically highlight anomalous data.

When a file is saved, the changes are recorded in a new metadata
special field called "Change History" which can be added as a
Textbox in the metadata config.

The data processors can be run manually or automatically when a
ride is opened - these are configured on the ride data tab in
the config pane.

Significant changes have been introduced in the codebase, the most
significant of which are; a RideFileCommand class for modifying
ride data has been introduced (as a member of RideFile) and the
RideItem class is now a QObject as well as QTreeWidgetItem to
enable signalling. The Ride Editor uses a RideFileTableModel that
can be re-used in other parts of the code. LTMoutliers class has been
introduced in support of anomaly detection in the editor (which
highlights anomalies with a wiggly red line).

Fixes #103.
This commit is contained in:
Mark Liversedge
2010-07-17 14:33:39 +01:00
parent 8569f812a6
commit cd3bbc4e64
41 changed files with 5314 additions and 54 deletions

View File

@@ -38,6 +38,7 @@
#include "RealtimeWindow.h"
#include "RideItem.h"
#include "IntervalItem.h"
#include "RideEditor.h"
#include "RideFile.h"
#include "RideSummaryWindow.h"
#include "RideImportWizard.h"
@@ -70,9 +71,6 @@
#define GC_VERSION "(developer build)"
#endif
#define FOLDER_TYPE 0
#define RIDE_TYPE 1
bool
MainWindow::parseRideFileName(const QString &name, QString *notesFileName, QDateTime *dt)
{
@@ -322,6 +320,11 @@ MainWindow::MainWindow(const QDir &home) :
googleMap = new GoogleMapControl(this);
tabs.append(TabInfo(googleMap, tr("Map")));
///////////////////////////// Editor //////////////////////////////////
rideEdit = new RideEditor(this);
tabs.append(TabInfo(rideEdit, tr("Editor")));
////////////////////////////// Signals //////////////////////////////
connect(calendar, SIGNAL(clicked(const QDate &)),
@@ -392,6 +395,29 @@ MainWindow::MainWindow(const QDir &home) :
//optionsMenu->addAction(tr("&Update Metrics..."), this,
// SLOT(scanForMissing()()), tr("Ctrl+U"));
// get the available processors
const DataProcessorFactory &factory = DataProcessorFactory::instance();
QMap<QString, DataProcessor*> processors = factory.getProcessors();
if (processors.count()) {
optionsMenu->addSeparator();
toolMapper = new QSignalMapper(this); // maps each option
QMapIterator<QString, DataProcessor*> i(processors);
connect(toolMapper, SIGNAL(mapped(const QString &)), this, SLOT(manualProcess(const QString &)));
i.toFront();
while (i.hasNext()) {
i.next();
QAction *action = new QAction(QString("%1...").arg(i.key()), this);
optionsMenu->addAction(action);
connect(action, SIGNAL(triggered()), toolMapper, SLOT(map()));
toolMapper->setMapping(action, i.key());
}
}
QMenu *viewMenu = menuBar()->addMenu(tr("&View"));
QStringList tabsToHide = settings->value(GC_TABS_TO_HIDE, "").toString().split(",");
for (int i = 0; i < tabs.size(); ++i) {
@@ -507,7 +533,7 @@ MainWindow::addRide(QString name, bool bSelect /*=true*/)
QTreeWidgetItem *item = allRides->child(index);
if (item->type() != RIDE_TYPE)
continue;
RideItem *other = reinterpret_cast<RideItem*>(item);
RideItem *other = static_cast<RideItem*>(item);
if(isAscending.toInt() > 0 ){
if (other->dateTime > dt)
@@ -541,7 +567,7 @@ MainWindow::removeCurrentRide()
QTreeWidgetItem *_item = treeWidget->currentItem();
if (_item->type() != RIDE_TYPE)
return;
RideItem *item = reinterpret_cast<RideItem*>(_item);
RideItem *item = static_cast<RideItem*>(_item);
rideDeleted(item);
@@ -856,6 +882,9 @@ MainWindow::showTreeContextMenuPopup(const QPoint &pos)
QAction *actSaveRide = new QAction(tr("Save Changes to Ride"), treeWidget);
connect(actSaveRide, SIGNAL(triggered(void)), this, SLOT(saveRide()));
QAction *revertRide = new QAction(tr("Revert to Saved Ride"), treeWidget);
connect(revertRide, SIGNAL(triggered(void)), this, SLOT(revertRide()));
QAction *actDeleteRide = new QAction(tr("Delete Ride"), treeWidget);
connect(actDeleteRide, SIGNAL(triggered(void)), this, SLOT(deleteRide()));
@@ -870,8 +899,10 @@ MainWindow::showTreeContextMenuPopup(const QPoint &pos)
if (rideItem->isDirty() == true)
if (rideItem->isDirty() == true) {
menu.addAction(actSaveRide);
menu.addAction(revertRide);
}
menu.addAction(actDeleteRide);
menu.addAction(actBestInt);
@@ -1175,7 +1206,10 @@ void
MainWindow::closeEvent(QCloseEvent* event)
{
if (saveRideExitDialog() == false) event->ignore();
saveNotes();
else {
saveNotes();
QApplication::clipboard()->setText("");
}
}
void
@@ -1295,6 +1329,17 @@ MainWindow::saveRide()
saveRideSingleDialog(ride); // will update Dirty flag if saved
}
void
MainWindow::revertRide()
{
ride->freeMemory();
ride->ride(); // force re-load
// in case reverted ride has different starttime
ride->setStartTime(ride->ride()->startTime()); // Note: this will also signal rideSelected()
ride->ride()->emitReverted();
}
void
MainWindow::splitRide()
{
@@ -1307,7 +1352,7 @@ MainWindow::deleteRide()
QTreeWidgetItem *_item = treeWidget->currentItem();
if (_item==NULL || _item->type() != RIDE_TYPE)
return;
RideItem *item = reinterpret_cast<RideItem*>(_item);
RideItem *item = static_cast<RideItem*>(_item);
QMessageBox msgBox;
msgBox.setText(tr("Are you sure you want to delete the ride:"));
msgBox.setInformativeText(item->fileName);
@@ -1364,3 +1409,19 @@ MainWindow::notifyRideSelected()
{
rideSelected();
}
void
MainWindow::manualProcess(QString name)
{
// open a dialog box and let the users
// configure the options to use
// and also show the explanation
// of what this function does
// then call it!
RideItem *rideitem = (RideItem*)currentRideItem();
if (rideitem) {
ManualDataProcessorDialog *p = new ManualDataProcessorDialog(this, name, rideitem);
p->setWindowModality(Qt::ApplicationModal); // don't allow select other ride or it all goes wrong!
p->exec();
}
}