私はかつて3Dモデリングアプリケーションを作成するプロジェクトを持っていましたが、そのために同じ要件がありました。私が作業しているときに理解した限りでは、何があっても操作は常にそれが何をしたかを知っている必要があり、したがってそれを元に戻す方法を知っている必要があります。そこで、操作ごとに基本クラスを作成し、その操作状態を以下のようにしました。
class OperationState
{
protected:
Operation& mParent;
OperationState(Operation& parent);
public:
virtual ~OperationState();
Operation& getParent();
};
class Operation
{
private:
const std::string mName;
public:
Operation(const std::string& name);
virtual ~Operation();
const std::string& getName() const{return mName;}
virtual OperationState* operator ()() = 0;
virtual bool undo(OperationState* state) = 0;
virtual bool redo(OperationState* state) = 0;
};
関数とその状態を作成すると、次のようになります。
class MoveState : public OperationState
{
public:
struct ObjectPos
{
Object* object;
Vector3 prevPosition;
};
MoveState(MoveOperation& parent):OperationState(parent){}
typedef std::list<ObjectPos> PrevPositions;
PrevPositions prevPositions;
};
class MoveOperation : public Operation
{
public:
MoveOperation():Operation("Move"){}
~MoveOperation();
// Implement the function and return the previous
// previous states of the objects this function
// changed.
virtual OperationState* operator ()();
// Implement the undo function
virtual bool undo(OperationState* state);
// Implement the redo function
virtual bool redo(OperationState* state);
};
以前はOperationManagerというクラスがありました。これにより、さまざまな関数が登録され、その中に次のようなインスタンスが作成されました。
OperationManager& opMgr = OperationManager::GetInstance();
opMgr.register<MoveOperation>();
レジスター機能は次のようになりました。
template <typename T>
void OperationManager::register()
{
T* op = new T();
const std::string& op_name = op->getName();
if(mOperations.count(op_name))
{
delete op;
}else{
mOperations[op_name] = op;
}
}
関数が実行されるときはいつでも、それは現在選択されているオブジェクトまたはそれが作業する必要があるものに基づいています。注:私の場合、各オブジェクトがアクティブな関数として設定された後、入力デバイスからMoveOperationによって計算されていたため、各オブジェクトの移動量の詳細を送信する必要はありませんでした。
OperationManagerでは、関数の実行は次のようになります。
void OperationManager::execute(const std::string& operation_name)
{
if(mOperations.count(operation_name))
{
Operation& op = *mOperations[operation_name];
OperationState* opState = op();
if(opState)
{
mUndoStack.push(opState);
}
}
}
元に戻す必要がある場合は、OperationManagerから次のように行います
OperationManager::GetInstance().undo();
。OperationManagerの元に戻す機能は次のようになります。
void OperationManager::undo()
{
if(!mUndoStack.empty())
{
OperationState* state = mUndoStack.pop();
if(state->getParent().undo(state))
{
mRedoStack.push(state);
}else{
// Throw an exception or warn the user.
}
}
}
これにより、OperationManagerは各関数に必要な引数を認識しなくなり、さまざまな関数を簡単に管理できるようになりました。