better design for range plays that leaves the range play button able to play the...
[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 {
35         _clearing = false;
36         gettimeofday (&_timestamp, 0);
37 }
38
39 UndoTransaction::UndoTransaction (const UndoTransaction& rhs)
40 {
41         _name = rhs._name;
42         _clearing = false;
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 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 }