1 /*
2  * Copyright (C) 2018-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_manager.h"
21 #include "audio/audio_region.h"
22 #include "audio/automation_region.h"
23 #include "audio/chord_region.h"
24 #include "audio/chord_track.h"
25 #include "audio/exporter.h"
26 #include "audio/marker_track.h"
27 #include "gui/backend/arranger_object.h"
28 #include "gui/backend/event.h"
29 #include "gui/backend/event_manager.h"
30 #include "gui/widgets/arranger.h"
31 #include "gui/widgets/arranger_object.h"
32 #include "gui/widgets/center_dock.h"
33 #include "gui/widgets/dialogs/bounce_dialog.h"
34 #include "gui/widgets/dialogs/export_progress_dialog.h"
35 #include "gui/widgets/dialogs/export_midi_file_dialog.h"
36 #include "gui/widgets/main_notebook.h"
37 #include "gui/widgets/ruler.h"
38 #include "gui/widgets/timeline_arranger.h"
39 #include "gui/widgets/timeline_panel.h"
40 #include "gui/widgets/timeline_ruler.h"
41 #include "gui/widgets/track.h"
42 #include "project.h"
43 #include "utils/error.h"
44 #include "utils/flags.h"
45 #include "utils/gtk.h"
46 #include "utils/objects.h"
47 #include "utils/resources.h"
48 #include "utils/symap.h"
49 #include "utils/ui.h"
50 #include "zrythm.h"
51 #include "zrythm_app.h"
52 
53 #include <gtk/gtk.h>
54 #include <glib/gi18n.h>
55 
56 #define ACTION_IS(x) \
57   (self->action == UI_OVERLAY_ACTION_##x)
58 
59 /**
60  * Hides the cut dashed line from hovered regions
61  * and redraws them.
62  *
63  * Used when alt was unpressed.
64  */
65 void
timeline_arranger_widget_set_cut_lines_visible(ArrangerWidget * self)66 timeline_arranger_widget_set_cut_lines_visible (
67   ArrangerWidget * self)
68 {
69 #if 0
70   ArrangerObject * obj =
71     arranger_widget_get_hit_arranger_object (
72       (ArrangerWidget *) self,
73       ARRANGER_OBJECT_TYPE_REGION,
74       self->hover_x,
75       self->hover_y);
76 
77   if (obj)
78     {
79       ArrangerObjectWidget * obj_w =
80         Z_ARRANGER_OBJECT_WIDGET (obj->widget);
81       ARRANGER_OBJECT_WIDGET_GET_PRIVATE (obj_w);
82 
83       GdkModifierType mask;
84       z_gtk_widget_get_mask (
85         GTK_WIDGET (obj_w),
86         &mask);
87       int alt_pressed =
88         mask & GDK_MOD1_MASK;
89 
90       /* if not cutting hide the cut line
91        * from the region immediately */
92       int show_cut =
93         arranger_object_widget_should_show_cut_lines (
94                                                             obj_w,
95           alt_pressed);
96 
97       if (show_cut != ao_prv->show_cut)
98         {
99           ao_prv->show_cut = show_cut;
100 
101           gtk_widget_queue_draw (
102             GTK_WIDGET (obj_w));
103         }
104     }
105 #endif
106 }
107 
108 TrackLane *
timeline_arranger_widget_get_track_lane_at_y(ArrangerWidget * self,double y)109 timeline_arranger_widget_get_track_lane_at_y (
110   ArrangerWidget * self,
111   double y)
112 {
113   Track * track =
114     timeline_arranger_widget_get_track_at_y (
115       self, y);
116   if (!track || !track->lanes_visible)
117     return NULL;
118 
119   /* y local to track */
120   int y_local =
121     track_widget_get_local_y (
122       track->widget, self, (int) y);
123 
124   TrackLane * lane;
125   for (int j = 0; j < track->num_lanes; j++)
126     {
127       lane = track->lanes[j];
128 
129       if (y_local >= lane->y &&
130           y_local < lane->y + lane->height)
131         return lane;
132     }
133 
134   return NULL;
135 }
136 
137 Track *
timeline_arranger_widget_get_track_at_y(ArrangerWidget * self,double y)138 timeline_arranger_widget_get_track_at_y (
139   ArrangerWidget * self,
140   double y)
141 {
142   Track * track;
143   for (int i = 0; i < TRACKLIST->num_tracks; i++)
144     {
145       track = TRACKLIST->tracks[i];
146 
147       if (
148         /* ignore invisible tracks */
149         !track->visible ||
150         /* ignore tracks in the other timeline */
151         self->is_pinned != track_is_pinned (track))
152         continue;
153 
154       if (!track_get_should_be_visible (track))
155         continue;
156 
157       g_return_val_if_fail (track->widget, NULL);
158 
159       if (ui_is_child_hit (
160             GTK_WIDGET (self),
161             GTK_WIDGET (track->widget),
162             0, 1, 0, y, 0, 1))
163         return track;
164     }
165 
166   return NULL;
167 }
168 
169 /**
170  * Returns the hit AutomationTrack at y.
171  */
172 AutomationTrack *
timeline_arranger_widget_get_at_at_y(ArrangerWidget * self,double y)173 timeline_arranger_widget_get_at_at_y (
174   ArrangerWidget * self,
175   double           y)
176 {
177   Track * track =
178     timeline_arranger_widget_get_track_at_y (
179       self, y);
180   if (!track)
181     return NULL;
182 
183   AutomationTracklist * atl =
184     track_get_automation_tracklist (track);
185   if (!atl || !track->automation_visible)
186     return NULL;
187 
188   /* y local to track */
189   int y_local =
190     track_widget_get_local_y (
191       track->widget, self, (int) y);
192 
193   for (int j = 0; j < atl->num_ats; j++)
194     {
195       AutomationTrack * at = atl->ats[j];
196 
197       if (!at->created || !at->visible)
198         continue;
199 
200       if (y_local >= at->y &&
201           y_local < at->y + at->height)
202         return at;
203     }
204 
205   return NULL;
206 }
207 
208 void
timeline_arranger_on_export_as_midi_file_clicked(GtkMenuItem * menuitem,ZRegion * r)209 timeline_arranger_on_export_as_midi_file_clicked (
210   GtkMenuItem * menuitem,
211   ZRegion *      r)
212 {
213   GtkDialog * dialog =
214     GTK_DIALOG (
215       export_midi_file_dialog_widget_new_for_region (
216         GTK_WINDOW (MAIN_WINDOW),
217         r));
218   int res = gtk_dialog_run (dialog);
219   char * filename;
220   switch (res)
221     {
222     case GTK_RESPONSE_ACCEPT:
223       // do_application_specific_something ();
224       filename =
225         gtk_file_chooser_get_filename (
226           GTK_FILE_CHOOSER (dialog));
227       g_message ("exporting to %s", filename);
228       midi_region_export_to_midi_file (
229         r, filename, 0, 0);
230       g_free (filename);
231       break;
232     default:
233       // do_nothing_since_dialog_was_cancelled ();
234       break;
235     }
236   gtk_widget_destroy (GTK_WIDGET (dialog));
237 }
238 
239 void
timeline_arranger_on_quick_bounce_clicked(GtkMenuItem * menuitem,ZRegion * r)240 timeline_arranger_on_quick_bounce_clicked (
241   GtkMenuItem * menuitem,
242   ZRegion *     r)
243 {
244   ArrangerSelections * sel =
245     (ArrangerSelections *) TL_SELECTIONS;
246   if (!arranger_selections_has_any (sel))
247     {
248       g_warning ("no selections to bounce");
249       return;
250     }
251 
252   ExportSettings settings;
253   settings.mode = EXPORT_MODE_REGIONS;
254   export_settings_set_bounce_defaults (
255     &settings, NULL, r->name);
256   timeline_selections_mark_for_bounce (
257     TL_SELECTIONS, settings.bounce_with_parents);
258 
259   /* start exporting in a new thread */
260   GThread * thread =
261     g_thread_new (
262       "bounce_thread",
263       (GThreadFunc) exporter_generic_export_thread,
264       &settings);
265 
266   /* create a progress dialog and block */
267   ExportProgressDialogWidget * progress_dialog =
268     export_progress_dialog_widget_new (
269       &settings, true, false, F_CANCELABLE);
270   gtk_window_set_transient_for (
271     GTK_WINDOW (progress_dialog),
272     GTK_WINDOW (MAIN_WINDOW));
273   gtk_dialog_run (GTK_DIALOG (progress_dialog));
274   gtk_widget_destroy (GTK_WIDGET (progress_dialog));
275 
276   g_thread_join (thread);
277 
278   if (!settings.progress_info.has_error &&
279       !settings.progress_info.cancelled)
280     {
281       /* create audio track with bounced material */
282       Position first_pos;
283       arranger_selections_get_start_pos (
284         (ArrangerSelections *) TL_SELECTIONS,
285         &first_pos, F_GLOBAL);
286       exporter_create_audio_track_after_bounce (
287         &settings, &first_pos);
288     }
289 
290   export_settings_free_members (&settings);
291 }
292 
293 void
timeline_arranger_on_bounce_clicked(GtkMenuItem * menuitem,ZRegion * r)294 timeline_arranger_on_bounce_clicked (
295   GtkMenuItem * menuitem,
296   ZRegion *      r)
297 {
298   ArrangerSelections * sel =
299     (ArrangerSelections *) TL_SELECTIONS;
300   if (!arranger_selections_has_any (sel))
301     {
302       g_warning ("no selections to bounce");
303       return;
304     }
305 
306   BounceDialogWidget * dialog =
307     bounce_dialog_widget_new (
308       BOUNCE_DIALOG_REGIONS, r->name);
309   gtk_dialog_run (GTK_DIALOG (dialog));
310   gtk_widget_destroy (GTK_WIDGET (dialog));
311 }
312 
313 /**
314  * Create a ZRegion at the given Position in the
315  * given Track's given TrackLane.
316  *
317  * @param type The type of region to create.
318  * @param pos The pre-snapped position.
319  * @param track Track, if non-automation.
320  * @param lane TrackLane, if midi/audio region.
321  * @param at AutomationTrack, if automation Region.
322  */
323 void
timeline_arranger_widget_create_region(ArrangerWidget * self,const RegionType type,Track * track,TrackLane * lane,AutomationTrack * at,const Position * pos)324 timeline_arranger_widget_create_region (
325   ArrangerWidget *  self,
326   const RegionType  type,
327   Track *           track,
328   TrackLane *       lane,
329   AutomationTrack * at,
330   const Position *  pos)
331 {
332   bool autofilling =
333     self->action == UI_OVERLAY_ACTION_AUTOFILLING;
334 
335   /* if autofilling, the action is already set */
336   if (!autofilling)
337     {
338       self->action =
339         UI_OVERLAY_ACTION_CREATING_RESIZING_R;
340     }
341 
342   g_message ("creating region");
343 
344   Position end_pos;
345   position_set_min_size (
346     pos, &end_pos,
347     self->snap_grid);
348 
349   /* create a new region */
350   ZRegion * region = NULL;
351   switch (type)
352     {
353     case REGION_TYPE_MIDI:
354       region =
355         midi_region_new (
356           pos, &end_pos,
357           track_get_name_hash (track),
358           /* create on lane 0 if creating in main
359            * track */
360           lane ? lane->pos : 0,
361           lane ? lane->num_regions :
362             track->lanes[0]->num_regions);
363       break;
364     case REGION_TYPE_AUDIO:
365       break;
366     case REGION_TYPE_CHORD:
367       region =
368         chord_region_new (
369           pos, &end_pos,
370           P_CHORD_TRACK->num_chord_regions);
371       break;
372     case REGION_TYPE_AUTOMATION:
373       region =
374         automation_region_new (
375           pos, &end_pos,
376           track_get_name_hash (track),
377           at->index,
378           at->num_regions);
379       break;
380     }
381 
382   ArrangerObject * r_obj =
383     (ArrangerObject *) region;
384   self->start_object = r_obj;
385   /*region_set_end_pos (*/
386     /*region, &end_pos, AO_UPDATE_ALL);*/
387   /*long length =*/
388     /*region_get_full_length_in_ticks (region);*/
389   /*position_from_ticks (*/
390     /*&region->true_end_pos, length);*/
391   /*region_set_true_end_pos (*/
392     /*region, &region->true_end_pos, AO_UPDATE_ALL);*/
393   /*position_init (&tmp);*/
394   /*region_set_clip_start_pos (*/
395     /*region, &tmp, AO_UPDATE_ALL);*/
396   /*region_set_loop_start_pos (*/
397     /*region, &tmp, AO_UPDATE_ALL);*/
398   /*region_set_loop_end_pos (*/
399     /*region, &region->true_end_pos, AO_UPDATE_ALL);*/
400 
401   switch (type)
402     {
403     case REGION_TYPE_MIDI:
404       track_add_region (
405         track, region, NULL,
406         lane ? lane->pos :
407         (track->num_lanes == 1 ?
408          0 : track->num_lanes - 2), F_GEN_NAME,
409         F_PUBLISH_EVENTS);
410       break;
411     case REGION_TYPE_AUDIO:
412       break;
413     case REGION_TYPE_CHORD:
414       track_add_region (
415         track, region, NULL,
416         -1, F_GEN_NAME, F_PUBLISH_EVENTS);
417       break;
418     case REGION_TYPE_AUTOMATION:
419       track_add_region (
420         track, region, at,
421         -1, F_GEN_NAME, F_PUBLISH_EVENTS);
422       break;
423     }
424 
425   /* set visibility */
426   /*arranger_object_gen_widget (r_obj);*/
427   /*arranger_object_set_widget_visibility_and_state (*/
428      /*r_obj, 1);*/
429 
430   arranger_object_set_position (
431     r_obj, &r_obj->end_pos,
432     ARRANGER_OBJECT_POSITION_TYPE_END,
433     F_NO_VALIDATE);
434   arranger_object_select (
435     r_obj, F_SELECT,
436     autofilling ? F_APPEND : F_NO_APPEND,
437     F_NO_PUBLISH_EVENTS);
438 }
439 
440 /**
441  * Wrapper for
442  * timeline_arranger_widget_create_chord() or
443  * timeline_arranger_widget_create_scale().
444  *
445  * @param y the y relative to the
446  *   ArrangerWidget.
447  */
448 void
timeline_arranger_widget_create_chord_or_scale(ArrangerWidget * self,Track * track,double y,const Position * pos)449 timeline_arranger_widget_create_chord_or_scale (
450   ArrangerWidget * self,
451   Track *                  track,
452   double                   y,
453   const Position *         pos)
454 {
455   int track_height =
456     gtk_widget_get_allocated_height (
457       GTK_WIDGET (track->widget));
458   gint wy;
459   gtk_widget_translate_coordinates (
460     GTK_WIDGET (self),
461     GTK_WIDGET (track->widget),
462     0, (int) y, NULL, &wy);
463 
464   if (y >= track_height / 2.0)
465     timeline_arranger_widget_create_scale (
466       self, track, pos);
467   else
468     timeline_arranger_widget_create_region (
469       self, REGION_TYPE_CHORD, track, NULL,
470       NULL, pos);
471 }
472 
473 /**
474  * Create a ScaleObject at the given Position in the
475  * given Track.
476  *
477  * @param pos The pre-snapped position.
478  */
479 void
timeline_arranger_widget_create_scale(ArrangerWidget * self,Track * track,const Position * pos)480 timeline_arranger_widget_create_scale (
481   ArrangerWidget * self,
482   Track *            track,
483   const Position *         pos)
484 {
485   g_warn_if_fail (track->type == TRACK_TYPE_CHORD);
486 
487   self->action =
488     UI_OVERLAY_ACTION_CREATING_MOVING;
489 
490   /* create a new scale */
491   MusicalScale * descr =
492     musical_scale_new (SCALE_AEOLIAN, NOTE_A);
493   ScaleObject * scale =
494     scale_object_new (descr);
495   ArrangerObject * scale_obj =
496     (ArrangerObject *) scale;
497 
498   /* add it to scale track */
499   chord_track_add_scale (track, scale);
500 
501   /*arranger_object_gen_widget (scale_obj);*/
502 
503   /* set visibility */
504   /*arranger_object_set_widget_visibility_and_state (*/
505     /*scale_obj, 1);*/
506 
507   arranger_object_pos_setter (
508     scale_obj, pos);
509 
510   EVENTS_PUSH (ET_ARRANGER_OBJECT_CREATED, scale);
511   arranger_object_select (
512     scale_obj, F_SELECT, F_NO_APPEND,
513     F_NO_PUBLISH_EVENTS);
514 }
515 
516 /**
517  * Create a Marker at the given Position in the
518  * given Track.
519  *
520  * @param pos The pre-snapped position.
521  */
522 void
timeline_arranger_widget_create_marker(ArrangerWidget * self,Track * track,const Position * pos)523 timeline_arranger_widget_create_marker (
524   ArrangerWidget * self,
525   Track *            track,
526   const Position *         pos)
527 {
528   g_warn_if_fail (
529     track->type == TRACK_TYPE_MARKER);
530 
531   self->action =
532     UI_OVERLAY_ACTION_CREATING_MOVING;
533 
534   /* create a new marker */
535   Marker * marker =
536     marker_new (_("Custom Marker"));
537   ArrangerObject * marker_obj =
538     (ArrangerObject *) marker;
539 
540   /* add it to marker track */
541   marker_track_add_marker (
542     track, marker);
543 
544   /*arranger_object_gen_widget (marker_obj);*/
545 
546   /* set visibility */
547   /*arranger_object_set_widget_visibility_and_state (*/
548     /*marker_obj, 1);*/
549 
550   arranger_object_pos_setter (
551     marker_obj, pos);
552 
553   EVENTS_PUSH (ET_ARRANGER_OBJECT_CREATED, marker);
554   arranger_object_select (
555     marker_obj, F_SELECT, F_NO_APPEND,
556     F_NO_PUBLISH_EVENTS);
557 }
558 
559 /**
560  * Determines the selection time (objects/range)
561  * and sets it.
562  */
563 void
timeline_arranger_widget_set_select_type(ArrangerWidget * self,double y)564 timeline_arranger_widget_set_select_type (
565   ArrangerWidget * self,
566   double           y)
567 {
568   Track * track =
569     timeline_arranger_widget_get_track_at_y (
570       self, y);
571 
572   if (track)
573     {
574       if (track_widget_is_cursor_in_range_select_half (
575             track->widget, y))
576         {
577           /* set resizing range flags */
578           self->resizing_range = true;
579           self->resizing_range_start = true;
580           self->action =
581             UI_OVERLAY_ACTION_RESIZING_R;
582         }
583       else
584         {
585           /* select objects */
586           self->resizing_range = false;
587         }
588     }
589   else
590     {
591       /* TODO something similar as above based on
592        * visible space */
593       self->resizing_range = false;
594     }
595 
596   /*arranger_widget_refresh_all_backgrounds ();*/
597   gtk_widget_queue_allocate (
598     GTK_WIDGET (MW_RULER));
599 }
600 
601 /**
602  * Snaps the region's start point.
603  *
604  * @param new_start_pos Position to snap to.
605  * @parram dry_run Don't resize notes; just check
606  *   if the resize is allowed (check if invalid
607  *   resizes will happen)
608  *
609  * @return 0 if the operation was successful,
610  *   nonzero otherwise.
611  */
612 static inline int
snap_region_l(ArrangerWidget * self,ZRegion * region,Position * new_pos,int dry_run)613 snap_region_l (
614   ArrangerWidget * self,
615   ZRegion *        region,
616   Position *       new_pos,
617   int              dry_run)
618 {
619   ArrangerObjectResizeType type =
620     ARRANGER_OBJECT_RESIZE_NORMAL;
621   if ACTION_IS (RESIZING_L_LOOP)
622     type = ARRANGER_OBJECT_RESIZE_LOOP;
623   else if ACTION_IS (RESIZING_L_FADE)
624     type = ARRANGER_OBJECT_RESIZE_FADE;
625   else if ACTION_IS (STRETCHING_L)
626     type = ARRANGER_OBJECT_RESIZE_STRETCH;
627 
628   /* negative positions not allowed */
629   if (!position_is_positive (new_pos))
630     return -1;
631 
632   if (SNAP_GRID_ANY_SNAP (self->snap_grid) &&
633         !self->shift_held &&
634         type != ARRANGER_OBJECT_RESIZE_FADE)
635     {
636       Track * track =
637         arranger_object_get_track (
638           (ArrangerObject *) region);
639       position_snap (
640         &self->earliest_obj_start_pos,
641         new_pos, track,
642         NULL, self->snap_grid);
643     }
644 
645   ArrangerObject * r_obj =
646     (ArrangerObject *) region;
647   Position cmp_pos;
648   if (type == ARRANGER_OBJECT_RESIZE_FADE)
649     {
650       cmp_pos = r_obj->fade_out_pos;
651     }
652   else
653     {
654       cmp_pos = r_obj->end_pos;
655     }
656   if (position_is_after_or_equal (
657         new_pos, &cmp_pos))
658     return -1;
659   else if (!dry_run)
660     {
661       int is_valid = 0;
662       double diff = 0;
663 
664       if (type == ARRANGER_OBJECT_RESIZE_FADE)
665         {
666           is_valid =
667             arranger_object_validate_pos (
668               r_obj, new_pos,
669               ARRANGER_OBJECT_POSITION_TYPE_FADE_IN);
670           diff =
671             position_to_ticks (new_pos) -
672             position_to_ticks (&r_obj->fade_in_pos);
673         }
674       else
675         {
676           is_valid =
677             arranger_object_validate_pos (
678               r_obj, new_pos,
679               ARRANGER_OBJECT_POSITION_TYPE_START);
680           diff =
681             position_to_ticks (new_pos) -
682             position_to_ticks (&r_obj->pos);
683         }
684 
685       if (is_valid)
686         {
687           arranger_object_resize (
688             r_obj, true, type, diff, true);
689         }
690     }
691 
692   return 0;
693 }
694 
695 /**
696  * Snaps both the transients (to show in the GUI)
697  * and the actual regions.
698  *
699  * @param pos Absolute position in the timeline.
700  * @param dry_run Don't resize notes; just check
701  *   if the resize is allowed (check if invalid
702  *   resizes will happen).
703  *
704  * @return 0 if the operation was successful,
705  *   nonzero otherwise.
706  */
707 int
timeline_arranger_widget_snap_regions_l(ArrangerWidget * self,Position * pos,int dry_run)708 timeline_arranger_widget_snap_regions_l (
709   ArrangerWidget * self,
710   Position *       pos,
711   int              dry_run)
712 {
713   ArrangerObject * start_r_obj =
714     self->start_object;
715 
716   /* get delta with first clicked region's start
717    * pos */
718   double delta;
719   if (ACTION_IS (RESIZING_L_FADE))
720     {
721       delta =
722         position_to_ticks (pos) -
723         (position_to_ticks (
724            &start_r_obj->pos) +
725          position_to_ticks (
726            &start_r_obj->fade_in_pos));
727     }
728   else
729     {
730       delta =
731         position_to_ticks (pos) -
732         position_to_ticks (
733           &start_r_obj->pos);
734     }
735 
736   /* new start pos for each region, calculated by
737    * adding delta to the region's original start
738    * pos */
739   Position new_pos;
740 
741   ZRegion * region;
742   ArrangerObject * r_obj;
743   int ret;
744   for (int i = 0;
745        i < TL_SELECTIONS->num_regions;
746        i++)
747     {
748       /* main trans region */
749       region =
750         TL_SELECTIONS->regions[i];
751       r_obj = (ArrangerObject *) region;
752 
753       /* caclulate new start position */
754       if (ACTION_IS (RESIZING_L_FADE))
755         {
756           position_set_to_pos (
757             &new_pos, &r_obj->fade_in_pos);
758         }
759       else
760         {
761           position_set_to_pos (
762             &new_pos, &r_obj->pos);
763         }
764       position_add_ticks (&new_pos, delta);
765 
766       ret =
767         snap_region_l (
768           self, region, &new_pos, dry_run);
769 
770       if (ret)
771         return ret;
772     }
773 
774   EVENTS_PUSH (
775     ET_ARRANGER_SELECTIONS_CHANGED,
776     TL_SELECTIONS);
777 
778   return 0;
779 }
780 
781 /**
782  * Snaps the region's end point.
783  *
784  * @param new_end_pos New end position to snap to.
785  * @parram dry_run Don't resize notes; just check
786  *   if the resize is allowed (check if invalid
787  *   resizes will happen)
788  *
789  * @return 0 if the operation was successful,
790  *   nonzero otherwise.
791  */
792 static int
snap_region_r(ArrangerWidget * self,ZRegion * region,Position * new_pos,int dry_run)793 snap_region_r (
794   ArrangerWidget * self,
795   ZRegion *        region,
796   Position *       new_pos,
797   int              dry_run)
798 {
799   ArrangerObjectResizeType type =
800     ARRANGER_OBJECT_RESIZE_NORMAL;
801   if ACTION_IS (RESIZING_R_LOOP)
802     type = ARRANGER_OBJECT_RESIZE_LOOP;
803   else if ACTION_IS (RESIZING_R_FADE)
804     type = ARRANGER_OBJECT_RESIZE_FADE;
805   else if ACTION_IS (STRETCHING_R)
806     type = ARRANGER_OBJECT_RESIZE_STRETCH;
807 
808   /* negative positions not allowed */
809   if (!position_is_positive (new_pos))
810     return -1;
811 
812   if (SNAP_GRID_ANY_SNAP (self->snap_grid)
813       && !self->shift_held
814       && type != ARRANGER_OBJECT_RESIZE_FADE)
815     {
816       Track * track =
817         arranger_object_get_track (
818           (ArrangerObject *) region);
819       position_snap (
820         &self->earliest_obj_start_pos,
821         new_pos, track,
822         NULL, self->snap_grid);
823     }
824 
825   ArrangerObject * r_obj =
826     (ArrangerObject *) region;
827   if (type == ARRANGER_OBJECT_RESIZE_FADE)
828     {
829       Position tmp;
830       position_from_ticks (
831         &tmp,
832         r_obj->end_pos.ticks -
833           r_obj->pos.ticks);
834       if (position_is_before_or_equal (
835             new_pos, &r_obj->fade_in_pos) ||
836           position_is_after (new_pos, &tmp))
837         return -1;
838     }
839   else
840     {
841       if (position_is_before_or_equal (
842             new_pos, &r_obj->pos))
843         return -1;
844     }
845 
846   if (!dry_run)
847     {
848       int is_valid = 0;
849       double diff = 0;
850       if (type == ARRANGER_OBJECT_RESIZE_FADE)
851         {
852           is_valid =
853             arranger_object_validate_pos (
854               r_obj, new_pos,
855               ARRANGER_OBJECT_POSITION_TYPE_FADE_OUT);
856           diff =
857             position_to_ticks (new_pos) -
858             position_to_ticks (&r_obj->fade_out_pos);
859         }
860       else
861         {
862           is_valid =
863             arranger_object_validate_pos (
864               r_obj, new_pos,
865               ARRANGER_OBJECT_POSITION_TYPE_END);
866           diff =
867             position_to_ticks (new_pos) -
868             position_to_ticks (&r_obj->end_pos);
869         }
870 
871       if (is_valid)
872         {
873           arranger_object_resize (
874             r_obj, false, type, diff, true);
875 
876           /* if creating also set the loop points
877            * appropriately */
878           if (self->action ==
879                 UI_OVERLAY_ACTION_CREATING_RESIZING_R)
880             {
881               double full_size =
882                 arranger_object_get_length_in_ticks (
883                   r_obj);
884               Position tmp;
885               position_set_to_pos (
886                 &tmp, &r_obj->loop_start_pos);
887               position_add_ticks (&tmp, full_size);
888 
889               /* use the setters */
890               arranger_object_loop_end_pos_setter (
891                 r_obj, &tmp);
892             }
893         }
894     }
895 
896   return 0;
897 }
898 
899 /**
900  * Snaps both the transients (to show in the GUI)
901  * and the actual regions.
902  *
903  * @param pos Absolute position in the timeline.
904  * @parram dry_run Don't resize notes; just check
905  *   if the resize is allowed (check if invalid
906  *   resizes will happen)
907  *
908  * @return 0 if the operation was successful,
909  *   nonzero otherwise.
910  */
911 int
timeline_arranger_widget_snap_regions_r(ArrangerWidget * self,Position * pos,int dry_run)912 timeline_arranger_widget_snap_regions_r (
913   ArrangerWidget * self,
914   Position *       pos,
915   int              dry_run)
916 {
917   ArrangerObject * start_r_obj =
918     self->start_object;
919 
920   /* get delta with first clicked region's end
921    * pos */
922   double delta;
923   if (ACTION_IS (RESIZING_R_FADE))
924     {
925       delta =
926         position_to_ticks (pos) -
927         (position_to_ticks (
928           &start_r_obj->pos) +
929          position_to_ticks (
930            &start_r_obj->fade_out_pos));
931     }
932   else
933     {
934       delta =
935         position_to_ticks (pos) -
936         position_to_ticks (
937           &start_r_obj->end_pos);
938     }
939 
940   /* new end pos for each region, calculated by
941    * adding delta to the region's original end
942    * pos */
943   Position new_pos;
944 
945   ZRegion * region;
946   ArrangerObject * r_obj;
947   int ret;
948   for (int i = 0;
949        i < TL_SELECTIONS->num_regions;
950        i++)
951     {
952       region =
953         TL_SELECTIONS->regions[i];
954       r_obj = (ArrangerObject *) region;
955 
956       if (ACTION_IS (RESIZING_R_FADE))
957         {
958           position_set_to_pos (
959             &new_pos, &r_obj->fade_out_pos);
960         }
961       else
962         {
963           position_set_to_pos (
964             &new_pos, &r_obj->end_pos);
965         }
966       position_add_ticks (&new_pos, delta);
967 
968       ret =
969         snap_region_r (
970           self, region, &new_pos, dry_run);
971 
972       if (ret)
973         return ret;
974     }
975 
976   EVENTS_PUSH (
977     ET_ARRANGER_SELECTIONS_CHANGED,
978     (ArrangerSelections *) TL_SELECTIONS);
979 
980   return 0;
981 }
982 
983 void
timeline_arranger_widget_snap_range_r(ArrangerWidget * self,Position * pos)984 timeline_arranger_widget_snap_range_r (
985   ArrangerWidget * self,
986   Position *       pos)
987 {
988   if (self->resizing_range_start)
989     {
990       /* set range 1 at current point */
991       ui_px_to_pos_timeline (
992         self->start_x,
993         &TRANSPORT->range_1,
994         1);
995       if (SNAP_GRID_ANY_SNAP (
996             self->snap_grid) &&
997           !self->shift_held)
998         {
999           position_snap_simple (
1000             &TRANSPORT->range_1,
1001             SNAP_GRID_TIMELINE);
1002         }
1003       position_set_to_pos (
1004         &TRANSPORT->range_2,
1005         &TRANSPORT->range_1);
1006 
1007       MW_TIMELINE->resizing_range_start = 0;
1008     }
1009 
1010   /* set range */
1011   if (SNAP_GRID_ANY_SNAP (self->snap_grid) &&
1012       !self->shift_held)
1013     position_snap_simple (
1014       pos,
1015       SNAP_GRID_TIMELINE);
1016   position_set_to_pos (
1017     &TRANSPORT->range_2, pos);
1018   transport_set_has_range (TRANSPORT, true);
1019 }
1020 
1021 /** Used when activating fade presets. */
1022 typedef struct CurveOptionInfo
1023 {
1024   CurveOptions     opts;
1025   ArrangerObject * obj;
1026 
1027   /** 1 for in, 0 for out. */
1028   int              fade_in;
1029 } CurveOptionInfo;
1030 
1031 static void
on_fade_preset_selected(GtkMenuItem * menu_item,CurveOptionInfo * info)1032 on_fade_preset_selected (
1033   GtkMenuItem *     menu_item,
1034   CurveOptionInfo * info)
1035 {
1036   ArrangerSelections * sel_before =
1037     arranger_selections_clone (
1038       (ArrangerSelections *) TL_SELECTIONS);
1039   if (info->fade_in)
1040     {
1041       info->obj->fade_in_opts.algo =
1042         info->opts.algo;
1043       info->obj->fade_in_opts.curviness =
1044         info->opts.curviness;
1045     }
1046   else
1047     {
1048       info->obj->fade_out_opts.algo =
1049         info->opts.algo;
1050       info->obj->fade_out_opts.curviness =
1051         info->opts.curviness;
1052     }
1053 
1054   g_warn_if_fail (
1055     arranger_object_is_selected (info->obj));
1056 
1057   GError * err = NULL;
1058   bool ret =
1059     arranger_selections_action_perform_edit (
1060       sel_before,
1061       (ArrangerSelections *) TL_SELECTIONS,
1062       ARRANGER_SELECTIONS_ACTION_EDIT_FADES,
1063       true, &err);
1064   if (!ret)
1065     {
1066       HANDLE_ERROR (
1067         err, "%s", _("Failed to edit fades"));
1068     }
1069 
1070   g_warn_if_fail (IS_ARRANGER_OBJECT (info->obj));
1071   EVENTS_PUSH (
1072     ET_ARRANGER_OBJECT_CHANGED, info->obj);
1073 
1074   object_zero_and_free (info);
1075 }
1076 
1077 /**
1078  * @param fade_in 1 for in, 0 for out.
1079  */
1080 static void
create_fade_preset_menu(ArrangerWidget * self,GtkWidget * menu,ArrangerObject * obj,int fade_in)1081 create_fade_preset_menu (
1082   ArrangerWidget * self,
1083   GtkWidget *      menu,
1084   ArrangerObject * obj,
1085   int              fade_in)
1086 {
1087   GtkWidget * menuitem =
1088     gtk_menu_item_new_with_label (_("Fade preset"));
1089   gtk_menu_shell_append (
1090     GTK_MENU_SHELL (menu), menuitem);
1091   GtkMenu * submenu = GTK_MENU (gtk_menu_new ());
1092   gtk_widget_set_visible (GTK_WIDGET (submenu), 1);
1093   GtkMenuItem * submenu_item;
1094   CurveOptionInfo * opts;
1095 
1096 #define CREATE_ITEM(name,xalgo,curve) \
1097   submenu_item = \
1098     GTK_MENU_ITEM ( \
1099       gtk_menu_item_new_with_label (name)); \
1100   opts = object_new (CurveOptionInfo); \
1101   opts->opts.algo = CURVE_ALGORITHM_##xalgo; \
1102   opts->opts.curviness = curve; \
1103   opts->fade_in = fade_in; \
1104   opts->obj = obj; \
1105   g_signal_connect ( \
1106     G_OBJECT (submenu_item), "activate", \
1107     G_CALLBACK (on_fade_preset_selected), opts); \
1108   gtk_menu_shell_append ( \
1109     GTK_MENU_SHELL (submenu), \
1110     GTK_WIDGET (submenu_item)); \
1111   gtk_widget_set_visible ( \
1112     GTK_WIDGET (submenu_item), 1)
1113 
1114   CREATE_ITEM (_("Linear"), SUPERELLIPSE, 0);
1115   CREATE_ITEM (_("Exponential"), EXPONENT, - 0.6);
1116   CREATE_ITEM (_("Elliptic"), SUPERELLIPSE, - 0.5);
1117   CREATE_ITEM (
1118     _("Logarithmic"), LOGARITHMIC, - 0.5);
1119   CREATE_ITEM (_("Vital"), VITAL, - 0.5);
1120 
1121 #undef CREATE_ITEM
1122 
1123   gtk_menu_item_set_submenu (
1124     GTK_MENU_ITEM (menuitem),
1125     GTK_WIDGET (submenu));
1126   gtk_widget_set_visible (
1127     GTK_WIDGET (menuitem), 1);
1128 }
1129 
1130 /** Used when selecting a musical mode. */
1131 typedef struct MusicalModeInfo
1132 {
1133   RegionMusicalMode mode;
1134   ArrangerObject *  obj;
1135 } MusicalModeInfo;
1136 
1137 static void
on_musical_mode_toggled(GtkCheckMenuItem * menu_item,MusicalModeInfo * info)1138 on_musical_mode_toggled (
1139   GtkCheckMenuItem * menu_item,
1140   MusicalModeInfo *  info)
1141 {
1142   if (!gtk_check_menu_item_get_active (menu_item))
1143     {
1144       return;
1145     }
1146 
1147   ArrangerSelections * sel_before =
1148     arranger_selections_clone (
1149       (ArrangerSelections *) TL_SELECTIONS);
1150 
1151   /* make the change */
1152   ZRegion * region = (ZRegion *) info->obj;
1153   region->musical_mode = info->mode;
1154 
1155   g_warn_if_fail (
1156     arranger_object_is_selected (info->obj));
1157 
1158   GError * err = NULL;
1159   bool ret =
1160     arranger_selections_action_perform_edit (
1161       sel_before,
1162       (ArrangerSelections *) TL_SELECTIONS,
1163       ARRANGER_SELECTIONS_ACTION_EDIT_PRIMITIVE,
1164       true, &err);
1165   if (!ret)
1166     {
1167       HANDLE_ERROR (
1168         err, "%s",
1169         _("Failed to edit selections"));
1170     }
1171 
1172   g_warn_if_fail (IS_ARRANGER_OBJECT (info->obj));
1173   EVENTS_PUSH (
1174     ET_ARRANGER_OBJECT_CHANGED, info->obj);
1175 
1176   object_zero_and_free (info);
1177 }
1178 
1179 /**
1180  * @param fade_in 1 for in, 0 for out.
1181  */
1182 static void
create_musical_mode_pset_menu(ArrangerWidget * self,GtkWidget * menu,ArrangerObject * obj)1183 create_musical_mode_pset_menu (
1184   ArrangerWidget * self,
1185   GtkWidget *      menu,
1186   ArrangerObject * obj)
1187 {
1188   GtkWidget * menuitem =
1189     gtk_menu_item_new_with_label (
1190       _("Musical Mode"));
1191   gtk_menu_shell_append (
1192     GTK_MENU_SHELL (menu), menuitem);
1193 
1194   GtkMenu * submenu = GTK_MENU (gtk_menu_new ());
1195   gtk_widget_set_visible (GTK_WIDGET (submenu), 1);
1196   GtkMenuItem * submenu_item[
1197     REGION_MUSICAL_MODE_ON + 2];
1198   MusicalModeInfo * nfo;
1199   GSList * group = NULL;
1200   ZRegion * region = (ZRegion *) obj;
1201   for (int i = 0; i <= REGION_MUSICAL_MODE_ON; i++)
1202     {
1203       submenu_item[i] =
1204         GTK_MENU_ITEM (
1205           gtk_radio_menu_item_new_with_label (
1206             group,
1207             _(region_musical_mode_strings[i].str)));
1208       if ((RegionMusicalMode) i ==
1209             region->musical_mode)
1210         {
1211           gtk_check_menu_item_set_active (
1212             GTK_CHECK_MENU_ITEM (
1213               submenu_item[i]), true);
1214         }
1215       gtk_menu_shell_append (
1216         GTK_MENU_SHELL (submenu),
1217         GTK_WIDGET (submenu_item[i]));
1218       gtk_widget_set_visible (
1219         GTK_WIDGET (submenu_item[i]), true);
1220 
1221       group =
1222         gtk_radio_menu_item_get_group (
1223           GTK_RADIO_MENU_ITEM (submenu_item[i]));
1224     }
1225 
1226   gtk_menu_item_set_submenu (
1227     GTK_MENU_ITEM (menuitem),
1228     GTK_WIDGET (submenu));
1229   gtk_widget_set_visible (
1230     GTK_WIDGET (menuitem), 1);
1231 
1232   for (int i = 0; i <= REGION_MUSICAL_MODE_ON; i++)
1233     {
1234       nfo = object_new (MusicalModeInfo);
1235       nfo->mode = i;
1236       nfo->obj = obj;
1237       g_signal_connect (
1238         G_OBJECT (submenu_item[i]), "toggled",
1239         G_CALLBACK (on_musical_mode_toggled), nfo);
1240     }
1241 }
1242 
1243 typedef struct ContextMenuData
1244 {
1245   AudioFunctionType audio_func;
1246 } ContextMenuData;
1247 
1248 static void
free_context_menu_data(ContextMenuData * data,GClosure * closure)1249 free_context_menu_data (
1250   ContextMenuData * data,
1251   GClosure *        closure)
1252 {
1253   free (data);
1254 }
1255 
1256 static void
on_audio_func_activate(GtkCheckMenuItem * item,ContextMenuData * data)1257 on_audio_func_activate (
1258   GtkCheckMenuItem * item,
1259   ContextMenuData *  data)
1260 {
1261   for (int i = 0; i < TL_SELECTIONS->num_regions;
1262        i++)
1263     {
1264       ZRegion * r = TL_SELECTIONS->regions[i];
1265       AudioSelections * sel =
1266         (AudioSelections *)
1267         arranger_selections_new (
1268           ARRANGER_SELECTIONS_TYPE_AUDIO);
1269       region_identifier_copy (
1270         &sel->region_id, &r->id);
1271       sel->has_selection = true;
1272       ArrangerObject * r_obj =
1273         (ArrangerObject *) r;
1274 
1275       /* timeline start pos */
1276       position_set_to_pos (
1277         &sel->sel_start, &r_obj->clip_start_pos);
1278       position_add_ticks (
1279         &sel->sel_start, r_obj->pos.ticks);
1280 
1281       /* timeline end pos */
1282       position_set_to_pos (
1283         &sel->sel_end, &r_obj->loop_end_pos);
1284       position_add_ticks (
1285         &sel->sel_end, r_obj->pos.ticks);
1286       if (position_is_after (
1287             &sel->sel_end, &r_obj->end_pos))
1288         {
1289           position_set_to_pos (
1290             &sel->sel_end, &r_obj->end_pos);
1291         }
1292 
1293       GError * err = NULL;
1294       bool ret =
1295         arranger_selections_action_perform_edit_audio_function (
1296           (ArrangerSelections *) sel,
1297           data->audio_func, NULL, &err);
1298       if (!ret)
1299         {
1300           HANDLE_ERROR (
1301             err, "%s",
1302             _("Failed to apply audio function"));
1303           break;
1304         }
1305       else
1306         {
1307           UndoableAction * ua =
1308             undo_manager_get_last_action (
1309               UNDO_MANAGER);
1310           ua->num_actions = i + 1;
1311         }
1312     }
1313 }
1314 
1315 static void
on_detect_bpm_activate(GtkMenuItem * item,void * user_data)1316 on_detect_bpm_activate (
1317   GtkMenuItem * item,
1318   void *        user_data)
1319 {
1320   ZRegion * r = (ZRegion *) user_data;
1321   g_return_if_fail (IS_REGION_AND_NONNULL (r));
1322 
1323   GArray * candidates =
1324     g_array_new (false, true, sizeof (float));
1325   bpm_t bpm =
1326     audio_region_detect_bpm (r, candidates);
1327 
1328   GString * gstr = g_string_new (NULL);
1329   g_string_append_printf (
1330     gstr, _("Detected BPM: %.2f"), bpm);
1331   g_string_append (gstr, "\n\n");
1332   g_string_append_printf (
1333     gstr, _("Candidates:"));
1334   for (size_t i = 0; i < candidates->len; i++)
1335     {
1336       float candidate =
1337         g_array_index (candidates, float, i);
1338       g_string_append_printf (
1339         gstr, " %.2f", candidate);
1340     }
1341   char * str = g_string_free (gstr, false);
1342   ui_show_message_printf (
1343     MAIN_WINDOW, GTK_MESSAGE_INFO,
1344     "%s", str);
1345   g_free (str);
1346 }
1347 
1348 /**
1349  * Show context menu at x, y.
1350  */
1351 void
timeline_arranger_widget_show_context_menu(ArrangerWidget * self,double x,double y)1352 timeline_arranger_widget_show_context_menu (
1353   ArrangerWidget * self,
1354   double           x,
1355   double           y)
1356 {
1357   GtkWidget *menu, *menuitem;
1358   menu = gtk_menu_new();
1359 
1360 #define APPEND_TO_MENU \
1361   gtk_menu_shell_append ( \
1362     GTK_MENU_SHELL (menu), menuitem)
1363 
1364   ArrangerObject * obj =
1365     arranger_widget_get_hit_arranger_object (
1366       (ArrangerWidget *) self,
1367       ARRANGER_OBJECT_TYPE_ALL, x, y);
1368 
1369   if (obj)
1370     {
1371       int local_x = (int) (x - obj->full_rect.x);
1372       int local_y = (int) (y - obj->full_rect.y);
1373 
1374       /* create cut, copy, duplicate, delete */
1375       menuitem =
1376         GTK_WIDGET (
1377           CREATE_CUT_MENU_ITEM ("app.cut"));
1378       APPEND_TO_MENU;
1379       menuitem =
1380         GTK_WIDGET (
1381           CREATE_COPY_MENU_ITEM ("app.copy"));
1382       APPEND_TO_MENU;
1383       menuitem =
1384         GTK_WIDGET (
1385           CREATE_DUPLICATE_MENU_ITEM (
1386             "app.duplicate"));
1387       APPEND_TO_MENU;
1388       menuitem =
1389         GTK_WIDGET (
1390           CREATE_DELETE_MENU_ITEM ("app.delete"));
1391       APPEND_TO_MENU;
1392       menuitem =
1393         gtk_separator_menu_item_new ();
1394       APPEND_TO_MENU;
1395 
1396       if (timeline_selections_contains_only_regions (TL_SELECTIONS))
1397         {
1398           ZRegion * r = (ZRegion *) obj;
1399 
1400           if (timeline_selections_contains_only_region_types (TL_SELECTIONS, REGION_TYPE_AUDIO))
1401             {
1402               if (TL_SELECTIONS->num_regions == 1)
1403                 {
1404                   menuitem =
1405                     GTK_WIDGET (
1406                       z_gtk_create_menu_item (
1407                         _("Detect BPM"), NULL,
1408                         F_NO_TOGGLE, NULL));
1409                   gtk_widget_set_visible (
1410                     GTK_WIDGET (menuitem), true);
1411                   g_signal_connect (
1412                     G_OBJECT (menuitem),
1413                     "activate",
1414                     G_CALLBACK (
1415                       on_detect_bpm_activate),
1416                     TL_SELECTIONS->regions[0]);
1417                   gtk_menu_shell_append (
1418                     GTK_MENU_SHELL (menu),
1419                     menuitem);
1420                 }
1421 
1422               menuitem =
1423                 GTK_WIDGET (
1424                   z_gtk_create_menu_item (
1425                     _("Apply Function"),
1426                     "modulator", F_NO_TOGGLE,
1427                     NULL));
1428               gtk_widget_set_visible (
1429                 GTK_WIDGET (menuitem), true);
1430 
1431               GtkMenu * submenu =
1432                 GTK_MENU (gtk_menu_new ());
1433               gtk_widget_set_visible (
1434                 GTK_WIDGET (submenu), true);
1435               for (int i = AUDIO_FUNCTION_INVERT;
1436                    i < AUDIO_FUNCTION_CUSTOM_PLUGIN;
1437                    i++)
1438                 {
1439                   if (i == AUDIO_FUNCTION_NORMALIZE_RMS
1440                       || i == AUDIO_FUNCTION_NORMALIZE_LUFS)
1441                     continue;
1442 
1443                   GtkWidget * submenu_item =
1444                     GTK_WIDGET (
1445                       z_gtk_create_menu_item (
1446                         _(audio_function_type_to_string (i)),
1447                         NULL, F_NO_TOGGLE, NULL));
1448                   ContextMenuData * data =
1449                     object_new (ContextMenuData);
1450                   data->audio_func = i;
1451                   g_signal_connect_data (
1452                     G_OBJECT (submenu_item),
1453                     "activate",
1454                     G_CALLBACK (
1455                       on_audio_func_activate),
1456                     data,
1457                     (GClosureNotify)
1458                     free_context_menu_data,
1459                     0);
1460                   gtk_widget_set_visible (
1461                     GTK_WIDGET (submenu_item),
1462                     true);
1463                   gtk_menu_shell_append (
1464                     GTK_MENU_SHELL (submenu),
1465                     submenu_item);
1466                 }
1467               gtk_menu_item_set_submenu (
1468                 GTK_MENU_ITEM (menuitem),
1469                 GTK_WIDGET (submenu));
1470               gtk_menu_shell_append (
1471                 GTK_MENU_SHELL (menu), menuitem);
1472             }
1473 
1474           if (arranger_object_get_muted (obj))
1475             {
1476               menuitem =
1477                 GTK_WIDGET (
1478                   CREATE_UNMUTE_MENU_ITEM (
1479                     "app.mute-selection"));
1480               gtk_actionable_set_action_target (
1481                 GTK_ACTIONABLE (menuitem),
1482                 "s", "timeline");
1483             }
1484           else
1485             {
1486               menuitem =
1487                 GTK_WIDGET (
1488                   CREATE_MUTE_MENU_ITEM (
1489                     "app.mute-selection"));
1490               gtk_actionable_set_action_target (
1491                 GTK_ACTIONABLE (menuitem),
1492                 "s", "timeline");
1493             }
1494           gtk_menu_shell_append (
1495             GTK_MENU_SHELL(menu), menuitem);
1496 
1497           if (timeline_selections_contains_only_region_types (TL_SELECTIONS, REGION_TYPE_MIDI))
1498             {
1499               menuitem =
1500                 gtk_menu_item_new_with_label (
1501                   _("Export as MIDI file"));
1502               gtk_menu_shell_append (
1503                 GTK_MENU_SHELL(menu), menuitem);
1504               g_signal_connect (
1505                 menuitem, "activate",
1506                 G_CALLBACK (
1507                   timeline_arranger_on_export_as_midi_file_clicked),
1508                 r);
1509             }
1510 
1511           if (r->id.type == REGION_TYPE_AUDIO)
1512             {
1513               /* create fade menus */
1514               if (arranger_object_is_fade_in (
1515                     obj, local_x, local_y, 0, 0))
1516                 {
1517                   create_fade_preset_menu (
1518                     self, menu, obj, 1);
1519                 }
1520               if (arranger_object_is_fade_out (
1521                     obj, local_x, local_y, 0, 0))
1522                 {
1523                   create_fade_preset_menu (
1524                     self, menu, obj, 0);
1525                 }
1526 
1527               /* create musical mode menu */
1528               create_musical_mode_pset_menu (
1529                 self, menu, obj);
1530             }
1531 
1532           menuitem =
1533             gtk_menu_item_new_with_label (
1534               _("Quick bounce"));
1535           gtk_menu_shell_append (
1536             GTK_MENU_SHELL(menu), menuitem);
1537           g_signal_connect (
1538             menuitem, "activate",
1539             G_CALLBACK (
1540               timeline_arranger_on_quick_bounce_clicked),
1541             r);
1542 
1543           menuitem =
1544             gtk_menu_item_new_with_label (
1545               _("Bounce..."));
1546           gtk_menu_shell_append (
1547             GTK_MENU_SHELL(menu), menuitem);
1548           g_signal_connect (
1549             menuitem, "activate",
1550             G_CALLBACK (
1551               timeline_arranger_on_bounce_clicked),
1552             r);
1553         }
1554     }
1555   else
1556     {
1557       menuitem =
1558         GTK_WIDGET (
1559           CREATE_PASTE_MENU_ITEM ("app.paste"));
1560       APPEND_TO_MENU;
1561     }
1562 
1563 #undef APPEND_TO_MENU
1564 
1565   gtk_menu_attach_to_widget (
1566     GTK_MENU (menu), GTK_WIDGET (self), NULL);
1567   gtk_widget_show_all (menu);
1568   gtk_menu_popup_at_pointer (
1569     GTK_MENU (menu), NULL);
1570 }
1571 
1572 /**
1573  * Fade up/down.
1574  *
1575  * @param fade_in 1 for in, 0 for out.
1576  */
1577 void
timeline_arranger_widget_fade_up(ArrangerWidget * self,double offset_y,int fade_in)1578 timeline_arranger_widget_fade_up (
1579   ArrangerWidget * self,
1580   double           offset_y,
1581   int              fade_in)
1582 {
1583   for (int i = 0; i < TL_SELECTIONS->num_regions; i++)
1584     {
1585       ZRegion * r = TL_SELECTIONS->regions[i];
1586       ArrangerObject * obj =
1587         (ArrangerObject *) r;
1588 
1589       CurveOptions * opts =
1590         fade_in ?
1591           &obj->fade_in_opts : &obj->fade_out_opts;
1592       double delta =
1593         (self->last_offset_y - offset_y) * 0.008;
1594       opts->curviness =
1595         CLAMP (opts->curviness + delta, - 1.0, 1.0);
1596     }
1597 }
1598 
1599 static void
highlight_timeline(ArrangerWidget * self,GdkModifierType mask,int x,int y,Track * track,TrackLane * lane)1600 highlight_timeline (
1601   ArrangerWidget * self,
1602   GdkModifierType  mask,
1603   int              x,
1604   int              y,
1605   Track *          track,
1606   TrackLane *      lane)
1607 {
1608   /* get default size */
1609   int ticks =
1610     snap_grid_get_default_ticks (
1611       SNAP_GRID_TIMELINE);
1612   Position length_pos;
1613   position_from_ticks (&length_pos, ticks);
1614   int width_px =
1615     ui_pos_to_px_timeline (&length_pos, false);
1616 
1617   /* get snapped x */
1618   Position pos;
1619   ui_px_to_pos_timeline (x, &pos, true);
1620   if (!(mask & GDK_SHIFT_MASK))
1621     {
1622       position_snap_simple (
1623         &pos, SNAP_GRID_TIMELINE);
1624       x = ui_pos_to_px_timeline (&pos, true);
1625     }
1626 
1627   int height = TRACK_DEF_HEIGHT;
1628   /* if track, get y/height inside track */
1629   if (track)
1630     {
1631       height = (int) track->main_height;
1632       int track_y_local =
1633         track_widget_get_local_y (
1634           track->widget, self, (int) y);
1635       if (lane)
1636         {
1637           y -= track_y_local - lane->y;
1638           height = (int) lane->height;
1639         }
1640       else
1641         {
1642           y -= track_y_local;
1643         }
1644     }
1645   /* else if no track, get y/height under last
1646    * visible track */
1647   else
1648     {
1649       /* get y below the track */
1650       int y_after_last_track = 0;
1651       for (int i = 0; i < TRACKLIST->num_tracks;
1652            i++)
1653         {
1654           Track * t = TRACKLIST->tracks[i];
1655           if (track_get_should_be_visible (t)
1656               &&
1657               track_is_pinned (t) ==
1658                 self->is_pinned)
1659             {
1660               y_after_last_track +=
1661                 (int)
1662                 track_get_full_visible_height (t);
1663             }
1664         }
1665       y = y_after_last_track;
1666     }
1667 
1668   GdkRectangle highlight_rect = {
1669     x, y, width_px, height };
1670   arranger_widget_set_highlight_rect (
1671     self, &highlight_rect);
1672 }
1673 
1674 static void
on_dnd_data_received(GtkWidget * widget,GdkDragContext * context,gint x,gint y,GtkSelectionData * data,guint info,guint time,ArrangerWidget * self)1675 on_dnd_data_received (
1676   GtkWidget        * widget,
1677   GdkDragContext   * context,
1678   gint               x,
1679   gint               y,
1680   GtkSelectionData * data,
1681   guint              info,
1682   guint              time,
1683   ArrangerWidget *   self)
1684 {
1685   GdkAtom target =
1686     gtk_selection_data_get_target (data);
1687   char * target_name = gdk_atom_name (target);
1688 
1689   Track * track =
1690     timeline_arranger_widget_get_track_at_y (
1691       self, y);
1692   TrackLane * lane =
1693     timeline_arranger_widget_get_track_lane_at_y (
1694       self, y);
1695   AutomationTrack * at =
1696     timeline_arranger_widget_get_at_at_y (
1697       self, y);
1698 
1699   GdkModifierType mask;
1700   z_gtk_widget_get_mask (widget, &mask);
1701 
1702   highlight_timeline (
1703     self, mask, x, y, track, lane);
1704 
1705   g_message (
1706     "(%u) dnd data received (timeline - is "
1707     "highlighted %d): %s",
1708     time, self->is_highlighted, target_name);
1709 
1710   /* determine if moving or copying */
1711   GdkDragAction action =
1712     gdk_drag_context_get_selected_action (
1713       context);
1714 
1715   if (target ==
1716         GET_ATOM (TARGET_ENTRY_CHORD_DESCR) &&
1717       self->is_highlighted && track &&
1718       track_type_has_piano_roll (track->type))
1719     {
1720       ChordDescriptor * descr = NULL;
1721       const guchar * my_data =
1722         gtk_selection_data_get_data (data);
1723       memcpy (&descr, my_data, sizeof (descr));
1724 
1725       /* create chord region */
1726       Position pos, end_pos;
1727       ui_px_to_pos_timeline (
1728         self->highlight_rect.x, &pos, true);
1729       ui_px_to_pos_timeline (
1730         self->highlight_rect.x +
1731           self->highlight_rect.width,
1732         &end_pos, true);
1733       int lane_pos =
1734         lane ? lane->pos :
1735         (track->num_lanes == 1 ?
1736          0 : track->num_lanes - 2);
1737       int idx_in_lane =
1738         track->lanes[lane_pos]->num_regions;
1739       ZRegion * region =
1740         midi_region_new_from_chord_descr (
1741           &pos, descr,
1742           track_get_name_hash (track),
1743           lane_pos, idx_in_lane);
1744       track_add_region (
1745         track, region, NULL, lane_pos,
1746         F_GEN_NAME, F_PUBLISH_EVENTS);
1747       arranger_object_select (
1748         (ArrangerObject *) region, F_SELECT,
1749         F_NO_APPEND,
1750         F_NO_PUBLISH_EVENTS);
1751 
1752       GError * err = NULL;
1753       bool ret =
1754         arranger_selections_action_perform_create (
1755           TL_SELECTIONS, &err);
1756       if (!ret)
1757         {
1758           HANDLE_ERROR (
1759             err, "%s",
1760             _("Failed to create selections"));
1761         }
1762     }
1763   else if (target ==
1764              GET_ATOM (TARGET_ENTRY_URI_LIST) ||
1765            target ==
1766              GET_ATOM (TARGET_ENTRY_SUPPORTED_FILE))
1767     {
1768       if (at)
1769         {
1770           /* nothing to do */
1771           goto finish_data_received;
1772         }
1773 
1774       SupportedFile * file = NULL;
1775       char ** uris = NULL;
1776       if (target ==
1777             GET_ATOM (TARGET_ENTRY_SUPPORTED_FILE))
1778         {
1779           const guchar *my_data =
1780             gtk_selection_data_get_data (data);
1781           memcpy (&file, my_data, sizeof (file));
1782         }
1783       else
1784         {
1785           uris = gtk_selection_data_get_uris (data);
1786         }
1787 
1788       Position pos;
1789       ui_px_to_pos_timeline (
1790         self->highlight_rect.x, &pos, true);
1791       tracklist_handle_file_drop (
1792         TRACKLIST, uris, file, track, lane, &pos,
1793         true);
1794     }
1795 
1796   if (action == GDK_ACTION_COPY)
1797     {
1798     }
1799   else if (action == GDK_ACTION_MOVE)
1800     {
1801     }
1802 
1803 finish_data_received:
1804   arranger_widget_set_highlight_rect (self, NULL);
1805 }
1806 
1807 static gboolean
on_dnd_motion(GtkWidget * widget,GdkDragContext * context,gint x,gint y,guint time,ArrangerWidget * self)1808 on_dnd_motion (
1809   GtkWidget      * widget,
1810   GdkDragContext * context,
1811   gint             x,
1812   gint             y,
1813   guint            time,
1814   ArrangerWidget * self)
1815 {
1816   AutomationTrack * at =
1817     timeline_arranger_widget_get_at_at_y (self, y);
1818   TrackLane * lane =
1819     timeline_arranger_widget_get_track_lane_at_y (
1820       self, y);
1821   (void) lane;
1822   Track * track =
1823     timeline_arranger_widget_get_track_at_y (
1824       self, y);
1825 
1826   GdkModifierType mask;
1827   z_gtk_widget_get_mask (widget, &mask);
1828 
1829   arranger_widget_set_highlight_rect (self, NULL);
1830 
1831   GdkAtom target =
1832     gtk_drag_dest_find_target (
1833       widget, context, NULL);
1834   if (target == GDK_NONE)
1835     {
1836       g_message ("target is none");
1837       gdk_drag_status (context, 0, time);
1838     }
1839   else if (target ==
1840              GET_ATOM (TARGET_ENTRY_CHORD_DESCR))
1841     {
1842       if (at || !track ||
1843           !track_type_has_piano_roll (track->type))
1844         {
1845           /* nothing to do */
1846           return false;
1847         }
1848 
1849       /* highlight track */
1850       highlight_timeline (
1851         self, mask, x, y, track, lane);
1852 
1853       return true;
1854     }
1855   else if (target ==
1856              GET_ATOM (TARGET_ENTRY_URI_LIST) ||
1857            target ==
1858              GET_ATOM (TARGET_ENTRY_SUPPORTED_FILE))
1859     {
1860       /* if current track exists and current track
1861        * supports dnd highlight */
1862       if (track)
1863         {
1864           if (track->type != TRACK_TYPE_MIDI &&
1865                track->type !=
1866                  TRACK_TYPE_INSTRUMENT &&
1867               track->type != TRACK_TYPE_AUDIO)
1868             {
1869               return false;
1870             }
1871 
1872           /* track is compatible, highlight */
1873           highlight_timeline (
1874             self, mask, x, y, track, lane);
1875           g_message ("highlighting track");
1876 
1877           return true;
1878         }
1879       /* else if no track, highlight below the
1880        * last track  TODO */
1881       else
1882         {
1883           highlight_timeline (
1884             self, mask, x, y, NULL, NULL);
1885         }
1886     }
1887   else
1888     {
1889     }
1890 
1891   return true;
1892 }
1893 
1894 static void
on_dnd_leave(GtkWidget * widget,GdkDragContext * context,guint time,ArrangerWidget * self)1895 on_dnd_leave (
1896   GtkWidget      * widget,
1897   GdkDragContext * context,
1898   guint            time,
1899   ArrangerWidget * self)
1900 {
1901   g_message (
1902     "(%u) dnd leaving timeline, unhighlighting "
1903     "rect",
1904     time);
1905 
1906   arranger_widget_set_highlight_rect (self, NULL);
1907 }
1908 
1909 /**
1910  * Sets up the timeline arranger as a drag dest.
1911  */
1912 void
timeline_arranger_setup_drag_dest(ArrangerWidget * self)1913 timeline_arranger_setup_drag_dest (
1914   ArrangerWidget * self)
1915 {
1916   /* set as drag dest */
1917   GtkTargetEntry entries[] = {
1918     {
1919       (char *) TARGET_ENTRY_CHORD_DESCR,
1920       GTK_TARGET_SAME_APP,
1921       symap_map (ZSYMAP, TARGET_ENTRY_CHORD_DESCR),
1922     },
1923     {
1924       (char *) TARGET_ENTRY_SUPPORTED_FILE,
1925       GTK_TARGET_SAME_APP,
1926       symap_map (
1927         ZSYMAP, TARGET_ENTRY_SUPPORTED_FILE),
1928     },
1929     {
1930       (char *) TARGET_ENTRY_URI_LIST,
1931       GTK_TARGET_SAME_APP,
1932       symap_map (ZSYMAP, TARGET_ENTRY_URI_LIST),
1933     },
1934     {
1935       (char *) TARGET_ENTRY_URI_LIST,
1936       GTK_TARGET_OTHER_APP,
1937       symap_map (ZSYMAP, TARGET_ENTRY_URI_LIST),
1938     },
1939   };
1940   gtk_drag_dest_set (
1941     GTK_WIDGET (self),
1942     GTK_DEST_DEFAULT_MOTION |
1943       GTK_DEST_DEFAULT_DROP,
1944     entries, G_N_ELEMENTS (entries),
1945     GDK_ACTION_MOVE | GDK_ACTION_COPY);
1946 
1947   g_signal_connect (
1948     GTK_WIDGET (self), "drag-data-received",
1949     G_CALLBACK (on_dnd_data_received), self);
1950   g_signal_connect (
1951     GTK_WIDGET (self), "drag-motion",
1952     G_CALLBACK (on_dnd_motion), self);
1953   g_signal_connect (
1954     GTK_WIDGET (self), "drag-leave",
1955     G_CALLBACK (on_dnd_leave), self);
1956 }
1957