OSDN Git Service

コア部分を共通プロジェクト化した
[fooeditengine/FooEditEngine.git] / Core / UndoManager.cs
1 /*
2  * Copyright (C) 2013 FooProject
3  * * 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
4  * the Free Software Foundation; either version 3 of the License, or (at your option) any later version.
5
6  * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 
7  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
8
9 You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>.
10  */
11 using System;
12 using System.Collections.Generic;
13 using System.Linq;
14
15 namespace FooEditEngine
16 {
17     interface ICommand
18     {
19         /// <summary>
20         /// アンドゥする
21         /// </summary>
22         void undo();
23         /// <summary>
24         /// リドゥする
25         /// </summary>
26         void redo();
27         /// <summary>
28         /// マージする
29         /// </summary>
30         /// <param name="a"></param>
31         /// <returns>マージできた場合は真、そうでない場合は偽を返す</returns>
32         bool marge(ICommand a);
33         /// <summary>
34         /// コマンドを結合した結果が空なら真。そうでないなら偽を返す
35         /// </summary>
36         /// <returns></returns>
37         bool isempty();
38
39     }
40
41     sealed class BeginActionCommand : ICommand
42     {
43         #region ICommand メンバー
44
45         public void undo()
46         {
47         }
48
49         public void redo()
50         {
51         }
52
53         public bool marge(ICommand a)
54         {
55             return false;
56         }
57
58         public bool isempty()
59         {
60             return false;
61         }
62         #endregion
63     }
64
65     sealed class EndActionCommand : ICommand
66     {
67         #region ICommand メンバー
68
69         public void undo()
70         {
71         }
72
73         public void redo()
74         {
75         }
76
77         public bool marge(ICommand a)
78         {
79             return false;
80         }
81
82         public bool isempty()
83         {
84             return false;
85         }
86         #endregion
87     }
88
89     /// <summary>
90     /// アンドゥバッファーを管理するクラス
91     /// </summary>
92     public sealed class UndoManager
93     {
94         private bool locked = false;
95         private Stack<ICommand> UndoStack = new Stack<ICommand>();
96         private Stack<ICommand> RedoStack = new Stack<ICommand>();
97         private int groupLevel = 0;
98
99         /// <summary>
100         /// コンストラクター
101         /// </summary>
102         internal UndoManager()
103         {
104             this.Grouping = false;
105         }
106
107         /// <summary>
108         /// 操作を履歴として残します
109         /// </summary>
110         /// <param name="cmd">ICommandインターフェイス</param>
111         internal void push(ICommand cmd)
112         {
113             if (this.locked == true)
114                 return;
115             ICommand last = null;
116             if (this.AutoMerge && UndoStack.Count() > 0)
117                 last = UndoStack.First();
118             if(last == null || last.marge(cmd) == false)
119                 UndoStack.Push(cmd);
120             if (last != null && last.isempty())
121                 UndoStack.Pop();
122             if (this.RedoStack.Count > 0)
123                 RedoStack.Clear();
124         }
125
126         /// <summary>
127         /// 履歴として残される操作が一連のグループとして追加されるなら真を返し、そうでなければ偽を返す
128         /// </summary>
129         internal bool Grouping
130         {
131             get;
132             set;
133         }
134
135
136         /// <summary>
137         /// アクションを結合するなら真。そうでないなら偽
138         /// </summary>
139         internal bool AutoMerge
140         {
141             get;
142             set;
143         }
144
145         /// <summary>
146         /// 一連のアンドゥアクションの開始を宣言します
147         /// </summary>
148         public void BeginUndoGroup()
149         {
150             if (this.Grouping)
151             {
152                 this.groupLevel++;
153             }
154             else
155             {
156                 this.push(new BeginActionCommand());
157                 this.Grouping = true;
158                 this.AutoMerge = true;
159             }
160         }
161
162         /// <summary>
163         /// 一連のアンドゥアクションの終了を宣言します
164         /// </summary>
165         public void EndUndoGroup()
166         {
167             if (this.Grouping == false)
168                 throw new InvalidOperationException("BeginUndoGroup()を呼び出してください");
169             if (this.groupLevel > 0)
170             {
171                 this.groupLevel--;
172             }
173             else
174             {
175                 ICommand last = UndoStack.First();
176                 if (last != null && last is BeginActionCommand)
177                     this.UndoStack.Pop();
178                 else
179                     this.push(new EndActionCommand());
180                 this.Grouping = false;
181                 this.AutoMerge = false;
182             }
183         }
184
185         /// <summary>
186         /// 元に戻します
187         /// </summary>
188         public void undo()
189         {
190             if (this.UndoStack.Count == 0 || this.locked == true)
191                 return; 
192   
193             ICommand cmd;
194             bool isGrouped = false;
195
196             do
197             {
198                 cmd = this.UndoStack.Pop();
199                 this.RedoStack.Push(cmd);
200                 this.BeginLock();
201                 cmd.undo();
202                 this.EndLock();
203                 //アンドゥスタック上ではEndActionCommand,...,BeginActionCommandの順番になる
204                 if (cmd is EndActionCommand)
205                     isGrouped = true;
206                 else if (cmd is BeginActionCommand)
207                     isGrouped = false;
208             } while (isGrouped);
209
210         }
211
212         /// <summary>
213         /// 元に戻した動作をやり直します
214         /// </summary>
215         public void redo()
216         {
217             if (this.RedoStack.Count == 0 || this.locked == true)
218                 return;
219             ICommand cmd;
220             bool isGrouped = false;
221
222             do
223             {
224                 cmd = this.RedoStack.Pop();
225                 this.UndoStack.Push(cmd);
226                 this.BeginLock();
227                 cmd.redo();
228                 this.EndLock();
229                 //リドゥスタック上ではBeginActionCommand,...,EndActionCommandの順番になる
230                 if (cmd is BeginActionCommand)
231                     isGrouped = true;
232                 else if (cmd is EndActionCommand)
233                     isGrouped = false;
234             } while (isGrouped);
235         }
236
237         /// <summary>
238         /// 操作履歴をすべて削除します
239         /// </summary>
240         public void clear()
241         {
242             if (this.locked == true)
243                 return;
244             this.UndoStack.Clear();
245             this.RedoStack.Clear();
246         }
247         /// <summary>
248         /// 以後の操作をアンドゥ不能にする
249         /// </summary>
250         public void BeginLock()
251         {
252             this.locked = true;
253         }
254
255         /// <summary>
256         /// 以後の操作をアンドゥ可能にする
257         /// </summary>
258         public void EndLock()
259         {
260             this.locked = false;
261         }
262     }
263 }