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 "audio/automation_region.h"
21 #include "audio/chord_track.h"
22 #include "audio/fade.h"
23 #include "audio/marker_track.h"
24 #include "gui/backend/arranger_object.h"
25 #include "gui/widgets/arranger_object.h"
26 #include "gui/widgets/arranger.h"
27 #include "gui/widgets/automation_point.h"
28 #include "gui/widgets/bot_dock_edge.h"
29 #include "gui/widgets/center_dock.h"
30 #include "gui/widgets/chord_editor_space.h"
31 #include "gui/widgets/chord_object.h"
32 #include "gui/widgets/chord_region.h"
33 #include "gui/widgets/clip_editor.h"
34 #include "gui/widgets/clip_editor_inner.h"
35 #include "gui/widgets/main_notebook.h"
36 #include "gui/widgets/marker.h"
37 #include "gui/widgets/midi_arranger.h"
38 #include "gui/widgets/midi_editor_space.h"
39 #include "gui/widgets/midi_modifier_arranger.h"
40 #include "gui/widgets/midi_note.h"
41 #include "gui/widgets/piano_roll_keys.h"
42 #include "gui/widgets/region.h"
43 #include "gui/widgets/scale_object.h"
44 #include "gui/widgets/timeline_arranger.h"
45 #include "gui/widgets/timeline_panel.h"
46 #include "gui/widgets/track.h"
47 #include "gui/widgets/velocity.h"
48 #include "project.h"
49 #include "utils/cairo.h"
50 #include "utils/flags.h"
51 #include "utils/math.h"
52 #include "utils/ui.h"
53 #include "zrythm_app.h"
54 
55 #include <glib/gi18n-lib.h>
56 
57 #define TYPE(x) \
58   ARRANGER_OBJECT_TYPE_##x
59 
60 /**
61  * Queues a redraw in the area covered by this
62  * object.
63  */
64 void
arranger_object_queue_redraw(ArrangerObject * self)65 arranger_object_queue_redraw (
66   ArrangerObject * self)
67 {
68   g_return_if_fail (IS_ARRANGER_OBJECT (self));
69 
70   ArrangerWidget * arranger =
71     arranger_object_get_arranger (self);
72   g_return_if_fail (arranger);
73   GdkRectangle arranger_rect;
74   arranger_widget_get_visible_rect (
75     arranger, &arranger_rect);
76 
77   /* if arranger is not visible ignore */
78   if (arranger_rect.width < 2 &&
79       arranger_rect.height < 2)
80     {
81 #if 0
82       arranger_object_print (self);
83       g_message (
84         "%s: arranger not visible, ignoring",
85         __func__);
86 #endif
87       return;
88     }
89 
90   /* set rectangle if not initialized yet */
91   if (self->full_rect.width == 0 &&
92       self->full_rect.height == 0)
93     arranger_object_set_full_rectangle (
94       self, arranger);
95 
96   GdkRectangle full_rect = self->full_rect;
97 
98   /* add some padding to the full rect for any
99    * effects or things like automation points */
100   static const int padding = 6;
101   if (self->type == TYPE (AUTOMATION_POINT))
102     {
103       full_rect.x = MAX (full_rect.x - padding, 0);
104       full_rect.y = MAX (full_rect.y - padding, 0);
105       full_rect.width += padding * 2;
106       full_rect.height += padding * 2;
107     }
108 
109   GdkRectangle draw_rect;
110   int draw_rect_visible =
111     arranger_object_get_draw_rectangle (
112       self, &arranger_rect, &full_rect,
113       &draw_rect);
114 
115   /* add some padding to the resulting draw rect */
116   draw_rect.x = MAX (full_rect.x - padding, 0);
117   draw_rect.y = MAX (full_rect.y - padding, 0);
118   draw_rect.width += padding * 2;
119   draw_rect.height += padding * 2;
120 
121   /* if velocity, add more padding for text */
122   if (self->type == ARRANGER_OBJECT_TYPE_VELOCITY)
123     {
124       draw_rect.width += 16;
125     }
126 
127   /* if draw rect is not visible ignore */
128   if (!draw_rect_visible)
129     {
130 #if 0
131       arranger_object_print (self);
132       g_message (
133         "%s: draw rect not visible, ignoring",
134         __func__);
135 #endif
136       return;
137     }
138 
139   arranger_widget_redraw_rectangle (
140     arranger, &draw_rect);
141 
142   /* if region and lanes are visible, redraw
143    * lane too */
144   if (self->type == TYPE (REGION))
145     {
146       Track * track =
147         arranger_object_get_track (self);
148       if (track->lanes_visible &&
149           arranger_object_can_have_lanes (self))
150         {
151           ZRegion * r = (ZRegion *) self;
152           region_get_lane_full_rect (
153             r, &full_rect);
154           arranger_object_get_draw_rectangle (
155             self, &arranger_rect, &full_rect,
156             &draw_rect);
157           arranger_widget_redraw_rectangle (
158             arranger, &draw_rect);
159         }
160     }
161 }
162 
163 /**
164  * Returns if the current position is for resizing
165  * L.
166  *
167  * @param x X in local coordinates.
168  */
169 bool
arranger_object_is_resize_l(ArrangerObject * self,const int x)170 arranger_object_is_resize_l (
171   ArrangerObject * self,
172   const int        x)
173 {
174   if (!arranger_object_type_has_length (self->type))
175     return 0;
176 
177   if (x < UI_RESIZE_CURSOR_SPACE)
178     {
179       return 1;
180     }
181   return 0;
182 }
183 
184 /**
185  * Returns if the current position is for resizing
186  * R.
187  *
188  * @param x X in local coordinates.
189  */
190 bool
arranger_object_is_resize_r(ArrangerObject * self,const int x)191 arranger_object_is_resize_r (
192   ArrangerObject * self,
193   const int        x)
194 {
195   if (!arranger_object_type_has_length (self->type))
196     return 0;
197 
198   long size_frames =
199     self->end_pos.frames - self->pos.frames;
200   Position pos;
201   position_from_frames (
202     &pos, size_frames);
203   int width_px =
204     arranger_widget_pos_to_px (
205       arranger_object_get_arranger (self),
206       &pos, 0);
207 
208   if (x > width_px - UI_RESIZE_CURSOR_SPACE)
209     {
210       return 1;
211     }
212   return 0;
213 }
214 
215 /**
216  * Returns if the current position is for moving the
217  * fade in/out mark (timeline only).
218  *
219  * @param in True for fade in, false for fade out.
220  * @param x X in local coordinates.
221  * @param only_handle Whether to only check if this
222  *   is inside the fade handle. If this is false,
223  *   \ref only_outer will be considered.
224  * @param only_outer Whether to only check if this
225  *   is inside the fade's outter (unplayed) region.
226  *   If this is false, the whole fade area will
227  *   be considered.
228  * @param check_lane Whether to check the lane
229  *   region instead of the main one (if region).
230  */
231 bool
arranger_object_is_fade(ArrangerObject * self,bool in,const int x,int y,bool only_handle,bool only_outer,bool check_lane)232 arranger_object_is_fade (
233   ArrangerObject * self,
234   bool             in,
235   const int        x,
236   int              y,
237   bool             only_handle,
238   bool             only_outer,
239   bool             check_lane)
240 {
241   if (!arranger_object_can_fade (self))
242     return false;
243 
244   const int fade_in_px =
245     ui_pos_to_px_timeline (&self->fade_in_pos, 0);
246   const int fade_out_px =
247     ui_pos_to_px_timeline (&self->fade_out_pos, 0);
248   const int fade_pt_halfwidth =
249     ARRANGER_OBJECT_FADE_POINT_HALFWIDTH;
250   GdkRectangle full_rect;
251   Track * track = arranger_object_get_track (self);
252   if (check_lane && track->lanes_visible)
253     {
254       ZRegion * r = (ZRegion *) self;
255       region_get_lane_full_rect (r, &full_rect);
256       y -= full_rect.y - self->full_rect.y;
257     }
258   else
259     {
260       full_rect = self->full_rect;
261     }
262 
263   bool ret = false;
264   if (only_handle)
265     {
266       if (in)
267         {
268           ret =
269             x >= fade_in_px - fade_pt_halfwidth &&
270             x <= fade_in_px + fade_pt_halfwidth &&
271             y <= fade_pt_halfwidth;
272         }
273       else
274         {
275           ret =
276             x >= fade_out_px - fade_pt_halfwidth &&
277             x <= fade_out_px + fade_pt_halfwidth &&
278             y <= fade_pt_halfwidth;
279         }
280     }
281   else if (only_outer)
282     {
283       if (in)
284         {
285           ret =
286             x <= fade_in_px && fade_in_px > 0 &&
287             (double) y <=
288               full_rect.height *
289               (1.0 -
290                 fade_get_y_normalized (
291                   (double) x / fade_in_px,
292                   &self->fade_in_opts, 1));
293         }
294       else
295         {
296           ret =
297             x >= fade_out_px &&
298             full_rect.width -
299               fade_out_px > 0 &&
300             (double) y <=
301               full_rect.height *
302               (1.0 -
303                 fade_get_y_normalized (
304                   (double) (x - fade_out_px) /
305                     (full_rect.width -
306                        fade_out_px),
307                   &self->fade_out_opts, 0));
308         }
309     }
310   else
311     {
312       if (in)
313         {
314           ret = x <= fade_in_px;
315         }
316       else
317         {
318           ret = x >= fade_out_px;
319         }
320     }
321 
322   return ret;
323 }
324 
325 /**
326  * Returns if the current position is for resizing
327  * up (eg, Velocity).
328  *
329  * @param x X in local coordinates.
330  * @param y Y in local coordinates.
331  */
332 bool
arranger_object_is_resize_up(ArrangerObject * self,const int x,const int y)333 arranger_object_is_resize_up (
334   ArrangerObject * self,
335   const int        x,
336   const int        y)
337 {
338   if (self->type == ARRANGER_OBJECT_TYPE_VELOCITY)
339     {
340       if (y < VELOCITY_RESIZE_THRESHOLD)
341         return 1;
342     }
343   else if (self->type ==
344              ARRANGER_OBJECT_TYPE_AUTOMATION_POINT)
345     {
346       AutomationPoint * ap =
347         (AutomationPoint*) self;
348       int curve_up =
349         automation_point_curves_up (ap);
350       if (curve_up)
351         {
352           if (x > AP_WIDGET_POINT_SIZE ||
353               self->full_rect.height - y >
354                 AP_WIDGET_POINT_SIZE)
355             return 1;
356         }
357       else
358         {
359           if (x > AP_WIDGET_POINT_SIZE ||
360               y > AP_WIDGET_POINT_SIZE)
361             return 1;
362         }
363     }
364   return 0;
365 }
366 
367 /**
368  * Returns if the current position is for resizing
369  * loop.
370  *
371  * @param y Y in local coordinates.
372  */
373 bool
arranger_object_is_resize_loop(ArrangerObject * self,const int y)374 arranger_object_is_resize_loop (
375   ArrangerObject * self,
376   const int        y)
377 {
378   if (!arranger_object_type_has_length (
379         self->type) ||
380       !arranger_object_type_can_loop (
381         self->type))
382     return 0;
383 
384   if (self->type == ARRANGER_OBJECT_TYPE_REGION)
385     {
386       ZRegion * r = (ZRegion *) self;
387       if (r->id.type == REGION_TYPE_AUDIO &&
388           P_TOOL != TOOL_SELECT_STRETCH)
389         {
390           return 1;
391         }
392 
393       if (region_is_looped (r))
394         {
395           return 1;
396         }
397 
398       /* TODO */
399       int height_px = 60;
400         /*gtk_widget_get_allocated_height (*/
401           /*GTK_WIDGET (self));*/
402 
403       if (y > height_px / 2)
404         {
405           return 1;
406         }
407       return 0;
408     }
409 
410   return 0;
411 }
412 
413 /**
414  * Returns if the current position is for renaming
415  * the object.
416  *
417  * @param x X in local coordinates.
418  * @param y Y in local coordinates.
419  */
420 bool
arranger_object_is_rename(ArrangerObject * self,const int x,const int y)421 arranger_object_is_rename (
422   ArrangerObject * self,
423   const int        x,
424   const int        y)
425 {
426   /* disable for now */
427   return false;
428 
429   if (self->type != ARRANGER_OBJECT_TYPE_REGION)
430     return false;
431 
432   GdkRectangle rect = self->last_name_rect;
433   /* make the clickable height area a little
434    * smaller */
435   rect.height = MAX (1, rect.height - 4);
436   if (ui_is_point_in_rect_hit (
437         &rect, true, true, x, y, 0, 0))
438     {
439       return true;
440     }
441 
442   return false;
443 }
444 
445 /**
446  * Returns if arranger_object widgets should show
447  * cut lines.
448  *
449  * To be used to set the arranger_object's
450  * "show_cut".
451  *
452  * @param alt_pressed Whether alt is currently
453  *   pressed.
454  */
455 bool
arranger_object_should_show_cut_lines(ArrangerObject * self,bool alt_pressed)456 arranger_object_should_show_cut_lines (
457   ArrangerObject * self,
458   bool             alt_pressed)
459 {
460   if (!arranger_object_type_has_length (self->type))
461     return 0;
462 
463   switch (P_TOOL)
464     {
465     case TOOL_SELECT_NORMAL:
466     case TOOL_SELECT_STRETCH:
467       if (alt_pressed)
468         return 1;
469       else
470         return 0;
471       break;
472     case TOOL_CUT:
473       return 1;
474       break;
475     default:
476       return 0;
477       break;
478     }
479   g_return_val_if_reached (-1);
480 }
481 
482 /**
483  * Returns Y in pixels from the value based on the
484  * allocation of the arranger.
485  */
486 static int
get_automation_point_y(AutomationPoint * ap,ArrangerWidget * arranger)487 get_automation_point_y (
488   AutomationPoint * ap,
489   ArrangerWidget *  arranger)
490 {
491   /* ratio of current value in the range */
492   float ap_ratio = ap->normalized_val;
493 
494   int allocated_h =
495     gtk_widget_get_allocated_height (
496       GTK_WIDGET (arranger));
497   int point =
498     allocated_h -
499     (int) (ap_ratio * (float) allocated_h);
500   return point;
501 }
502 
503 /**
504  * Gets the full rectangle for a linked object.
505  */
506 int
arranger_object_get_full_rect_x_for_region_child(ArrangerObject * self,ZRegion * region,GdkRectangle * full_rect)507 arranger_object_get_full_rect_x_for_region_child (
508   ArrangerObject * self,
509   ZRegion *        region,
510   GdkRectangle *   full_rect)
511 {
512   g_return_val_if_fail (region, 0);
513   ArrangerObject * region_obj =
514     (ArrangerObject *) region;
515 
516   double region_start_ticks =
517     region_obj->pos.ticks;
518   Position tmp;
519 
520   /* use absolute position */
521   position_from_ticks (
522     &tmp,
523     region_start_ticks +
524       self->pos.ticks);
525   return ui_pos_to_px_editor (&tmp, 1);
526 }
527 
528 /**
529  */
530 void
arranger_object_set_full_rectangle(ArrangerObject * self,ArrangerWidget * arranger)531 arranger_object_set_full_rectangle (
532   ArrangerObject * self,
533   ArrangerWidget * arranger)
534 {
535   g_return_if_fail (
536     Z_IS_ARRANGER_WIDGET (arranger) &&
537     IS_ARRANGER_OBJECT (self));
538 
539 #define WARN_IF_HAS_NEGATIVE_DIMENSIONS \
540   if (self->full_rect.x < 0) \
541     { \
542       int diff = - self->full_rect.x; \
543       self->full_rect.x = 0; \
544       self->full_rect.width -= diff; \
545       if (self->full_rect.width < 1) \
546         { \
547           self->full_rect.width = 1; \
548         } \
549     } \
550   g_warn_if_fail ( \
551     self->full_rect.x >= 0 && \
552     self->full_rect.y >= 0 && \
553     self->full_rect.width >= 0 && \
554     self->full_rect.height >= 0)
555 
556   switch (self->type)
557     {
558     case TYPE (CHORD_OBJECT):
559       {
560         ChordObject * co = (ChordObject *) self;
561         ChordDescriptor * descr =
562           chord_object_get_chord_descriptor (co);
563 
564         ZRegion * region =
565           arranger_object_get_region (self);
566         ArrangerObject * region_obj =
567           (ArrangerObject *) region;
568 
569         double region_start_ticks =
570           region_obj->pos.ticks;
571         Position tmp;
572         int adj_px_per_key =
573           chord_editor_space_widget_get_chord_height (
574             MW_CHORD_EDITOR_SPACE) + 1;
575 
576         /* use absolute position */
577         position_from_ticks (
578           &tmp,
579           region_start_ticks +
580           self->pos.ticks);
581         self->full_rect.x =
582           ui_pos_to_px_editor (&tmp, 1);
583         self->full_rect.y =
584           adj_px_per_key * co->chord_index;
585 
586         char chord_str[100];
587         chord_descriptor_to_string (
588           descr, chord_str);
589 
590         chord_object_recreate_pango_layouts (
591           (ChordObject *) self);
592         self->full_rect.width =
593           self->textw +
594           CHORD_OBJECT_WIDGET_TRIANGLE_W +
595           Z_CAIRO_TEXT_PADDING * 2;
596 
597         self->full_rect.width =
598           self->textw +
599           CHORD_OBJECT_WIDGET_TRIANGLE_W +
600           Z_CAIRO_TEXT_PADDING * 2;
601 
602         self->full_rect.height = adj_px_per_key;
603 
604         WARN_IF_HAS_NEGATIVE_DIMENSIONS;
605       }
606       break;
607     case TYPE (AUTOMATION_POINT):
608       {
609         AutomationPoint * ap =
610           (AutomationPoint *) self;
611         ZRegion * region =
612           arranger_object_get_region (self);
613         ArrangerObject * region_obj =
614           (ArrangerObject *) region;
615 
616         /* use absolute position */
617         double region_start_ticks =
618           region_obj->pos.ticks;
619         Position tmp;
620         position_from_ticks (
621           &tmp,
622           region_start_ticks +
623           self->pos.ticks);
624         self->full_rect.x =
625           ui_pos_to_px_editor (&tmp, 1) -
626             AP_WIDGET_POINT_SIZE / 2;
627 
628         AutomationPoint * next_ap =
629           automation_region_get_next_ap (
630             region, ap, true,
631             arranger->action ==
632               UI_OVERLAY_ACTION_MOVING_COPY);
633 
634         if (next_ap)
635           {
636             ArrangerObject * next_obj =
637               (ArrangerObject *) next_ap;
638 
639             /* get relative position from the
640              * start AP to the next ap. */
641             position_from_ticks (
642               &tmp,
643               next_obj->pos.ticks -
644                 self->pos.ticks);
645 
646             /* width is the relative position in px
647              * plus half an AP_WIDGET_POINT_SIZE for
648              * each side */
649             self->full_rect.width =
650               AP_WIDGET_POINT_SIZE +
651               ui_pos_to_px_editor (&tmp, 0);
652 
653             g_warn_if_fail (
654               self->full_rect.width >= 0);
655 
656             int cur_y =
657               get_automation_point_y (ap, arranger);
658             int next_y =
659               get_automation_point_y (
660                 next_ap, arranger);
661 
662             self->full_rect.y =
663               MAX (
664                 (cur_y > next_y ? next_y : cur_y) -
665                   AP_WIDGET_POINT_SIZE / 2,
666                 0);
667 
668             /* make sure y is not negative */
669 
670             /* height is the relative relative diff in
671              * px between the two points plus half an
672              * AP_WIDGET_POINT_SIZE for each side */
673             self->full_rect.height =
674               (cur_y > next_y ?
675                cur_y - next_y :
676                next_y - cur_y) + AP_WIDGET_POINT_SIZE;
677             WARN_IF_HAS_NEGATIVE_DIMENSIONS;
678           }
679         else
680           {
681             self->full_rect.y =
682               MAX (
683                 get_automation_point_y (
684                    ap, arranger) -
685                 AP_WIDGET_POINT_SIZE / 2, 0);
686 
687             self->full_rect.width =
688               AP_WIDGET_POINT_SIZE;
689             self->full_rect.height =
690               AP_WIDGET_POINT_SIZE;
691 
692             WARN_IF_HAS_NEGATIVE_DIMENSIONS;
693           }
694         }
695       break;
696     case TYPE (REGION):
697       {
698         ZRegion * region =
699           (ZRegion *) self;
700         Track * track =
701           arranger_object_get_track (self);
702 
703         if (!track->widget)
704           track->widget = track_widget_new (track);
705 
706         self->full_rect.x =
707           ui_pos_to_px_timeline (
708             &self->pos, true);
709         Position tmp;
710         position_from_ticks (
711           &tmp,
712           self->end_pos.ticks -
713             self->pos.ticks);
714         self->full_rect.width =
715           ui_pos_to_px_timeline (
716             &tmp, false) - 1;
717         if (self->full_rect.width < 1)
718           {
719             self->full_rect.width = 1;
720           }
721 
722         gint wx, wy;
723 
724         gtk_widget_translate_coordinates(
725           (GtkWidget *) (track->widget),
726           (GtkWidget *) (arranger),
727           0, 0,
728           &wx, &wy);
729         /* for some reason it returns a few
730          * negatives at first */
731         if (wy < 0)
732           wy = 0;
733 
734         if (region->id.type == REGION_TYPE_CHORD)
735           {
736             chord_region_recreate_pango_layouts (
737               region);
738 
739             self->full_rect.y = wy;
740             /* full height minus the space the
741              * scales would require, plus some
742              * padding */
743             self->full_rect.height =
744               (int) track->main_height -
745                 (self->texth +
746                  Z_CAIRO_TEXT_PADDING * 4);
747 
748             WARN_IF_HAS_NEGATIVE_DIMENSIONS;
749           }
750         else if (region->id.type ==
751                    REGION_TYPE_AUTOMATION)
752           {
753             AutomationTrack * at =
754               region_get_automation_track (region);
755             g_return_if_fail (at);
756             if (!at->created ||
757                 !track->automation_visible)
758               return;
759 
760             gtk_widget_translate_coordinates(
761               (GtkWidget *) (track->widget),
762               (GtkWidget *) (arranger),
763               0, 0, &wx, &wy);
764             /* for some reason it returns a few
765              * negatives at first */
766             if (wy < 0)
767               wy = 0;
768 
769             self->full_rect.y =
770               wy + at->y;
771             self->full_rect.height =
772               (int) at->height;
773 
774             WARN_IF_HAS_NEGATIVE_DIMENSIONS;
775           }
776         else
777           {
778             self->full_rect.y = wy;
779             self->full_rect.height =
780               (int) track->main_height;
781 
782             WARN_IF_HAS_NEGATIVE_DIMENSIONS;
783           }
784         /* leave some space for the line below
785          * the region */
786         self->full_rect.height--;
787       }
788       break;
789     case TYPE (MIDI_NOTE):
790       {
791         MidiNote * mn = (MidiNote *) self;
792         ZRegion * region =
793           arranger_object_get_region (self);
794         g_return_if_fail (region);
795         ArrangerObject * region_obj =
796           (ArrangerObject *) region;
797         Track * track =
798           arranger_object_get_track (self);
799 
800         double region_start_ticks =
801           region_obj->pos.ticks;
802         Position tmp;
803         double adj_px_per_key =
804           MW_PIANO_ROLL_KEYS->px_per_key + 1.0;
805 
806         /* use absolute position */
807         position_from_ticks (
808           &tmp,
809           region_start_ticks +
810             self->pos.ticks);
811         self->full_rect.x =
812           ui_pos_to_px_editor (&tmp, 1);
813         const MidiNoteDescriptor * descr =
814           piano_roll_find_midi_note_descriptor_by_val (
815            PIANO_ROLL, track->drum_mode, mn->val);
816         self->full_rect.y =
817           (int)
818           (adj_px_per_key * (127 - descr->value));
819 
820         self->full_rect.height =
821           (int) adj_px_per_key;
822         if (track->drum_mode)
823           {
824             self->full_rect.width =
825               self->full_rect.height;
826             self->full_rect.x -=
827               self->full_rect.width / 2;
828 
829             /*WARN_IF_HAS_NEGATIVE_DIMENSIONS;*/
830           }
831         else
832           {
833             /* use absolute position */
834             position_from_ticks (
835               &tmp,
836               region_start_ticks +
837               self->end_pos.ticks);
838             self->full_rect.width =
839               ui_pos_to_px_editor (
840                 &tmp, 1) - self->full_rect.x;
841 
842             WARN_IF_HAS_NEGATIVE_DIMENSIONS;
843           }
844       }
845       break;
846     case TYPE (SCALE_OBJECT):
847       {
848         Track * track = P_CHORD_TRACK;
849 
850         gint wx, wy;
851         gtk_widget_translate_coordinates(
852           (GtkWidget *) (track->widget),
853           (GtkWidget *) (arranger),
854           0, 0,
855           &wx, &wy);
856         /* for some reason it returns a few
857          * negatives at first */
858         if (wy < 0)
859           wy = 0;
860 
861         self->full_rect.x =
862           ui_pos_to_px_timeline (
863             &self->pos, 1);
864         scale_object_recreate_pango_layouts (
865           (ScaleObject *) self);
866         self->full_rect.width =
867           self->textw +
868           SCALE_OBJECT_WIDGET_TRIANGLE_W +
869           Z_CAIRO_TEXT_PADDING * 2;
870 
871         int obj_height =
872           self->texth + Z_CAIRO_TEXT_PADDING * 2;
873         self->full_rect.y =
874           (wy + (int) track->main_height) -
875             obj_height;
876         self->full_rect.height = obj_height;
877 
878         WARN_IF_HAS_NEGATIVE_DIMENSIONS;
879       }
880       break;
881     case TYPE (MARKER):
882       {
883         Track * track = P_MARKER_TRACK;
884 
885         gint wx, wy;
886         gtk_widget_translate_coordinates(
887           (GtkWidget *) (track->widget),
888           (GtkWidget *) (arranger),
889           0, 0,
890           &wx, &wy);
891         /* for some reason it returns a few
892          * negatives at first */
893         if (wy < 0)
894           wy = 0;
895 
896         self->full_rect.x =
897           ui_pos_to_px_timeline (
898             &self->pos, 1);
899         marker_recreate_pango_layouts (
900           (Marker *) self);
901         self->full_rect.width =
902           self->textw + MARKER_WIDGET_TRIANGLE_W +
903           Z_CAIRO_TEXT_PADDING * 2;
904 
905         int global_y_start =
906           wy + (int) track->main_height;
907         int obj_height =
908           MIN (
909             (int) track->main_height,
910             self->texth + Z_CAIRO_TEXT_PADDING * 2);
911         self->full_rect.y =
912           global_y_start - obj_height;
913         self->full_rect.height = obj_height;
914 
915         WARN_IF_HAS_NEGATIVE_DIMENSIONS;
916       }
917       break;
918     case TYPE (VELOCITY):
919       {
920         /* use transient or non transient note
921          * depending on which is visible */
922         Velocity * vel = (Velocity *) self;
923         MidiNote * mn =
924           velocity_get_midi_note (vel);
925         g_return_if_fail (mn);
926         ArrangerObject * mn_obj =
927           (ArrangerObject *) mn;
928         ZRegion * region =
929           arranger_object_get_region (mn_obj);
930         g_return_if_fail (region);
931         ArrangerObject * region_obj =
932           (ArrangerObject *) region;
933 
934         /* use absolute position */
935         double region_start_ticks =
936           region_obj->pos.ticks;
937         Position tmp;
938         position_from_ticks (
939           &tmp,
940           region_start_ticks +
941           mn_obj->pos.ticks);
942         self->full_rect.x =
943           ui_pos_to_px_editor (&tmp, 1);
944 
945         /* adjust x to start before the MIDI note
946          * so that velocity appears centered at
947          * start of MIDI note */
948         self->full_rect.x -= VELOCITY_WIDTH / 2;
949 
950         int height =
951           gtk_widget_get_allocated_height (
952             GTK_WIDGET (arranger));
953 
954         int vel_px =
955           (int)
956           ((float) height *
957             ((float) vel->vel / 127.f));
958         self->full_rect.y = height - vel_px;
959 
960         self->full_rect.width = VELOCITY_WIDTH;
961         self->full_rect.height = vel_px;
962 
963         WARN_IF_HAS_NEGATIVE_DIMENSIONS;
964 
965         /* adjust for circle radius */
966         self->full_rect.height +=
967           VELOCITY_WIDTH / 2;
968         self->full_rect.y -= VELOCITY_WIDTH / 2;
969       }
970       break;
971     default:
972       g_warn_if_reached ();
973       break;
974     }
975 
976   bool drum_mode = false;
977   if (self->type == TYPE (MIDI_NOTE)
978       || self->type == TYPE (VELOCITY))
979     {
980       Track * track =
981         arranger_object_get_track (self);
982       g_return_if_fail (
983         IS_TRACK_AND_NONNULL (track));
984       drum_mode = track->drum_mode;
985     }
986 
987   if ((self->full_rect.x < 0 &&
988          self->type != TYPE (MIDI_NOTE) &&
989          !drum_mode) ||
990       (self->full_rect.y < 0 &&
991          self->type != TYPE (VELOCITY)) ||
992       (self->full_rect.y < - VELOCITY_WIDTH / 2 &&
993        self->type == TYPE (VELOCITY)) ||
994       self->full_rect.width < 0 ||
995       self->full_rect.height < 0)
996     {
997       g_message ("Object:");
998       arranger_object_print (self);
999       g_warning (
1000         "The full rectangle of widget %p has "
1001         "negative dimensions: (%d,%d) w: %d h: %d. "
1002         "This should not happen. A rendering error "
1003         "is expected to occur.",
1004         self,
1005         self->full_rect.x, self->full_rect.y,
1006         self->full_rect.width,
1007         self->full_rect.height);
1008     }
1009 
1010 #undef WARN_IF_HAS_NEGATIVE_DIMENSIONS
1011 
1012   /* make sure width/height are > 0 */
1013   if (self->full_rect.width < 1)
1014     {
1015       self->full_rect.width = 1;
1016     }
1017   if (self->full_rect.height < 1)
1018     {
1019       self->full_rect.height = 1;
1020     }
1021 }
1022 
1023 /**
1024  * Gets the draw rectangle based on the given
1025  * full rectangle of the arranger object.
1026  *
1027  * @param parent_rect The current arranger
1028  *   rectangle.
1029  * @param full_rect The object's full rectangle.
1030  *   This will usually be ArrangerObject->full_rect,
1031  *   unless drawing in a lane (for Region's).
1032  * @param draw_rect The rectangle will be set
1033  *   here.
1034  *
1035  * @return Whether the draw rect is visible.
1036  */
1037 int
arranger_object_get_draw_rectangle(ArrangerObject * self,GdkRectangle * parent_rect,GdkRectangle * full_rect,GdkRectangle * draw_rect)1038 arranger_object_get_draw_rectangle (
1039   ArrangerObject * self,
1040   GdkRectangle *   parent_rect,
1041   GdkRectangle *   full_rect,
1042   GdkRectangle *   draw_rect)
1043 {
1044   g_return_val_if_fail (
1045     full_rect->width > 0, false);
1046 
1047   if (!ui_rectangle_overlap (
1048         parent_rect, full_rect))
1049     return 0;
1050 
1051   draw_rect->x =
1052     MAX (full_rect->x, parent_rect->x);
1053   draw_rect->width =
1054     MIN (
1055       (parent_rect->x + parent_rect->width) -
1056         draw_rect->x,
1057       (full_rect->x + full_rect->width) -
1058       draw_rect->x);
1059   g_warn_if_fail (draw_rect->width >= 0);
1060   draw_rect->y =
1061     MAX (full_rect->y, parent_rect->y);
1062   draw_rect->height =
1063     MIN (
1064       (parent_rect->y + parent_rect->height) -
1065         draw_rect->y,
1066       (full_rect->y + full_rect->height) -
1067       draw_rect->y);
1068   g_warn_if_fail (draw_rect->height >= 0);
1069 
1070   return 1;
1071   /*g_message ("full rect: (%d, %d) w: %d h: %d",*/
1072     /*full_rect->x, full_rect->y,*/
1073     /*full_rect->width, full_rect->height);*/
1074   /*g_message ("draw rect: (%d, %d) w: %d h: %d",*/
1075     /*draw_rect->x, draw_rect->y,*/
1076     /*draw_rect->width, draw_rect->height);*/
1077 }
1078 
1079 /**
1080  * Draws the given object.
1081  *
1082  * To be called from the arranger's draw callback.
1083  *
1084  * @param cr Cairo context of the arranger.
1085  * @param rect Rectangle in the arranger.
1086  */
1087 void
arranger_object_draw(ArrangerObject * self,ArrangerWidget * arranger,cairo_t * cr,GdkRectangle * rect)1088 arranger_object_draw (
1089   ArrangerObject * self,
1090   ArrangerWidget * arranger,
1091   cairo_t *        cr,
1092   GdkRectangle *   rect)
1093 {
1094 #if 0
1095   if (!ui_rectangle_overlap (
1096         &self->full_rect, rect))
1097     {
1098       g_debug (
1099         "%s: object not visible, skipping",
1100         __func__);
1101     }
1102 #endif
1103 
1104   switch (self->type)
1105     {
1106     case TYPE (AUTOMATION_POINT):
1107       automation_point_draw (
1108         (AutomationPoint *) self, cr, rect,
1109         arranger->ap_layout);
1110       break;
1111     case TYPE (REGION):
1112       region_draw (
1113         (ZRegion *) self, cr, rect);
1114       break;
1115     case TYPE (MIDI_NOTE):
1116       midi_note_draw (
1117         (MidiNote *) self, cr, rect);
1118       break;
1119     case TYPE (MARKER):
1120       marker_draw (
1121         (Marker *) self, cr, rect);
1122       break;
1123     case TYPE (SCALE_OBJECT):
1124       scale_object_draw (
1125         (ScaleObject *) self, cr, rect);
1126       break;
1127     case TYPE (CHORD_OBJECT):
1128       chord_object_draw (
1129         (ChordObject *) self, cr, rect);
1130       break;
1131     case TYPE (VELOCITY):
1132       velocity_draw (
1133         (Velocity *) self, cr, rect);
1134       break;
1135     default:
1136       g_warn_if_reached ();
1137       break;
1138     }
1139 }
1140