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