223c533ff80141ab0c569b2066d31336d7983323
[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         struct timeval start, end, diff;
126         gettimeofday (&start, 0);
127         for (list<Command*>::reverse_iterator i = actions.rbegin(); i != actions.rend(); ++i) {
128                 (*i)->undo();
129         }
130         gettimeofday (&end, 0);
131         timersub (&end, &start, &diff);
132         cerr << "Undo took " << diff.tv_sec << '.' << diff.tv_usec << endl;
133 }
134
135 void
136 UndoTransaction::redo ()
137 {
138         struct timeval start, end, diff;
139         gettimeofday (&start, 0);
140         (*this)();
141         gettimeofday (&end, 0);
142         timersub (&end, &start, &diff);
143         cerr << "Undo took " << diff.tv_sec << '.' << diff.tv_usec << endl;
144 }
145
146 XMLNode &UndoTransaction::get_state()
147 {
148     XMLNode *node = new XMLNode ("UndoTransaction");
149     stringstream ss;
150     ss << _timestamp.tv_sec;
151     node->add_property("tv_sec", ss.str());
152     ss.str("");
153     ss << _timestamp.tv_usec;
154     node->add_property("tv_usec", ss.str());
155     node->add_property("name", _name);
156
157     list<Command*>::iterator it;
158     for (it=actions.begin(); it!=actions.end(); it++)
159         node->add_child_nocopy((*it)->get_state());
160
161     return *node;
162 }
163
164 UndoHistory::UndoHistory ()
165 {
166         _clearing = false;
167         _depth = 0;
168 }
169
170 void
171 UndoHistory::set_depth (uint32_t d)
172 {
173         UndoTransaction* ut;
174         uint32_t current_depth = UndoList.size();
175
176         _depth = d;
177
178         if (d > current_depth) {
179                 /* not even transactions to meet request */
180                 return;
181         }
182
183         if (_depth > 0) {
184
185                 uint32_t cnt = current_depth - d;
186
187                 while (cnt--) {
188                         ut = UndoList.front();
189                         UndoList.pop_front ();
190                         delete ut;
191                 }
192         }
193 }
194
195 void
196 UndoHistory::add (UndoTransaction* const ut)
197 {
198         uint32_t current_depth = UndoList.size();
199
200         ut->DropReferences.connect_same_thread (*this, boost::bind (&UndoHistory::remove, this, ut));
201
202         /* if the current undo history is larger than or equal to the currently
203            requested depth, then pop off at least 1 element to make space
204            at the back for new one.
205         */
206
207         if ((_depth > 0) && current_depth && (current_depth >= _depth)) {
208
209                 uint32_t cnt = 1 + (current_depth - _depth);
210
211                 while (cnt--) {
212                         UndoTransaction* ut;
213                         ut = UndoList.front ();
214                         UndoList.pop_front ();
215                         delete ut;
216                 }
217         }
218
219         UndoList.push_back (ut);
220
221         /* we are now owners of the transaction and must delete it when finished with it */
222
223         Changed (); /* EMIT SIGNAL */
224 }
225
226 void
227 UndoHistory::remove (UndoTransaction* const ut)
228 {
229         if (_clearing) {
230                 return;
231         }
232
233         UndoList.remove (ut);
234         RedoList.remove (ut);
235
236         Changed (); /* EMIT SIGNAL */
237 }
238
239 /** Undo some transactions.
240  * @param n Number of transactions to undo.
241  */
242 void
243 UndoHistory::undo (unsigned int n)
244 {
245         while (n--) {
246                 if (UndoList.size() == 0) {
247                         return;
248                 }
249                 UndoTransaction* ut = UndoList.back ();
250                 UndoList.pop_back ();
251                 ut->undo ();
252                 RedoList.push_back (ut);
253         }
254
255         Changed (); /* EMIT SIGNAL */
256 }
257
258 void
259 UndoHistory::redo (unsigned int n)
260 {
261         while (n--) {
262                 if (RedoList.size() == 0) {
263                         return;
264                 }
265                 UndoTransaction* ut = RedoList.back ();
266                 RedoList.pop_back ();
267                 ut->redo ();
268                 UndoList.push_back (ut);
269         }
270
271         Changed (); /* EMIT SIGNAL */
272 }
273
274 void
275 UndoHistory::clear_redo ()
276 {
277         _clearing = true;
278         RedoList.clear ();
279         _clearing = false;
280
281         Changed (); /* EMIT SIGNAL */
282
283 }
284
285 void
286 UndoHistory::clear_undo ()
287 {
288         _clearing = true;
289         UndoList.clear ();
290         _clearing = false;
291
292         Changed (); /* EMIT SIGNAL */
293 }
294
295 void
296 UndoHistory::clear ()
297 {
298         clear_undo ();
299         clear_redo ();
300
301         Changed (); /* EMIT SIGNAL */
302 }
303
304 XMLNode& 
305 UndoHistory::get_state (int32_t depth)
306 {
307     XMLNode *node = new XMLNode ("UndoHistory");
308
309     if (depth == 0) {
310
311             return (*node);
312
313     } else if (depth < 0) {
314
315             /* everything */
316
317             for (list<UndoTransaction*>::iterator it = UndoList.begin(); it != UndoList.end(); ++it) {
318                     node->add_child_nocopy((*it)->get_state());
319             }
320
321     } else {
322
323             /* just the last "depth" transactions */
324
325             list<UndoTransaction*> in_order;
326
327             for (list<UndoTransaction*>::reverse_iterator it = UndoList.rbegin(); it != UndoList.rend() && depth; ++it, depth--) {
328                     in_order.push_front (*it);
329             }
330
331             for (list<UndoTransaction*>::iterator it = in_order.begin(); it != in_order.end(); it++) {
332                     node->add_child_nocopy((*it)->get_state());
333             }
334     }
335
336     return *node;
337 }
338
339