/* * Copyright (C) 2013 FooProject * * 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 3 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, see . */ using System; using System.Collections.Generic; using System.Linq; namespace FooEditEngine { interface ICommand { /// /// アンドゥする /// void undo(); /// /// リドゥする /// void redo(); /// /// マージする /// /// /// マージできた場合は真、そうでない場合は偽を返す bool marge(ICommand a); /// /// コマンドを結合した結果が空なら真。そうでないなら偽を返す /// /// bool isempty(); } sealed class BeginActionCommand : ICommand { #region ICommand メンバー public void undo() { } public void redo() { } public bool marge(ICommand a) { return false; } public bool isempty() { return false; } #endregion } sealed class EndActionCommand : ICommand { #region ICommand メンバー public void undo() { } public void redo() { } public bool marge(ICommand a) { return false; } public bool isempty() { return false; } #endregion } /// /// アンドゥバッファーを管理するクラス /// public sealed class UndoManager { private bool locked = false; private Stack UndoStack = new Stack(); private Stack RedoStack = new Stack(); private int groupLevel = 0; /// /// コンストラクター /// internal UndoManager() { this.Grouping = false; } /// /// 操作を履歴として残します /// /// ICommandインターフェイス internal void push(ICommand cmd) { if (this.locked == true) return; ICommand last = null; if (this.AutoMerge && UndoStack.Count() > 0) last = UndoStack.First(); if(last == null || last.marge(cmd) == false) UndoStack.Push(cmd); if (last != null && last.isempty()) UndoStack.Pop(); if (this.RedoStack.Count > 0) RedoStack.Clear(); } /// /// 履歴として残される操作が一連のグループとして追加されるなら真を返し、そうでなければ偽を返す /// internal bool Grouping { get; set; } /// /// アクションを結合するなら真。そうでないなら偽 /// internal bool AutoMerge { get; set; } /// /// 一連のアンドゥアクションの開始を宣言します /// public void BeginUndoGroup() { if (this.Grouping) { this.groupLevel++; } else { this.push(new BeginActionCommand()); this.Grouping = true; this.AutoMerge = true; } } /// /// 一連のアンドゥアクションの終了を宣言します /// public void EndUndoGroup() { if (this.Grouping == false) throw new InvalidOperationException("BeginUndoGroup()を呼び出してください"); if (this.groupLevel > 0) { this.groupLevel--; } else { ICommand last = UndoStack.First(); if (last != null && last is BeginActionCommand) this.UndoStack.Pop(); else this.push(new EndActionCommand()); this.Grouping = false; this.AutoMerge = false; } } /// /// 元に戻します /// public void undo() { if (this.UndoStack.Count == 0 || this.locked == true) return; ICommand cmd; bool isGrouped = false; do { cmd = this.UndoStack.Pop(); this.RedoStack.Push(cmd); this.BeginLock(); cmd.undo(); this.EndLock(); //アンドゥスタック上ではEndActionCommand,...,BeginActionCommandの順番になる if (cmd is EndActionCommand) isGrouped = true; else if (cmd is BeginActionCommand) isGrouped = false; } while (isGrouped); } /// /// 元に戻した動作をやり直します /// public void redo() { if (this.RedoStack.Count == 0 || this.locked == true) return; ICommand cmd; bool isGrouped = false; do { cmd = this.RedoStack.Pop(); this.UndoStack.Push(cmd); this.BeginLock(); cmd.redo(); this.EndLock(); //リドゥスタック上ではBeginActionCommand,...,EndActionCommandの順番になる if (cmd is BeginActionCommand) isGrouped = true; else if (cmd is EndActionCommand) isGrouped = false; } while (isGrouped); } /// /// 操作履歴をすべて削除します /// public void clear() { if (this.locked == true) return; this.UndoStack.Clear(); this.RedoStack.Clear(); } /// /// 以後の操作をアンドゥ不能にする /// public void BeginLock() { this.locked = true; } /// /// 以後の操作をアンドゥ可能にする /// public void EndLock() { this.locked = false; } } }