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 <math.h>
21 
22 #include "actions/actions.h"
23 #include "audio/position.h"
24 #include "audio/tempo_track.h"
25 #include "audio/transport.h"
26 #include "gui/backend/event.h"
27 #include "gui/backend/event_manager.h"
28 #include "gui/backend/timeline.h"
29 #include "gui/widgets/arranger.h"
30 #include "gui/widgets/bot_bar.h"
31 #include "gui/widgets/bot_dock_edge.h"
32 #include "gui/widgets/center_dock.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/main_window.h"
37 #include "gui/widgets/midi_arranger.h"
38 #include "gui/widgets/midi_modifier_arranger.h"
39 #include "gui/widgets/editor_ruler.h"
40 #include "gui/widgets/ruler.h"
41 #include "gui/widgets/ruler_marker.h"
42 #include "gui/widgets/ruler_range.h"
43 #include "gui/widgets/timeline_arranger.h"
44 #include "gui/widgets/timeline_panel.h"
45 #include "gui/widgets/timeline_ruler.h"
46 #include "project.h"
47 #include "settings/settings.h"
48 #include "utils/cairo.h"
49 #include "utils/math.h"
50 #include "utils/string.h"
51 #include "utils/ui.h"
52 #include "zrythm.h"
53 #include "zrythm_app.h"
54 
55 #include <gtk/gtk.h>
56 #include <glib/gi18n.h>
57 
G_DEFINE_TYPE(RulerWidget,ruler_widget,GTK_TYPE_DRAWING_AREA)58 G_DEFINE_TYPE (
59   RulerWidget,
60   ruler_widget,
61   GTK_TYPE_DRAWING_AREA)
62 
63 #define Y_SPACING 5
64 
65      /* FIXME delete these, see ruler_marker.h */
66 #define START_MARKER_TRIANGLE_HEIGHT 8
67 #define START_MARKER_TRIANGLE_WIDTH 8
68 #define Q_HEIGHT 12
69 #define Q_WIDTH 7
70 
71 #define TYPE(x) RULER_WIDGET_TYPE_##x
72 
73 #define ACTION_IS(x) \
74   (self->action == UI_OVERLAY_ACTION_##x)
75 
76 double
77 ruler_widget_get_zoom_level (
78   RulerWidget * self)
79 {
80   if (self->type == RULER_WIDGET_TYPE_TIMELINE)
81     {
82       return
83         PRJ_TIMELINE->editor_settings.hzoom_level;
84     }
85   else if (self->type ==
86              RULER_WIDGET_TYPE_EDITOR)
87     {
88       ArrangerWidget * arr =
89         clip_editor_inner_widget_get_visible_arranger (
90           MW_CLIP_EDITOR_INNER);
91       EditorSettings * settings =
92         arranger_widget_get_editor_settings (arr);
93       g_return_val_if_fail (settings, 1.f);
94 
95       return settings->hzoom_level;
96     }
97 
98   g_return_val_if_reached (1.f);
99 }
100 
101 /**
102  * Returns the beat interval for drawing vertical
103  * lines.
104  */
105 int
ruler_widget_get_beat_interval(RulerWidget * self)106 ruler_widget_get_beat_interval (
107   RulerWidget * self)
108 {
109   int i;
110 
111   /* gather divisors of the number of beats per
112    * bar */
113   int beats_per_bar =
114     tempo_track_get_beats_per_bar (P_TEMPO_TRACK);
115   int beat_divisors[16];
116   int num_beat_divisors = 0;
117   for (i = 1; i <= beats_per_bar; i++)
118     {
119       if (beats_per_bar % i == 0)
120         beat_divisors[num_beat_divisors++] = i;
121     }
122 
123   /* decide the raw interval to keep between beats */
124   int _beat_interval =
125     MAX (
126       (int)
127       (RW_PX_TO_HIDE_BEATS / self->px_per_beat),
128       1);
129 
130   /* round the interval to the divisors */
131   int beat_interval = -1;
132   for (i = 0; i < num_beat_divisors; i++)
133     {
134       if (_beat_interval <= beat_divisors[i])
135         {
136           if (beat_divisors[i] != beats_per_bar)
137             beat_interval = beat_divisors[i];
138           break;
139         }
140     }
141 
142   return beat_interval;
143 }
144 
145 /**
146  * Returns the sixteenth interval for drawing
147  * vertical lines.
148  */
149 int
ruler_widget_get_sixteenth_interval(RulerWidget * self)150 ruler_widget_get_sixteenth_interval (
151   RulerWidget * self)
152 {
153   int i;
154 
155   /* gather divisors of the number of sixteenths per
156    * beat */
157 #define sixteenths_per_beat \
158   TRANSPORT->sixteenths_per_beat
159   int divisors[16];
160   int num_divisors = 0;
161   for (i = 1; i <= sixteenths_per_beat; i++)
162     {
163       if (sixteenths_per_beat % i == 0)
164         divisors[num_divisors++] = i;
165     }
166 
167   /* decide the raw interval to keep between sixteenths */
168   int _sixteenth_interval =
169     MAX (
170       (int)
171       (RW_PX_TO_HIDE_BEATS /
172       self->px_per_sixteenth), 1);
173 
174   /* round the interval to the divisors */
175   int sixteenth_interval = -1;
176   for (i = 0; i < num_divisors; i++)
177     {
178       if (_sixteenth_interval <= divisors[i])
179         {
180           if (divisors[i] != sixteenths_per_beat)
181             sixteenth_interval = divisors[i];
182           break;
183         }
184     }
185 
186   return sixteenth_interval;
187 }
188 
189 /**
190  * Returns the 10 sec interval.
191  */
192 int
ruler_widget_get_10sec_interval(RulerWidget * self)193 ruler_widget_get_10sec_interval (
194   RulerWidget * self)
195 {
196   int i;
197 
198   /* gather divisors of the number of beats per
199    * bar */
200 #define ten_secs_per_min 6
201   int ten_sec_divisors[36];
202   int num_ten_sec_divisors = 0;
203   for (i = 1; i <= ten_secs_per_min; i++)
204     {
205       if (ten_secs_per_min % i == 0)
206         ten_sec_divisors[num_ten_sec_divisors++] = i;
207     }
208 
209   /* decide the raw interval to keep between
210    * 10 secs */
211   int _10sec_interval =
212     MAX (
213       (int)
214       (RW_PX_TO_HIDE_BEATS / self->px_per_10sec),
215       1);
216 
217   /* round the interval to the divisors */
218   int ten_sec_interval = -1;
219   for (i = 0; i < num_ten_sec_divisors; i++)
220     {
221       if (_10sec_interval <= ten_sec_divisors[i])
222         {
223           if (ten_sec_divisors[i] != ten_secs_per_min)
224             ten_sec_interval = ten_sec_divisors[i];
225           break;
226         }
227     }
228 
229   return ten_sec_interval;
230 }
231 
232 /**
233  * Returns the sec interval.
234  */
235 int
ruler_widget_get_sec_interval(RulerWidget * self)236 ruler_widget_get_sec_interval (
237   RulerWidget * self)
238 {
239   int i;
240 
241   /* gather divisors of the number of sixteenths per
242    * beat */
243 #define secs_per_10_sec 10
244   int divisors[16];
245   int num_divisors = 0;
246   for (i = 1; i <= secs_per_10_sec; i++)
247     {
248       if (secs_per_10_sec % i == 0)
249         divisors[num_divisors++] = i;
250     }
251 
252   /* decide the raw interval to keep between secs */
253   int _sec_interval =
254     MAX (
255       (int)
256       (RW_PX_TO_HIDE_BEATS /
257       self->px_per_sec), 1);
258 
259   /* round the interval to the divisors */
260   int sec_interval = -1;
261   for (i = 0; i < num_divisors; i++)
262     {
263       if (_sec_interval <= divisors[i])
264         {
265           if (divisors[i] != secs_per_10_sec)
266             sec_interval = divisors[i];
267           break;
268         }
269     }
270 
271   return sec_interval;
272 }
273 
274 /**
275  * Draws a region other than the editor one.
276  */
277 static void
draw_other_region(RulerWidget * self,cairo_t * cr,ZRegion * region)278 draw_other_region (
279   RulerWidget *  self,
280   cairo_t *      cr,
281   ZRegion *      region)
282 {
283   /*g_debug (
284    * "drawing other region %s", region->name);*/
285 
286   int height =
287     gtk_widget_get_allocated_height (
288       GTK_WIDGET (self));
289 
290   ArrangerObject * r_obj =
291     (ArrangerObject *) region;
292   Track * track = arranger_object_get_track (r_obj);
293   cairo_set_source_rgba (
294     cr, track->color.red, track->color.green,
295     track->color.blue, 0.5);
296 
297   int px_start, px_end;
298   px_start =
299     ui_pos_to_px_editor (&r_obj->pos, true);
300   px_end =
301     ui_pos_to_px_editor (&r_obj->end_pos, true);
302   cairo_rectangle (
303     cr, px_start, 0,
304     px_end - px_start, height / 4.0);
305   cairo_fill (cr);
306 }
307 
308 /**
309  * Draws the regions in the editor ruler.
310  */
311 static void
draw_regions(RulerWidget * self,GdkRectangle * rect)312 draw_regions (
313   RulerWidget *  self,
314   GdkRectangle * rect)
315 {
316   cairo_t * cr = self->cached_cr;
317 
318   int height =
319     gtk_widget_get_allocated_height (
320       GTK_WIDGET (self));
321 
322   /* get a visible region - the clip editor
323    * region is removed temporarily while moving
324    * regions so this could be NULL */
325   ZRegion * region =
326     clip_editor_get_region (CLIP_EDITOR);
327   if (!region)
328     return;
329 
330   ArrangerObject * region_obj =
331     (ArrangerObject *) region;
332 
333   Track * track =
334     arranger_object_get_track (region_obj);
335 
336   int px_start, px_end;
337 
338   /* draw the main region */
339   GdkRGBA color = track->color;
340   ui_get_arranger_object_color (
341     &color,
342     MW_TIMELINE->hovered_object == region_obj,
343     region_is_selected (region),
344     false, arranger_object_get_muted (region_obj));
345   gdk_cairo_set_source_rgba (cr, &color);
346   px_start =
347     ui_pos_to_px_editor (
348       &region_obj->pos, 1);
349   px_end =
350     ui_pos_to_px_editor (
351       &region_obj->end_pos, 1);
352   cairo_rectangle (
353     cr, px_start - rect->x, - rect->y,
354     px_end - px_start, height / 4.0);
355   cairo_fill (cr);
356 
357   /* draw its transient if copy-moving TODO */
358   if (arranger_object_should_orig_be_visible (
359         region_obj))
360     {
361       px_start =
362         ui_pos_to_px_editor (
363           &region_obj->pos, 1);
364       px_end =
365         ui_pos_to_px_editor (
366           &region_obj->end_pos, 1);
367       cairo_rectangle (
368         cr, px_start - rect->x, - rect->y,
369         px_end - px_start, height / 4.0);
370       cairo_fill (cr);
371     }
372 
373   /* draw the other regions */
374   ZRegion * other_regions[1000];
375   int num_other_regions =
376     track_get_regions_in_range (
377       track, NULL, NULL, other_regions);
378   for (int i = 0; i < num_other_regions; i++)
379     {
380       ZRegion * other_region = other_regions[i];
381       if (region_identifier_is_equal (
382             &region->id, &other_region->id) ||
383           region->id.type != other_region->id.type)
384         {
385           continue;
386         }
387 
388       cairo_save (cr);
389       cairo_translate (
390         cr, - rect->x, - rect->y);
391 
392       draw_other_region (
393         self, cr, other_region);
394 
395       cairo_restore (cr);
396     }
397 }
398 
399 static void
get_loop_start_rect(RulerWidget * self,GdkRectangle * rect)400 get_loop_start_rect (
401   RulerWidget *  self,
402   GdkRectangle * rect)
403 {
404   rect->x = 0;
405   if (self->type == TYPE (EDITOR))
406     {
407       ZRegion * region =
408         clip_editor_get_region (CLIP_EDITOR);
409       if (region)
410         {
411           ArrangerObject * region_obj =
412             (ArrangerObject *) region;
413           double start_ticks =
414             position_to_ticks (&region_obj->pos);
415           double loop_start_ticks =
416             position_to_ticks (
417               &region_obj->loop_start_pos) +
418             start_ticks;
419           Position tmp;
420           position_from_ticks (
421             &tmp, loop_start_ticks);
422           rect->x =
423             ui_pos_to_px_editor (&tmp, 1);
424         }
425       else
426         {
427           rect->x = 0;
428         }
429     }
430   else if (self->type == TYPE (TIMELINE))
431     {
432       rect->x =
433         ui_pos_to_px_timeline (
434           &TRANSPORT->loop_start_pos, 1);
435     }
436   rect->y = 0;
437   rect->width = RW_RULER_MARKER_SIZE;
438   rect->height = RW_RULER_MARKER_SIZE;
439 }
440 
441 static void
draw_loop_start(RulerWidget * self,cairo_t * cr,GdkRectangle * rect)442 draw_loop_start (
443   RulerWidget *  self,
444   cairo_t *      cr,
445   GdkRectangle * rect)
446 {
447   /* draw rect */
448   GdkRectangle dr;
449   get_loop_start_rect (self, &dr);
450 
451   if (dr.x >=
452         rect->x - dr.width &&
453       dr.x <=
454         rect->x + rect->width)
455     {
456       cairo_set_source_rgb (cr, 0, 0.9, 0.7);
457       cairo_set_line_width (cr, 2);
458       cairo_move_to (
459         cr, dr.x - rect->x, dr.y);
460       cairo_line_to (
461         cr, dr.x - rect->x, dr.y + dr.height);
462       cairo_line_to (
463         cr, (dr.x + dr.width) - rect->x, dr.y);
464       cairo_fill (cr);
465     }
466 }
467 
468 static void
get_loop_end_rect(RulerWidget * self,GdkRectangle * rect)469 get_loop_end_rect (
470   RulerWidget *  self,
471   GdkRectangle * rect)
472 {
473   rect->x = 0;
474   if (self->type == TYPE (EDITOR))
475     {
476       ZRegion * region =
477         clip_editor_get_region (CLIP_EDITOR);
478       if (region)
479         {
480           ArrangerObject * region_obj =
481             (ArrangerObject *) region;
482           double start_ticks =
483             position_to_ticks (
484               &region_obj->pos);
485           double loop_end_ticks =
486             position_to_ticks (
487               &region_obj->loop_end_pos) +
488             start_ticks;
489           Position tmp;
490           position_from_ticks (
491             &tmp, loop_end_ticks);
492           rect->x =
493             ui_pos_to_px_editor (
494               &tmp, 1) - RW_RULER_MARKER_SIZE;
495         }
496       else
497         {
498           rect->x = 0;
499         }
500     }
501   else if (self->type == TYPE (TIMELINE))
502     {
503       rect->x =
504         ui_pos_to_px_timeline (
505           &TRANSPORT->loop_end_pos, 1) -
506         RW_RULER_MARKER_SIZE;
507     }
508   rect->y = 0;
509   rect->width = RW_RULER_MARKER_SIZE;
510   rect->height = RW_RULER_MARKER_SIZE;
511 }
512 
513 static void
draw_loop_end(RulerWidget * self,cairo_t * cr,GdkRectangle * rect)514 draw_loop_end (
515   RulerWidget *  self,
516   cairo_t *      cr,
517   GdkRectangle * rect)
518 {
519   /* draw rect */
520   GdkRectangle dr;
521   get_loop_end_rect (self, &dr);
522 
523   if (dr.x >=
524         rect->x - dr.width &&
525       dr.x <=
526         rect->x + rect->width)
527     {
528       cairo_set_source_rgb (cr, 0, 0.9, 0.7);
529       cairo_set_line_width (cr, 2);
530       cairo_move_to (cr, dr.x - rect->x, dr.y);
531       cairo_line_to (
532         cr, (dr.x + dr.width) - rect->x, dr.y);
533       cairo_line_to (
534         cr, (dr.x + dr.width) - rect->x,
535         dr.y + dr.height);
536       cairo_fill (cr);
537     }
538 }
539 
540 static void
get_punch_in_rect(RulerWidget * self,GdkRectangle * rect)541 get_punch_in_rect (
542   RulerWidget *  self,
543   GdkRectangle * rect)
544 {
545   rect->x =
546     ui_pos_to_px_timeline (
547       &TRANSPORT->punch_in_pos, 1);
548   rect->y = RW_RULER_MARKER_SIZE;
549   rect->width = RW_RULER_MARKER_SIZE;
550   rect->height = RW_RULER_MARKER_SIZE;
551 }
552 
553 static void
draw_punch_in(RulerWidget * self,cairo_t * cr,GdkRectangle * rect)554 draw_punch_in (
555   RulerWidget *  self,
556   cairo_t *      cr,
557   GdkRectangle * rect)
558 {
559   /* draw rect */
560   GdkRectangle dr;
561   get_punch_in_rect (self, &dr);
562 
563   if (dr.x >=
564         rect->x - dr.width &&
565       dr.x <=
566         rect->x + rect->width)
567     {
568       cairo_set_source_rgb (cr, 0.9, 0.1, 0.1);
569       cairo_set_line_width (cr, 2);
570       cairo_move_to (
571         cr, dr.x - rect->x, dr.y);
572       cairo_line_to (
573         cr, dr.x - rect->x, dr.y + dr.height);
574       cairo_line_to (
575         cr, (dr.x + dr.width) - rect->x, dr.y);
576       cairo_fill (cr);
577     }
578 }
579 
580 static void
get_punch_out_rect(RulerWidget * self,GdkRectangle * rect)581 get_punch_out_rect (
582   RulerWidget *  self,
583   GdkRectangle * rect)
584 {
585   rect->x =
586     ui_pos_to_px_timeline (
587       &TRANSPORT->punch_out_pos, 1) -
588     RW_RULER_MARKER_SIZE;
589   rect->y = RW_RULER_MARKER_SIZE;
590   rect->width = RW_RULER_MARKER_SIZE;
591   rect->height = RW_RULER_MARKER_SIZE;
592 }
593 
594 static void
draw_punch_out(RulerWidget * self,cairo_t * cr,GdkRectangle * rect)595 draw_punch_out (
596   RulerWidget *  self,
597   cairo_t *      cr,
598   GdkRectangle * rect)
599 {
600   /* draw rect */
601   GdkRectangle dr;
602   get_punch_out_rect (self, &dr);
603 
604   if (dr.x >=
605         rect->x - dr.width &&
606       dr.x <=
607         rect->x + rect->width)
608     {
609       cairo_set_source_rgb (cr, 0.9, 0.1, 0.1);
610       cairo_set_line_width (cr, 2);
611       cairo_move_to (cr, dr.x - rect->x, dr.y);
612       cairo_line_to (
613         cr, (dr.x + dr.width) - rect->x, dr.y);
614       cairo_line_to (
615         cr, (dr.x + dr.width) - rect->x,
616         dr.y + dr.height);
617       cairo_fill (cr);
618     }
619 }
620 
621 static void
get_clip_start_rect(RulerWidget * self,GdkRectangle * rect)622 get_clip_start_rect (
623   RulerWidget *  self,
624   GdkRectangle * rect)
625 {
626   /* TODO these were added to fix unused
627    * warnings - check again if valid */
628   rect->x = 0;
629   rect->y = 0;
630 
631   if (self->type == TYPE (EDITOR))
632     {
633       ZRegion * region =
634         clip_editor_get_region (CLIP_EDITOR);
635       if (region)
636         {
637           ArrangerObject * region_obj =
638             (ArrangerObject *) region;
639           double start_ticks =
640             position_to_ticks (
641               &region_obj->pos);
642           double clip_start_ticks =
643             position_to_ticks (
644               &region_obj->clip_start_pos) +
645             start_ticks;
646           Position tmp;
647           position_from_ticks (
648             &tmp, clip_start_ticks);
649           rect->x =
650             ui_pos_to_px_editor (&tmp, 1);
651         }
652       else
653         {
654           rect->x = 0;
655         }
656       rect->y =
657         ((gtk_widget_get_allocated_height (
658           GTK_WIDGET (self)) -
659             RW_RULER_MARKER_SIZE) -
660          RW_CUE_MARKER_HEIGHT) - 1;
661     }
662   rect->width = RW_CUE_MARKER_WIDTH;
663   rect->height = RW_CUE_MARKER_HEIGHT;
664 }
665 
666 /**
667  * Draws the cue point (or clip start if this is
668  * the editor ruler.
669  */
670 static void
draw_cue_point(RulerWidget * self,cairo_t * cr,GdkRectangle * rect)671 draw_cue_point (
672   RulerWidget *  self,
673   cairo_t *      cr,
674   GdkRectangle * rect)
675 {
676   /* draw rect */
677   GdkRectangle dr;
678   get_clip_start_rect (self, &dr);
679 
680   if (self->type == TYPE (TIMELINE))
681     {
682       dr.x =
683         ui_pos_to_px_timeline (
684           &TRANSPORT->cue_pos,
685           1);
686       dr.y =
687         RW_RULER_MARKER_SIZE;
688     }
689 
690   if (dr.x >=
691         rect->x - dr.width &&
692       dr.x <=
693         rect->x + rect->width)
694     {
695       if (self->type == TYPE (EDITOR))
696         {
697           cairo_set_source_rgb (cr, 0.2, 0.6, 0.9);
698         }
699       else if (self->type == TYPE (TIMELINE))
700         {
701           cairo_set_source_rgb (cr, 0, 0.6, 0.9);
702         }
703       cairo_set_line_width (cr, 2);
704       cairo_move_to (
705         cr, dr.x - rect->x, dr.y);
706       cairo_line_to (
707         cr, (dr.x + dr.width) - rect->x,
708         dr.y + dr.height / 2);
709       cairo_line_to (
710         cr, dr.x - rect->x, dr.y + dr.height);
711       cairo_fill (cr);
712     }
713 }
714 
715 /**
716  * Returns the playhead's x coordinate in absolute
717  * coordinates.
718  */
719 static int
get_playhead_px(RulerWidget * self)720 get_playhead_px (
721   RulerWidget * self)
722 {
723   if (self->type == TYPE (EDITOR))
724     {
725       return
726         ui_pos_to_px_editor (PLAYHEAD, 1);
727     }
728   else if (self->type == TYPE (TIMELINE))
729     {
730       return
731         ui_pos_to_px_timeline (PLAYHEAD, 1);
732     }
733   g_return_val_if_reached (-1);
734 }
735 
736 static void
draw_playhead(RulerWidget * self,cairo_t * cr,GdkRectangle * rect)737 draw_playhead (
738   RulerWidget *  self,
739   cairo_t *      cr,
740   GdkRectangle * rect)
741 {
742   int px = get_playhead_px (self);
743 
744   if (px >=
745         rect->x - RW_PLAYHEAD_TRIANGLE_WIDTH / 2 &&
746       px <=
747         (rect->x + rect->width) -
748         RW_PLAYHEAD_TRIANGLE_WIDTH / 2)
749     {
750       self->last_playhead_px = px;
751 
752       /* draw rect */
753       GdkRectangle dr = { 0, 0, 0, 0 };
754       dr.x =
755         px -
756         (RW_PLAYHEAD_TRIANGLE_WIDTH / 2);
757       dr.y =
758         gtk_widget_get_allocated_height (
759           GTK_WIDGET (self)) -
760           RW_PLAYHEAD_TRIANGLE_HEIGHT;
761       dr.width = RW_PLAYHEAD_TRIANGLE_WIDTH;
762       dr.height = RW_PLAYHEAD_TRIANGLE_HEIGHT;
763 
764       cairo_set_source_rgb (cr, 0.7, 0.7, 0.7);
765       cairo_set_line_width (cr, 2);
766       cairo_move_to (cr, dr.x - rect->x, dr.y);
767       cairo_line_to (
768         cr, (dr.x + dr.width / 2) - rect->x,
769         dr.y + dr.height);
770       cairo_line_to (
771         cr, (dr.x + dr.width) - rect->x, dr.y);
772       cairo_fill (cr);
773     }
774 }
775 
776 /**
777  * Draws the grid lines and labels.
778  *
779  * @param rect Clip rectangle.
780  */
781 static void
draw_lines_and_labels(RulerWidget * self,cairo_t * cr,GdkRectangle * rect)782 draw_lines_and_labels (
783   RulerWidget *  self,
784   cairo_t *      cr,
785   GdkRectangle * rect)
786 {
787   /* draw lines */
788   int i = 0;
789   double curr_px;
790   char text[40];
791   int textw, texth;
792 
793   int beats_per_bar =
794     tempo_track_get_beats_per_bar (P_TEMPO_TRACK);
795   int height =
796     gtk_widget_get_allocated_height (
797       GTK_WIDGET (self));
798 
799   /* if time display */
800   if (g_settings_get_enum (
801         S_UI, "ruler-display") ==
802           TRANSPORT_DISPLAY_TIME)
803     {
804       /* get sec interval */
805       int sec_interval =
806         ruler_widget_get_sec_interval (self);
807 
808       /* get 10 sec interval */
809       int ten_sec_interval =
810         ruler_widget_get_10sec_interval (self);
811 
812       /* get the interval for mins */
813       int min_interval =
814         (int)
815         MAX ((RW_PX_TO_HIDE_BEATS) /
816              (double) self->px_per_min, 1.0);
817 
818       /* draw mins */
819       i = - min_interval;
820       while (
821         (curr_px =
822            self->px_per_min * (i += min_interval) +
823              SPACE_BEFORE_START) <
824          rect->x + rect->width + 20.0)
825         {
826           if (curr_px + 20.0 < rect->x)
827             continue;
828 
829           cairo_set_source_rgb (cr, 1, 1, 1);
830           cairo_set_line_width (cr, 1);
831           double x = curr_px - rect->x;
832           cairo_move_to (cr, x, 0);
833           cairo_line_to (cr, x, height / 3);
834           cairo_stroke (cr);
835           cairo_set_source_rgb (cr, 0.8, 0.8, 0.8);
836           sprintf (text, "%d", i);
837           pango_layout_set_markup (
838             self->layout_normal, text, -1);
839           pango_layout_get_pixel_size (
840             self->layout_normal, &textw, &texth);
841           cairo_move_to (
842             cr,
843             x - textw / 2, height / 3 + 2);
844           pango_cairo_update_layout (
845             cr, self->layout_normal);
846           pango_cairo_show_layout (
847             cr, self->layout_normal);
848         }
849       /* draw 10secs */
850       i = 0;
851       if (ten_sec_interval > 0)
852         {
853           while ((curr_px =
854                   self->px_per_10sec *
855                     (i += ten_sec_interval) +
856                   SPACE_BEFORE_START) <
857                  rect->x + rect->width)
858             {
859               if (curr_px < rect->x)
860                 continue;
861 
862               cairo_set_source_rgb (
863                 cr, 0.7, 0.7, 0.7);
864               cairo_set_line_width (
865                 cr, 0.5);
866               double x = curr_px - rect->x;
867               cairo_move_to (
868                 cr, x, 0);
869               cairo_line_to (
870                 cr, x, height / 4);
871               cairo_stroke (cr);
872               if ((self->px_per_10sec >
873                      RW_PX_TO_HIDE_BEATS * 2) &&
874                   i % ten_secs_per_min != 0)
875                 {
876                   cairo_set_source_rgb (
877                     cr,
878                     0.5, 0.5, 0.5);
879                   sprintf (
880                     text, "%d:%02d",
881                     i / ten_secs_per_min,
882                     (i % ten_secs_per_min) * 10);
883                   pango_layout_set_markup (
884                     self->layout_small, text, -1);
885                   pango_layout_get_pixel_size (
886                     self->layout_small, &textw, &texth);
887                   cairo_move_to (
888                     cr, x - textw / 2,
889                     height / 4 + 2);
890                   pango_cairo_update_layout (
891                     cr, self->layout_small);
892                   pango_cairo_show_layout (
893                     cr, self->layout_small);
894                 }
895             }
896         }
897       /* draw secs */
898       i = 0;
899       if (sec_interval > 0)
900         {
901           while ((curr_px =
902                   self->px_per_sec *
903                     (i += sec_interval) +
904                   SPACE_BEFORE_START) <
905                  rect->x + rect->width)
906             {
907               if (curr_px < rect->x)
908                 continue;
909 
910               cairo_set_source_rgb (
911                 cr, 0.6, 0.6, 0.6);
912               cairo_set_line_width (
913                 cr, 0.3);
914               double x = curr_px - rect->x;
915               cairo_move_to (
916                 cr, x, 0);
917               cairo_line_to (
918                 cr, x, height / 6);
919               cairo_stroke (cr);
920 
921               if ((self->px_per_sec >
922                      RW_PX_TO_HIDE_BEATS * 2) &&
923                   i % secs_per_10_sec != 0)
924                 {
925                   cairo_set_source_rgb (
926                     cr, 0.5, 0.5, 0.5);
927                   int secs_per_min = 60;
928                   sprintf (
929                     text, "%d:%02d",
930                     i / secs_per_min,
931                     ((i / secs_per_10_sec) %
932                       ten_secs_per_min) * 10 +
933                     i % secs_per_10_sec);
934                   pango_layout_set_markup (
935                     self->layout_small, text, -1);
936                   pango_layout_get_pixel_size (
937                     self->layout_small, &textw, &texth);
938                   cairo_move_to (
939                     cr, x - textw / 2,
940                     height / 4 + 2);
941                   pango_cairo_update_layout (
942                     cr, self->layout_small);
943                   pango_cairo_show_layout (
944                     cr, self->layout_small);
945                 }
946             }
947         }
948     }
949   else /* else if BBT display */
950     {
951       /* get sixteenth interval */
952       int sixteenth_interval =
953         ruler_widget_get_sixteenth_interval (self);
954 
955       /* get beat interval */
956       int beat_interval =
957         ruler_widget_get_beat_interval (self);
958 
959       /* get the interval for bars */
960       int bar_interval =
961         (int)
962         MAX ((RW_PX_TO_HIDE_BEATS) /
963              (double) self->px_per_bar, 1.0);
964 
965       /* draw bars */
966       i = - bar_interval;
967       while (
968         (curr_px =
969            self->px_per_bar * (i += bar_interval) +
970              SPACE_BEFORE_START) <
971          rect->x + rect->width + 20.0)
972         {
973           if (curr_px + 20.0 < rect->x)
974             continue;
975 
976           cairo_set_source_rgb (cr, 1, 1, 1);
977           cairo_set_line_width (cr, 1);
978           double x = curr_px - rect->x;
979           cairo_move_to (cr, x, 0);
980           cairo_line_to (cr, x, height / 3);
981           cairo_stroke (cr);
982           cairo_set_source_rgb (cr, 0.8, 0.8, 0.8);
983           sprintf (text, "%d", i + 1);
984           pango_layout_set_markup (
985             self->layout_normal, text, -1);
986           pango_layout_get_pixel_size (
987             self->layout_normal, &textw, &texth);
988           cairo_move_to (
989             cr,
990             x - textw / 2, height / 3 + 2);
991           pango_cairo_update_layout (
992             cr, self->layout_normal);
993           pango_cairo_show_layout (
994             cr, self->layout_normal);
995         }
996       /* draw beats */
997       i = 0;
998       if (beat_interval > 0)
999         {
1000           while ((curr_px =
1001                   self->px_per_beat *
1002                     (i += beat_interval) +
1003                   SPACE_BEFORE_START) <
1004                  rect->x + rect->width)
1005             {
1006               if (curr_px < rect->x)
1007                 continue;
1008 
1009               cairo_set_source_rgb (
1010                 cr, 0.7, 0.7, 0.7);
1011               cairo_set_line_width (
1012                 cr, 0.5);
1013               double x = curr_px - rect->x;
1014               cairo_move_to (
1015                 cr, x, 0);
1016               cairo_line_to (
1017                 cr, x, height / 4);
1018               cairo_stroke (cr);
1019               if ((self->px_per_beat >
1020                      RW_PX_TO_HIDE_BEATS * 2) &&
1021                   i % beats_per_bar != 0)
1022                 {
1023                   cairo_set_source_rgb (
1024                     cr,
1025                     0.5, 0.5, 0.5);
1026                   sprintf (
1027                     text, "%d.%d",
1028                     i / beats_per_bar + 1,
1029                     i % beats_per_bar + 1);
1030                   pango_layout_set_markup (
1031                     self->layout_small, text, -1);
1032                   pango_layout_get_pixel_size (
1033                     self->layout_small, &textw, &texth);
1034                   cairo_move_to (
1035                     cr, x - textw / 2,
1036                     height / 4 + 2);
1037                   pango_cairo_update_layout (
1038                     cr, self->layout_small);
1039                   pango_cairo_show_layout (
1040                     cr, self->layout_small);
1041                 }
1042             }
1043         }
1044       /* draw sixteenths */
1045       i = 0;
1046       if (sixteenth_interval > 0)
1047         {
1048           while ((curr_px =
1049                   self->px_per_sixteenth *
1050                     (i += sixteenth_interval) +
1051                   SPACE_BEFORE_START) <
1052                  rect->x + rect->width)
1053             {
1054               if (curr_px < rect->x)
1055                 continue;
1056 
1057               cairo_set_source_rgb (
1058                 cr, 0.6, 0.6, 0.6);
1059               cairo_set_line_width (
1060                 cr, 0.3);
1061               double x = curr_px - rect->x;
1062               cairo_move_to (
1063                 cr, x, 0);
1064               cairo_line_to (
1065                 cr, x, height / 6);
1066               cairo_stroke (cr);
1067 
1068               if ((self->px_per_sixteenth >
1069                      RW_PX_TO_HIDE_BEATS * 2) &&
1070                   i % sixteenths_per_beat != 0)
1071                 {
1072                   cairo_set_source_rgb (
1073                     cr, 0.5, 0.5, 0.5);
1074                   sprintf (
1075                     text, "%d.%d.%d",
1076                     i / TRANSPORT->
1077                       sixteenths_per_bar + 1,
1078                     ((i / sixteenths_per_beat) %
1079                       beats_per_bar) + 1,
1080                     i % sixteenths_per_beat + 1);
1081                   pango_layout_set_markup (
1082                     self->layout_small, text, -1);
1083                   pango_layout_get_pixel_size (
1084                     self->layout_small, &textw, &texth);
1085                   cairo_move_to (
1086                     cr, x - textw / 2,
1087                     height / 4 + 2);
1088                   pango_cairo_update_layout (
1089                     cr, self->layout_small);
1090                   pango_cairo_show_layout (
1091                     cr, self->layout_small);
1092                 }
1093             }
1094         }
1095     }
1096 }
1097 
1098 static gboolean
ruler_draw_cb(GtkWidget * widget,cairo_t * cr,RulerWidget * self)1099 ruler_draw_cb (
1100   GtkWidget *   widget,
1101   cairo_t *     cr,
1102   RulerWidget * self)
1103 {
1104   /* engine is run only set after everything is set
1105    * up so this is a good way to decide if we
1106    * should  draw or not */
1107   if (!PROJECT || !AUDIO_ENGINE ||
1108       !g_atomic_int_get (&AUDIO_ENGINE->run) ||
1109       self->px_per_bar < 2.0)
1110     {
1111       return FALSE;
1112     }
1113 
1114   GdkRectangle rect;
1115   gdk_cairo_get_clip_rectangle (cr, &rect);
1116 
1117   if (self->redraw ||
1118       !gdk_rectangle_equal (
1119          &rect, &self->last_rect))
1120     {
1121       self->last_rect = rect;
1122 
1123       GtkStyleContext *context =
1124         gtk_widget_get_style_context (
1125           GTK_WIDGET (self));
1126 
1127       z_cairo_reset_caches (
1128         &self->cached_cr,
1129         &self->cached_surface, rect.width,
1130         rect.height, cr);
1131 
1132       cairo_t * cr_to_use = self->cached_cr;
1133 
1134       /* ----- ruler background ------- */
1135 
1136       int height =
1137         gtk_widget_get_allocated_height (
1138           GTK_WIDGET (self));
1139 
1140       gtk_render_background (
1141         context, cr_to_use,
1142         0, 0, rect.width, rect.height);
1143 
1144       /* if timeline, draw loop background */
1145       /* FIXME use rect */
1146       double start_px = 0, end_px = 0;
1147       if (self->type == TYPE (TIMELINE))
1148         {
1149           start_px =
1150             ui_pos_to_px_timeline (
1151               &TRANSPORT->loop_start_pos, 1);
1152           end_px =
1153             ui_pos_to_px_timeline (
1154               &TRANSPORT->loop_end_pos, 1);
1155         }
1156       else if (self->type == TYPE (EDITOR))
1157         {
1158           start_px =
1159             ui_pos_to_px_editor (
1160               &TRANSPORT->loop_start_pos, 1);
1161           end_px =
1162             ui_pos_to_px_editor (
1163               &TRANSPORT->loop_end_pos, 1);
1164         }
1165 
1166       if (TRANSPORT->loop)
1167         cairo_set_source_rgba (
1168           cr_to_use, 0, 0.9, 0.7, 0.25);
1169       else
1170         cairo_set_source_rgba (
1171           cr_to_use, 0.5, 0.5, 0.5, 0.25);
1172       cairo_set_line_width (cr_to_use, 2);
1173 
1174       /* if transport loop start is within the
1175        * screen */
1176       if (start_px + 2.0 > rect.x &&
1177           start_px <= rect.x + rect.width)
1178         {
1179           /* draw the loop start line */
1180           double x =
1181             (start_px - rect.x);
1182           cairo_move_to (
1183             cr_to_use, x, 0);
1184           cairo_line_to (
1185             cr_to_use, x, rect.height);
1186           cairo_stroke (cr_to_use);
1187         }
1188       /* if transport loop end is within the
1189        * screen */
1190       if (end_px + 2.0 > rect.x &&
1191           end_px <= rect.x + rect.width)
1192         {
1193           /* draw the loop end line */
1194           double x =
1195             (end_px - rect.x);
1196           cairo_move_to (
1197             cr_to_use, x, 0);
1198           cairo_line_to (
1199             cr_to_use, x, rect.height);
1200           cairo_stroke (cr_to_use);
1201         }
1202 
1203       /* create gradient for loop area */
1204       cairo_pattern_t * pat =
1205         cairo_pattern_create_linear (
1206           0.0, 0.0, 0.0, (height * 3) / 4);
1207       if (TRANSPORT->loop)
1208         {
1209           cairo_pattern_add_color_stop_rgba (
1210             pat, 1, 0, 0.9, 0.7, 0.1);
1211           cairo_pattern_add_color_stop_rgba (
1212             pat, 0, 0, 0.9, 0.7, 0.2);
1213         }
1214       else
1215         {
1216           cairo_pattern_add_color_stop_rgba (
1217             pat, 1, 0.5, 0.5, 0.5, 0.1);
1218           cairo_pattern_add_color_stop_rgba (
1219             pat, 0, 0.5, 0.5, 0.5, 0.2);
1220         }
1221 
1222       double loop_start_local_x =
1223         MAX (0, start_px - rect.x);
1224       cairo_rectangle (
1225         cr_to_use,
1226         loop_start_local_x, 0,
1227         end_px - MAX (rect.x, start_px),
1228         rect.height);
1229       cairo_set_source (cr_to_use, pat);
1230       cairo_fill (cr_to_use);
1231       cairo_pattern_destroy (pat);
1232 
1233       draw_lines_and_labels (
1234         self, cr_to_use, &rect);
1235 
1236       /* ----- draw range --------- */
1237 
1238       if (TRANSPORT->has_range)
1239         {
1240           int range1_first =
1241             position_is_before_or_equal (
1242               &TRANSPORT->range_1,
1243               &TRANSPORT->range_2);
1244 
1245           GdkRectangle dr;
1246           if (range1_first)
1247             {
1248               dr.x =
1249                 ui_pos_to_px_timeline (
1250                   &TRANSPORT->range_1, true);
1251               dr.width =
1252                 ui_pos_to_px_timeline (
1253                   &TRANSPORT->range_2, true) - dr.x;
1254             }
1255           else
1256             {
1257               dr.x =
1258                 ui_pos_to_px_timeline (
1259                   &TRANSPORT->range_2, true);
1260               dr.width =
1261                 ui_pos_to_px_timeline (
1262                   &TRANSPORT->range_1, true) - dr.x;
1263             }
1264           dr.y = 0;
1265           dr.height =
1266             gtk_widget_get_allocated_height (
1267               GTK_WIDGET (self)) /
1268             RW_RANGE_HEIGHT_DIVISOR;
1269 
1270           dr.x -= rect.x;
1271 
1272           /* fill */
1273           cairo_set_source_rgba (
1274             cr_to_use, 1, 1, 1, 0.27);
1275           cairo_rectangle (
1276             cr_to_use, dr.x, dr.y,
1277             dr.width, dr.height);
1278           cairo_fill (cr_to_use);
1279 
1280           /* draw edges */
1281           cairo_set_source_rgba (
1282             cr_to_use, 1, 1, 1, 0.7);
1283           cairo_rectangle (
1284             cr_to_use, dr.x, dr.y, 2, dr.height);
1285           cairo_fill (cr_to_use);
1286           cairo_rectangle (
1287             cr_to_use, dr.x + dr.width, dr.y, 2,
1288             dr.height - dr.y);
1289           cairo_fill (cr_to_use);
1290         }
1291 
1292       /* ----- draw regions --------- */
1293 
1294       if (self->type == TYPE (EDITOR))
1295         {
1296           draw_regions (self, &rect);
1297         }
1298 
1299       /* ------ draw markers ------- */
1300 
1301       draw_cue_point (
1302         self, cr_to_use, &rect);
1303       draw_loop_start (
1304         self, cr_to_use, &rect);
1305       draw_loop_end (
1306         self, cr_to_use, &rect);
1307 
1308       if (self->type == TYPE (TIMELINE) &&
1309           TRANSPORT->punch_mode)
1310         {
1311           draw_punch_in (
1312             self, cr_to_use, &rect);
1313           draw_punch_out (
1314             self, cr_to_use, &rect);
1315         }
1316 
1317       /* --------- draw playhead ---------- */
1318 
1319       draw_playhead (self, cr_to_use, &rect);
1320 
1321       self->redraw = 0;
1322     }
1323 
1324   cairo_set_source_surface (
1325     cr, self->cached_surface, rect.x, rect.y);
1326   cairo_paint (cr);
1327 
1328  return FALSE;
1329 }
1330 
1331 #undef beats_per_bar
1332 #undef sixteenths_per_beat
1333 
1334 static int
is_loop_start_hit(RulerWidget * self,double x,double y)1335 is_loop_start_hit (
1336   RulerWidget * self,
1337   double        x,
1338   double        y)
1339 {
1340   GdkRectangle rect;
1341   get_loop_start_rect (self, &rect);
1342 
1343   return
1344     x >= rect.x && x <= rect.x + rect.width &&
1345     y >= rect.y && y <= rect.y + rect.height;
1346 }
1347 
1348 static int
is_loop_end_hit(RulerWidget * self,double x,double y)1349 is_loop_end_hit (
1350   RulerWidget * self,
1351   double        x,
1352   double        y)
1353 {
1354   GdkRectangle rect;
1355   get_loop_end_rect (self, &rect);
1356 
1357   return
1358     x >= rect.x && x <= rect.x + rect.width &&
1359     y >= rect.y && y <= rect.y + rect.height;
1360 }
1361 
1362 static int
is_punch_in_hit(RulerWidget * self,double x,double y)1363 is_punch_in_hit (
1364   RulerWidget * self,
1365   double        x,
1366   double        y)
1367 {
1368   GdkRectangle rect;
1369   get_punch_in_rect (self, &rect);
1370 
1371   return
1372     x >= rect.x && x <= rect.x + rect.width &&
1373     y >= rect.y && y <= rect.y + rect.height;
1374 }
1375 
1376 static int
is_punch_out_hit(RulerWidget * self,double x,double y)1377 is_punch_out_hit (
1378   RulerWidget * self,
1379   double        x,
1380   double        y)
1381 {
1382   GdkRectangle rect;
1383   get_punch_out_rect (self, &rect);
1384 
1385   return
1386     x >= rect.x && x <= rect.x + rect.width &&
1387     y >= rect.y && y <= rect.y + rect.height;
1388 }
1389 
1390 static bool
is_clip_start_hit(RulerWidget * self,double x,double y)1391 is_clip_start_hit (
1392   RulerWidget * self,
1393   double        x,
1394   double        y)
1395 {
1396   if (self->type == TYPE (EDITOR))
1397     {
1398       GdkRectangle rect;
1399       get_clip_start_rect (self, &rect);
1400 
1401       return
1402         x >= rect.x && x <= rect.x + rect.width &&
1403         y >= rect.y && y <= rect.y + rect.height;
1404     }
1405   else
1406     return false;
1407 }
1408 
1409 static void
get_range_rect(RulerWidget * self,RulerWidgetRangeType type,GdkRectangle * rect)1410 get_range_rect (
1411   RulerWidget *  self,
1412   RulerWidgetRangeType type,
1413   GdkRectangle * rect)
1414 {
1415   g_return_if_fail (self->type == TYPE (TIMELINE));
1416 
1417   Position tmp;
1418   transport_get_range_pos (
1419     TRANSPORT,
1420     type == RW_RANGE_END ? false : true,
1421     &tmp);
1422   rect->x = ui_pos_to_px_timeline (&tmp, true);
1423   if (type == RW_RANGE_END)
1424     {
1425       rect->x -= RW_CUE_MARKER_WIDTH;
1426     }
1427   rect->y = 0;
1428   if (type == RW_RANGE_FULL)
1429     {
1430       transport_get_range_pos (
1431         TRANSPORT, false, &tmp);
1432       double px =
1433         ui_pos_to_px_timeline (&tmp, true);
1434       rect->width = (int) px - rect->x;
1435     }
1436   else
1437     {
1438       rect->width = RW_CUE_MARKER_WIDTH;
1439     }
1440   rect->height =
1441     gtk_widget_get_allocated_height (
1442       GTK_WIDGET (self)) /
1443       RW_RANGE_HEIGHT_DIVISOR;
1444 }
1445 
1446 bool
ruler_widget_is_range_hit(RulerWidget * self,RulerWidgetRangeType type,double x,double y)1447 ruler_widget_is_range_hit (
1448   RulerWidget *        self,
1449   RulerWidgetRangeType type,
1450   double               x,
1451   double               y)
1452 {
1453   if (self->type == TYPE (TIMELINE) &&
1454       TRANSPORT->has_range)
1455     {
1456       GdkRectangle rect;
1457       memset (&rect, 0, sizeof (GdkRectangle));
1458       get_range_rect (self, type, &rect);
1459 
1460       return
1461         x >= rect.x && x <= rect.x + rect.width &&
1462         y >= rect.y && y <= rect.y + rect.height;
1463     }
1464   else
1465     {
1466       return false;
1467     }
1468 }
1469 
1470 static gboolean
multipress_pressed(GtkGestureMultiPress * gesture,gint n_press,gdouble x,gdouble y,RulerWidget * self)1471 multipress_pressed (
1472   GtkGestureMultiPress *gesture,
1473   gint                  n_press,
1474   gdouble               x,
1475   gdouble               y,
1476   RulerWidget *         self)
1477 {
1478   GdkModifierType state_mask;
1479   ui_get_modifier_type_from_gesture (
1480     GTK_GESTURE_SINGLE (gesture),
1481     &state_mask);
1482   if (state_mask & GDK_SHIFT_MASK)
1483     self->shift_held = 1;
1484 
1485   if (n_press == 2)
1486     {
1487       if (self->type == TYPE (TIMELINE))
1488         {
1489           Position pos;
1490           ui_px_to_pos_timeline (
1491             x, &pos, 1);
1492           if (!self->shift_held
1493               &&
1494               SNAP_GRID_ANY_SNAP (
1495                 SNAP_GRID_TIMELINE))
1496             {
1497               position_snap (
1498                 &pos, &pos, NULL, NULL,
1499                 SNAP_GRID_TIMELINE);
1500             }
1501           position_set_to_pos (&TRANSPORT->cue_pos,
1502                                &pos);
1503         }
1504       if (self->type == TYPE (EDITOR))
1505         {
1506         }
1507     }
1508 
1509   return FALSE;
1510 }
1511 
1512 static void
on_display_type_changed(GtkCheckMenuItem * menuitem,RulerWidget * self)1513 on_display_type_changed (
1514   GtkCheckMenuItem * menuitem,
1515   RulerWidget *      self)
1516 {
1517   if (menuitem == self->bbt_display_check &&
1518         gtk_check_menu_item_get_active (menuitem))
1519     {
1520       g_settings_set_enum (
1521         S_UI, "ruler-display",
1522         TRANSPORT_DISPLAY_BBT);
1523     }
1524   else if (menuitem == self->time_display_check &&
1525              gtk_check_menu_item_get_active (
1526                menuitem))
1527     {
1528       g_settings_set_enum (
1529         S_UI, "ruler-display",
1530         TRANSPORT_DISPLAY_TIME);
1531     }
1532 
1533   EVENTS_PUSH (ET_RULER_DISPLAY_TYPE_CHANGED, NULL);
1534 }
1535 
1536 static gboolean
multipress_right_pressed(GtkGestureMultiPress * gesture,gint n_press,gdouble x,gdouble y,RulerWidget * self)1537 multipress_right_pressed (
1538   GtkGestureMultiPress *gesture,
1539   gint                  n_press,
1540   gdouble               x,
1541   gdouble               y,
1542   RulerWidget *         self)
1543 {
1544   /* right click */
1545   if (n_press == 1)
1546     {
1547       GtkWidget *menu, *menuitem;
1548       menu = gtk_menu_new();
1549 
1550       /* switch display */
1551       GSList *group = NULL;
1552       menuitem =
1553         gtk_radio_menu_item_new_with_label (
1554           group, _("BBT display"));
1555       group =
1556         gtk_radio_menu_item_get_group (
1557           GTK_RADIO_MENU_ITEM (menuitem));
1558       gtk_check_menu_item_set_active (
1559         GTK_CHECK_MENU_ITEM (menuitem),
1560         g_settings_get_enum (
1561           S_UI, "ruler-display") ==
1562           TRANSPORT_DISPLAY_BBT);
1563       g_signal_connect (
1564         G_OBJECT (menuitem), "toggled",
1565         G_CALLBACK (on_display_type_changed), self);
1566       self->bbt_display_check =
1567         GTK_CHECK_MENU_ITEM (menuitem);
1568       gtk_menu_shell_append (
1569         GTK_MENU_SHELL (menu), menuitem);
1570 
1571       menuitem =
1572         gtk_radio_menu_item_new_with_label (
1573           group, _("Time display"));
1574       gtk_check_menu_item_set_active (
1575         GTK_CHECK_MENU_ITEM (menuitem),
1576         g_settings_get_enum (
1577           S_UI, "ruler-display") ==
1578           TRANSPORT_DISPLAY_TIME);
1579       g_signal_connect (
1580         G_OBJECT (menuitem), "toggled",
1581         G_CALLBACK (on_display_type_changed), self);
1582       self->time_display_check =
1583         GTK_CHECK_MENU_ITEM (menuitem);
1584       gtk_menu_shell_append (
1585         GTK_MENU_SHELL (menu), menuitem);
1586 
1587       gtk_widget_show_all (menu);
1588 
1589       gtk_menu_popup_at_pointer (
1590         GTK_MENU (menu), NULL);
1591     }
1592 
1593   return FALSE;
1594 }
1595 
1596 static void
drag_begin(GtkGestureDrag * gesture,gdouble start_x,gdouble start_y,RulerWidget * self)1597 drag_begin (
1598   GtkGestureDrag * gesture,
1599   gdouble          start_x,
1600   gdouble          start_y,
1601   RulerWidget *    self)
1602 {
1603   self->start_x = start_x;
1604   self->start_y = start_y;
1605 
1606   if (self->type == TYPE (TIMELINE))
1607     {
1608       self->range1_first =
1609         position_is_before_or_equal (
1610           &TRANSPORT->range_1,
1611           &TRANSPORT->range_2);
1612     }
1613 
1614   int height =
1615     gtk_widget_get_allocated_height (
1616       GTK_WIDGET (self));
1617 
1618   int punch_in_hit =
1619     is_punch_in_hit (self, start_x, start_y);
1620   int punch_out_hit =
1621     is_punch_out_hit (self, start_x, start_y);
1622   int loop_start_hit =
1623     is_loop_start_hit (self, start_x, start_y);
1624   int loop_end_hit =
1625     is_loop_end_hit (self, start_x, start_y);
1626   int clip_start_hit =
1627     is_clip_start_hit (self, start_x, start_y);
1628 
1629   ZRegion * region =
1630     clip_editor_get_region (CLIP_EDITOR);
1631   ArrangerObject * r_obj =
1632     (ArrangerObject *) region;
1633 
1634   /* if one of the markers hit */
1635   if (punch_in_hit)
1636     {
1637       self->action =
1638         UI_OVERLAY_ACTION_STARTING_MOVING;
1639       self->target =
1640         RW_TARGET_PUNCH_IN;
1641     }
1642   else if (punch_out_hit)
1643     {
1644       self->action =
1645         UI_OVERLAY_ACTION_STARTING_MOVING;
1646       self->target =
1647         RW_TARGET_PUNCH_OUT;
1648     }
1649   else if (loop_start_hit)
1650     {
1651       self->action =
1652         UI_OVERLAY_ACTION_STARTING_MOVING;
1653       self->target =
1654         RW_TARGET_LOOP_START;
1655       if (self->type == TYPE (EDITOR))
1656         {
1657           g_return_if_fail (region);
1658           self->drag_start_pos =
1659             r_obj->loop_start_pos;
1660         }
1661       else
1662         {
1663           self->drag_start_pos =
1664             TRANSPORT->loop_start_pos;
1665         }
1666     }
1667   else if (loop_end_hit)
1668     {
1669       self->action =
1670         UI_OVERLAY_ACTION_STARTING_MOVING;
1671       self->target =
1672         RW_TARGET_LOOP_END;
1673       if (self->type == TYPE (EDITOR))
1674         {
1675           g_return_if_fail (region);
1676           self->drag_start_pos =
1677             r_obj->loop_end_pos;
1678         }
1679       else
1680         {
1681           self->drag_start_pos =
1682             TRANSPORT->loop_end_pos;
1683         }
1684     }
1685   else if (clip_start_hit)
1686     {
1687       self->action =
1688         UI_OVERLAY_ACTION_STARTING_MOVING;
1689       self->target = RW_TARGET_CLIP_START;
1690       if (self->type == TYPE (EDITOR))
1691         {
1692           g_return_if_fail (region);
1693           self->drag_start_pos =
1694             r_obj->clip_start_pos;
1695         }
1696     }
1697   else
1698     {
1699       if (self->type == TYPE (TIMELINE))
1700         {
1701           timeline_ruler_on_drag_begin_no_marker_hit (
1702             self, start_x, start_y, height);
1703         }
1704       else if (self->type == TYPE (EDITOR))
1705         {
1706           editor_ruler_on_drag_begin_no_marker_hit (
1707             self, start_x, start_y);
1708         }
1709     }
1710 
1711   self->last_offset_x = 0;
1712   self->dragging = 1;
1713 }
1714 
1715 static void
drag_end(GtkGestureDrag * gesture,gdouble offset_x,gdouble offset_y,RulerWidget * self)1716 drag_end (GtkGestureDrag *gesture,
1717           gdouble         offset_x,
1718           gdouble         offset_y,
1719           RulerWidget *   self)
1720 {
1721   self->start_x = 0;
1722   self->start_y = 0;
1723   self->shift_held = 0;
1724   self->dragging = 0;
1725 
1726   if (self->type == TYPE (TIMELINE))
1727     timeline_ruler_on_drag_end (self);
1728   else if (self->type == TYPE (EDITOR))
1729     editor_ruler_on_drag_end (self);
1730 
1731   self->action = UI_OVERLAY_ACTION_NONE;
1732 }
1733 
1734 static gboolean
on_grab_broken(GtkWidget * widget,GdkEvent * event,gpointer user_data)1735 on_grab_broken (
1736   GtkWidget *widget,
1737   GdkEvent  *event,
1738   gpointer   user_data)
1739 {
1740   g_message ("ruler grab broken");
1741   return FALSE;
1742 }
1743 
1744 static void
on_motion(GtkDrawingArea * da,GdkEventMotion * event,RulerWidget * self)1745 on_motion (
1746   GtkDrawingArea * da,
1747   GdkEventMotion *event,
1748   RulerWidget *    self)
1749 {
1750   /* drag-update didn't work so do the drag-update
1751    * here */
1752   if (self->dragging
1753       && event->type != GDK_LEAVE_NOTIFY)
1754     {
1755       GdkModifierType state_mask =
1756         event->state;
1757 
1758       if (state_mask & GDK_SHIFT_MASK)
1759         self->shift_held = 1;
1760       else
1761         self->shift_held = 0;
1762 
1763       if (ACTION_IS (STARTING_MOVING))
1764         {
1765           self->action = UI_OVERLAY_ACTION_MOVING;
1766         }
1767 
1768       if (self->type == TYPE (TIMELINE))
1769         {
1770           timeline_ruler_on_drag_update (
1771             self, event->x - self->start_x,
1772             event->y - self->start_y);
1773         }
1774       else if (self->type == TYPE (EDITOR))
1775         {
1776           editor_ruler_on_drag_update (
1777             self, event->x - self->start_x,
1778             event->y - self->start_y);
1779         }
1780 
1781       self->last_offset_x =
1782         event->x - self->start_x;
1783 
1784       return;
1785     }
1786 
1787   int height =
1788     gtk_widget_get_allocated_height (
1789       GTK_WIDGET (self));
1790   if (event->type == GDK_MOTION_NOTIFY)
1791     {
1792       bool punch_in_hit =
1793         is_punch_in_hit (
1794           self, event->x, event->y);
1795       bool punch_out_hit =
1796         is_punch_out_hit (
1797           self, event->x, event->y);
1798       bool loop_start_hit =
1799         is_loop_start_hit (
1800           self, event->x, event->y);
1801       bool loop_end_hit =
1802         is_loop_end_hit (
1803           self, event->x, event->y);
1804       bool clip_start_hit =
1805         is_clip_start_hit (
1806           self, event->x, event->y);
1807       bool range_start_hit =
1808         ruler_widget_is_range_hit (
1809           self, RW_RANGE_START,
1810           event->x, event->y);
1811       bool range_end_hit =
1812         ruler_widget_is_range_hit (
1813           self, RW_RANGE_END, event->x, event->y);
1814 
1815       if (punch_in_hit ||
1816           loop_start_hit || clip_start_hit ||
1817           range_start_hit)
1818         {
1819           ui_set_cursor_from_name (
1820             GTK_WIDGET (self), "w-resize");
1821         }
1822       else if (punch_out_hit ||
1823                loop_end_hit || range_end_hit)
1824         {
1825           ui_set_cursor_from_name (
1826             GTK_WIDGET (self), "e-resize");
1827         }
1828       /* if lower 3/4ths */
1829       else if (event->y > (height * 1) / 4)
1830         {
1831           /* set cursor to normal */
1832           ui_set_cursor_from_name (
1833             GTK_WIDGET (self), "default");
1834         }
1835       else /* upper 1/4th */
1836         {
1837           if (ruler_widget_is_range_hit (
1838                 self, RW_RANGE_FULL, event->x,
1839                 event->y))
1840             {
1841               /* set cursor to movable */
1842               ui_set_hand_cursor (self);
1843             }
1844           else
1845             {
1846               /* set cursor to range selection */
1847               ui_set_cursor_from_name (
1848                 GTK_WIDGET (self), "text");
1849             }
1850         }
1851     }
1852   else if (event->type == GDK_LEAVE_NOTIFY)
1853     {
1854       ui_set_cursor_from_name (
1855         GTK_WIDGET (self), "default");
1856     }
1857 }
1858 
1859 static GtkScrolledWindow *
get_scrolled_window(RulerWidget * self)1860 get_scrolled_window (
1861   RulerWidget * self)
1862 {
1863   switch (self->type)
1864     {
1865     case TYPE (TIMELINE):
1866       return MW_TIMELINE_PANEL->ruler_scroll;
1867     case TYPE (EDITOR):
1868       return MW_CLIP_EDITOR_INNER->ruler_scroll;
1869     }
1870 
1871   return NULL;
1872 }
1873 
1874 /**
1875  * Returns the current rectangle to draw in.
1876  *
1877  * @param rect The rectangle to fill in.
1878  */
1879 static void
get_current_rect(RulerWidget * self,GdkRectangle * rect)1880 get_current_rect (
1881   RulerWidget * self,
1882   GdkRectangle *   rect)
1883 {
1884   GtkScrolledWindow * scroll =
1885     get_scrolled_window (self);
1886   GtkAdjustment * xadj =
1887     gtk_scrolled_window_get_hadjustment (
1888       scroll);
1889   rect->x =
1890     (int) gtk_adjustment_get_value (xadj);
1891   GtkAdjustment * yadj =
1892     gtk_scrolled_window_get_vadjustment (scroll);
1893   rect->y =
1894     (int) gtk_adjustment_get_value (yadj);
1895   rect->height =
1896     gtk_widget_get_allocated_height (
1897       GTK_WIDGET (scroll));
1898   rect->width =
1899     gtk_widget_get_allocated_width (
1900       GTK_WIDGET (scroll));
1901 }
1902 
1903 /**
1904  * Queues a redraw of the whole visible ruler.
1905  */
1906 void
ruler_widget_redraw_whole(RulerWidget * self)1907 ruler_widget_redraw_whole (
1908   RulerWidget * self)
1909 {
1910   GdkRectangle rect;
1911   get_current_rect (self, &rect);
1912 
1913   /* redraw visible area */
1914   self->redraw = 1;
1915   gtk_widget_queue_draw_area (
1916     GTK_WIDGET (self), rect.x, rect.y,
1917     rect.width, rect.height);
1918 }
1919 
1920 /**
1921  * Only redraws the playhead part.
1922  */
1923 void
ruler_widget_redraw_playhead(RulerWidget * self)1924 ruler_widget_redraw_playhead (
1925   RulerWidget * self)
1926 {
1927   GdkRectangle rect;
1928   get_current_rect (self, &rect);
1929 
1930   int playhead_x = get_playhead_px (self);
1931   int min_x =
1932     MIN (self->last_playhead_px, playhead_x);
1933   min_x =
1934     MAX (
1935       min_x - (RW_PLAYHEAD_TRIANGLE_WIDTH + 40),
1936       rect.x);
1937   int max_x =
1938     MAX (self->last_playhead_px, playhead_x);
1939   max_x =
1940     MIN (
1941       max_x + RW_PLAYHEAD_TRIANGLE_WIDTH,
1942       rect.x + rect.width);
1943 
1944   /* skip if playhead is not in the visible
1945    * rectangle */
1946   int width = max_x - min_x;
1947   if (width < 0)
1948     {
1949 #if 0
1950       g_debug (
1951         "playhead not currently visible in ruler, "
1952         "skipping redraw");
1953 #endif
1954       return;
1955     }
1956 
1957   gtk_widget_queue_draw_area (
1958     GTK_WIDGET (self), min_x, rect.y,
1959     width, rect.height);
1960 }
1961 
1962 void
ruler_widget_refresh(RulerWidget * self)1963 ruler_widget_refresh (RulerWidget * self)
1964 {
1965   /*adjust for zoom level*/
1966   self->px_per_tick =
1967     DEFAULT_PX_PER_TICK *
1968     ruler_widget_get_zoom_level (self);
1969   self->px_per_sixteenth =
1970     self->px_per_tick * TICKS_PER_SIXTEENTH_NOTE;
1971   self->px_per_beat =
1972     self->px_per_tick * TRANSPORT->ticks_per_beat;
1973   int beats_per_bar =
1974     tempo_track_get_beats_per_bar (P_TEMPO_TRACK);
1975   self->px_per_bar =
1976     self->px_per_beat * beats_per_bar;
1977 
1978   Position pos;
1979   position_from_seconds (&pos, 1.0);
1980   self->px_per_min =
1981     60.0 * pos.ticks * self->px_per_tick;
1982   self->px_per_10sec =
1983     10.0 * pos.ticks * self->px_per_tick;
1984   self->px_per_sec =
1985     pos.ticks * self->px_per_tick;
1986   self->px_per_100ms =
1987     0.1 * pos.ticks * self->px_per_tick;
1988 
1989   position_set_to_bar (
1990     &pos, TRANSPORT->total_bars + 1);
1991   double prev_total_px = self->total_px;
1992   self->total_px =
1993     self->px_per_tick *
1994     (double) position_to_ticks (&pos);
1995 
1996   /* if size changed */
1997   if (!math_doubles_equal_epsilon (
1998          prev_total_px, self->total_px, 0.1))
1999     {
2000       /* set the size */
2001       gtk_widget_set_size_request (
2002         GTK_WIDGET (self),
2003         (int) self->total_px, -1);
2004 
2005       if (self->type == TYPE (TIMELINE))
2006         {
2007           /*gtk_widget_set_visible (*/
2008             /*GTK_WIDGET (timeline_ruler->range),*/
2009             /*PROJECT->has_range);*/
2010         }
2011 
2012       EVENTS_PUSH (
2013         ET_RULER_SIZE_CHANGED, self);
2014     }
2015 }
2016 
2017 GtkScrolledWindow *
ruler_widget_get_parent_scroll(RulerWidget * self)2018 ruler_widget_get_parent_scroll (
2019   RulerWidget * self)
2020 {
2021   if (self->type == TYPE (TIMELINE))
2022     {
2023       return MW_TIMELINE_PANEL->ruler_scroll;
2024     }
2025   else if (self->type == TYPE (EDITOR))
2026     {
2027       return MW_CLIP_EDITOR_INNER->ruler_scroll;
2028     }
2029 
2030   g_return_val_if_reached (NULL);
2031 }
2032 
2033 /**
2034  * Sets zoom level and disables/enables buttons
2035  * accordingly.
2036  *
2037  * @return Whether the zoom level was set.
2038  */
2039 bool
ruler_widget_set_zoom_level(RulerWidget * self,double zoom_level)2040 ruler_widget_set_zoom_level (
2041   RulerWidget * self,
2042   double        zoom_level)
2043 {
2044   if (zoom_level > MAX_ZOOM_LEVEL)
2045     {
2046       actions_set_app_action_enabled (
2047         "zoom-in", false);
2048     }
2049   else
2050     {
2051       actions_set_app_action_enabled (
2052         "zoom-in", true);
2053     }
2054   if (zoom_level < MIN_ZOOM_LEVEL)
2055     {
2056       actions_set_app_action_enabled (
2057         "zoom-out", false);
2058     }
2059   else
2060     {
2061       actions_set_app_action_enabled (
2062         "zoom-out", true);
2063     }
2064 
2065   int update = zoom_level >= MIN_ZOOM_LEVEL &&
2066     zoom_level <= MAX_ZOOM_LEVEL;
2067 
2068   if (update)
2069     {
2070       if (self->type == RULER_WIDGET_TYPE_TIMELINE)
2071         {
2072           PRJ_TIMELINE->editor_settings.
2073             hzoom_level =
2074               zoom_level;
2075         }
2076       else if (self->type ==
2077                  RULER_WIDGET_TYPE_EDITOR)
2078         {
2079           ArrangerWidget * arr =
2080             clip_editor_inner_widget_get_visible_arranger (
2081               MW_CLIP_EDITOR_INNER);
2082           EditorSettings * settings =
2083             arranger_widget_get_editor_settings (
2084               arr);
2085           g_return_val_if_fail (settings, false);
2086           settings->hzoom_level = zoom_level;
2087         }
2088       ruler_widget_refresh (self);
2089       return true;
2090     }
2091   else
2092     {
2093       return false;
2094     }
2095 }
2096 
2097 static void
ruler_widget_init(RulerWidget * self)2098 ruler_widget_init (RulerWidget * self)
2099 {
2100   /* make the widget able to notify */
2101   gtk_widget_add_events (
2102     GTK_WIDGET (self), GDK_ALL_EVENTS_MASK);
2103 
2104   self->drag = GTK_GESTURE_DRAG (
2105     gtk_gesture_drag_new (GTK_WIDGET (self)));
2106   self->multipress = GTK_GESTURE_MULTI_PRESS (
2107     gtk_gesture_multi_press_new (
2108       GTK_WIDGET (self)));
2109   GtkGestureMultiPress * right_mp =
2110     GTK_GESTURE_MULTI_PRESS (
2111       gtk_gesture_multi_press_new (
2112         GTK_WIDGET (self)));
2113   gtk_gesture_single_set_button (
2114     GTK_GESTURE_SINGLE (right_mp),
2115     GDK_BUTTON_SECONDARY);
2116 
2117   self->layout_normal =
2118     gtk_widget_create_pango_layout (
2119       GTK_WIDGET (self), NULL);
2120   PangoFontDescription *desc =
2121     pango_font_description_from_string (
2122       "Monospace 11");
2123   pango_layout_set_font_description (
2124     self->layout_normal, desc);
2125   pango_font_description_free (desc);
2126   self->layout_small =
2127     gtk_widget_create_pango_layout (
2128       GTK_WIDGET (self), NULL);
2129   desc =
2130     pango_font_description_from_string (
2131       "Monospace 6");
2132   pango_layout_set_font_description (
2133     self->layout_small, desc);
2134   pango_font_description_free (desc);
2135 
2136   g_signal_connect (
2137     G_OBJECT (self), "draw",
2138     G_CALLBACK (ruler_draw_cb), self);
2139   g_signal_connect (
2140     G_OBJECT (self), "motion-notify-event",
2141     G_CALLBACK (on_motion),  self);
2142   g_signal_connect (
2143     G_OBJECT (self), "leave-notify-event",
2144     G_CALLBACK (on_motion),  self);
2145   g_signal_connect (
2146     G_OBJECT(self->drag), "drag-begin",
2147     G_CALLBACK (drag_begin),  self);
2148   /*g_signal_connect (*/
2149     /*G_OBJECT(self->drag), "drag-update",*/
2150     /*G_CALLBACK (drag_update),  self);*/
2151   g_signal_connect (
2152     G_OBJECT(self->drag), "drag-end",
2153     G_CALLBACK (drag_end),  self);
2154   g_signal_connect (
2155     G_OBJECT (self), "grab-broken-event",
2156     G_CALLBACK (on_grab_broken), self);
2157   g_signal_connect (
2158     G_OBJECT (self->multipress), "pressed",
2159     G_CALLBACK (multipress_pressed), self);
2160   g_signal_connect (
2161     G_OBJECT (right_mp), "pressed",
2162     G_CALLBACK (multipress_right_pressed), self);
2163 }
2164 
2165 static void
ruler_widget_class_init(RulerWidgetClass * _klass)2166 ruler_widget_class_init (RulerWidgetClass * _klass)
2167 {
2168   GtkWidgetClass * klass =
2169     GTK_WIDGET_CLASS (_klass);
2170   gtk_widget_class_set_css_name (
2171     klass, "ruler");
2172 }
2173