1 /*
2 * Copyright (C) 2019-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 this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
20 #include "audio/chord_track.h"
21 #include "audio/engine.h"
22 #include "audio/master_track.h"
23 #include "audio/position.h"
24 #include "audio/track.h"
25 #include "audio/transport.h"
26 #include "gui/backend/event.h"
27 #include "gui/backend/event_manager.h"
28 #include "gui/backend/tracklist_selections.h"
29 #include "gui/widgets/main_window.h"
30 #include "gui/widgets/track.h"
31 #include "project.h"
32 #include "utils/arrays.h"
33 #include "utils/error.h"
34 #include "utils/flags.h"
35 #include "utils/objects.h"
36 #include "utils/ui.h"
37 #include "zrythm_app.h"
38
39 #include <gtk/gtk.h>
40
41 #include <glib/gi18n.h>
42
43 void
tracklist_selections_init_loaded(TracklistSelections * self)44 tracklist_selections_init_loaded (
45 TracklistSelections * self)
46 {
47 for (int i = 0; i < self->num_tracks; i++)
48 {
49 Track * track = self->tracks[i];
50 int track_pos = track->pos;
51 track_init_loaded (track, NULL, self);
52 if (self->is_project)
53 {
54 self->tracks[i] =
55 TRACKLIST->tracks[track_pos];
56 /* TODO */
57 /*track_disconnect (track, true, false);*/
58 /*track_free (track);*/
59 }
60 }
61 }
62
63 /**
64 * Gets highest track in the selections.
65 */
66 Track *
tracklist_selections_get_highest_track(TracklistSelections * ts)67 tracklist_selections_get_highest_track (
68 TracklistSelections * ts)
69 {
70 Track * track;
71 int min_pos = 1000;
72 Track * min_track = NULL;
73 for (int i = 0; i < ts->num_tracks; i++)
74 {
75 track = ts->tracks[i];
76
77 if (track->pos < min_pos)
78 {
79 min_pos = track->pos;
80 min_track = track;
81 }
82 }
83
84 return min_track;
85 }
86
87 /**
88 * Gets lowest track in the selections.
89 */
90 Track *
tracklist_selections_get_lowest_track(TracklistSelections * ts)91 tracklist_selections_get_lowest_track (
92 TracklistSelections * ts)
93 {
94 Track * track;
95 int max_pos = -1;
96 Track * max_track = NULL;
97 for (int i = 0; i < ts->num_tracks; i++)
98 {
99 track = ts->tracks[i];
100
101 if (track->pos > max_pos)
102 {
103 max_pos = track->pos;
104 max_track = track;
105 }
106 }
107
108 return max_track;
109 }
110
111 /**
112 * @param is_project Whether these selections are
113 * the project selections (as opposed to clones).
114 */
115 TracklistSelections *
tracklist_selections_new(bool is_project)116 tracklist_selections_new (
117 bool is_project)
118 {
119 TracklistSelections * self =
120 object_new (TracklistSelections);
121
122 self->schema_version =
123 TRACKLIST_SELECTIONS_SCHEMA_VERSION;
124
125 self->is_project = is_project;
126
127 return self;
128 }
129
130 /**
131 * Adds a Track to the selections.
132 */
133 void
tracklist_selections_add_track(TracklistSelections * self,Track * track,bool fire_events)134 tracklist_selections_add_track (
135 TracklistSelections * self,
136 Track * track,
137 bool fire_events)
138 {
139 if (!array_contains (self->tracks,
140 self->num_tracks,
141 track))
142 {
143 array_append (self->tracks,
144 self->num_tracks,
145 track);
146
147 if (fire_events)
148 {
149 EVENTS_PUSH (ET_TRACK_CHANGED, track);
150 EVENTS_PUSH (
151 ET_TRACKLIST_SELECTIONS_CHANGED, NULL);
152 }
153 }
154
155 g_debug (
156 "%s currently recording: %d, have channel: %d",
157 track->name,
158 track_type_can_record (track->type) &&
159 track_get_recording (track),
160 track->channel != NULL);
161
162 /* if recording is not already on, turn these on */
163 if (track_type_can_record (track->type) &&
164 !track_get_recording (track) && track->channel)
165 {
166 track_set_recording (
167 track, true, fire_events);
168 track->record_set_automatically = true;
169 }
170 }
171
172 void
tracklist_selections_add_tracks_in_range(TracklistSelections * self,int min_pos,int max_pos,bool fire_events)173 tracklist_selections_add_tracks_in_range (
174 TracklistSelections * self,
175 int min_pos,
176 int max_pos,
177 bool fire_events)
178 {
179 g_message (
180 "selecting tracks from %d to %d...",
181 min_pos, max_pos);
182
183 tracklist_selections_clear (self);
184
185 for (int i = min_pos; i <= max_pos; i++)
186 {
187 Track * track = TRACKLIST->tracks[i];
188 tracklist_selections_add_track (
189 self, track, fire_events);
190 }
191
192 g_message ("done");
193 }
194
195 /**
196 * Clears the selections.
197 */
198 void
tracklist_selections_clear(TracklistSelections * self)199 tracklist_selections_clear (
200 TracklistSelections * self)
201 {
202 g_message ("clearing tracklist selections...");
203
204 for (int i = self->num_tracks - 1; i >= 0; i--)
205 {
206 Track * track = self->tracks[i];
207 tracklist_selections_remove_track (
208 self, track, 0);
209
210 if (track_is_in_active_project (track))
211 {
212 /* process now because the track might
213 * get deleted after this */
214 if (GTK_IS_WIDGET (track->widget))
215 {
216 gtk_widget_set_visible (
217 GTK_WIDGET (track->widget),
218 track->visible);
219 track_widget_force_redraw (
220 track->widget);
221 }
222 }
223 }
224
225 if (self->is_project && ZRYTHM_HAVE_UI &&
226 PROJECT->loaded)
227 {
228 EVENTS_PUSH (
229 ET_TRACKLIST_SELECTIONS_CHANGED, NULL);
230 }
231
232 g_message ("done");
233 }
234
235 /**
236 * Make sure all children of foldable tracks in
237 * the selection are also selected.
238 */
239 void
tracklist_selections_select_foldable_children(TracklistSelections * self)240 tracklist_selections_select_foldable_children (
241 TracklistSelections * self)
242 {
243 int num_tracklist_sel = self->num_tracks;
244 for (int i = 0; i < num_tracklist_sel; i++)
245 {
246 Track * cur_track = self->tracks[i];
247 int cur_idx = cur_track->pos;
248 if (track_type_is_foldable (cur_track->type))
249 {
250 for (int j = 1; j < cur_track->size; j++)
251 {
252 Track * child_track =
253 TRACKLIST->tracks[j + cur_idx];
254 if (!track_is_selected (child_track))
255 {
256 track_select (
257 child_track, F_SELECT,
258 F_NOT_EXCLUSIVE,
259 F_NO_PUBLISH_EVENTS);
260 }
261 }
262 }
263 }
264 }
265
266 /**
267 * Handle a click selection.
268 */
269 void
tracklist_selections_handle_click(Track * track,bool ctrl,bool shift,bool dragged)270 tracklist_selections_handle_click (
271 Track * track,
272 bool ctrl,
273 bool shift,
274 bool dragged)
275 {
276 bool is_selected = track_is_selected (track);
277 if (is_selected)
278 {
279 if ((ctrl || shift) && !dragged)
280 {
281 if (TRACKLIST_SELECTIONS->num_tracks > 1)
282 {
283 track_select (
284 track, F_NO_SELECT, F_NOT_EXCLUSIVE,
285 F_PUBLISH_EVENTS);
286 }
287 }
288 else
289 {
290 /* do nothing */
291 }
292 }
293 else /* not selected */
294 {
295 if (shift)
296 {
297 if (TRACKLIST_SELECTIONS->num_tracks > 0)
298 {
299 Track * highest =
300 tracklist_selections_get_highest_track (
301 TRACKLIST_SELECTIONS);
302 Track * lowest =
303 tracklist_selections_get_lowest_track (
304 TRACKLIST_SELECTIONS);
305 g_return_if_fail (
306 IS_TRACK_AND_NONNULL (highest) &&
307 IS_TRACK_AND_NONNULL (lowest));
308 if (track->pos > highest->pos)
309 {
310 /* select all tracks in between */
311 tracklist_selections_add_tracks_in_range (
312 TRACKLIST_SELECTIONS,
313 highest->pos, track->pos,
314 F_PUBLISH_EVENTS);
315 }
316 else if (track->pos < lowest->pos)
317 {
318 /* select all tracks in between */
319 tracklist_selections_add_tracks_in_range (
320 TRACKLIST_SELECTIONS,
321 track->pos, lowest->pos,
322 F_PUBLISH_EVENTS);
323 }
324 }
325 }
326 else if (ctrl)
327 {
328 track_select (
329 track, F_SELECT, F_NOT_EXCLUSIVE,
330 F_PUBLISH_EVENTS);
331 }
332 else
333 {
334 track_select (
335 track, F_SELECT, F_EXCLUSIVE,
336 F_PUBLISH_EVENTS);
337 }
338 }
339 }
340
341 /**
342 * Selects all Track's.
343 *
344 * @param visible_only Only select visible tracks.
345 */
346 void
tracklist_selections_select_all(TracklistSelections * ts,int visible_only)347 tracklist_selections_select_all (
348 TracklistSelections * ts,
349 int visible_only)
350 {
351 Track * track;
352 for (int i = 0; i < TRACKLIST->num_tracks; i++)
353 {
354 track = TRACKLIST->tracks[i];
355
356 if (track->visible || !visible_only)
357 {
358 tracklist_selections_add_track (
359 ts, track, 0);
360 }
361 }
362
363 EVENTS_PUSH (ET_TRACKLIST_SELECTIONS_CHANGED,
364 NULL);
365 }
366
367 void
tracklist_selections_remove_track(TracklistSelections * ts,Track * track,int fire_events)368 tracklist_selections_remove_track (
369 TracklistSelections * ts,
370 Track * track,
371 int fire_events)
372 {
373 if (!array_contains (
374 ts->tracks, ts->num_tracks, track))
375 {
376 if (fire_events)
377 {
378 EVENTS_PUSH (ET_TRACK_CHANGED, track);
379 EVENTS_PUSH (
380 ET_TRACKLIST_SELECTIONS_CHANGED, NULL);
381 }
382 return;
383 }
384
385 /* if record mode was set automatically
386 * when the track was selected, turn record
387 * off - unless currently recording */
388 if (track->channel
389 && track->record_set_automatically
390 &&
391 !(TRANSPORT_IS_RECORDING &&
392 TRANSPORT_IS_ROLLING))
393 {
394 track_set_recording (track, 0, fire_events);
395 track->record_set_automatically = false;
396 }
397
398 array_delete (
399 ts->tracks,
400 ts->num_tracks,
401 track);
402
403 if (fire_events)
404 {
405 EVENTS_PUSH (
406 ET_TRACKLIST_SELECTIONS_CHANGED, NULL);
407 }
408 }
409
410 bool
tracklist_selections_contains_undeletable_track(TracklistSelections * self)411 tracklist_selections_contains_undeletable_track (
412 TracklistSelections * self)
413 {
414 for (int i = 0; i < self->num_tracks; i++)
415 {
416 Track * track = self->tracks[i];
417
418 if (!track_type_is_deletable (track->type))
419 {
420 return true;
421 }
422 }
423
424 return false;
425 }
426
427 /**
428 * Returns whether the selections contain a soloed
429 * track if @ref soloed is true or an unsoloed track
430 * if @ref soloed is false.
431 *
432 * @param soloed Whether to check for soloed or
433 * unsoloed tracks.
434 */
435 bool
tracklist_selections_contains_soloed_track(TracklistSelections * self,bool soloed)436 tracklist_selections_contains_soloed_track (
437 TracklistSelections * self,
438 bool soloed)
439 {
440 for (int i = 0; i < self->num_tracks; i++)
441 {
442 Track * track = self->tracks[i];
443 if (!track_type_has_channel (track->type))
444 continue;
445
446 if (track_get_soloed (track) == soloed)
447 return true;
448 }
449
450 return false;
451 }
452
453 /**
454 * Returns whether the selections contain a muted
455 * track if @ref muted is true or an unmuted track
456 * if @ref muted is false.
457 *
458 * @param muted Whether to check for muted or
459 * unmuted tracks.
460 */
461 bool
tracklist_selections_contains_muted_track(TracklistSelections * self,bool muted)462 tracklist_selections_contains_muted_track (
463 TracklistSelections * self,
464 bool muted)
465 {
466 for (int i = 0; i < self->num_tracks; i++)
467 {
468 Track * track = self->tracks[i];
469 if (!track_type_has_channel (track->type))
470 continue;
471
472 if (track_get_muted (track) == muted)
473 return true;
474 }
475
476 return false;
477 }
478
479 /**
480 * Returns whether the selections contain a listened
481 * track if @ref listened is true or an unlistened
482 * track if @ref listened is false.
483 *
484 * @param listened Whether to check for listened or
485 * unlistened tracks.
486 */
487 bool
tracklist_selections_contains_listened_track(TracklistSelections * self,bool listened)488 tracklist_selections_contains_listened_track (
489 TracklistSelections * self,
490 bool listened)
491 {
492 for (int i = 0; i < self->num_tracks; i++)
493 {
494 Track * track = self->tracks[i];
495 if (!track_type_has_channel (track->type))
496 continue;
497
498 if (track_get_listened (track) == listened)
499 return true;
500 }
501
502 return false;
503 }
504
505 bool
tracklist_selections_contains_enabled_track(TracklistSelections * self,bool enabled)506 tracklist_selections_contains_enabled_track (
507 TracklistSelections * self,
508 bool enabled)
509 {
510 for (int i = 0; i < self->num_tracks; i++)
511 {
512 Track * track = self->tracks[i];
513 if (track_is_enabled (track) == enabled)
514 return true;
515 }
516
517 return false;
518 }
519
520 bool
tracklist_selections_contains_track(TracklistSelections * self,Track * track)521 tracklist_selections_contains_track (
522 TracklistSelections * self,
523 Track * track)
524 {
525 return
526 array_contains (
527 self->tracks, self->num_tracks, track);
528 }
529
530 bool
tracklist_selections_contains_track_index(TracklistSelections * self,int track_idx)531 tracklist_selections_contains_track_index (
532 TracklistSelections * self,
533 int track_idx)
534 {
535 for (int i = 0; i < self->num_tracks; i++)
536 {
537 Track * track = self->tracks[i];
538 if (track->pos == track_idx)
539 return true;
540 }
541
542 return false;
543 }
544
545 /**
546 * For debugging.
547 */
548 void
tracklist_selections_print(TracklistSelections * self)549 tracklist_selections_print (
550 TracklistSelections * self)
551 {
552 g_message ("------ tracklist selections ------");
553
554 Track * track;
555 for (int i = 0; i < self->num_tracks; i++)
556 {
557 track = self->tracks[i];
558 g_message ("[idx %d] %s (pos %d)",
559 i, track->name,
560 track->pos);
561 }
562 g_message ("-------- end --------");
563 }
564
565 /**
566 * Selects a single track after clearing the
567 * selections.
568 */
569 void
tracklist_selections_select_single(TracklistSelections * ts,Track * track,bool fire_events)570 tracklist_selections_select_single (
571 TracklistSelections * ts,
572 Track * track,
573 bool fire_events)
574 {
575 tracklist_selections_clear (ts);
576
577 tracklist_selections_add_track (
578 ts, track, 0);
579
580 if (fire_events)
581 {
582 EVENTS_PUSH (
583 ET_TRACKLIST_SELECTIONS_CHANGED, NULL);
584 }
585 }
586
587 /**
588 * Selects the last visible track after clearing the
589 * selections.
590 */
591 void
tracklist_selections_select_last_visible(TracklistSelections * ts)592 tracklist_selections_select_last_visible (
593 TracklistSelections * ts)
594 {
595 Track * track =
596 tracklist_get_last_track (
597 TRACKLIST,
598 TRACKLIST_PIN_OPTION_BOTH, true);
599 g_warn_if_fail (track);
600 tracklist_selections_select_single (
601 ts, track, F_PUBLISH_EVENTS);
602 }
603
604 static int
sort_tracks_func_desc(const void * a,const void * b)605 sort_tracks_func_desc (const void *a, const void *b)
606 {
607 Track * aa = * (Track * const *) a;
608 Track * bb = * (Track * const *) b;
609 return aa->pos < bb->pos;
610 }
611
612 static int
sort_tracks_func(const void * a,const void * b)613 sort_tracks_func (const void *a, const void *b)
614 {
615 Track * aa = * (Track * const *) a;
616 Track * bb = * (Track * const *) b;
617 return aa->pos > bb->pos;
618 }
619
620 /**
621 * Sorts the tracks by position.
622 *
623 * @param asc Ascending or not.
624 */
625 void
tracklist_selections_sort(TracklistSelections * self,bool asc)626 tracklist_selections_sort (
627 TracklistSelections * self,
628 bool asc)
629 {
630 qsort (
631 self->tracks,
632 (size_t) self->num_tracks,
633 sizeof (Track *),
634 asc ? sort_tracks_func : sort_tracks_func_desc);
635 }
636
637 /**
638 * Toggle visibility of the selected tracks.
639 */
640 void
tracklist_selections_toggle_visibility(TracklistSelections * ts)641 tracklist_selections_toggle_visibility (
642 TracklistSelections * ts)
643 {
644 Track * track;
645 for (int i = 0; i < ts->num_tracks; i++)
646 {
647 track = ts->tracks[i];
648 track->visible = !track->visible;
649 }
650
651 EVENTS_PUSH (ET_TRACK_VISIBILITY_CHANGED, NULL);
652 }
653
654 #if 0
655 /**
656 * Toggle pin/unpin of the selected tracks.
657 */
658 void
659 tracklist_selections_toggle_pinned (
660 TracklistSelections * ts)
661 {
662 Track * track;
663 for (int i = 0; i < ts->num_tracks; i++)
664 {
665 track = ts->tracks[i];
666 tracklist_set_track_pinned (
667 TRACKLIST, track, !track->pinned,
668 F_PUBLISH_EVENTS, F_RECALC_GRAPH);
669 }
670 }
671 #endif
672
673 /**
674 * Clone the struct for copying, undoing, etc.
675 */
676 NONNULL_ARGS (1)
677 TracklistSelections *
tracklist_selections_clone(TracklistSelections * src,GError ** error)678 tracklist_selections_clone (
679 TracklistSelections * src,
680 GError ** error)
681 {
682 g_return_val_if_fail (!error || !*error, NULL);
683
684 TracklistSelections * self =
685 object_new (TracklistSelections);
686
687 self->is_project = src->is_project;
688
689 for (int i = 0; i < src->num_tracks; i++)
690 {
691 Track * r = src->tracks[i];
692
693 GError * err = NULL;
694 Track * new_r = track_clone (r, &err);
695 if (!new_r)
696 {
697 PROPAGATE_PREFIXED_ERROR (
698 error, err,
699 _("Failed to clone track '%s'"),
700 r->name);
701 object_free_w_func_and_null (
702 tracklist_selections_free, self);
703 return NULL;
704 }
705 array_append (
706 self->tracks, self->num_tracks,
707 new_r);
708 }
709
710 self->is_project = false;
711
712 return self;
713 }
714
715 void
tracklist_selections_paste_to_pos(TracklistSelections * ts,int pos)716 tracklist_selections_paste_to_pos (
717 TracklistSelections * ts,
718 int pos)
719 {
720 /* TODO */
721 g_warn_if_reached ();
722 return;
723 }
724
725 /**
726 * Marks the tracks to be bounced.
727 *
728 * @param with_parents Also mark all the track's
729 * parents recursively.
730 * @param mark_master Also mark the master track.
731 * Set to true when exporting the mixdown, false
732 * otherwise.
733 */
734 void
tracklist_selections_mark_for_bounce(TracklistSelections * ts,bool with_parents,bool mark_master)735 tracklist_selections_mark_for_bounce (
736 TracklistSelections * ts,
737 bool with_parents,
738 bool mark_master)
739 {
740 engine_reset_bounce_mode (AUDIO_ENGINE);
741
742 for (int i = 0; i < ts->num_tracks; i++)
743 {
744 Track * track = ts->tracks[i];
745 track_mark_for_bounce (
746 track, F_BOUNCE, F_MARK_REGIONS,
747 F_MARK_CHILDREN, with_parents);
748 }
749
750 if (mark_master)
751 {
752 track_mark_for_bounce (
753 P_MASTER_TRACK, F_BOUNCE, F_NO_MARK_REGIONS,
754 F_NO_MARK_CHILDREN, F_NO_MARK_PARENTS);
755 }
756 }
757
758 void
tracklist_selections_free(TracklistSelections * self)759 tracklist_selections_free (
760 TracklistSelections * self)
761 {
762 if (!self->is_project || self->free_tracks)
763 {
764 for (int i = 0; i < self->num_tracks; i++)
765 {
766 g_return_if_fail (
767 IS_TRACK_AND_NONNULL (self->tracks[i]));
768
769 #if 0
770 /* skip project tracks */
771 if (self->tracks[i]->is_project)
772 continue;
773 #endif
774
775 track_disconnect (
776 self->tracks[i], F_NO_REMOVE_PL,
777 F_NO_RECALC_GRAPH);
778
779 object_free_w_func_and_null (
780 track_free, self->tracks[i]);
781 }
782 }
783
784 object_zero_and_free (self);
785 }
786