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