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