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