/* * 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 #include // 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 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 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; itype != 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; idoCommand(); // 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 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 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 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; ideletePoint(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; }