Remove invalid error message
[ardour.git] / libs / pbd / undo.cc
1 /*
2  * Copyright (C) 2001 Brett Viren
3  * Copyright (C) 2001-2015 Paul Davis <paul@linuxaudiosystems.com>
4  * Copyright (C) 2006 Hans Fugal <hans@fugal.net>
5  * Copyright (C) 2007-2009 David Robillard <d@drobilla.net>
6  * Copyright (C) 2012-2017 Tim Mayberry <mojofunk@gmail.com>
7  * Copyright (C) 2015 Nick Mainsbridge <mainsbridge@gmail.com>
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License along
20  * with this program; if not, write to the Free Software Foundation, Inc.,
21  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22  */
23
24 #include <string>
25 #include <sstream>
26 #include <time.h>
27
28 #include "pbd/undo.h"
29 #include "pbd/xml++.h"
30
31 #include <sigc++/bind.h>
32
33 using namespace std;
34 using namespace sigc;
35
36 UndoTransaction::UndoTransaction ()
37         : _clearing(false)
38 {
39         gettimeofday (&_timestamp, 0);
40 }
41
42 UndoTransaction::UndoTransaction (const UndoTransaction& rhs)
43         : Command(rhs._name)
44         , _clearing(false)
45 {
46         _timestamp = rhs._timestamp;
47         clear ();
48         actions.insert(actions.end(),rhs.actions.begin(),rhs.actions.end());
49 }
50
51 UndoTransaction::~UndoTransaction ()
52 {
53         drop_references ();
54         clear ();
55 }
56
57 void
58 command_death (UndoTransaction* ut, Command* c)
59 {
60         if (ut->clearing()) {
61                 return;
62         }
63
64         ut->remove_command (c);
65
66         if (ut->empty()) {
67                 delete ut;
68         }
69 }
70
71 UndoTransaction&
72 UndoTransaction::operator= (const UndoTransaction& rhs)
73 {
74         if (this == &rhs) return *this;
75         _name = rhs._name;
76         clear ();
77         actions.insert(actions.end(),rhs.actions.begin(),rhs.actions.end());
78         return *this;
79 }
80
81 void
82 UndoTransaction::add_command (Command *const cmd)
83 {
84         /* catch death of command (e.g. caused by death of object to
85            which it refers. command_death() is a normal static function
86            so there is no need to manage this connection.
87          */
88
89         cmd->DropReferences.connect_same_thread (*this, boost::bind (&command_death, this, cmd));
90         actions.push_back (cmd);
91 }
92
93 void
94 UndoTransaction::remove_command (Command* const action)
95 {
96         actions.remove (action);
97 }
98
99 bool
100 UndoTransaction::empty () const
101 {
102         return actions.empty();
103 }
104
105 void
106 UndoTransaction::clear ()
107 {
108         _clearing = true;
109         for (list<Command*>::iterator i = actions.begin(); i != actions.end(); ++i) {
110                 delete *i;
111         }
112         actions.clear ();
113         _clearing = false;
114 }
115
116 void
117 UndoTransaction::operator() ()
118 {
119         for (list<Command*>::iterator i = actions.begin(); i != actions.end(); ++i) {
120                 (*(*i))();
121         }
122 }
123
124 void
125 UndoTransaction::undo ()
126 {
127         for (list<Command*>::reverse_iterator i = actions.rbegin(); i != actions.rend(); ++i) {
128                 (*i)->undo();
129         }
130 }
131
132 void
133 UndoTransaction::redo ()
134 {
135         (*this)();
136 }
137
138 XMLNode &UndoTransaction::get_state()
139 {
140     XMLNode *node = new XMLNode ("UndoTransaction");
141     node->set_property("tv-sec", (int64_t)_timestamp.tv_sec);
142     node->set_property("tv-usec", (int64_t)_timestamp.tv_usec);
143     node->set_property("name", _name);
144
145     list<Command*>::iterator it;
146     for (it=actions.begin(); it!=actions.end(); it++)
147         node->add_child_nocopy((*it)->get_state());
148
149     return *node;
150 }
151
152 class UndoRedoSignaller {
153 public:
154     UndoRedoSignaller (UndoHistory& uh)
155             : _history (uh) {
156             _history.BeginUndoRedo();
157     }
158     ~UndoRedoSignaller() {
159             _history.EndUndoRedo();
160     }
161
162 private:
163     UndoHistory& _history;
164 };
165
166 UndoHistory::UndoHistory ()
167 {
168         _clearing = false;
169         _depth = 0;
170 }
171
172 void
173 UndoHistory::set_depth (uint32_t d)
174 {
175         UndoTransaction* ut;
176         uint32_t current_depth = UndoList.size();
177
178         _depth = d;
179
180         if (d > current_depth) {
181                 /* not even transactions to meet request */
182                 return;
183         }
184
185         if (_depth > 0) {
186
187                 uint32_t cnt = current_depth - d;
188
189                 while (cnt--) {
190                         ut = UndoList.front();
191                         UndoList.pop_front ();
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->DropReferences.connect_same_thread (*this, boost::bind (&UndoHistory::remove, this, 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                         delete ut;
218                 }
219         }
220
221         UndoList.push_back (ut);
222         /* Adding a transacrion makes the redo list meaningless. */
223         _clearing = true;
224         for (std::list<UndoTransaction*>::iterator i = RedoList.begin(); i != RedoList.end(); ++i) {
225                 delete *i;
226         }
227         RedoList.clear ();
228         _clearing = false;
229
230         /* we are now owners of the transaction and must delete it when finished with it */
231
232         Changed (); /* EMIT SIGNAL */
233 }
234
235 void
236 UndoHistory::remove (UndoTransaction* const ut)
237 {
238         if (_clearing) {
239                 return;
240         }
241
242         UndoList.remove (ut);
243         RedoList.remove (ut);
244
245         Changed (); /* EMIT SIGNAL */
246 }
247
248 /** Undo some transactions.
249  * @param n Number of transactions to undo.
250  */
251 void
252 UndoHistory::undo (unsigned int n)
253 {
254         if (n == 0) {
255                 return;
256         }
257
258         {
259                 UndoRedoSignaller exception_safe_signaller (*this);
260
261                 while (n--) {
262                         if (UndoList.size() == 0) {
263                                 return;
264                         }
265                         UndoTransaction* ut = UndoList.back ();
266                         UndoList.pop_back ();
267                         ut->undo ();
268                         RedoList.push_back (ut);
269                 }
270         }
271
272         Changed (); /* EMIT SIGNAL */
273 }
274
275 void
276 UndoHistory::redo (unsigned int n)
277 {
278         if (n == 0) {
279                 return;
280         }
281
282         {
283                 UndoRedoSignaller exception_safe_signaller (*this);
284
285                 while (n--) {
286                         if (RedoList.size() == 0) {
287                                 return;
288                         }
289                         UndoTransaction* ut = RedoList.back ();
290                         RedoList.pop_back ();
291                         ut->redo ();
292                         UndoList.push_back (ut);
293                 }
294         }
295
296         Changed (); /* EMIT SIGNAL */
297 }
298
299 void
300 UndoHistory::clear_redo ()
301 {
302         _clearing = true;
303         for (std::list<UndoTransaction*>::iterator i = RedoList.begin(); i != RedoList.end(); ++i) {
304                 delete *i;
305         }
306         RedoList.clear ();
307         _clearing = false;
308
309         Changed (); /* EMIT SIGNAL */
310
311 }
312
313 void
314 UndoHistory::clear_undo ()
315 {
316         _clearing = true;
317         for (std::list<UndoTransaction*>::iterator i = UndoList.begin(); i != UndoList.end(); ++i) {
318                 delete *i;
319         }
320         UndoList.clear ();
321         _clearing = false;
322
323         Changed (); /* EMIT SIGNAL */
324 }
325
326 void
327 UndoHistory::clear ()
328 {
329         clear_undo ();
330         clear_redo ();
331
332         Changed (); /* EMIT SIGNAL */
333 }
334
335 XMLNode&
336 UndoHistory::get_state (int32_t depth)
337 {
338     XMLNode *node = new XMLNode ("UndoHistory");
339
340     if (depth == 0) {
341
342             return (*node);
343
344     } else if (depth < 0) {
345
346             /* everything */
347
348             for (list<UndoTransaction*>::iterator it = UndoList.begin(); it != UndoList.end(); ++it) {
349                     node->add_child_nocopy((*it)->get_state());
350             }
351
352     } else {
353
354             /* just the last "depth" transactions */
355
356             list<UndoTransaction*> in_order;
357
358             for (list<UndoTransaction*>::reverse_iterator it = UndoList.rbegin(); it != UndoList.rend() && depth; ++it, depth--) {
359                     in_order.push_front (*it);
360             }
361
362             for (list<UndoTransaction*>::iterator it = in_order.begin(); it != in_order.end(); it++) {
363                     node->add_child_nocopy((*it)->get_state());
364             }
365     }
366
367     return *node;
368 }
369
370