add begin/end undo/redo signals so that playlist can freeze/thaw itself around potent...
[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         , PBD::ScopedConnectionList ()
43         , _clearing(false)
44 {
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 action)
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         action->DropReferences.connect_same_thread (*this, boost::bind (&command_death, this, action));
88         actions.push_back (action);
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         struct timeval start, end, diff;
254         gettimeofday (&start, 0);
255
256         {
257                 UndoRedoSignaller exception_safe_signaller (*this);
258
259                 while (n--) {
260                         if (UndoList.size() == 0) {
261                                 return;
262                         }
263                         UndoTransaction* ut = UndoList.back ();
264                         UndoList.pop_back ();
265                         ut->undo ();
266                         RedoList.push_back (ut);
267                 }
268                 gettimeofday (&end, 0);
269                 timersub (&end, &start, &diff);
270                 cerr << "Undo-pre-signals took " << diff.tv_sec << '.' << diff.tv_usec << endl;
271
272         }
273
274         gettimeofday (&end, 0);
275         timersub (&end, &start, &diff);
276         cerr << "Undo took " << diff.tv_sec << '.' << diff.tv_usec << endl;
277
278         Changed (); /* EMIT SIGNAL */
279 }
280
281 void
282 UndoHistory::redo (unsigned int n)
283 {
284         if (n == 0) {
285                 return;
286         }
287
288         struct timeval start, end, diff;
289         gettimeofday (&start, 0);
290
291         {
292                 UndoRedoSignaller exception_safe_signaller (*this);
293                 
294                 while (n--) {
295                         if (RedoList.size() == 0) {
296                                 return;
297                         }
298                         UndoTransaction* ut = RedoList.back ();
299                         RedoList.pop_back ();
300                         ut->redo ();
301                         UndoList.push_back (ut);
302                 }
303                 gettimeofday (&end, 0);
304                 timersub (&end, &start, &diff);
305                 cerr << "Redo-pre-signals took " << diff.tv_sec << '.' << diff.tv_usec << endl;
306         }
307
308         gettimeofday (&end, 0);
309         timersub (&end, &start, &diff);
310         cerr << "Redo took " << diff.tv_sec << '.' << diff.tv_usec << endl;
311
312         EndUndoRedo (); /* EMIT SIGNAL */
313         Changed (); /* EMIT SIGNAL */
314 }
315
316 void
317 UndoHistory::clear_redo ()
318 {
319         _clearing = true;
320         RedoList.clear ();
321         _clearing = false;
322
323         Changed (); /* EMIT SIGNAL */
324
325 }
326
327 void
328 UndoHistory::clear_undo ()
329 {
330         _clearing = true;
331         UndoList.clear ();
332         _clearing = false;
333
334         Changed (); /* EMIT SIGNAL */
335 }
336
337 void
338 UndoHistory::clear ()
339 {
340         clear_undo ();
341         clear_redo ();
342
343         Changed (); /* EMIT SIGNAL */
344 }
345
346 XMLNode& 
347 UndoHistory::get_state (int32_t depth)
348 {
349     XMLNode *node = new XMLNode ("UndoHistory");
350
351     if (depth == 0) {
352
353             return (*node);
354
355     } else if (depth < 0) {
356
357             /* everything */
358
359             for (list<UndoTransaction*>::iterator it = UndoList.begin(); it != UndoList.end(); ++it) {
360                     node->add_child_nocopy((*it)->get_state());
361             }
362
363     } else {
364
365             /* just the last "depth" transactions */
366
367             list<UndoTransaction*> in_order;
368
369             for (list<UndoTransaction*>::reverse_iterator it = UndoList.rbegin(); it != UndoList.rend() && depth; ++it, depth--) {
370                     in_order.push_front (*it);
371             }
372
373             for (list<UndoTransaction*>::iterator it = in_order.begin(); it != in_order.end(); it++) {
374                     node->add_child_nocopy((*it)->get_state());
375             }
376     }
377
378     return *node;
379 }
380
381