Remove unnecessary 0 checks before delete; see http://www.parashift.com/c++-faq-lite...
[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 <string>
22 #include <sstream>
23 #include <time.h>
24
25 #include <pbd/undo.h>
26 #include <pbd/xml++.h>
27
28 #include <sigc++/bind.h>
29
30 using namespace std;
31 using namespace sigc;
32
33 UndoTransaction::UndoTransaction ()
34         : _clearing(false)
35 {
36         gettimeofday (&_timestamp, 0);
37 }
38
39 UndoTransaction::UndoTransaction (const UndoTransaction& rhs)
40         : Command(rhs._name)
41         , _clearing(false)
42 {
43         clear ();
44         actions.insert(actions.end(),rhs.actions.begin(),rhs.actions.end());
45 }
46
47 UndoTransaction::~UndoTransaction ()
48 {
49         GoingAway ();
50         clear ();
51 }
52
53 void 
54 command_death (UndoTransaction* ut, Command* c)
55 {
56         if (ut->clearing()) {
57                 return;
58         }
59
60         ut->remove_command (c);
61
62         if (ut->empty()) {
63                 delete ut;
64         }
65 }
66
67 UndoTransaction& 
68 UndoTransaction::operator= (const UndoTransaction& rhs)
69 {
70         if (this == &rhs) return *this;
71         _name = rhs._name;
72         clear ();
73         actions.insert(actions.end(),rhs.actions.begin(),rhs.actions.end());
74         return *this;
75 }
76
77 void
78 UndoTransaction::add_command (Command *const action)
79 {
80         /* catch death of command (e.g. caused by death of object to
81            which it refers.
82          */
83         shivas.push_back (new PBD::ProxyShiva<Command,UndoTransaction> (*action, *this, &command_death));
84         actions.push_back (action);
85 }
86
87 void
88 UndoTransaction::remove_command (Command* const action)
89 {
90         actions.remove (action);
91 }
92
93 void
94 UndoTransaction::about_to_explicitly_delete ()
95 {
96         /* someone is going to call our destructor and its not Shiva,
97            the god of destruction and chaos. This happens when an UndoHistory
98            is pruning itself. we must remove Shivas to avoid the god
99            striking us down a second time, unnecessarily and illegally.
100         */
101
102         for (list<PBD::ProxyShiva<Command,UndoTransaction>*>::iterator i = shivas.begin(); i != shivas.end(); ++i) {
103                 delete *i;
104         }
105         shivas.clear ();
106 }
107
108 bool
109 UndoTransaction::empty () const
110 {
111         return actions.empty();
112 }
113
114 void
115 UndoTransaction::clear ()
116 {
117         _clearing = true;
118         for (list<Command*>::iterator i = actions.begin(); i != actions.end(); ++i) {
119                 delete *i;
120         }
121         actions.clear ();
122         _clearing = false;
123 }
124
125 void
126 UndoTransaction::operator() ()
127 {
128         for (list<Command*>::iterator i = actions.begin(); i != actions.end(); ++i) {
129                 (*(*i))();
130         }
131 }
132
133 void
134 UndoTransaction::undo ()
135 {
136         for (list<Command*>::reverse_iterator i = actions.rbegin(); i != actions.rend(); ++i) {
137                 (*i)->undo();
138         }
139 }
140
141 void
142 UndoTransaction::redo ()
143 {
144         (*this)();
145 }
146
147 XMLNode &UndoTransaction::get_state()
148 {
149     XMLNode *node = new XMLNode ("UndoTransaction");
150     stringstream ss;
151     ss << _timestamp.tv_sec;
152     node->add_property("tv_sec", ss.str());
153     ss.str("");
154     ss << _timestamp.tv_usec;
155     node->add_property("tv_usec", ss.str());
156     node->add_property("name", _name);
157
158     list<Command*>::iterator it;
159     for (it=actions.begin(); it!=actions.end(); it++)
160         node->add_child_nocopy((*it)->get_state());
161
162     return *node;
163 }
164
165 UndoHistory::UndoHistory ()
166 {
167         _clearing = false;
168         _depth = 0;
169 }
170
171 void
172 UndoHistory::set_depth (uint32_t d)
173 {
174         UndoTransaction* ut;
175         uint32_t current_depth = UndoList.size();
176
177         _depth = d;
178
179         if (d > current_depth) {
180                 /* not even transactions to meet request */
181                 return;
182         }
183
184         if (_depth > 0) {
185
186                 uint32_t cnt = current_depth - d;
187
188                 while (cnt--) {
189                         ut = UndoList.front();
190                         UndoList.pop_front ();
191                         ut->about_to_explicitly_delete ();
192                         delete ut;
193                 }
194         }
195 }
196
197 void
198 UndoHistory::add (UndoTransaction* const ut)
199 {
200         uint32_t current_depth = UndoList.size();
201
202         ut->GoingAway.connect (bind (mem_fun (*this, &UndoHistory::remove), ut));
203
204         /* if the current undo history is larger than or equal to the currently
205            requested depth, then pop off at least 1 element to make space
206            at the back for new one.
207         */
208
209         if ((_depth > 0) && current_depth && (current_depth >= _depth)) {
210
211                 uint32_t cnt = 1 + (current_depth - _depth);
212
213                 while (cnt--) {
214                         UndoTransaction* ut;
215                         ut = UndoList.front ();
216                         UndoList.pop_front ();
217                         ut->about_to_explicitly_delete ();
218                         delete ut;
219                 }
220         }
221
222         UndoList.push_back (ut);
223
224         /* we are now owners of the transaction and must delete it when finished with it */
225
226         Changed (); /* EMIT SIGNAL */
227 }
228
229 void
230 UndoHistory::remove (UndoTransaction* const ut)
231 {
232         if (_clearing) {
233                 return;
234         }
235
236         UndoList.remove (ut);
237         RedoList.remove (ut);
238
239         Changed (); /* EMIT SIGNAL */
240 }
241
242 /** Undo some transactions.
243  * @param n Number of transactions to undo.
244  */
245 void
246 UndoHistory::undo (unsigned int n)
247 {
248         while (n--) {
249                 if (UndoList.size() == 0) {
250                         return;
251                 }
252                 UndoTransaction* ut = UndoList.back ();
253                 UndoList.pop_back ();
254                 ut->undo ();
255                 RedoList.push_back (ut);
256         }
257
258         Changed (); /* EMIT SIGNAL */
259 }
260
261 void
262 UndoHistory::redo (unsigned int n)
263 {
264         while (n--) {
265                 if (RedoList.size() == 0) {
266                         return;
267                 }
268                 UndoTransaction* ut = RedoList.back ();
269                 RedoList.pop_back ();
270                 ut->redo ();
271                 UndoList.push_back (ut);
272         }
273
274         Changed (); /* EMIT SIGNAL */
275 }
276
277 void
278 UndoHistory::clear_redo ()
279 {
280         _clearing = true;
281         RedoList.clear ();
282         _clearing = false;
283
284         Changed (); /* EMIT SIGNAL */
285
286 }
287
288 void
289 UndoHistory::clear_undo ()
290 {
291         _clearing = true;
292         UndoList.clear ();
293         _clearing = false;
294
295         Changed (); /* EMIT SIGNAL */
296 }
297
298 void
299 UndoHistory::clear ()
300 {
301         clear_undo ();
302         clear_redo ();
303
304         Changed (); /* EMIT SIGNAL */
305 }
306
307 XMLNode& 
308 UndoHistory::get_state (int32_t depth)
309 {
310     XMLNode *node = new XMLNode ("UndoHistory");
311
312     if (depth == 0) {
313
314             return (*node);
315
316     } else if (depth < 0) {
317
318             /* everything */
319
320             for (list<UndoTransaction*>::iterator it = UndoList.begin(); it != UndoList.end(); ++it) {
321                     node->add_child_nocopy((*it)->get_state());
322             }
323
324     } else {
325
326             /* just the last "depth" transactions */
327
328             list<UndoTransaction*> in_order;
329
330             for (list<UndoTransaction*>::reverse_iterator it = UndoList.rbegin(); it != UndoList.rend() && depth; ++it, depth--) {
331                     in_order.push_front (*it);
332             }
333
334             for (list<UndoTransaction*>::iterator it = in_order.begin(); it != in_order.end(); it++) {
335                     node->add_child_nocopy((*it)->get_state());
336             }
337     }
338
339     return *node;
340 }
341
342