mirror of
https://github.com/GoldenCheetah/GoldenCheetah.git
synced 2026-02-14 00:28:42 +00:00
410 lines
10 KiB
C++
410 lines
10 KiB
C++
/*
|
|
* Copyright (c) 2010 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 "RideFile.h"
|
|
#include "RideFileCommand.h"
|
|
#include "RideEditor.h"
|
|
#include <math.h>
|
|
#include <float.h>
|
|
|
|
// comparing doubles is nasty
|
|
static bool doubles_equal(double a, double b)
|
|
{
|
|
double errorB = b * DBL_EPSILON;
|
|
return (a >= b - errorB) && (a <= b + errorB);
|
|
}
|
|
//----------------------------------------------------------------------
|
|
// The public interface to the commands
|
|
//----------------------------------------------------------------------
|
|
RideFileCommand::RideFileCommand(RideFile *ride) : ride(ride), stackptr(0), inLUW(false), luw(NULL)
|
|
{
|
|
connect(ride, SIGNAL(saved()), this, SLOT(clearHistory()));
|
|
connect(ride, SIGNAL(reverted()), this, SLOT(clearHistory()));
|
|
}
|
|
|
|
RideFileCommand::~RideFileCommand()
|
|
{
|
|
clearHistory();
|
|
}
|
|
|
|
void
|
|
RideFileCommand::setPointValue(int index, RideFile::SeriesType series, double value)
|
|
{
|
|
SetPointValueCommand *cmd = new SetPointValueCommand(ride, index, series,
|
|
ride->getPointValue(index, series), value);
|
|
doCommand(cmd);
|
|
}
|
|
|
|
void
|
|
RideFileCommand::deletePoint(int index)
|
|
{
|
|
RideFilePoint current = *ride->dataPoints()[index];
|
|
DeletePointCommand *cmd = new DeletePointCommand(ride, index, current);
|
|
doCommand(cmd);
|
|
}
|
|
|
|
void
|
|
RideFileCommand::deletePoints(int index, int count)
|
|
{
|
|
QVector<RideFilePoint> current;
|
|
for(int i=0; i< count; i++) {
|
|
RideFilePoint point = *ride->dataPoints()[index+i];
|
|
current.append(point);
|
|
}
|
|
DeletePointsCommand *cmd = new DeletePointsCommand(ride, index, count, current);
|
|
doCommand(cmd);
|
|
}
|
|
|
|
void
|
|
RideFileCommand::insertPoint(int index, RideFilePoint *points)
|
|
{
|
|
InsertPointCommand *cmd = new InsertPointCommand(ride, index, points);
|
|
doCommand(cmd);
|
|
}
|
|
|
|
void
|
|
RideFileCommand::appendPoints(QVector <RideFilePoint> newRows)
|
|
{
|
|
AppendPointsCommand *cmd = new AppendPointsCommand(ride, ride->dataPoints().count(), newRows);
|
|
doCommand(cmd);
|
|
}
|
|
|
|
void
|
|
RideFileCommand::setDataPresent(RideFile::SeriesType type, bool newvalue)
|
|
{
|
|
bool oldvalue = ride->isDataPresent(type);
|
|
SetDataPresentCommand *cmd = new SetDataPresentCommand(ride, type, newvalue, oldvalue);
|
|
doCommand(cmd);
|
|
}
|
|
|
|
QString
|
|
RideFileCommand::changeLog()
|
|
{
|
|
QString log;
|
|
for (int i=0; i<stackptr; i++) {
|
|
if (stack[i]->type != RideCommand::NoOp)
|
|
log += stack[i]->description + '\n';
|
|
}
|
|
return log;
|
|
}
|
|
//----------------------------------------------------------------------
|
|
// Manage the Command Stack
|
|
//----------------------------------------------------------------------
|
|
void
|
|
RideFileCommand::clearHistory()
|
|
{
|
|
foreach (RideCommand *cmd, stack) delete cmd;
|
|
stack.clear();
|
|
stackptr = 0;
|
|
}
|
|
|
|
void
|
|
RideFileCommand::startLUW(QString name)
|
|
{
|
|
luw = new LUWCommand(this, name, ride);
|
|
inLUW = true;
|
|
beginCommand(false, luw);
|
|
}
|
|
|
|
void
|
|
RideFileCommand::endLUW()
|
|
{
|
|
if (inLUW == false) return; // huh?
|
|
inLUW = false;
|
|
|
|
// add to the stack if it isn't empty
|
|
if (luw->worklist.count()) doCommand(luw, true);
|
|
}
|
|
|
|
void
|
|
RideFileCommand::doCommand(RideCommand *cmd, bool noexec)
|
|
{
|
|
|
|
// we must add to the LUW, but also must
|
|
// execute immediately since state data
|
|
// is collected by each command as it is
|
|
// created.
|
|
if (inLUW) {
|
|
luw->addCommand(cmd);
|
|
beginCommand(false, cmd);
|
|
cmd->doCommand(); // luw must be executed as added!!!
|
|
cmd->docount++;
|
|
endCommand(false, cmd);
|
|
return;
|
|
}
|
|
|
|
// place onto stack
|
|
if (stack.count()) {
|
|
// wipe away commands we can no longer redo
|
|
for (int i=stackptr; i<stack.count(); i++) delete stack.at(i);
|
|
stack.remove(stackptr, stack.count() - stackptr);
|
|
}
|
|
stack.append(cmd);
|
|
stackptr++;
|
|
|
|
if (noexec == false) {
|
|
beginCommand(false, cmd); // signal
|
|
cmd->doCommand(); // execute
|
|
}
|
|
cmd->docount++;
|
|
endCommand(false, cmd); // signal - even if LUW
|
|
|
|
// we changed it!
|
|
ride->emitModified();
|
|
}
|
|
|
|
void
|
|
RideFileCommand::redoCommand()
|
|
{
|
|
if (stackptr < stack.count()) {
|
|
beginCommand(false, stack[stackptr]); // signal
|
|
stack[stackptr]->doCommand();
|
|
stack[stackptr]->docount++;
|
|
stackptr++; // increment before end to keep in sync in case
|
|
// it is queried 'after' the command is executed
|
|
// i.e. within a slot connected to this signal
|
|
endCommand(false, stack[stackptr-1]); // signal
|
|
}
|
|
}
|
|
|
|
void
|
|
RideFileCommand::undoCommand()
|
|
{
|
|
if (stackptr > 0) {
|
|
stackptr--;
|
|
|
|
beginCommand(true, stack[stackptr]); // signal
|
|
stack[stackptr]->undoCommand();
|
|
endCommand(true, stack[stackptr]); // signal
|
|
}
|
|
}
|
|
|
|
int
|
|
RideFileCommand::undoCount()
|
|
{
|
|
return stackptr;
|
|
}
|
|
|
|
int
|
|
RideFileCommand::redoCount()
|
|
{
|
|
return stack.count() - stackptr;
|
|
}
|
|
|
|
void
|
|
RideFileCommand::emitBeginCommand(bool undo, RideCommand *cmd)
|
|
{
|
|
emit beginCommand(undo, cmd);
|
|
}
|
|
|
|
void
|
|
RideFileCommand::emitEndCommand(bool undo, RideCommand *cmd)
|
|
{
|
|
emit endCommand(undo, cmd);
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
// Commands...
|
|
//----------------------------------------------------------------------
|
|
|
|
// Logical Unit of work
|
|
LUWCommand::LUWCommand(RideFileCommand *commander, QString name, RideFile *ride) :
|
|
RideCommand(ride), commander(commander)
|
|
{
|
|
type = RideCommand::LUW;
|
|
description = name;
|
|
}
|
|
|
|
LUWCommand::~LUWCommand()
|
|
{
|
|
foreach(RideCommand *cmd, worklist) delete cmd;
|
|
}
|
|
|
|
bool
|
|
LUWCommand::doCommand()
|
|
{
|
|
foreach(RideCommand *cmd, worklist) {
|
|
commander->emitBeginCommand(false, cmd);
|
|
cmd->doCommand();
|
|
commander->emitEndCommand(false, cmd);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
LUWCommand::undoCommand()
|
|
{
|
|
for (int i=worklist.count(); i > 0; i--) {
|
|
RideCommand *cmd = worklist[i-1];
|
|
commander->emitBeginCommand(true, cmd);
|
|
cmd->undoCommand();
|
|
commander->emitEndCommand(true, cmd);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Set point value
|
|
SetPointValueCommand::SetPointValueCommand(RideFile *ride, int row,
|
|
RideFile::SeriesType series, double oldvalue, double newvalue) :
|
|
RideCommand(ride), // base class looks after these
|
|
row(row), series(series), oldvalue(oldvalue), newvalue(newvalue)
|
|
{
|
|
type = RideCommand::SetPointValue;
|
|
description = tr("Set Value");
|
|
}
|
|
|
|
bool
|
|
SetPointValueCommand::doCommand()
|
|
{
|
|
// check it has changed first!
|
|
if (!doubles_equal(oldvalue, newvalue)) {
|
|
ride->setPointValue(row,series,newvalue);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
SetPointValueCommand::undoCommand()
|
|
{
|
|
if (!doubles_equal(oldvalue, newvalue)) {
|
|
ride->setPointValue(row,series,oldvalue);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Remove a point
|
|
DeletePointCommand::DeletePointCommand(RideFile *ride, int row, RideFilePoint point) :
|
|
RideCommand(ride), // base class looks after these
|
|
row(row), point(point)
|
|
{
|
|
type = RideCommand::DeletePoint;
|
|
description = tr("Remove Point");
|
|
}
|
|
|
|
bool
|
|
DeletePointCommand::doCommand()
|
|
{
|
|
ride->deletePoint(row);
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
DeletePointCommand::undoCommand()
|
|
{
|
|
ride->insertPoint(row, new RideFilePoint(point));
|
|
return true;
|
|
}
|
|
|
|
// Remove points
|
|
DeletePointsCommand::DeletePointsCommand(RideFile *ride, int row, int count,
|
|
QVector<RideFilePoint> current) :
|
|
RideCommand(ride), // base class looks after these
|
|
row(row), count(count), points(current)
|
|
{
|
|
type = RideCommand::DeletePoints;
|
|
description = tr("Remove Points");
|
|
}
|
|
|
|
bool
|
|
DeletePointsCommand::doCommand()
|
|
{
|
|
ride->deletePoints(row, count);
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
DeletePointsCommand::undoCommand()
|
|
{
|
|
for (int i=(count-1); i>=0; i--) ride->insertPoint(row, new RideFilePoint(points[i]));
|
|
return true;
|
|
}
|
|
|
|
// Insert a point
|
|
InsertPointCommand::InsertPointCommand(RideFile *ride, int row, RideFilePoint *point) :
|
|
RideCommand(ride), // base class looks after these
|
|
row(row), point(*point)
|
|
{
|
|
type = RideCommand::InsertPoint;
|
|
description = tr("Insert Point");
|
|
}
|
|
|
|
bool
|
|
InsertPointCommand::doCommand()
|
|
{
|
|
ride->insertPoint(row, new RideFilePoint(point));
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
InsertPointCommand::undoCommand()
|
|
{
|
|
ride->deletePoint(row);
|
|
return true;
|
|
}
|
|
|
|
// Append points
|
|
AppendPointsCommand::AppendPointsCommand(RideFile *ride, int row, QVector<RideFilePoint> points) :
|
|
RideCommand(ride), // base class looks after these
|
|
row(row), count (points.count()), points(points)
|
|
{
|
|
type = RideCommand::AppendPoints;
|
|
description = tr("Append Points");
|
|
}
|
|
|
|
bool
|
|
AppendPointsCommand::doCommand()
|
|
{
|
|
QVector<RideFilePoint *> newPoints;
|
|
foreach (RideFilePoint point, points) {
|
|
RideFilePoint *p = new RideFilePoint(point);
|
|
newPoints.append(p);
|
|
}
|
|
ride->appendPoints(newPoints);
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
AppendPointsCommand::undoCommand()
|
|
{
|
|
for (int i=0; i<count; i++) ride->deletePoint(row);
|
|
return true;
|
|
}
|
|
|
|
SetDataPresentCommand::SetDataPresentCommand(RideFile *ride,
|
|
RideFile::SeriesType series, bool newvalue, bool oldvalue) :
|
|
RideCommand(ride), // base class looks after these
|
|
series(series), oldvalue(oldvalue), newvalue(newvalue)
|
|
{
|
|
type = RideCommand::SetDataPresent;
|
|
description = tr("Set Data Present");
|
|
}
|
|
|
|
bool
|
|
SetDataPresentCommand::doCommand()
|
|
{
|
|
ride->setDataPresent(series, newvalue);
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
SetDataPresentCommand::undoCommand()
|
|
{
|
|
ride->setDataPresent(series, oldvalue);
|
|
return true;
|
|
}
|