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