1 /*
2 * Copyright (C) 2020-2021 Alexandros Theodotou <alex at zrythm dot org>
3 *
4 * This file is part of Zrythm
5 *
6 * Zrythm is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU Affero General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * Zrythm is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU Affero General Public License for more details.
15 *
16 * You should have received a copy of the GNU Affero General Public License
17 * along with Zrythm. If not, see <https://www.gnu.org/licenses/>.
18 */
19
20 #include "actions/undo_stack.h"
21 #include "settings/settings.h"
22 #include "utils/arrays.h"
23 #include "utils/mem.h"
24 #include "utils/objects.h"
25 #include "utils/stack.h"
26 #include "zrythm.h"
27 #include "zrythm_app.h"
28
29 NONNULL
30 size_t
undo_stack_get_total_cached_actions(UndoStack * self)31 undo_stack_get_total_cached_actions (
32 UndoStack * self)
33 {
34 size_t total =
35 self->num_as_actions +
36 self->num_mixer_selections_actions +
37 self->num_tracklist_selections_actions +
38 self->num_channel_send_actions +
39 self->num_midi_mapping_actions +
40 self->num_port_actions +
41 self->num_port_connection_actions +
42 self->num_range_actions +
43 self->num_transport_actions;
44
45 if ((int) total > self->stack->max_length)
46 {
47 g_critical (
48 "total stack members (%zu) higher than "
49 "max length (%d)",
50 total, self->stack->max_length);
51 }
52
53 return total;
54 }
55
56 void
undo_stack_init_loaded(UndoStack * self)57 undo_stack_init_loaded (
58 UndoStack * self)
59 {
60 /* use the remembered max length instead of the
61 * one in settings so they match */
62 int undo_stack_length = self->stack->max_length;
63 stack_free (self->stack);
64 self->stack =
65 stack_new (undo_stack_length);
66 self->stack->top = -1;
67
68 size_t as_actions_idx = 0;
69 size_t mixer_selections_actions_idx = 0;
70 size_t tracklist_selections_actions_idx = 0;
71 size_t channel_send_actions_idx = 0;
72 size_t port_actions_idx = 0;
73 size_t port_connection_actions_idx = 0;
74 size_t midi_mapping_actions_idx = 0;
75 size_t range_actions_idx = 0;
76 size_t transport_actions_idx = 0;
77
78 size_t total_actions =
79 undo_stack_get_total_cached_actions (self);
80
81 #define DO_SIMPLE(cc,sc) \
82 /* if there are still actions of this type */ \
83 if (sc##_actions_idx < self->num_##sc##_actions) \
84 { \
85 cc##Action * a = \
86 self->sc##_actions[sc##_actions_idx]; \
87 UndoableAction * ua = \
88 (UndoableAction *) a; \
89 undoable_action_init_loaded (ua); \
90 if (self->stack->top + 1 == ua->stack_idx) \
91 { \
92 STACK_PUSH (self->stack, ua); \
93 sc##_actions_idx++; \
94 } \
95 }
96
97 for (size_t i = 0; i < total_actions; i++)
98 {
99 DO_SIMPLE (ArrangerSelections, as)
100 DO_SIMPLE (TracklistSelections, tracklist_selections)
101 DO_SIMPLE (ChannelSend, channel_send)
102 DO_SIMPLE (MixerSelections, mixer_selections)
103 DO_SIMPLE (PortConnection, port_connection)
104 DO_SIMPLE (Port, port)
105 DO_SIMPLE (MidiMapping, midi_mapping)
106 DO_SIMPLE (Range, range)
107 DO_SIMPLE (Transport, transport)
108 }
109
110 g_return_if_fail (
111 self->stack->top + 1 ==
112 (int)
113 undo_stack_get_total_cached_actions (self));
114 }
115
116 UndoStack *
undo_stack_new(void)117 undo_stack_new (void)
118 {
119 g_return_val_if_fail (
120 (ZRYTHM && ZRYTHM->testing) ||
121 G_IS_SETTINGS (S_P_EDITING_UNDO), NULL);
122
123 UndoStack * self = object_new (UndoStack);
124 self->schema_version = UNDO_STACK_SCHEMA_VERSION;
125
126 int undo_stack_length =
127 ZRYTHM_TESTING ?
128 ZRYTHM->undo_stack_len :
129 g_settings_get_int (
130 S_P_EDITING_UNDO, "undo-stack-length");
131 self->stack =
132 stack_new (undo_stack_length);
133 self->stack->top = -1;
134
135 return self;
136 }
137
138 UndoStack *
undo_stack_clone(const UndoStack * src)139 undo_stack_clone (
140 const UndoStack * src)
141 {
142 UndoStack * self = object_new (UndoStack);
143 self->schema_version = UNDO_STACK_SCHEMA_VERSION;
144
145 int top_before = src->stack->top;
146
147 self->stack =
148 stack_new (src->stack->max_length);
149 self->stack->top = -1;
150
151 /* clone all actions */
152 #define CLONE_ACTIONS(arr,fn,type) \
153 self->arr = \
154 object_new_n (src->num_##arr, type *); \
155 for (size_t i = 0; i < src->num_##arr; i++) \
156 { \
157 self->arr[i] = fn##_clone (src->arr[i]); \
158 g_return_val_if_fail (self->arr[i], NULL); \
159 } \
160 self->num_##arr = src->num_##arr
161
162 CLONE_ACTIONS (
163 as_actions, arranger_selections_action,
164 ArrangerSelectionsAction);
165 CLONE_ACTIONS (
166 mixer_selections_actions,
167 mixer_selections_action,
168 MixerSelectionsAction);
169 CLONE_ACTIONS (
170 tracklist_selections_actions,
171 tracklist_selections_action,
172 TracklistSelectionsAction);
173 CLONE_ACTIONS (
174 channel_send_actions, channel_send_action,
175 ChannelSendAction);
176 CLONE_ACTIONS (
177 port_connection_actions, port_connection_action,
178 PortConnectionAction);
179 CLONE_ACTIONS (
180 port_actions, port_action,
181 PortAction);
182 CLONE_ACTIONS (
183 midi_mapping_actions, midi_mapping_action,
184 MidiMappingAction);
185 CLONE_ACTIONS (
186 range_actions, range_action, RangeAction);
187 CLONE_ACTIONS (
188 transport_actions, transport_action,
189 TransportAction);
190
191 #undef CLONE_ACTIONS
192
193 /* init loaded to load the actions on the stack */
194 undo_stack_init_loaded (self);
195
196 g_return_val_if_fail (
197 top_before == self->stack->top, NULL);
198
199 return self;
200 }
201
202 /**
203 * Gets the list of actions as a string.
204 */
205 char *
undo_stack_get_as_string(UndoStack * self,int limit)206 undo_stack_get_as_string (
207 UndoStack * self,
208 int limit)
209 {
210 GString * g_str = g_string_new (NULL);
211
212 Stack * stack = self->stack;
213 for (int i = stack->top; i >= 0; i--)
214 {
215 UndoableAction * action =
216 (UndoableAction *) stack->elements[i];
217
218 char * action_str =
219 undoable_action_to_string (action);
220 g_string_append_printf (
221 g_str, "[%d] %s\n",
222 stack->top - i, action_str);
223 g_free (action_str);
224
225 if (stack->top - i == limit)
226 break;
227 }
228
229 return g_string_free (g_str, false);
230 }
231
232 void
undo_stack_push(UndoStack * self,UndoableAction * action)233 undo_stack_push (
234 UndoStack * self,
235 UndoableAction * action)
236 {
237 g_message ("pushed to undo/redo stack");
238
239 /* push to stack */
240 STACK_PUSH (self->stack, action);
241
242 action->stack_idx = self->stack->top;
243
244 /* CAPS, CamelCase, snake_case */
245 #define APPEND_ELEMENT(caps,cc,sc) \
246 case UA_##caps: \
247 array_double_size_if_full ( \
248 self->sc##_actions, \
249 self->num_##sc##_actions, \
250 self->sc##_actions_size, \
251 cc##Action *); \
252 array_append ( \
253 self->sc##_actions, \
254 self->num_##sc##_actions, \
255 (cc##Action *) action); \
256 break
257
258 switch (action->type)
259 {
260 APPEND_ELEMENT (
261 TRACKLIST_SELECTIONS, TracklistSelections,
262 tracklist_selections);
263 APPEND_ELEMENT (
264 CHANNEL_SEND, ChannelSend, channel_send);
265 APPEND_ELEMENT (
266 MIXER_SELECTIONS, MixerSelections,
267 mixer_selections);
268 APPEND_ELEMENT (
269 PORT_CONNECTION, PortConnection,
270 port_connection);
271 APPEND_ELEMENT (PORT, Port, port);
272 APPEND_ELEMENT (
273 MIDI_MAPPING, MidiMapping,
274 midi_mapping);
275 APPEND_ELEMENT (
276 RANGE, Range, range);
277 APPEND_ELEMENT (
278 TRANSPORT, Transport, transport);
279 APPEND_ELEMENT (
280 ARRANGER_SELECTIONS, ArrangerSelections, as);
281 }
282 }
283
284 static bool
remove_action(UndoStack * self,UndoableAction * action)285 remove_action (
286 UndoStack * self,
287 UndoableAction * action)
288 {
289 /* CAPS, CamelCase, snake_case */
290 #define REMOVE_ELEMENT(caps,cc,sc) \
291 case UA_##caps: \
292 array_delete_confirm ( \
293 self->sc##_actions, \
294 self->num_##sc##_actions, \
295 (cc##Action *) action, \
296 removed); \
297 g_return_val_if_fail (removed, removed); \
298 if ((int) self->num_##sc##_actions > \
299 g_atomic_int_get ( \
300 &self->stack->top) + 1) \
301 { \
302 g_critical ( \
303 "num " #sc " actions (%zu) is greater " \
304 "than current stack top (%d)", \
305 self->num_##sc##_actions, \
306 g_atomic_int_get ( \
307 &self->stack->top) + 1); \
308 return removed; \
309 } \
310 break
311
312 bool removed = false;
313 switch (action->type)
314 {
315 REMOVE_ELEMENT (
316 TRACKLIST_SELECTIONS, TracklistSelections,
317 tracklist_selections);
318 REMOVE_ELEMENT (
319 CHANNEL_SEND, ChannelSend, channel_send);
320 REMOVE_ELEMENT (
321 MIXER_SELECTIONS, MixerSelections,
322 mixer_selections);
323 REMOVE_ELEMENT (
324 PORT_CONNECTION, PortConnection,
325 port_connection);
326 REMOVE_ELEMENT (PORT, Port, port);
327 REMOVE_ELEMENT (
328 MIDI_MAPPING, MidiMapping,
329 midi_mapping);
330 REMOVE_ELEMENT (
331 RANGE, Range, range);
332 REMOVE_ELEMENT (
333 TRANSPORT, Transport, transport);
334 case UA_ARRANGER_SELECTIONS:
335 array_delete_confirm (
336 self->as_actions,
337 self->num_as_actions,
338 (ArrangerSelectionsAction *) action,
339 removed);
340 g_return_val_if_fail (
341 (int) self->num_as_actions <=
342 g_atomic_int_get (&self->stack->top) + 1,
343 removed);
344 break;
345 }
346
347 /* re-set the indices */
348 for (int i = 0;
349 i <= g_atomic_int_get (&self->stack->top);
350 i++)
351 {
352 UndoableAction * ua =
353 (UndoableAction *)
354 self->stack->elements[i];
355 ua->stack_idx = i;
356 }
357
358 return removed;
359 }
360
361 UndoableAction *
undo_stack_pop(UndoStack * self)362 undo_stack_pop (
363 UndoStack * self)
364 {
365 /* pop from stack */
366 UndoableAction * action =
367 (UndoableAction *) stack_pop (self->stack);
368
369 /* remove the action */
370 int removed = remove_action (self, action);
371 g_return_val_if_fail (removed, action);
372
373 /* return it */
374 return action;
375 }
376
377 /**
378 * Pops the last element and moves everything back.
379 */
380 UndoableAction *
undo_stack_pop_last(UndoStack * self)381 undo_stack_pop_last (
382 UndoStack * self)
383 {
384 g_message (
385 "<undo stack> popping last (top = %d)",
386 g_atomic_int_get (&self->stack->top));
387
388 /* pop from stack */
389 UndoableAction * action =
390 (UndoableAction *) stack_pop_last (self->stack);
391
392 /* remove the action */
393 bool removed = remove_action (self, action);
394 g_return_val_if_fail (removed, action);
395
396 /* return it */
397 return action;
398 }
399
400 bool
undo_stack_contains_clip(UndoStack * self,AudioClip * clip)401 undo_stack_contains_clip (
402 UndoStack * self,
403 AudioClip * clip)
404 {
405 for (int i = 0; i <= self->stack->top; i++)
406 {
407 UndoableAction * ua =
408 (UndoableAction *)
409 self->stack->elements[i];
410
411 if (undoable_action_contains_clip (ua, clip))
412 return true;
413 }
414
415 return false;
416 }
417
418 /**
419 * Checks if the undo stack contains the given
420 * action pointer.
421 */
422 bool
undo_stack_contains_action(UndoStack * self,UndoableAction * ua)423 undo_stack_contains_action (
424 UndoStack * self,
425 UndoableAction * ua)
426 {
427 for (int i = 0; i <= self->stack->top; i++)
428 {
429 UndoableAction * action =
430 (UndoableAction *)
431 self->stack->elements[i];
432 if (ua == action)
433 return true;
434 }
435
436 return false;
437 }
438
439 /**
440 * Clears the stack, optionally freeing all the
441 * elements.
442 */
443 void
undo_stack_clear(UndoStack * self,bool free)444 undo_stack_clear (
445 UndoStack * self,
446 bool free)
447 {
448 while (!undo_stack_is_empty (self))
449 {
450 UndoableAction * ua = undo_stack_pop (self);
451 if (free)
452 {
453 undoable_action_free (ua);
454 }
455 }
456 }
457
458 void
undo_stack_free(UndoStack * self)459 undo_stack_free (
460 UndoStack * self)
461 {
462 g_message ("%s: freeing...", __func__);
463
464 while (!undo_stack_is_empty (self))
465 {
466 UndoableAction * ua = undo_stack_pop (self);
467 char * type_str =
468 undoable_action_to_string (ua);
469 g_debug (
470 "%s: freeing %s", __func__, type_str);
471 g_free (type_str);
472 undoable_action_free (ua);
473 }
474
475 object_zero_and_free (self);
476
477 g_message ("%s: done", __func__);
478 }
479