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 <stdlib.h>
21 #include <math.h>
22 
23 #include "audio/control_port.h"
24 #include "audio/position.h"
25 #include "audio/quantize_options.h"
26 #include "audio/router.h"
27 #include "audio/snap_grid.h"
28 #include "audio/tempo_track.h"
29 #include "audio/transport.h"
30 #include "gui/backend/event.h"
31 #include "gui/backend/event_manager.h"
32 #include "gui/widgets/bot_dock_edge.h"
33 #include "gui/widgets/center_dock.h"
34 #include "gui/widgets/digital_meter.h"
35 #include "gui/widgets/main_window.h"
36 #include "gui/widgets/midi_arranger.h"
37 #include "gui/widgets/midi_modifier_arranger.h"
38 #include "gui/widgets/editor_ruler.h"
39 #include "gui/widgets/ruler.h"
40 #include "gui/widgets/timeline_arranger.h"
41 #include "gui/widgets/timeline_ruler.h"
42 #include "project.h"
43 #include "settings/settings.h"
44 #include "utils/cairo.h"
45 #include "utils/error.h"
46 #include "utils/flags.h"
47 #include "utils/gtk.h"
48 #include "zrythm.h"
49 #include "zrythm_app.h"
50 
51 #include <gtk/gtk.h>
52 
53 #include <glib/gi18n.h>
54 
G_DEFINE_TYPE(DigitalMeterWidget,digital_meter_widget,GTK_TYPE_DRAWING_AREA)55 G_DEFINE_TYPE (
56   DigitalMeterWidget,
57   digital_meter_widget,
58   GTK_TYPE_DRAWING_AREA)
59 
60 #define FONT_SIZE 16
61 /*#define SEG7_FONT "Segment7 Bold 16"*/
62 #define SEG7_FONT "DSEG14 Classic Mini Italic 12"
63 #define NORMAL_FONT "sans-serif Bold 11"
64 #define CAPTION_FONT "sans-serif 7"
65 #define SPACE_BETWEEN 6
66 #define HALF_SPACE_BETWEEN 2
67 #define PADDING_W 4
68 #define PADDING_TOP 0
69 
70 #define DISPLAY_TIME \
71   (self->is_transport && \
72    g_settings_get_enum ( \
73      S_UI, "transport-display") == \
74      TRANSPORT_DISPLAY_TIME)
75 
76 #define SET_POS \
77   ((*self->setter) (self->obj, &pos))
78 #define GET_POS \
79   ((*self->getter) (self->obj, &pos))
80 
81 
82 static gboolean
83 digital_meter_draw_cb (
84   GtkWidget * widget,
85   cairo_t *cr,
86   DigitalMeterWidget * self)
87 {
88   if (!PROJECT->loaded)
89     return FALSE;
90 
91   g_return_val_if_fail (
92     Z_IS_DIGITAL_METER_WIDGET (self) &&
93     PANGO_IS_LAYOUT (self->caption_layout) &&
94     PANGO_IS_LAYOUT (self->seg7_layout) &&
95     PANGO_IS_LAYOUT (self->normal_layout),
96     FALSE);
97 
98   GtkStyleContext *context =
99     gtk_widget_get_style_context (widget);
100   int width =
101     gtk_widget_get_allocated_width (widget);
102   int height =
103     gtk_widget_get_allocated_height (widget);
104 
105   gtk_render_background (
106     context, cr, 0, 0, width, height);
107 
108   /* draw caption and get its extents */
109   int caption_textw, caption_texth;
110   if (gtk_widget_is_sensitive (GTK_WIDGET (self)))
111     {
112       cairo_set_source_rgba (
113         cr, 1.0, 1.0, 1.0, 1.0);
114     }
115   else
116     {
117       cairo_set_source_rgba (
118         cr, 0.6, 0.6, 0.6, 1.0);
119     }
120   z_cairo_get_text_extents_for_widget (
121     self, self->caption_layout, self->caption,
122     &caption_textw, &caption_texth);
123   z_cairo_draw_text_full (
124     cr, widget, self->caption_layout,
125     self->caption,
126     width / 2 - caption_textw / 2,
127     PADDING_TOP);
128   /* uncomment to make text slightly thickerr */
129   /*z_cairo_draw_text_full (*/
130     /*cr, self->caption, width / 2 - caption_textw / 2,*/
131     /*PADDING_TOP, CAPTION_FONT);*/
132 
133   /* draw line */
134   if (self->draw_line)
135     {
136       cairo_set_line_width (cr, 1.0);
137       cairo_move_to (
138         cr, 0,
139         caption_texth +
140         PADDING_TOP);
141       cairo_line_to (
142         cr, width,
143         caption_texth +
144         PADDING_TOP);
145       cairo_stroke (cr);
146     }
147 
148   /*GdkRGBA color;*/
149   /*gdk_rgba_parse (&color, "#D68A0C");*/
150   /*gdk_cairo_set_source_rgba (cr, &color);*/
151   if (gtk_widget_is_sensitive (GTK_WIDGET (self)))
152     {
153       cairo_set_source_rgba (
154         cr, 0.0, 1.0, 0.1, 1.0);
155     }
156   else
157     {
158       cairo_set_source_rgba (
159         cr, 0.0, 0.6, 0.06, 1.0);
160     }
161   char text[20];
162   char * heap_text = NULL;
163   int num_part, dec_part, bars, beats, sixteenths, ticks;
164   int textw, texth;
165   Position pos;
166   switch (self->type)
167     {
168     case DIGITAL_METER_TYPE_BPM:
169 
170       num_part =
171         (int)
172         tempo_track_get_current_bpm (P_TEMPO_TRACK);
173       dec_part =
174         (int)
175         (tempo_track_get_current_bpm (
176            P_TEMPO_TRACK) * 100) % 100;
177 
178       z_cairo_get_text_extents_for_widget (
179         widget, self->seg7_layout, "88888",
180         &textw, &texth);
181       self->num_part_start_pos =
182         ((width / 2) - textw / 2) -
183         HALF_SPACE_BETWEEN;
184       z_cairo_get_text_extents_for_widget (
185         widget, self->seg7_layout, "888",
186         &textw, &texth);
187       self->num_part_end_pos =
188         self->num_part_start_pos + textw;
189       self->dec_part_start_pos =
190         self->num_part_end_pos + SPACE_BETWEEN;
191       z_cairo_get_text_extents_for_widget (
192         widget, self->seg7_layout, "88",
193         &textw, &texth);
194       self->dec_part_end_pos =
195         self->dec_part_start_pos + textw;
196       self->height_start_pos =
197         PADDING_TOP +
198         caption_texth + HALF_SPACE_BETWEEN;
199       self->height_end_pos =
200         self->height_start_pos + texth;
201 
202       /* draw integer part */
203       if (num_part < 100)
204         sprintf (text, "!%d.", num_part);
205       else
206         sprintf (text, "%d.", num_part);
207       z_cairo_draw_text_full (
208         cr, widget, self->seg7_layout, text,
209         self->num_part_start_pos,
210         self->height_start_pos);
211 
212       /* draw decimal part */
213       if (dec_part < 10)
214         sprintf (text, "0%d", dec_part);
215       else
216         sprintf (text, "%d", dec_part);
217       z_cairo_draw_text_full (
218         cr, widget, self->seg7_layout, text,
219         self->dec_part_start_pos,
220         self->height_start_pos);
221 
222       break;
223     case DIGITAL_METER_TYPE_POSITION:
224 
225       GET_POS;
226       if (DISPLAY_TIME)
227         {
228           long ms = position_to_ms (&pos);
229           long secs = ms / 1000;
230           int mins = (int) secs / 60;
231           ms = ms % 1000;
232           secs = secs % 60;
233 
234           z_cairo_get_text_extents_for_widget (
235             widget, self->seg7_layout,
236             /* MM:SS:ms 1 for each digit */
237             "888888888", &textw, &texth);
238           self->minutes_start_pos =
239             ((width / 2) - textw / 2) -
240             HALF_SPACE_BETWEEN * 3;
241           z_cairo_get_text_extents_for_widget (
242             widget, self->seg7_layout, "88",
243             &textw, &texth);
244           self->minutes_end_pos =
245             self->minutes_start_pos + textw;
246           self->seconds_start_pos =
247             self->minutes_end_pos + SPACE_BETWEEN;
248           z_cairo_get_text_extents_for_widget (
249             widget, self->seg7_layout, "88",
250             &textw, &texth);
251           self->seconds_end_pos =
252             self->seconds_start_pos + textw;
253           self->ms_start_pos =
254             self->seconds_end_pos + SPACE_BETWEEN;
255           z_cairo_get_text_extents_for_widget (
256             widget, self->seg7_layout, "888",
257             &textw, &texth);
258           self->ms_end_pos =
259             self->ms_start_pos + textw;
260 
261           self->height_start_pos =
262             PADDING_TOP +
263             caption_texth + HALF_SPACE_BETWEEN;
264           self->height_end_pos =
265             self->height_start_pos + texth;
266 
267           /* draw minutes */
268           if (mins < 10)
269             sprintf (text, "!%d.", mins);
270           else
271             sprintf (text, "%d.", mins);
272           z_cairo_draw_text_full (
273             cr, widget, self->seg7_layout, text,
274             self->minutes_start_pos,
275             self->height_start_pos);
276 
277           /* draw seconds */
278           if (secs < 10)
279             sprintf (text, "0%ld.", secs);
280           else
281             sprintf (text, "%ld.", secs);
282           z_cairo_draw_text_full (
283             cr, widget, self->seg7_layout, text,
284             self->seconds_start_pos,
285             self->height_start_pos);
286 
287           /* draw ms */
288           if (ms < 10)
289             sprintf (text, "00%ld", ms);
290           else if (ms < 100)
291             sprintf (text, "0%ld", ms);
292           else
293             sprintf (text, "%ld", ms);
294           z_cairo_draw_text_full (
295             cr, widget, self->seg7_layout, text,
296             self->ms_start_pos,
297             self->height_start_pos);
298         }
299       else
300         {
301           bars = position_get_bars (&pos, true);
302           beats = position_get_beats (&pos, true);
303           sixteenths =
304             position_get_sixteenths (&pos, true);
305           ticks =
306             (int) floor (position_get_ticks (&pos));
307 
308           z_cairo_get_text_extents_for_widget (
309             widget, self->seg7_layout,
310             "-8888888888", &textw, &texth);
311           self->bars_start_pos =
312             ((width / 2) - textw / 2) -
313             HALF_SPACE_BETWEEN * 3;
314           z_cairo_get_text_extents_for_widget (
315             widget, self->seg7_layout, "-888",
316             &textw, &texth);
317           self->bars_end_pos =
318             self->bars_start_pos + textw;
319           self->beats_start_pos =
320             self->bars_end_pos + SPACE_BETWEEN;
321           z_cairo_get_text_extents_for_widget (
322             widget, self->seg7_layout, "8",
323             &textw, &texth);
324           self->beats_end_pos =
325             self->beats_start_pos + textw;
326           self->sixteenths_start_pos =
327             self->beats_end_pos + SPACE_BETWEEN;
328           self->sixteenths_end_pos =
329             self->sixteenths_start_pos + textw;
330           self->ticks_start_pos =
331             self->sixteenths_end_pos + SPACE_BETWEEN;
332           z_cairo_get_text_extents_for_widget (
333             widget, self->seg7_layout, "888",
334             &textw, &texth);
335           self->ticks_end_pos =
336             self->ticks_start_pos + textw;
337           self->height_start_pos =
338             PADDING_TOP +
339             caption_texth + HALF_SPACE_BETWEEN;
340           self->height_end_pos =
341             self->height_start_pos + texth;
342 
343           if (bars < -100)
344             sprintf (text, "%d", bars);
345           else if (bars < -10)
346             sprintf (text, "!%d", bars);
347           else if (bars < 0)
348             sprintf (text, "!!%d", bars);
349           else if (bars < 10)
350             sprintf (text, "!!!%d", bars);
351           else if (bars < 100)
352             sprintf (text, "!!%d", bars);
353           else
354             sprintf (text, "!%d", bars);
355           strcat (text, ".");
356           z_cairo_draw_text_full (
357             cr, widget, self->seg7_layout, text,
358             self->bars_start_pos,
359             self->height_start_pos);
360 
361           sprintf (text, "%d.", abs (beats));
362           z_cairo_draw_text_full (
363             cr, widget, self->seg7_layout, text,
364             self->beats_start_pos,
365             self->height_start_pos);
366 
367           sprintf (text, "%d.", abs (sixteenths));
368           z_cairo_draw_text_full (
369             cr, widget, self->seg7_layout, text,
370             self->sixteenths_start_pos,
371             self->height_start_pos);
372 
373           if (abs (ticks) < 10)
374             sprintf (text, "00%d", abs (ticks));
375           else if (abs (ticks) < 100)
376             sprintf (text, "0%d", abs (ticks));
377           else
378             sprintf (text, "%d", abs (ticks));
379           z_cairo_draw_text_full (
380             cr, widget, self->seg7_layout, text,
381             self->ticks_start_pos,
382             self->height_start_pos);
383         }
384       break;
385     case DIGITAL_METER_TYPE_NOTE_LENGTH:
386       heap_text =
387         snap_grid_stringize (
388           *self->note_length,
389           *self->note_type);
390       z_cairo_get_text_extents_for_widget (
391         widget, self->seg7_layout, heap_text,
392         &textw, &texth);
393       self->height_start_pos =
394         PADDING_TOP +
395         caption_texth + HALF_SPACE_BETWEEN;
396       self->height_end_pos =
397         self->height_start_pos + texth;
398       z_cairo_draw_text_full (
399         cr, widget, self->seg7_layout,
400         heap_text, width / 2 - textw / 2,
401         self->height_start_pos);
402       g_free (heap_text);
403 
404       break;
405     case DIGITAL_METER_TYPE_NOTE_TYPE:
406       switch (*self->note_type)
407         {
408         case NOTE_TYPE_NORMAL:
409           heap_text = _("normal");
410           break;
411         case NOTE_TYPE_DOTTED:
412           heap_text = _("dotted");
413           break;
414         case NOTE_TYPE_TRIPLET:
415           heap_text = _("triplet");
416           break;
417         }
418       z_cairo_get_text_extents_for_widget (
419         widget, self->seg7_layout, heap_text,
420         &textw, &texth);
421       self->height_start_pos =
422         PADDING_TOP +
423         caption_texth + HALF_SPACE_BETWEEN;
424       self->height_end_pos =
425         self->height_start_pos + texth;
426       z_cairo_draw_text_full (
427         cr, widget, self->seg7_layout, heap_text,
428         width / 2 - textw / 2,
429         self->height_start_pos);
430 
431       break;
432     case DIGITAL_METER_TYPE_TIMESIG:
433 
434       z_cairo_get_text_extents_for_widget (
435         widget, self->seg7_layout, "16/16",
436         &textw, &texth);
437       self->height_start_pos =
438         PADDING_TOP +
439         caption_texth + HALF_SPACE_BETWEEN;
440       self->height_end_pos =
441         self->height_start_pos + texth;
442 
443       BeatUnit bu =
444         tempo_track_get_beat_unit_enum (
445           P_TEMPO_TRACK);
446       const char * beat_unit =
447         beat_unit_strings[bu].str;
448       int beats_per_bar =
449         tempo_track_get_beats_per_bar (
450           P_TEMPO_TRACK);
451       if (beats_per_bar < 10)
452         {
453           text[0] = ' ';
454           text[1] =
455             (char) (beats_per_bar + '0');
456         }
457       else
458         {
459           text[0] =
460             (char) ((beats_per_bar / 10) + '0');
461           text[1] =
462             (char) ((beats_per_bar % 10) + '0');
463         }
464       text[2] = '\0';
465       heap_text =
466         g_strdup_printf ("%s/%s", text, beat_unit);
467       z_cairo_draw_text_full (
468         cr, widget, self->seg7_layout, heap_text,
469         width / 2 - textw / 2,
470         self->height_start_pos);
471       g_free (heap_text);
472 
473       break;
474     }
475 
476  return FALSE;
477 }
478 
479 /**
480  * Updates the flags to know what to update when
481  * scrolling/dragging.
482  */
483 static void
update_flags(DigitalMeterWidget * self,double x,double y)484 update_flags (
485   DigitalMeterWidget * self,
486   double               x,
487   double               y)
488 {
489   int width =
490     gtk_widget_get_allocated_width (
491       GTK_WIDGET (self));
492   switch (self->type)
493     {
494     case DIGITAL_METER_TYPE_BPM:
495       if (y >= self->height_start_pos &&
496           y <= self->height_end_pos)
497         {
498           if (x >= self->num_part_start_pos &&
499               x <= self->num_part_end_pos)
500             {
501               self->update_num = 1;
502             }
503           else if (x >= self->dec_part_start_pos &&
504                    x <= self->dec_part_end_pos)
505             {
506               self->update_dec = 1;
507             }
508         }
509       break;
510 
511     case DIGITAL_METER_TYPE_POSITION:
512       if (y >= self->height_start_pos &&
513           y <= self->height_end_pos)
514         {
515           if (DISPLAY_TIME)
516             {
517               if (x >= self->minutes_start_pos &&
518                   x <= self->minutes_end_pos)
519                 {
520                   self->update_minutes = 1;
521                 }
522               else if (x >= self->seconds_start_pos &&
523                        x <= self->seconds_end_pos)
524                 {
525                   self->update_seconds = 1;
526                 }
527               else if (x >= self->ms_start_pos &&
528                        x <= self->ms_end_pos)
529                 {
530                   self->update_ms = 1;
531                 }
532             }
533           else
534             {
535               if (x >= self->bars_start_pos &&
536                   x <= self->bars_end_pos)
537                 {
538                   self->update_bars = 1;
539                 }
540               else if (x >= self->beats_start_pos &&
541                        x <= self->beats_end_pos)
542                 {
543                   self->update_beats = 1;
544                 }
545               else if (x >= self->sixteenths_start_pos &&
546                        x <= self->sixteenths_end_pos)
547                 {
548                   self->update_sixteenths = 1;
549                 }
550               else if (x >= self->ticks_start_pos &&
551                        x <= self->ticks_end_pos)
552                 {
553                   self->update_ticks = 1;
554                 }
555             }
556         }
557 
558       break;
559     case DIGITAL_METER_TYPE_NOTE_LENGTH:
560       self->update_note_length = 1;
561       break;
562     case DIGITAL_METER_TYPE_NOTE_TYPE:
563       self->update_note_type = 1;
564       break;
565     case DIGITAL_METER_TYPE_TIMESIG:
566       if (x <= width / 2)
567         {
568           self->update_timesig_top = 1;
569         }
570       else
571         {
572           self->update_timesig_bot = 1;
573         }
574       break;
575     }
576 }
577 
578 /**
579  * To be called when a change has started (eg
580  * drag or scroll).
581  */
582 static void
on_change_started(DigitalMeterWidget * self)583 on_change_started (
584   DigitalMeterWidget * self)
585 {
586   switch (self->type)
587     {
588     case DIGITAL_METER_TYPE_NOTE_LENGTH:
589       self->start_note_length =
590         (NoteLength)
591         *self->note_length;
592       break;
593     case DIGITAL_METER_TYPE_NOTE_TYPE:
594       self->start_note_type =
595         (NoteType)
596         *self->note_type;
597       break;
598     case DIGITAL_METER_TYPE_TIMESIG:
599       self->beats_per_bar_at_start =
600         tempo_track_get_beats_per_bar (
601           P_TEMPO_TRACK);
602       self->beat_unit_at_start =
603         tempo_track_get_beat_unit (P_TEMPO_TRACK);
604       break;
605     case DIGITAL_METER_TYPE_POSITION:
606       if (self->on_drag_begin)
607         ((*self->on_drag_begin) (self->obj));
608       break;
609     case DIGITAL_METER_TYPE_BPM:
610       self->bpm_at_start =
611         tempo_track_get_current_bpm (P_TEMPO_TRACK);
612       self->last_set_bpm = self->bpm_at_start;
613       transport_prepare_audio_regions_for_stretch (
614         TRANSPORT, NULL);
615       break;
616     default:
617       break;
618     }
619 }
620 
621 /**
622  * To be called when a change has completed (eg
623  * drag or scroll).
624  */
625 static void
on_change_finished(DigitalMeterWidget * self)626 on_change_finished (
627   DigitalMeterWidget * self)
628 {
629   self->last_x = 0;
630   self->last_y = 0;
631   self->update_num = 0;
632   self->update_dec = 0;
633   self->update_bars = 0;
634   self->update_beats = 0;
635   self->update_sixteenths = 0;
636   self->update_ticks = 0;
637   self->update_minutes = 0;
638   self->update_seconds = 0;
639   self->update_ms = 0;
640   /* FIXME super reduntant */
641   if (self->update_note_length ||
642       self->update_note_type)
643     {
644       snap_grid_update_snap_points_default (
645         SNAP_GRID_TIMELINE);
646       snap_grid_update_snap_points_default (
647         SNAP_GRID_EDITOR);
648       quantize_options_update_quantize_points (
649         QUANTIZE_OPTIONS_TIMELINE);
650       quantize_options_update_quantize_points (
651         QUANTIZE_OPTIONS_EDITOR);
652     }
653   self->update_note_length = 0;
654   self->update_note_type = 0;
655   self->update_timesig_top = 0;
656   self->update_timesig_bot = 0;
657 
658   switch (self->type)
659     {
660     case DIGITAL_METER_TYPE_POSITION:
661       if (self->on_drag_end)
662         ((*self->on_drag_end) (self->obj));
663       break;
664     case DIGITAL_METER_TYPE_BPM:
665       tempo_track_set_bpm (
666         P_TEMPO_TRACK,
667         self->last_set_bpm, self->bpm_at_start,
668         Z_F_NOT_TEMPORARY, F_PUBLISH_EVENTS);
669       break;
670     case DIGITAL_METER_TYPE_TIMESIG:
671       {
672         /* no update if rolling */
673         if (TRANSPORT_IS_ROLLING)
674           {
675             break;
676           }
677 
678         int beats_per_bar =
679           tempo_track_get_beats_per_bar (
680             P_TEMPO_TRACK);
681         int beat_unit =
682           tempo_track_get_beat_unit (
683             P_TEMPO_TRACK);
684         if (self->beats_per_bar_at_start !=
685               beats_per_bar)
686           {
687             GError * err = NULL;
688             bool ret =
689               transport_action_perform_time_sig_change (
690                 TRANSPORT_ACTION_BEATS_PER_BAR_CHANGE,
691                 self->beats_per_bar_at_start,
692                 beats_per_bar,
693                 F_ALREADY_EDITED, &err);
694             if (!ret)
695               {
696                 HANDLE_ERROR (
697                   err, "%s",
698                   _("Failed to change time "
699                   "signature"));
700               }
701           }
702         else if (self->beat_unit_at_start !=
703                    beat_unit)
704           {
705             GError * err = NULL;
706             bool ret =
707               transport_action_perform_time_sig_change (
708                 TRANSPORT_ACTION_BEAT_UNIT_CHANGE,
709                 self->beat_unit_at_start, beat_unit,
710                 F_ALREADY_EDITED, &err);
711             if (!ret)
712               {
713                 HANDLE_ERROR (
714                   err, "%s",
715                   _("Failed to change time "
716                   "signature"));
717               }
718           }
719       }
720     break;
721     default:
722       break;
723     }
724 }
725 
726 void
digital_meter_set_draw_line(DigitalMeterWidget * self,int draw_line)727 digital_meter_set_draw_line (
728   DigitalMeterWidget * self,
729   int                  draw_line)
730 {
731   self->draw_line = draw_line;
732   gtk_widget_queue_draw (GTK_WIDGET (self));
733 }
734 
735 static int
on_scroll(GtkWidget * widget,GdkEventScroll * event,DigitalMeterWidget * self)736 on_scroll (
737   GtkWidget *          widget,
738   GdkEventScroll *     event,
739   DigitalMeterWidget * self)
740 {
741   int num =
742     event->direction == GDK_SCROLL_UP ? 1 : -1;
743   Position pos;
744 
745   update_flags (self, event->x, event->y);
746   on_change_started (self);
747 
748   ControlPortChange change = { 0 };
749 
750   switch (self->type)
751     {
752     case DIGITAL_METER_TYPE_BPM:
753       change.flag1 = PORT_FLAG_BPM;
754       if (self->update_num)
755         {
756           change.real_val =
757             self->last_set_bpm + (bpm_t) num;
758           self->last_set_bpm = change.real_val;
759           router_queue_control_port_change (
760             ROUTER, &change);
761         }
762       else if (self->update_dec)
763         {
764           change.real_val =
765             self->last_set_bpm +
766             (bpm_t) num / 100.f;
767           self->last_set_bpm = change.real_val;
768           router_queue_control_port_change (
769             ROUTER, &change);
770         }
771 
772       break;
773 
774     case DIGITAL_METER_TYPE_POSITION:
775       GET_POS;
776       if (DISPLAY_TIME)
777         {
778           long ms = 0;
779           if (self->update_minutes)
780             {
781               ms = num * 60 * 1000;
782             }
783           else if (self->update_seconds)
784             {
785               ms = num * 1000;
786             }
787           else if (self->update_ms)
788             {
789               ms = num;
790             }
791           position_add_ms (&pos, ms);
792         }
793       else
794         {
795           if (self->update_bars)
796             {
797               position_add_bars (&pos, num);
798             }
799           else if (self->update_beats)
800             {
801               position_add_beats (&pos, num);
802             }
803           else if (self->update_sixteenths)
804             {
805               position_add_sixteenths (&pos, num);
806             }
807           else if (self->update_ticks)
808             {
809               position_add_ticks (&pos, num);
810             }
811           SET_POS;
812         }
813       break;
814     case DIGITAL_METER_TYPE_NOTE_LENGTH:
815       if (self->update_note_length)
816         {
817           num += self->start_note_length;
818           if (num < 0)
819             *self->note_length = 0;
820           else
821             *self->note_length =
822               (NoteLength)
823               (num > NOTE_LENGTH_1_128
824                ? NOTE_LENGTH_1_128 : num);
825         }
826       break;
827     case DIGITAL_METER_TYPE_NOTE_TYPE:
828       if (self->update_note_type)
829         {
830           num += self->start_note_type;
831           if (num < 0)
832             *self->note_type = 0;
833           else
834             *self->note_type =
835               (NoteType)
836               (num > NOTE_TYPE_TRIPLET
837                ? NOTE_TYPE_TRIPLET : num);
838         }
839       break;
840     case DIGITAL_METER_TYPE_TIMESIG:
841       /* no update if rolling */
842       if (TRANSPORT_IS_ROLLING)
843         {
844           break;
845         }
846       if (self->update_timesig_top)
847         {
848           num += self->beats_per_bar_at_start;
849           change.flag2 =
850             PORT_FLAG2_BEATS_PER_BAR;
851           if (num < TEMPO_TRACK_MIN_BEATS_PER_BAR)
852             {
853               change.ival =
854                 TEMPO_TRACK_MIN_BEATS_PER_BAR;
855             }
856           else
857             {
858               change.ival =
859                 num > TEMPO_TRACK_MAX_BEATS_PER_BAR
860                 ? TEMPO_TRACK_MAX_BEATS_PER_BAR
861                 : num;
862             }
863           router_queue_control_port_change (
864             ROUTER, &change);
865         }
866       else if (self->update_timesig_bot)
867         {
868           num +=
869             (int)
870             tempo_track_beat_unit_to_enum (
871               self->beat_unit_at_start);
872           change.flag2 = PORT_FLAG2_BEAT_UNIT;
873           if (num < 0)
874             {
875               change.beat_unit = BEAT_UNIT_2;
876             }
877           else
878             {
879               change.beat_unit =
880                 num > BEAT_UNIT_16
881                 ? BEAT_UNIT_16 : (BeatUnit) num;
882             }
883           router_queue_control_port_change (
884             ROUTER, &change);
885         }
886       if (self->update_timesig_top ||
887           self->update_timesig_bot)
888         {
889           EVENTS_PUSH (
890             ET_TIME_SIGNATURE_CHANGED, NULL);
891         }
892       break;
893     }
894   on_change_finished (self);
895   gtk_widget_queue_draw (GTK_WIDGET (self));
896 
897   return FALSE;
898 }
899 
900 static void
drag_begin(GtkGestureDrag * gesture,gdouble offset_x,gdouble offset_y,DigitalMeterWidget * self)901 drag_begin (
902   GtkGestureDrag * gesture,
903   gdouble         offset_x,
904   gdouble         offset_y,
905   DigitalMeterWidget * self)
906 {
907   on_change_started (self);
908 }
909 
910 static void
drag_update(GtkGestureDrag * gesture,gdouble offset_x,gdouble offset_y,DigitalMeterWidget * self)911 drag_update (
912   GtkGestureDrag *     gesture,
913   gdouble              offset_x,
914   gdouble              offset_y,
915   DigitalMeterWidget * self)
916 {
917   offset_y = - offset_y;
918   int use_x = fabs (offset_x) > fabs (offset_y);
919   double diff =
920     use_x ?
921     offset_x - self->last_x :
922     offset_y - self->last_y;
923   int num;
924   float dec;
925   Position pos;
926   switch (self->type)
927     {
928     case DIGITAL_METER_TYPE_BPM:
929       /*g_message ("update num ? %d", self->update_num);*/
930       if (self->update_num)
931         {
932           num = (int) diff / 4;
933           /*g_message ("updating num with %d", num);*/
934           if (abs (num) > 0)
935             {
936               ControlPortChange change = { 0 };
937               change.flag1 = PORT_FLAG_BPM;
938               change.real_val =
939                 self->last_set_bpm + (bpm_t) num;
940               self->last_set_bpm = change.real_val;
941               router_queue_control_port_change (
942                 ROUTER, &change);
943               self->last_y = offset_y;
944               self->last_x = offset_x;
945             }
946         }
947       else if (self->update_dec)
948         {
949           dec = (float) diff / 400.f;
950           g_message ("%f", (double) dec);
951           if (fabs (dec) > 0)
952             {
953               ControlPortChange change = { 0 };
954               change.flag1 = PORT_FLAG_BPM;
955               change.real_val =
956                 self->last_set_bpm + (bpm_t) dec;
957               self->last_set_bpm = change.real_val;
958               router_queue_control_port_change (
959                 ROUTER, &change);
960               self->last_y = offset_y;
961               self->last_x = offset_x;
962             }
963 
964         }
965 
966       break;
967 
968     case DIGITAL_METER_TYPE_POSITION:
969       GET_POS;
970       if (DISPLAY_TIME)
971         {
972           if (self->update_minutes)
973             {
974               g_message ("UPDATE MINS");
975               num = (int) diff / 4;
976               if (abs (num) > 0)
977                 {
978               g_message ("UPDATE MINS %d", num);
979                   position_print (&pos);
980                   position_add_minutes (
981                     &pos, num);
982                   position_print (&pos);
983                   self->last_y = offset_y;
984                   self->last_x = offset_x;
985                 }
986             }
987           else if (self->update_seconds)
988             {
989               num = (int) diff / 4;
990               if (abs (num) > 0)
991                 {
992                   position_add_seconds (
993                     &pos, num);
994                   self->last_y = offset_y;
995                   self->last_x = offset_x;
996                 }
997             }
998           else if (self->update_ms)
999             {
1000               num = (int) diff / 4;
1001               if (abs (num) > 0)
1002                 {
1003                   position_add_ms (
1004                     &pos, num);
1005                   self->last_y = offset_y;
1006                   self->last_x = offset_x;
1007                 }
1008             }
1009         }
1010       else
1011         {
1012           if (self->update_bars)
1013             {
1014               num = (int) diff / 4;
1015               if (abs (num) > 0)
1016                 {
1017                   position_add_bars (&pos, num);
1018                   self->last_y = offset_y;
1019                   self->last_x = offset_x;
1020                 }
1021             }
1022           else if (self->update_beats)
1023             {
1024               num = (int) diff / 4;
1025               if (abs (num) > 0)
1026                 {
1027                   position_add_beats (&pos, num);
1028                   self->last_y = offset_y;
1029                   self->last_x = offset_x;
1030                 }
1031             }
1032           else if (self->update_sixteenths)
1033             {
1034               num = (int) diff / 4;
1035               if (abs (num) > 0)
1036                 {
1037                   position_add_sixteenths (
1038                     &pos, num);
1039                   self->last_y = offset_y;
1040                   self->last_x = offset_x;
1041                 }
1042             }
1043           else if (self->update_ticks)
1044             {
1045               num = (int) diff / 4;
1046               if (abs (num) > 0)
1047                 {
1048                   position_add_ticks (&pos, num);
1049                   self->last_y = offset_y;
1050                   self->last_x = offset_x;
1051                 }
1052             }
1053         }
1054         SET_POS;
1055       break;
1056     case DIGITAL_METER_TYPE_NOTE_LENGTH:
1057       if (self->update_note_length)
1058         {
1059           num = self->start_note_length + (int) offset_y / 24;
1060           if (num < 0)
1061             *self->note_length = 0;
1062           else
1063             *self->note_length =
1064               (NoteLength)
1065               (num > NOTE_LENGTH_1_128
1066                ? NOTE_LENGTH_1_128 : num);
1067         }
1068       break;
1069     case DIGITAL_METER_TYPE_NOTE_TYPE:
1070       if (self->update_note_type)
1071         {
1072           num = self->start_note_type + (int) offset_y / 24;
1073           if (num < 0)
1074             *self->note_type = 0;
1075           else
1076             *self->note_type =
1077               num > NOTE_TYPE_TRIPLET
1078               ? NOTE_TYPE_TRIPLET : (NoteType) num;
1079         }
1080       break;
1081     case DIGITAL_METER_TYPE_TIMESIG:
1082       /* no update if rolling */
1083       if (TRANSPORT_IS_ROLLING)
1084         {
1085           break;
1086         }
1087       if (self->update_timesig_top)
1088         {
1089           num =
1090             self->beats_per_bar_at_start +
1091             (int) diff / 24;
1092           ControlPortChange change = { 0 };
1093           change.flag2 =
1094             PORT_FLAG2_BEATS_PER_BAR;
1095           if (num < TEMPO_TRACK_MIN_BEATS_PER_BAR)
1096             {
1097               change.ival =
1098                 TEMPO_TRACK_MIN_BEATS_PER_BAR;
1099             }
1100           else
1101             {
1102               change.ival =
1103                 num > TEMPO_TRACK_MAX_BEATS_PER_BAR
1104                 ? TEMPO_TRACK_MAX_BEATS_PER_BAR
1105                 : num;
1106             }
1107           router_queue_control_port_change (
1108             ROUTER, &change);
1109         }
1110       else if (self->update_timesig_bot)
1111         {
1112           num =
1113             (int)
1114             tempo_track_beat_unit_to_enum (
1115               self->beat_unit_at_start) +
1116             (int) diff / 24;
1117           ControlPortChange change = { 0 };
1118           change.flag2 = PORT_FLAG2_BEAT_UNIT;
1119           if (num < 0)
1120             {
1121               change.beat_unit = BEAT_UNIT_2;
1122             }
1123           else
1124             {
1125               change.beat_unit =
1126                 num > BEAT_UNIT_16
1127                 ? BEAT_UNIT_16 : (BeatUnit) num;
1128             }
1129           router_queue_control_port_change (
1130             ROUTER, &change);
1131         }
1132       if (self->update_timesig_top ||
1133           self->update_timesig_bot)
1134         {
1135           EVENTS_PUSH (
1136             ET_TIME_SIGNATURE_CHANGED, NULL);
1137         }
1138       break;
1139     }
1140   gtk_widget_queue_draw (GTK_WIDGET (self));
1141 }
1142 
1143 static void
drag_end(GtkGestureDrag * gesture,gdouble offset_x,gdouble offset_y,DigitalMeterWidget * self)1144 drag_end (
1145   GtkGestureDrag *gesture,
1146   gdouble         offset_x,
1147   gdouble         offset_y,
1148   DigitalMeterWidget * self)
1149 {
1150   on_change_finished (self);
1151 }
1152 
1153 static gboolean
button_press_cb(GtkWidget * event_box,GdkEventButton * event,DigitalMeterWidget * self)1154 button_press_cb (
1155   GtkWidget      * event_box,
1156   GdkEventButton * event,
1157   DigitalMeterWidget * self)
1158 {
1159   /*g_message ("%d, %d", self->height_start_pos, self->height_end_pos);*/
1160   update_flags (self, event->x, event->y);
1161   return 0;
1162 }
1163 
1164 static void
recreate_pango_layouts(DigitalMeterWidget * self)1165 recreate_pango_layouts (
1166   DigitalMeterWidget * self)
1167 {
1168   if (PANGO_IS_LAYOUT (self->caption_layout))
1169     g_object_unref (self->caption_layout);
1170   if (PANGO_IS_LAYOUT (self->seg7_layout))
1171     g_object_unref (self->seg7_layout);
1172   if (PANGO_IS_LAYOUT (self->normal_layout))
1173     g_object_unref (self->normal_layout);
1174 
1175   self->caption_layout =
1176     z_cairo_create_pango_layout_from_string (
1177       GTK_WIDGET (self), CAPTION_FONT,
1178       PANGO_ELLIPSIZE_NONE, -1);
1179   self->seg7_layout =
1180     z_cairo_create_pango_layout_from_string (
1181       GTK_WIDGET (self), SEG7_FONT,
1182       PANGO_ELLIPSIZE_NONE, -1);
1183   self->normal_layout =
1184     z_cairo_create_pango_layout_from_string (
1185       GTK_WIDGET (self), NORMAL_FONT,
1186       PANGO_ELLIPSIZE_NONE, -1);
1187 }
1188 
1189 static void
digital_meter_widget_on_size_allocate(GtkWidget * widget,GdkRectangle * allocation,DigitalMeterWidget * self)1190 digital_meter_widget_on_size_allocate (
1191   GtkWidget *          widget,
1192   GdkRectangle *       allocation,
1193   DigitalMeterWidget * self)
1194 {
1195   recreate_pango_layouts (self);
1196 }
1197 
1198 static void
on_screen_changed(GtkWidget * widget,GdkScreen * previous_screen,DigitalMeterWidget * self)1199 on_screen_changed (
1200   GtkWidget *          widget,
1201   GdkScreen *          previous_screen,
1202   DigitalMeterWidget * self)
1203 {
1204   recreate_pango_layouts (self);
1205 }
1206 
1207 static void
init_dm(DigitalMeterWidget * self)1208 init_dm (
1209   DigitalMeterWidget * self)
1210 {
1211   g_return_if_fail (Z_DIGITAL_METER_WIDGET (self));
1212 
1213   recreate_pango_layouts (self);
1214 
1215   int caption_textw, caption_texth;
1216   z_cairo_get_text_extents_for_widget (
1217     self, self->caption_layout, self->caption,
1218     &caption_textw, &caption_texth);
1219   int textw, texth;
1220   switch (self->type)
1221     {
1222     case DIGITAL_METER_TYPE_BPM:
1223       {
1224         gtk_widget_set_tooltip_text (
1225           (GtkWidget *) self, _("Tempo/BPM"));
1226         z_cairo_get_text_extents_for_widget (
1227           self, self->seg7_layout, "888888",
1228           &textw, &texth);
1229         /* caption + padding between caption and
1230          * BPM + padding top/bottom */
1231         gtk_widget_set_size_request (
1232           GTK_WIDGET (self), textw + PADDING_W * 2,
1233           caption_texth + HALF_SPACE_BETWEEN +
1234           texth + PADDING_TOP * 2);
1235       }
1236       break;
1237     case DIGITAL_METER_TYPE_POSITION:
1238       gtk_widget_set_tooltip_text (
1239         (GtkWidget *) self, _("Position"));
1240       z_cairo_get_text_extents_for_widget (
1241         self, self->seg7_layout, "-888888888",
1242         &textw, &texth);
1243       /* caption + padding between caption and
1244        * BPM + padding top/bottom */
1245       gtk_widget_set_size_request (
1246         GTK_WIDGET (self),
1247         textw + PADDING_W * 2 + HALF_SPACE_BETWEEN * 3,
1248         caption_texth + HALF_SPACE_BETWEEN +
1249           texth + PADDING_TOP * 2);
1250 
1251       break;
1252     case DIGITAL_METER_TYPE_NOTE_LENGTH:
1253       gtk_widget_set_size_request (
1254         GTK_WIDGET (self),
1255         -1,
1256         30);
1257       break;
1258 
1259     case DIGITAL_METER_TYPE_NOTE_TYPE:
1260       gtk_widget_set_size_request (
1261         GTK_WIDGET (self),
1262         -1,
1263         30);
1264       break;
1265     case DIGITAL_METER_TYPE_TIMESIG:
1266       {
1267         gtk_widget_set_tooltip_text (
1268           GTK_WIDGET (self),
1269           _("Time Signature - Beats per bar / "
1270             "Beat unit"));
1271         z_cairo_get_text_extents_for_widget (
1272           self, self->seg7_layout, "16/16",
1273           &textw, &texth);
1274         /* caption + padding between caption and
1275          * BPM + padding top/bottom */
1276         gtk_widget_set_size_request (
1277           GTK_WIDGET (self), textw + PADDING_W * 2,
1278           caption_texth + HALF_SPACE_BETWEEN +
1279           texth + PADDING_TOP * 2);
1280       }
1281       break;
1282     }
1283 
1284   g_signal_connect (
1285     G_OBJECT (self), "draw",
1286     G_CALLBACK (digital_meter_draw_cb), self);
1287 }
1288 
1289 /**
1290  * Creates a digital meter with the given type (bpm or position).
1291  */
1292 DigitalMeterWidget *
digital_meter_widget_new(DigitalMeterType type,NoteLength * note_length,NoteType * note_type,const char * caption)1293 digital_meter_widget_new (
1294   DigitalMeterType  type,
1295   NoteLength *      note_length,
1296   NoteType *        note_type,
1297   const char *      caption)
1298 {
1299   DigitalMeterWidget * self =
1300     g_object_new (DIGITAL_METER_WIDGET_TYPE, NULL);
1301 
1302   self->type = type;
1303   self->caption = g_strdup (caption);
1304   self->note_length = note_length;
1305   self->note_type = note_type;
1306   init_dm (self);
1307 
1308   return self;
1309 }
1310 
1311 /**
1312  * Creates a digital meter for an arbitrary position.
1313  *
1314  * @param obj The object to call the get/setters with.
1315  *
1316  *   E.g. Region.
1317  * @param get_val The getter func to get the position,
1318  *   passing the obj and the position to save to.
1319  * @param set_val The setter function to set the
1320  *   position.
1321  */
1322 DigitalMeterWidget *
_digital_meter_widget_new_for_position(void * obj,void (* on_drag_begin)(void *),void (* get_val)(void *,Position *),void (* set_val)(void *,Position *),void (* on_drag_end)(void *),const char * caption)1323 _digital_meter_widget_new_for_position (
1324   void * obj,
1325   void (*on_drag_begin)(void *),
1326   void (*get_val)(void *, Position *),
1327   void (*set_val)(void *, Position *),
1328   void (*on_drag_end)(void *),
1329   const char * caption)
1330 {
1331   DigitalMeterWidget * self =
1332     g_object_new (DIGITAL_METER_WIDGET_TYPE, NULL);
1333 
1334   self->obj = obj;
1335   self->on_drag_begin = on_drag_begin;
1336   self->getter = get_val;
1337   self->setter = set_val;
1338   self->on_drag_end = on_drag_end;
1339   self->caption = g_strdup (caption);
1340   self->type = DIGITAL_METER_TYPE_POSITION;
1341   init_dm (self);
1342 
1343   return self;
1344 }
1345 
1346 static void
finalize(DigitalMeterWidget * self)1347 finalize (
1348   DigitalMeterWidget * self)
1349 {
1350   if (self->caption)
1351     g_free (self->caption);
1352   if (self->drag)
1353     g_object_unref (self->drag);
1354   if (self->caption_layout)
1355     g_object_unref (self->caption_layout);
1356   if (self->seg7_layout)
1357     g_object_unref (self->seg7_layout);
1358   if (self->normal_layout)
1359     g_object_unref (self->normal_layout);
1360 
1361   G_OBJECT_CLASS (
1362     digital_meter_widget_parent_class)->
1363       finalize (G_OBJECT (self));
1364 }
1365 
1366 static void
digital_meter_widget_class_init(DigitalMeterWidgetClass * klass)1367 digital_meter_widget_class_init (
1368   DigitalMeterWidgetClass * klass)
1369 {
1370   GObjectClass * oklass =
1371     G_OBJECT_CLASS (klass);
1372   oklass->finalize =
1373     (GObjectFinalizeFunc) finalize;
1374 }
1375 
1376 static void
digital_meter_widget_init(DigitalMeterWidget * self)1377 digital_meter_widget_init (
1378   DigitalMeterWidget * self)
1379 {
1380   g_return_if_fail (
1381     Z_IS_DIGITAL_METER_WIDGET (self));
1382 
1383   /* make it able to notify */
1384   gtk_widget_set_has_window (
1385     (GtkWidget *) self, TRUE);
1386   gtk_widget_add_events (
1387     (GtkWidget *) self,
1388     GDK_SCROLL_MASK);
1389 
1390   self->drag =
1391     GTK_GESTURE_DRAG (
1392       gtk_gesture_drag_new (GTK_WIDGET (self)));
1393 
1394   g_signal_connect (
1395     G_OBJECT (self), "scroll-event",
1396     G_CALLBACK (on_scroll),  self);
1397   g_signal_connect (
1398     G_OBJECT (self->drag), "drag-begin",
1399     G_CALLBACK (drag_begin),  self);
1400   g_signal_connect (
1401     G_OBJECT (self->drag), "drag-update",
1402     G_CALLBACK (drag_update),  self);
1403   g_signal_connect (
1404     G_OBJECT (self->drag), "drag-end",
1405     G_CALLBACK (drag_end),  self);
1406   g_signal_connect (
1407     G_OBJECT (self), "button_press_event",
1408     G_CALLBACK (button_press_cb),  self);
1409   g_signal_connect (
1410     G_OBJECT (self), "screen-changed",
1411     G_CALLBACK (on_screen_changed),  self);
1412   g_signal_connect (
1413     G_OBJECT (self), "size-allocate",
1414     G_CALLBACK (
1415       digital_meter_widget_on_size_allocate),
1416     self);
1417 
1418   gtk_widget_set_visible (
1419     GTK_WIDGET (self), 1);
1420 }
1421