1 /*
2 * Copyright (C) 2001 Brett Viren
3 * Copyright (C) 2001-2015 Paul Davis <paul@linuxaudiosystems.com>
4 * Copyright (C) 2006 Hans Fugal <hans@fugal.net>
5 * Copyright (C) 2007-2009 David Robillard <d@drobilla.net>
6 * Copyright (C) 2012-2017 Tim Mayberry <mojofunk@gmail.com>
7 * Copyright (C) 2015 Nick Mainsbridge <mainsbridge@gmail.com>
8 *
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License along
20 * with this program; if not, write to the Free Software Foundation, Inc.,
21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22 */
23
24 #include <sstream>
25 #include <string>
26 #include <time.h>
27
28 #include "pbd/undo.h"
29 #include "pbd/xml++.h"
30
31 using namespace std;
32 using namespace sigc;
33
UndoTransaction()34 UndoTransaction::UndoTransaction ()
35 : _clearing (false)
36 {
37 gettimeofday (&_timestamp, 0);
38 }
39
UndoTransaction(const UndoTransaction & rhs)40 UndoTransaction::UndoTransaction (const UndoTransaction& rhs)
41 : Command (rhs._name)
42 , _clearing (false)
43 {
44 _timestamp = rhs._timestamp;
45 clear ();
46 actions.insert (actions.end (), rhs.actions.begin (), rhs.actions.end ());
47 }
48
~UndoTransaction()49 UndoTransaction::~UndoTransaction ()
50 {
51 drop_references ();
52 clear ();
53 }
54
55 static void
command_death(UndoTransaction * ut,Command * c)56 command_death (UndoTransaction* ut, Command* c)
57 {
58 if (ut->clearing ()) {
59 return;
60 }
61
62 ut->remove_command (c);
63
64 if (ut->empty ()) {
65 delete ut;
66 }
67 }
68
69 UndoTransaction&
operator =(const UndoTransaction & rhs)70 UndoTransaction::operator= (const UndoTransaction& rhs)
71 {
72 if (this == &rhs) {
73 return *this;
74 }
75 _name = rhs._name;
76 clear ();
77 actions.insert (actions.end (), rhs.actions.begin (), rhs.actions.end ());
78 return *this;
79 }
80
81 void
add_command(Command * const cmd)82 UndoTransaction::add_command (Command* const cmd)
83 {
84 /* catch death of command (e.g. caused by death of object to
85 * which it refers. command_death() is a normal static function
86 * so there is no need to manage this connection.
87 */
88
89 cmd->DropReferences.connect_same_thread (*this, boost::bind (&command_death, this, cmd));
90 actions.push_back (cmd);
91 }
92
93 void
remove_command(Command * const action)94 UndoTransaction::remove_command (Command* const action)
95 {
96 list<Command*>::iterator i =std::find (actions.begin (), actions.end (), action);
97 if (i == actions.end ()) {
98 return;
99 }
100 actions.erase (i);
101 delete action;
102 }
103
104 bool
empty() const105 UndoTransaction::empty () const
106 {
107 return actions.empty ();
108 }
109
110 void
clear()111 UndoTransaction::clear ()
112 {
113 _clearing = true;
114 for (list<Command*>::iterator i = actions.begin (); i != actions.end (); ++i) {
115 delete *i;
116 }
117 actions.clear ();
118 _clearing = false;
119 }
120
121 void
operator ()()122 UndoTransaction::operator() ()
123 {
124 for (list<Command*>::iterator i = actions.begin (); i != actions.end (); ++i) {
125 (*(*i)) ();
126 }
127 }
128
129 void
undo()130 UndoTransaction::undo ()
131 {
132 for (list<Command*>::reverse_iterator i = actions.rbegin (); i != actions.rend (); ++i) {
133 (*i)->undo ();
134 }
135 }
136
137 void
redo()138 UndoTransaction::redo ()
139 {
140 (*this) ();
141 }
142
143 XMLNode&
get_state()144 UndoTransaction::get_state ()
145 {
146 XMLNode* node = new XMLNode ("UndoTransaction");
147 node->set_property ("tv-sec", (int64_t)_timestamp.tv_sec);
148 node->set_property ("tv-usec", (int64_t)_timestamp.tv_usec);
149 node->set_property ("name", _name);
150
151 list<Command*>::iterator it;
152 for (it = actions.begin (); it != actions.end (); it++) {
153 node->add_child_nocopy ((*it)->get_state ());
154 }
155
156 return *node;
157 }
158
159 class UndoRedoSignaller
160 {
161 public:
UndoRedoSignaller(UndoHistory & uh)162 UndoRedoSignaller (UndoHistory& uh)
163 : _history (uh)
164 {
165 _history.BeginUndoRedo ();
166 }
167
~UndoRedoSignaller()168 ~UndoRedoSignaller ()
169 {
170 _history.EndUndoRedo ();
171 }
172
173 private:
174 UndoHistory& _history;
175 };
176
UndoHistory()177 UndoHistory::UndoHistory ()
178 {
179 _clearing = false;
180 _depth = 0;
181 }
182
183 void
set_depth(uint32_t d)184 UndoHistory::set_depth (uint32_t d)
185 {
186 uint32_t current_depth = UndoList.size ();
187
188 _depth = d;
189
190 if (d > current_depth) {
191 /* not even transactions to meet request */
192 return;
193 }
194
195 if (_depth > 0) {
196 uint32_t cnt = current_depth - d;
197
198 while (cnt--) {
199 UndoTransaction* ut = UndoList.front ();
200 UndoList.pop_front ();
201 delete ut;
202 }
203 }
204 }
205
206 void
add(UndoTransaction * const ut)207 UndoHistory::add (UndoTransaction* const ut)
208 {
209 uint32_t current_depth = UndoList.size ();
210
211 ut->DropReferences.connect_same_thread (*this, boost::bind (&UndoHistory::remove, this, ut));
212
213 /* if the current undo history is larger than or equal to the currently
214 requested depth, then pop off at least 1 element to make space
215 at the back for new one.
216 */
217
218 if ((_depth > 0) && current_depth && (current_depth >= _depth)) {
219 uint32_t cnt = 1 + (current_depth - _depth);
220
221 while (cnt--) {
222 UndoTransaction* ut;
223 ut = UndoList.front ();
224 UndoList.pop_front ();
225 delete ut;
226 }
227 }
228
229 UndoList.push_back (ut);
230 /* Adding a transacrion makes the redo list meaningless. */
231 _clearing = true;
232 for (std::list<UndoTransaction*>::iterator i = RedoList.begin (); i != RedoList.end (); ++i) {
233 delete *i;
234 }
235 RedoList.clear ();
236 _clearing = false;
237
238 /* we are now owners of the transaction and must delete it when finished with it */
239
240 Changed (); /* EMIT SIGNAL */
241 }
242
243 void
remove(UndoTransaction * const ut)244 UndoHistory::remove (UndoTransaction* const ut)
245 {
246 if (_clearing) {
247 return;
248 }
249
250 UndoList.remove (ut);
251 RedoList.remove (ut);
252
253 Changed (); /* EMIT SIGNAL */
254 }
255
256 /** Undo some transactions.
257 * @param n Number of transactions to undo.
258 */
259 void
undo(unsigned int n)260 UndoHistory::undo (unsigned int n)
261 {
262 if (n == 0) {
263 return;
264 }
265
266 {
267 UndoRedoSignaller exception_safe_signaller (*this);
268
269 while (n--) {
270 if (UndoList.size () == 0) {
271 return;
272 }
273 UndoTransaction* ut = UndoList.back ();
274 UndoList.pop_back ();
275 ut->undo ();
276 RedoList.push_back (ut);
277 }
278 }
279
280 Changed (); /* EMIT SIGNAL */
281 }
282
283 void
redo(unsigned int n)284 UndoHistory::redo (unsigned int n)
285 {
286 if (n == 0) {
287 return;
288 }
289
290 {
291 UndoRedoSignaller exception_safe_signaller (*this);
292
293 while (n--) {
294 if (RedoList.size () == 0) {
295 return;
296 }
297 UndoTransaction* ut = RedoList.back ();
298 RedoList.pop_back ();
299 ut->redo ();
300 UndoList.push_back (ut);
301 }
302 }
303
304 Changed (); /* EMIT SIGNAL */
305 }
306
307 void
clear_redo()308 UndoHistory::clear_redo ()
309 {
310 _clearing = true;
311 for (std::list<UndoTransaction*>::iterator i = RedoList.begin (); i != RedoList.end (); ++i) {
312 delete *i;
313 }
314 RedoList.clear ();
315 _clearing = false;
316
317 Changed (); /* EMIT SIGNAL */
318 }
319
320 void
clear_undo()321 UndoHistory::clear_undo ()
322 {
323 _clearing = true;
324 for (std::list<UndoTransaction*>::iterator i = UndoList.begin (); i != UndoList.end (); ++i) {
325 delete *i;
326 }
327 UndoList.clear ();
328 _clearing = false;
329
330 Changed (); /* EMIT SIGNAL */
331 }
332
333 void
clear()334 UndoHistory::clear ()
335 {
336 clear_undo ();
337 clear_redo ();
338
339 Changed (); /* EMIT SIGNAL */
340 }
341
342 XMLNode&
get_state(int32_t depth)343 UndoHistory::get_state (int32_t depth)
344 {
345 XMLNode* node = new XMLNode ("UndoHistory");
346
347 if (depth == 0) {
348 return (*node);
349
350 } else if (depth < 0) {
351 /* everything */
352
353 for (list<UndoTransaction*>::iterator it = UndoList.begin (); it != UndoList.end (); ++it) {
354 node->add_child_nocopy ((*it)->get_state ());
355 }
356
357 } else {
358 /* just the last "depth" transactions */
359
360 list<UndoTransaction*> in_order;
361
362 for (list<UndoTransaction*>::reverse_iterator it = UndoList.rbegin (); it != UndoList.rend () && depth; ++it, depth--) {
363 in_order.push_front (*it);
364 }
365
366 for (list<UndoTransaction*>::iterator it = in_order.begin (); it != in_order.end (); it++) {
367 node->add_child_nocopy ((*it)->get_state ());
368 }
369 }
370
371 return *node;
372 }
373