when editing an audio clock, swallow illegal keys rather than allowing them to reach...
[ardour.git] / gtk2_ardour / audio_clock.cc
1 /*
2     Copyright (C) 1999 Paul Davis
3
4     This program is free software; you can redistribute it and/or modify
5     it under the terms of the GNU General Public License as published by
6     the Free Software Foundation; either version 2 of the License, or
7     (at your option) any later version.
8
9     This program is distributed in the hope that it will be useful,
10     but WITHOUT ANY WARRANTY; without even the implied warranty of
11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12     GNU General Public License for more details.
13
14     You should have received a copy of the GNU General Public License
15     along with this program; if not, write to the Free Software
16     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17
18 */
19
20 #include <cstdio> // for sprintf
21 #include <cmath>
22
23 #include "pbd/convert.h"
24 #include "pbd/enumwriter.h"
25
26 #include <gtkmm/style.h>
27 #include <sigc++/bind.h>
28
29 #include "gtkmm2ext/cairocell.h"
30 #include "gtkmm2ext/utils.h"
31 #include "gtkmm2ext/rgb_macros.h"
32
33 #include "ardour/profile.h"
34 #include "ardour/lmath.h"
35 #include "ardour/session.h"
36 #include "ardour/slave.h"
37 #include "ardour/tempo.h"
38 #include "ardour/types.h"
39
40 #include "audio_clock.h"
41 #include "utils.h"
42 #include "keyboard.h"
43 #include "gui_thread.h"
44 #include "ui_config.h"
45 #include "i18n.h"
46
47 using namespace ARDOUR;
48 using namespace ARDOUR_UI_UTILS;
49 using namespace PBD;
50 using namespace Gtk;
51 using namespace std;
52
53 using Gtkmm2ext::Keyboard;
54
55 sigc::signal<void> AudioClock::ModeChanged;
56 vector<AudioClock*> AudioClock::clocks;
57 const double AudioClock::info_font_scale_factor = 0.68;
58 const double AudioClock::separator_height = 0.0;
59 const double AudioClock::x_leading_padding = 6.0;
60
61 #define BBT_BAR_CHAR "|"
62 #define BBT_SCANF_FORMAT "%" PRIu32 "%*c%" PRIu32 "%*c%" PRIu32
63 #define INFO_FONT_SIZE ((int)lrint(font_size * info_font_scale_factor))
64 #define TXTSPAN "<span font-family=\"Sans\" foreground=\"white\">"
65
66 AudioClock::AudioClock (const string& clock_name, bool transient, const string& widget_name,
67                         bool allow_edit, bool follows_playhead, bool duration, bool with_info,
68                         bool accept_on_focus_out)
69         : ops_menu (0)
70         , _name (clock_name)
71         , is_transient (transient)
72         , is_duration (duration)
73         , editable (allow_edit)
74         , _follows_playhead (follows_playhead)
75         , _accept_on_focus_out (accept_on_focus_out)
76         , _off (false)
77         , em_width (0)
78         , _edit_by_click_field (false)
79         , _negative_allowed (false)
80         , edit_is_negative (false)
81         , editing_attr (0)
82         , foreground_attr (0)
83         , first_height (0)
84         , first_width (0)
85         , style_resets_first (true)
86         , layout_height (0)
87         , layout_width (0)
88         , info_height (0)
89         , upper_height (0)
90         , mode_based_info_ratio (1.0)
91         , corner_radius (4)
92         , font_size (10240)
93         , editing (false)
94         , bbt_reference_time (-1)
95         , last_when(0)
96         , last_pdelta (0)
97         , last_sdelta (0)
98         , dragging (false)
99         , drag_field (Field (0))
100         , xscale (1.0)
101         , yscale (1.0)
102 {
103         set_flags (CAN_FOCUS);
104
105         _layout = Pango::Layout::create (get_pango_context());
106         _layout->set_attributes (normal_attributes);
107
108         if (with_info) {
109                 _left_layout = Pango::Layout::create (get_pango_context());
110                 _right_layout = Pango::Layout::create (get_pango_context());
111         }
112
113         set_widget_name (widget_name);
114
115         _mode = BBT; /* lie to force mode switch */
116         set_mode (Timecode);
117         set (last_when, true);
118
119         if (!is_transient) {
120                 clocks.push_back (this);
121         }
122
123         UIConfiguration::instance().ColorsChanged.connect (sigc::mem_fun (*this, &AudioClock::set_colors));
124         UIConfiguration::instance().DPIReset.connect (sigc::mem_fun (*this, &AudioClock::dpi_reset));
125 }
126
127 AudioClock::~AudioClock ()
128 {
129         delete foreground_attr;
130         delete editing_attr;
131 }
132
133 void
134 AudioClock::set_widget_name (const string& str)
135 {
136         if (str.empty()) {
137                 set_name ("clock");
138         } else {
139                 set_name (str + " clock");
140         }
141
142         if (is_realized()) {
143                 set_colors ();
144         }
145 }
146
147
148 void
149 AudioClock::on_realize ()
150 {
151         Gtk::Requisition req;
152
153         CairoWidget::on_realize ();
154         
155         set_clock_dimensions (req);
156
157         first_width = req.width;
158         first_height = req.height;
159
160         // XXX FIX ME: define font based on ... ???
161         // set_font ();
162         set_colors ();
163 }
164
165 void
166 AudioClock::set_font (Pango::FontDescription font)
167 {
168         Glib::RefPtr<Gtk::Style> style = get_style ();
169         Pango::AttrFontDesc* font_attr;
170
171         font_size = font.get_size();
172         font_attr = new Pango::AttrFontDesc (Pango::Attribute::create_attr_font_desc (font));
173
174         normal_attributes.change (*font_attr);
175         editing_attributes.change (*font_attr);
176
177         /* now a smaller version of the same font */
178
179         delete font_attr;
180         font.set_size (INFO_FONT_SIZE);
181         font.set_weight (Pango::WEIGHT_NORMAL);
182         font_attr = new Pango::AttrFontDesc (Pango::Attribute::create_attr_font_desc (font));
183
184         info_attributes.change (*font_attr);
185
186         /* and an even smaller one */
187
188         delete font_attr;
189
190         /* get the figure width for the font. This doesn't have to super
191          * accurate since we only use it to measure the (roughly 1 character)
192          * offset from the position Pango tells us for the "cursor"
193          */
194
195         Glib::RefPtr<Pango::Layout> tmp = Pango::Layout::create (get_pango_context());
196         int ignore_height;
197
198         tmp->set_text ("8");
199         tmp->get_pixel_size (em_width, ignore_height);
200
201         /* force redraw of markup with new font-size */
202         set (last_when, true);
203 }
204
205 void
206 AudioClock::set_active_state (Gtkmm2ext::ActiveState s)
207 {
208         CairoWidget::set_active_state (s);
209         set_colors ();
210 }
211
212 void
213 AudioClock::set_colors ()
214 {
215         int r, g, b, a;
216
217         uint32_t bg_color;
218         uint32_t text_color;
219         uint32_t editing_color;
220         uint32_t cursor_color;
221
222         if (active_state()) {
223                 bg_color = UIConfiguration::instance().color (string_compose ("%1 active: background", get_name()));
224                 text_color = UIConfiguration::instance().color (string_compose ("%1 active: text", get_name()));
225                 editing_color = UIConfiguration::instance().color (string_compose ("%1 active: edited text", get_name()));
226                 cursor_color = UIConfiguration::instance().color (string_compose ("%1 active: cursor", get_name()));
227         } else {
228                 bg_color = UIConfiguration::instance().color (string_compose ("%1: background", get_name()));
229                 text_color = UIConfiguration::instance().color (string_compose ("%1: text", get_name()));
230                 editing_color = UIConfiguration::instance().color (string_compose ("%1: edited text", get_name()));
231                 cursor_color = UIConfiguration::instance().color (string_compose ("%1: cursor", get_name()));
232         }
233
234         /* store for bg and cursor in render() */
235
236         UINT_TO_RGBA (bg_color, &r, &g, &b, &a);
237
238         bg_r = r/255.0;
239         bg_g = g/255.0;
240         bg_b = b/255.0;
241         bg_a = a/255.0;
242
243         UINT_TO_RGBA (cursor_color, &r, &g, &b, &a);
244
245         cursor_r = r/255.0;
246         cursor_g = g/255.0;
247         cursor_b = b/255.0;
248         cursor_a = a/255.0;
249
250         /* rescale for Pango colors ... sigh */
251
252         r = lrint (r * 65535.0);
253         g = lrint (g * 65535.0);
254         b = lrint (b * 65535.0);
255
256         UINT_TO_RGBA (text_color, &r, &g, &b, &a);
257         r = lrint ((r/255.0) * 65535.0);
258         g = lrint ((g/255.0) * 65535.0);
259         b = lrint ((b/255.0) * 65535.0);
260         delete foreground_attr;
261         foreground_attr = new Pango::AttrColor (Pango::Attribute::create_attr_foreground (r, g, b));
262
263         UINT_TO_RGBA (editing_color, &r, &g, &b, &a);
264         r = lrint ((r/255.0) * 65535.0);
265         g = lrint ((g/255.0) * 65535.0);
266         b = lrint ((b/255.0) * 65535.0);
267         delete editing_attr;
268         editing_attr = new Pango::AttrColor (Pango::Attribute::create_attr_foreground (r, g, b));
269
270         normal_attributes.change (*foreground_attr);
271         info_attributes.change (*foreground_attr);
272         editing_attributes.change (*foreground_attr);
273         editing_attributes.change (*editing_attr);
274
275         if (!editing) {
276                 _layout->set_attributes (normal_attributes);
277         } else {
278                 _layout->set_attributes (editing_attributes);
279         }
280
281         queue_draw ();
282 }
283
284 void
285 AudioClock::set_scale (double x, double y)
286 {
287         xscale = x;
288         yscale = y;
289
290         queue_draw ();
291 }
292
293 void
294 AudioClock::render (cairo_t* cr, cairo_rectangle_t*)
295 {
296         /* main layout: rounded rect, plus the text */
297
298         if (_need_bg) {
299                 cairo_set_source_rgba (cr, bg_r, bg_g, bg_b, bg_a);
300                 if (corner_radius) {
301                         if (_left_layout) {
302                                 Gtkmm2ext::rounded_top_half_rectangle (cr, 0, 0, get_width(), upper_height, corner_radius);
303                         } else {
304                                 Gtkmm2ext::rounded_rectangle (cr, 0, 0, get_width(), upper_height, corner_radius);
305                         }
306                 } else {
307                         cairo_rectangle (cr, 0, 0, get_width(), upper_height);
308                 }
309                 cairo_fill (cr);
310         }
311
312         double lw = layout_width * xscale;
313         double lh = layout_height * yscale;
314
315         cairo_move_to (cr, (get_width() - lw) / 2.0, (upper_height - lh) / 2.0);
316
317         if (xscale != 1.0 || yscale != 1.0) {
318                 cairo_save (cr);
319                 cairo_scale (cr, xscale, yscale);
320         }
321         
322         pango_cairo_show_layout (cr, _layout->gobj());
323
324         if (xscale != 1.0 || yscale != 1.0) {
325                 cairo_restore (cr);
326         }
327
328         if (_left_layout) {
329
330                 double h = get_height() - upper_height - separator_height;
331
332                 if (_need_bg) {
333                         cairo_set_source_rgba (cr, bg_r, bg_g, bg_b, bg_a);
334                 }
335
336                 if (mode_based_info_ratio != 1.0) {
337
338                         double left_rect_width = get_left_rect_width();
339
340                         if (_need_bg) {
341                                 if (corner_radius) {
342                                         Gtkmm2ext::rounded_bottom_half_rectangle (cr, 0, upper_height + separator_height,
343                                                         left_rect_width + (separator_height == 0 ? corner_radius : 0),
344                                                         h, corner_radius);
345                                 } else {
346                                         cairo_rectangle (cr, 0, upper_height + separator_height, left_rect_width, h);
347                                 }
348                                 cairo_fill (cr);
349                         }
350
351                         cairo_move_to (cr, x_leading_padding, upper_height + separator_height + ((h - info_height)/2.0));
352                         pango_cairo_show_layout (cr, _left_layout->gobj());
353
354                         if (_need_bg) {
355                                 if (corner_radius) {
356                                         Gtkmm2ext::rounded_bottom_half_rectangle (cr, left_rect_width + separator_height,
357                                                         upper_height + separator_height,
358                                                         get_width() - separator_height - left_rect_width,
359                                                         h, corner_radius);
360                                 } else {
361                                         cairo_rectangle (cr, left_rect_width + separator_height, upper_height + separator_height,
362                                                          get_width() - separator_height - left_rect_width, h);
363                                 }
364                                 cairo_fill (cr);
365                         }
366
367
368                         if (_right_layout->get_alignment() == Pango::ALIGN_RIGHT) {
369                                 /* right-align does not work per se beacuse layout width is unset.
370                                  * Using _right_layout->set_width([value >=0]) would also enable
371                                  * word-wrapping which is not wanted here.
372                                  * The solution is to custom align the layout depending on its size.
373                                  * if it is larger than the available space it will be cropped on the
374                                  * right edge rather than override text on the left side.
375                                  */
376                                 int x, rw, rh;
377                                 _right_layout->get_pixel_size(rw, rh);
378                                 x = get_width() - rw - separator_height - x_leading_padding;
379                                 if (x < x_leading_padding + left_rect_width + separator_height) {
380                                         /* rather cut off the right end than overlap with the text on the left */
381                                         x = x_leading_padding + left_rect_width + separator_height;
382                                 }
383                                 cairo_move_to (cr, x, upper_height + separator_height + ((h - info_height)/2.0));
384                         } else {
385                                 cairo_move_to (cr, x_leading_padding + left_rect_width + separator_height, upper_height + separator_height + ((h - info_height)/2.0));
386                         }
387                         pango_cairo_show_layout (cr, _right_layout->gobj());
388
389                 } else {
390                         /* no info to display, or just one */
391
392                         if (_need_bg) {
393                                 if (corner_radius) {
394                                         Gtkmm2ext::rounded_bottom_half_rectangle (cr, 0, upper_height + separator_height, get_width(), h, corner_radius);
395                                 } else {
396                                         cairo_rectangle (cr, 0, upper_height + separator_height, get_width(), h);
397                                 }
398                                 cairo_fill (cr);
399                         }
400                 }
401         }
402
403         if (editing) {
404                 if (!insert_map.empty()) {
405
406                         int xcenter = (get_width() - layout_width) /2;
407
408                         if (input_string.length() < insert_map.size()) {
409                                 Pango::Rectangle cursor;
410
411                                 if (input_string.empty()) {
412                                         /* nothing entered yet, put cursor at the end
413                                            of string
414                                         */
415                                         cursor = _layout->get_cursor_strong_pos (edit_string.length() - 1);
416                                 } else {
417                                         cursor = _layout->get_cursor_strong_pos (insert_map[input_string.length()]);
418                                 }
419
420                                 cairo_set_source_rgba (cr, cursor_r, cursor_g, cursor_b, cursor_a);
421                                 cairo_rectangle (cr,
422                                                  min (get_width() - 2.0,
423                                                       (double) xcenter + cursor.get_x()/PANGO_SCALE + em_width),
424                                                  (upper_height - layout_height)/2.0,
425                                                  2.0, cursor.get_height()/PANGO_SCALE);
426                                 cairo_fill (cr);
427                         } else {
428                                 /* we've entered all possible digits, no cursor */
429                         }
430
431                 } else {
432                         if (input_string.empty()) {
433                                 cairo_set_source_rgba (cr, cursor_r, cursor_g, cursor_b, cursor_a);
434                                 cairo_rectangle (cr,
435                                                  (get_width()/2.0),
436                                                  (upper_height - layout_height)/2.0,
437                                                  2.0, upper_height);
438                                 cairo_fill (cr);
439                         }
440                 }
441         }
442 }
443
444 void
445 AudioClock::on_size_allocate (Gtk::Allocation& alloc)
446 {
447         CairoWidget::on_size_allocate (alloc);
448
449         if (_left_layout) {
450                 upper_height = (get_height()/2.0) - 1.0;
451         } else {
452                 upper_height = get_height();
453         }
454 }
455
456 void
457 AudioClock::set_clock_dimensions (Gtk::Requisition& req)
458 {
459         Glib::RefPtr<Pango::Layout> tmp;
460         Glib::RefPtr<Gtk::Style> style = get_style ();
461         Pango::FontDescription font;
462
463         tmp = Pango::Layout::create (get_pango_context());
464
465         if (!is_realized()) {
466                 font = get_font_for_style (get_name());
467         } else {
468                 font = style->get_font();
469         }
470
471         tmp->set_font_description (font);
472
473         /* this string is the longest thing we will ever display */
474         if (_mode == MinSec)
475                 tmp->set_text (" 88:88:88,888 ");
476         else
477                 tmp->set_text (" 88:88:88,88 ");
478         tmp->get_pixel_size (req.width, req.height);
479
480         layout_height = req.height;
481         layout_width = req.width;
482 }
483
484 void
485 AudioClock::on_size_request (Gtk::Requisition* req)
486 {
487         /* even for non fixed width clocks, the size we *ask* for never changes,
488            even though the size we receive might. so once we've computed it,
489            just return it.
490         */
491
492         if (first_width) {
493                 req->width = first_width;
494                 req->height = first_height;
495                 return;
496         }
497
498         set_clock_dimensions (*req);
499
500         /* now tackle height, for which we need to know the height of the lower
501          * layout
502          */
503
504         if (_left_layout) {
505
506                 Glib::RefPtr<Pango::Layout> tmp;
507                 Glib::RefPtr<Gtk::Style> style = get_style ();
508                 Pango::FontDescription font;
509                 int w;
510                 
511                 tmp = Pango::Layout::create (get_pango_context());
512                 
513                 if (!is_realized()) {
514                         font = get_font_for_style (get_name());
515                 } else {
516                         font = style->get_font();
517                 }
518                 
519                 tmp->set_font_description (font);
520
521                 font.set_size (INFO_FONT_SIZE);
522                 font.set_weight (Pango::WEIGHT_NORMAL);
523                 tmp->set_font_description (font);
524
525                 /* we only care about height, so put as much stuff in here
526                    as possible that might change the height.
527                 */
528                 tmp->set_text ("qyhH|"); /* one ascender, one descender */
529
530                 tmp->get_pixel_size (w, info_height);
531
532                 /* silly extra padding that seems necessary to correct the info
533                  * that pango just gave us. I have no idea why.
534                  */
535
536                 req->height += info_height;
537                 req->height += separator_height;
538         }
539 }
540
541 void
542 AudioClock::show_edit_status (int length)
543 {
544         editing_attr->set_start_index (edit_string.length() - length);
545         editing_attr->set_end_index (edit_string.length());
546
547         editing_attributes.change (*foreground_attr);
548         editing_attributes.change (*editing_attr);
549
550         _layout->set_attributes (editing_attributes);
551 }
552
553 void
554 AudioClock::start_edit (Field f)
555 {
556         if (!editing) {
557                 pre_edit_string = _layout->get_text ();
558                 if (!insert_map.empty()) {
559                         edit_string = pre_edit_string;
560                 } else {
561                         edit_string.clear ();
562                         _layout->set_text ("");
563                 }
564                 
565                 input_string.clear ();
566                 editing = true;
567                 edit_is_negative = false;
568                 
569                 if (f) {
570                         input_string = get_field (f);
571                         show_edit_status (merge_input_and_edit_string ());
572                         _layout->set_text (edit_string);
573                 }
574                 
575                 queue_draw ();
576
577                 Keyboard::magic_widget_grab_focus ();
578                 grab_focus ();
579         }
580 }
581
582 string
583 AudioClock::get_field (Field f)
584 {
585         switch (f) {
586         case Timecode_Hours:
587                 return edit_string.substr (1, 2);
588                 break;
589         case Timecode_Minutes:
590                 return edit_string.substr (4, 2);
591                 break;
592         case Timecode_Seconds:
593                 return edit_string.substr (7, 2);
594                 break;
595         case Timecode_Frames:
596                 return edit_string.substr (10, 2);
597                 break;
598         case MS_Hours:
599                 return edit_string.substr (1, 2);
600                 break;
601         case MS_Minutes:
602                 return edit_string.substr (4, 2);
603                 break;
604         case MS_Seconds:
605                 return edit_string.substr (7, 2);
606                 break;
607         case MS_Milliseconds:
608                 return edit_string.substr (10, 3);
609                 break;
610         case Bars:
611                 return edit_string.substr (1, 3);
612                 break;
613         case Beats:
614                 return edit_string.substr (5, 2);
615                 break;
616         case Ticks:
617                 return edit_string.substr (8, 4);
618                 break;
619         case AudioFrames:
620                 return edit_string;
621                 break;
622         }
623         return "";
624 }
625
626 void
627 AudioClock::end_edit (bool modify)
628 {
629         if (modify) {
630
631                 bool ok = true;
632
633                 switch (_mode) {
634                 case Timecode:
635                         ok = timecode_validate_edit (edit_string);
636                         break;
637
638                 case BBT:
639                         ok = bbt_validate_edit (edit_string);
640                         break;
641
642                 case MinSec:
643                         ok = minsec_validate_edit (edit_string);
644                         break;
645
646                 case Frames:
647                         if (edit_string.length() < 1) {
648                                 edit_string = pre_edit_string;
649                         }
650                         break;
651                 }
652
653                 if (!ok) {
654                         edit_string = pre_edit_string;
655                         input_string.clear ();
656                         _layout->set_text (edit_string);
657                         show_edit_status (0);
658                         /* edit attributes remain in use */
659                 } else {
660
661                         editing = false;
662                         framepos_t pos = 0; /* stupid gcc */
663
664                         switch (_mode) {
665                         case Timecode:
666                                 pos = frames_from_timecode_string (edit_string);
667                                 break;
668
669                         case BBT:
670                                 if (is_duration) {
671                                         pos = frame_duration_from_bbt_string (0, edit_string);
672                                 } else {
673                                         pos = frames_from_bbt_string (0, edit_string);
674                                 }
675                                 break;
676
677                         case MinSec:
678                                 pos = frames_from_minsec_string (edit_string);
679                                 break;
680
681                         case Frames:
682                                 pos = frames_from_audioframes_string (edit_string);
683                                 break;
684                         }
685
686                         set (pos, true);
687                         _layout->set_attributes (normal_attributes);
688                         ValueChanged(); /* EMIT_SIGNAL */
689                 }
690
691         } else {
692
693                 editing = false;
694                 edit_is_negative = false;
695                 _layout->set_attributes (normal_attributes);
696                 _layout->set_text (pre_edit_string);
697         }
698
699         queue_draw ();
700
701         if (!editing) {
702                 drop_focus ();
703         }
704 }
705
706 void
707 AudioClock::drop_focus ()
708 {
709         Keyboard::magic_widget_drop_focus ();
710
711         if (has_focus()) {
712
713                 /* move focus back to the default widget in the top level window */
714
715                 Widget* top = get_toplevel();
716
717                 if (top->is_toplevel ()) {
718                         Window* win = dynamic_cast<Window*> (top);
719                         win->grab_focus ();
720                 }
721         }
722 }
723
724 framecnt_t
725 AudioClock::parse_as_frames_distance (const std::string& str)
726 {
727         framecnt_t f;
728
729         if (sscanf (str.c_str(), "%" PRId64, &f) == 1) {
730                 return f;
731         }
732
733         return 0;
734 }
735
736 framecnt_t
737 AudioClock::parse_as_minsec_distance (const std::string& str)
738 {
739         framecnt_t sr = _session->frame_rate();
740         int msecs;
741         int secs;
742         int mins;
743         int hrs;
744
745         switch (str.length()) {
746         case 0:
747                 return 0;
748         case 1:
749         case 2:
750         case 3:
751         case 4:
752                 sscanf (str.c_str(), "%" PRId32, &msecs);
753                 return msecs * (sr / 1000);
754
755         case 5:
756                 sscanf (str.c_str(), "%1" PRId32 "%" PRId32, &secs, &msecs);
757                 return (secs * sr) + (msecs * (sr/1000));
758
759         case 6:
760                 sscanf (str.c_str(), "%2" PRId32 "%" PRId32, &secs, &msecs);
761                 return (secs * sr) + (msecs * (sr/1000));
762
763         case 7:
764                 sscanf (str.c_str(), "%1" PRId32 "%2" PRId32 "%" PRId32, &mins, &secs, &msecs);
765                 return (mins * 60 * sr) + (secs * sr) + (msecs * (sr/1000));
766
767         case 8:
768                 sscanf (str.c_str(), "%2" PRId32 "%2" PRId32 "%" PRId32, &mins, &secs, &msecs);
769                 return (mins * 60 * sr) + (secs * sr) + (msecs * (sr/1000));
770
771         case 9:
772                 sscanf (str.c_str(), "%1" PRId32 "%2" PRId32 "%2" PRId32 "%" PRId32, &hrs, &mins, &secs, &msecs);
773                 return (hrs * 3600 * sr) + (mins * 60 * sr) + (secs * sr) + (msecs * (sr/1000));
774
775         case 10:
776                 sscanf (str.c_str(), "%1" PRId32 "%2" PRId32 "%2" PRId32 "%" PRId32, &hrs, &mins, &secs, &msecs);
777                 return (hrs * 3600 * sr) + (mins * 60 * sr) + (secs * sr) + (msecs * (sr/1000));
778
779         default:
780                 break;
781         }
782
783         return 0;
784 }
785
786 framecnt_t
787 AudioClock::parse_as_timecode_distance (const std::string& str)
788 {
789         double fps = _session->timecode_frames_per_second();
790         framecnt_t sr = _session->frame_rate();
791         int frames;
792         int secs;
793         int mins;
794         int hrs;
795
796         switch (str.length()) {
797         case 0:
798                 return 0;
799         case 1:
800         case 2:
801                 sscanf (str.c_str(), "%" PRId32, &frames);
802                 return llrint ((frames/(float)fps) * sr);
803
804         case 3:
805                 sscanf (str.c_str(), "%1" PRId32 "%" PRId32, &secs, &frames);
806                 return (secs * sr) + llrint ((frames/(float)fps) * sr);
807
808         case 4:
809                 sscanf (str.c_str(), "%2" PRId32 "%" PRId32, &secs, &frames);
810                 return (secs * sr) + llrint ((frames/(float)fps) * sr);
811
812         case 5:
813                 sscanf (str.c_str(), "%1" PRId32 "%2" PRId32 "%" PRId32, &mins, &secs, &frames);
814                 return (mins * 60 * sr) + (secs * sr) + llrint ((frames/(float)fps) * sr);
815
816         case 6:
817                 sscanf (str.c_str(), "%2" PRId32 "%2" PRId32 "%" PRId32, &mins, &secs, &frames);
818                 return (mins * 60 * sr) + (secs * sr) + llrint ((frames/(float)fps) * sr);
819
820         case 7:
821                 sscanf (str.c_str(), "%1" PRId32 "%2" PRId32 "%2" PRId32 "%" PRId32, &hrs, &mins, &secs, &frames);
822                 return (hrs * 3600 * sr) + (mins * 60 * sr) + (secs * sr) + llrint ((frames/(float)fps) * sr);
823
824         case 8:
825                 sscanf (str.c_str(), "%2" PRId32 "%2" PRId32 "%2" PRId32 "%" PRId32, &hrs, &mins, &secs, &frames);
826                 return (hrs * 3600 * sr) + (mins * 60 * sr) + (secs * sr) + llrint ((frames/(float)fps) * sr);
827
828         default:
829                 break;
830         }
831
832         return 0;
833 }
834
835 framecnt_t
836 AudioClock::parse_as_bbt_distance (const std::string&)
837 {
838         return 0;
839 }
840
841 framecnt_t
842 AudioClock::parse_as_distance (const std::string& instr)
843 {
844         switch (_mode) {
845         case Timecode:
846                 return parse_as_timecode_distance (instr);
847                 break;
848         case Frames:
849                 return parse_as_frames_distance (instr);
850                 break;
851         case BBT:
852                 return parse_as_bbt_distance (instr);
853                 break;
854         case MinSec:
855                 return parse_as_minsec_distance (instr);
856                 break;
857         }
858         return 0;
859 }
860
861 void
862 AudioClock::end_edit_relative (bool add)
863 {
864         bool ok = true;
865
866         switch (_mode) {
867         case Timecode:
868                 ok = timecode_validate_edit (edit_string);
869                 break;
870
871         case BBT:
872                 ok = bbt_validate_edit (edit_string);
873                 break;
874
875         case MinSec:
876                 ok = minsec_validate_edit (edit_string);
877                 break;
878
879         case Frames:
880                 break;
881         }
882
883         if (!ok) {
884                 edit_string = pre_edit_string;
885                 input_string.clear ();
886                 _layout->set_text (edit_string);
887                 show_edit_status (0);
888                 /* edit attributes remain in use */
889                 queue_draw ();
890                 return;
891         }
892
893         framecnt_t frames = parse_as_distance (input_string);
894
895         editing = false;
896
897         editing = false;
898         _layout->set_attributes (normal_attributes);
899
900         if (frames != 0) {
901                 if (add) {
902                         set (current_time() + frames, true);
903                 } else {
904                         framepos_t c = current_time();
905
906                         if (c > frames || _negative_allowed) {
907                                 set (c - frames, true);
908                         } else {
909                                 set (0, true);
910                         }
911                 }
912                 ValueChanged (); /* EMIT SIGNAL */
913         }
914
915         input_string.clear ();
916         queue_draw ();
917         drop_focus ();
918 }
919
920 void
921 AudioClock::session_property_changed (const PropertyChange&)
922 {
923         set (last_when, true);
924 }
925
926 void
927 AudioClock::session_configuration_changed (std::string p)
928 {
929         if (_negative_allowed) {
930                 /* session option editor clock */
931                 return;
932         }
933
934         if (p == "sync-source" || p == "external-sync") {
935                 set (current_time(), true);
936                 return;
937         }
938
939         if (p != "timecode-offset" && p != "timecode-offset-negative") {
940                 return;
941         }
942
943         framecnt_t current;
944
945         switch (_mode) {
946         case Timecode:
947                 if (is_duration) {
948                         current = current_duration ();
949                 } else {
950                         current = current_time ();
951                 }
952                 set (current, true);
953                 break;
954         default:
955                 break;
956         }
957 }
958
959 void
960 AudioClock::set (framepos_t when, bool force, framecnt_t offset)
961 {
962         if ((!force && !is_visible()) || _session == 0) {
963                 return;
964         }
965
966         if (is_duration) {
967                 when = when - offset;
968         }
969
970         if (when == last_when && !force) {
971 #if 0 // XXX return if no change and no change forced. verify Aug/2014
972                 if (_mode != Timecode && _mode != MinSec) {
973                         /* may need to force display of TC source
974                          * time, so don't return early.
975                          */
976                         /* ^^ Why was that?,  delta times?
977                          * Timecode FPS, pull-up/down, etc changes
978                          * trigger a 'session_property_changed' which
979                          * eventually calls set(last_when, true)
980                          *
981                          * re-rendering the clock every 40ms or so just
982                          * because we can is not ideal.
983                          */
984                         return;
985                 }
986 #else
987                 return;
988 #endif
989         }
990
991         if (!editing) {
992                 if (_right_layout) {
993                         _right_layout->set_alignment(Pango::ALIGN_LEFT);
994                 }
995
996                 switch (_mode) {
997                 case Timecode:
998                         if (_right_layout) {
999                                 _right_layout->set_alignment(Pango::ALIGN_RIGHT);
1000                         }
1001                         set_timecode (when, force);
1002                         break;
1003
1004                 case BBT:
1005                         set_bbt (when, force);
1006                         break;
1007
1008                 case MinSec:
1009                         if (_right_layout) {
1010                                 _right_layout->set_alignment(Pango::ALIGN_RIGHT);
1011                         }
1012                         set_minsec (when, force);
1013                         break;
1014
1015                 case Frames:
1016                         set_frames (when, force);
1017                         break;
1018                 }
1019         }
1020
1021         queue_draw ();
1022         last_when = when;
1023 }
1024
1025 void
1026 AudioClock::set_slave_info ()
1027 {
1028         if (!_left_layout || !_right_layout) {
1029                 return;
1030         }
1031
1032         SyncSource sync_src = Config->get_sync_source();
1033
1034         if (_session->config.get_external_sync()) {
1035                 Slave* slave = _session->slave();
1036
1037                 switch (sync_src) {
1038                 case Engine:
1039                         _left_layout->set_markup (string_compose ("<span size=\"%1\">" TXTSPAN "%2</span></span>",
1040                                                 INFO_FONT_SIZE, sync_source_to_string(sync_src, true)));
1041                         _right_layout->set_text ("");
1042                         break;
1043                 case MIDIClock:
1044                         if (slave) {
1045                                 _left_layout->set_markup (string_compose ("<span size=\"%1\">" TXTSPAN "%2</span></span>",
1046                                                         INFO_FONT_SIZE, sync_source_to_string(sync_src, true)));
1047                                 _right_layout->set_markup (string_compose ("<span size=\"%1\">" TXTSPAN "%2</span></span>",
1048                                                         INFO_FONT_SIZE, slave->approximate_current_delta()));
1049                         } else {
1050                                 _left_layout->set_markup (string_compose ("<span size=\"%1\">" TXTSPAN "%2</span></span>",
1051                                                         INFO_FONT_SIZE, _("--pending--")));
1052                                 _right_layout->set_text ("");
1053                         }
1054                         break;
1055                 case LTC:
1056                 case MTC:
1057                         if (slave) {
1058                                 bool matching;
1059                                 TimecodeSlave* tcslave;
1060                                 if ((tcslave = dynamic_cast<TimecodeSlave*>(_session->slave())) != 0) {
1061                                         matching = (tcslave->apparent_timecode_format() == _session->config.get_timecode_format());
1062                                         _left_layout->set_markup (string_compose ("<span size=\"%1\">" TXTSPAN "%2</span><span foreground=\"%3\">%4</span></span>",
1063                                                                                   INFO_FONT_SIZE, sync_source_to_string(sync_src, true)[0], (matching?"green":"red"),
1064                                                                                   dynamic_cast<TimecodeSlave*>(slave)->approximate_current_position()));
1065                                         _right_layout->set_markup (string_compose ("<span size=\"%1\">" TXTSPAN "%2</span></span>",
1066                                                                                    INFO_FONT_SIZE, slave->approximate_current_delta()));
1067                                 }
1068                         } else {
1069                                 _left_layout->set_markup (string_compose ("<span size=\"%1\">" TXTSPAN "%2</span></span>",
1070                                                         INFO_FONT_SIZE, _("--pending--")));
1071                                 _right_layout->set_text ("");
1072                         }
1073                         break;
1074                 }
1075         } else {
1076                 _left_layout->set_markup (string_compose ("<span size=\"%1\">" TXTSPAN "INT/%2</span></span>",
1077                                         INFO_FONT_SIZE, sync_source_to_string(sync_src, true)));
1078                 _right_layout->set_text ("");
1079         }
1080 }
1081
1082 void
1083 AudioClock::set_frames (framepos_t when, bool /*force*/)
1084 {
1085         char buf[32];
1086         bool negative = false;
1087
1088         if (_off) {
1089                 _layout->set_text (" ----------");
1090
1091                 if (_left_layout) {
1092                         _left_layout->set_text ("");
1093                         _right_layout->set_text ("");
1094                 }
1095
1096                 return;
1097         }
1098
1099         if (when < 0) {
1100                 when = -when;
1101                 negative = true;
1102         }
1103
1104         if (negative) {
1105                 snprintf (buf, sizeof (buf), "-%10" PRId64, when);
1106         } else {
1107                 snprintf (buf, sizeof (buf), " %10" PRId64, when);
1108         }
1109
1110         _layout->set_text (buf);
1111
1112         if (_left_layout) {
1113                 framecnt_t rate = _session->frame_rate();
1114
1115                 if (fmod (rate, 100.0) == 0.0) {
1116                         sprintf (buf, "%.1fkHz", rate/1000.0);
1117                 } else {
1118                         sprintf (buf, "%" PRId64 "Hz", rate);
1119                 }
1120
1121                 _left_layout->set_markup (string_compose ("<span size=\"%1\">" TXTSPAN "%2 </span><span foreground=\"green\">%3</span></span>",
1122                                 INFO_FONT_SIZE, _("SR"), buf));
1123
1124                 float vid_pullup = _session->config.get_video_pullup();
1125
1126                 if (vid_pullup == 0.0) {
1127                         _right_layout->set_markup (string_compose ("<span size=\"%1\">" TXTSPAN "%2 </span><span foreground=\"green\">off</span></span>",
1128                                         INFO_FONT_SIZE, _("Pull")));
1129                 } else {
1130                         sprintf (buf, _("%+.4f%%"), vid_pullup);
1131                         _right_layout->set_markup (string_compose ("<span size=\"%1\">" TXTSPAN "%2 </span><span foreground=\"green\">%3</span></span>",
1132                                         INFO_FONT_SIZE, _("Pull"), buf));
1133                 }
1134         }
1135 }
1136
1137 void
1138 AudioClock::print_minsec (framepos_t when, char* buf, size_t bufsize, float frame_rate)
1139 {
1140         framecnt_t left;
1141         int hrs;
1142         int mins;
1143         int secs;
1144         int millisecs;
1145         bool negative;
1146
1147         if (when < 0) {
1148                 when = -when;
1149                 negative = true;
1150         } else {
1151                 negative = false;
1152         }
1153
1154         left = when;
1155         hrs = (int) floor (left / (frame_rate * 60.0f * 60.0f));
1156         left -= (framecnt_t) floor (hrs * frame_rate * 60.0f * 60.0f);
1157         mins = (int) floor (left / (frame_rate * 60.0f));
1158         left -= (framecnt_t) floor (mins * frame_rate * 60.0f);
1159         secs = (int) floor (left / (float) frame_rate);
1160         left -= (framecnt_t) floor ((double)(secs * frame_rate));
1161         millisecs = floor (left * 1000.0 / (float) frame_rate);
1162
1163         if (negative) {
1164                 snprintf (buf, bufsize, "-%02" PRId32 ":%02" PRId32 ":%02" PRId32 ".%03" PRId32, hrs, mins, secs, millisecs);
1165         } else {
1166                 snprintf (buf, bufsize, " %02" PRId32 ":%02" PRId32 ":%02" PRId32 ".%03" PRId32, hrs, mins, secs, millisecs);
1167         }
1168
1169 }
1170
1171 void
1172 AudioClock::set_minsec (framepos_t when, bool /*force*/)
1173 {
1174         char buf[32];
1175
1176         if (_off) {
1177                 _layout->set_text (" --:--:--.---");
1178
1179                 if (_left_layout) {
1180                         _left_layout->set_text ("");
1181                         _right_layout->set_text ("");
1182                 }
1183
1184                 return;
1185         }
1186
1187         print_minsec (when, buf, sizeof (buf), _session->frame_rate());
1188
1189         _layout->set_text (buf);
1190         set_slave_info();
1191 }
1192
1193 void
1194 AudioClock::set_timecode (framepos_t when, bool /*force*/)
1195 {
1196         Timecode::Time TC;
1197         bool negative = false;
1198
1199         if (_off) {
1200                 _layout->set_text (" --:--:--:--");
1201                 if (_left_layout) {
1202                         _left_layout->set_text ("");
1203                         _right_layout->set_text ("");
1204                 }
1205
1206                 return;
1207         }
1208
1209         if (when < 0) {
1210                 when = -when;
1211                 negative = true;
1212         }
1213
1214         if (is_duration) {
1215                 _session->timecode_duration (when, TC);
1216         } else {
1217                 _session->timecode_time (when, TC);
1218         }
1219
1220         TC.negative = TC.negative || negative;
1221
1222         _layout->set_text (Timecode::timecode_format_time(TC));
1223
1224         set_slave_info();
1225 }
1226
1227 void
1228 AudioClock::set_bbt (framepos_t when, bool /*force*/)
1229 {
1230         char buf[16];
1231         Timecode::BBT_Time BBT;
1232         bool negative = false;
1233
1234         if (_off) {
1235                 _layout->set_text (" ---|--|----");
1236                 if (_left_layout) {
1237                         _left_layout->set_text ("");
1238                         _right_layout->set_text ("");
1239                 }
1240                 return;
1241         }
1242
1243         if (when < 0) {
1244                 when = -when;
1245                 negative = true;
1246         }
1247
1248         /* handle a common case */
1249         if (is_duration) {
1250                 if (when == 0) {
1251                         BBT.bars = 0;
1252                         BBT.beats = 0;
1253                         BBT.ticks = 0;
1254                 } else {
1255                         _session->tempo_map().bbt_time (when, BBT);
1256                         BBT.bars--;
1257                         BBT.beats--;
1258                 }
1259         } else {
1260                 _session->tempo_map().bbt_time (when, BBT);
1261         }
1262
1263         if (negative) {
1264                 snprintf (buf, sizeof (buf), "-%03" PRIu32 BBT_BAR_CHAR "%02" PRIu32 BBT_BAR_CHAR "%04" PRIu32,
1265                           BBT.bars, BBT.beats, BBT.ticks);
1266         } else {
1267                 snprintf (buf, sizeof (buf), " %03" PRIu32 BBT_BAR_CHAR "%02" PRIu32 BBT_BAR_CHAR "%04" PRIu32,
1268                           BBT.bars, BBT.beats, BBT.ticks);
1269         }
1270
1271         _layout->set_text (buf);
1272
1273         if (_right_layout) {
1274                 framepos_t pos;
1275
1276                 if (bbt_reference_time < 0) {
1277                         pos = when;
1278                 } else {
1279                         pos = bbt_reference_time;
1280                 }
1281
1282                 TempoMetric m (_session->tempo_map().metric_at (pos));
1283
1284                 sprintf (buf, "%-5.1f", m.tempo().beats_per_minute());
1285                 _left_layout->set_markup (string_compose ("<span size=\"%1\">" TXTSPAN "%3</span> <span foreground=\"green\">%2</span></span>",
1286                                                           INFO_FONT_SIZE, buf, _("Tempo")));
1287
1288                 sprintf (buf, "%g/%g", m.meter().divisions_per_bar(), m.meter().note_divisor());
1289                 _right_layout->set_markup (string_compose ("<span size=\"%1\">" TXTSPAN "%3</span> <span foreground=\"green\">%2</span></span>",
1290                                                            INFO_FONT_SIZE, buf, _("Meter")));
1291         }
1292 }
1293
1294 void
1295 AudioClock::set_session (Session *s)
1296 {
1297         SessionHandlePtr::set_session (s);
1298
1299         if (_session) {
1300
1301                 _session->config.ParameterChanged.connect (_session_connections, invalidator (*this), boost::bind (&AudioClock::session_configuration_changed, this, _1), gui_context());
1302                 _session->tempo_map().PropertyChanged.connect (_session_connections, invalidator (*this), boost::bind (&AudioClock::session_property_changed, this, _1), gui_context());
1303
1304                 const XMLProperty* prop;
1305                 XMLNode* node = _session->extra_xml (X_("ClockModes"));
1306                 AudioClock::Mode amode;
1307
1308                 if (node) {
1309                         for (XMLNodeList::const_iterator i = node->children().begin(); i != node->children().end(); ++i) {
1310                                 if ((prop = (*i)->property (X_("name"))) && prop->value() == _name) {
1311
1312                                         if ((prop = (*i)->property (X_("mode"))) != 0) {
1313                                                 amode = AudioClock::Mode (string_2_enum (prop->value(), amode));
1314                                                 set_mode (amode);
1315                                         }
1316                                         if ((prop = (*i)->property (X_("on"))) != 0) {
1317                                                 set_off (!string_is_affirmative (prop->value()));
1318                                         }
1319                                         break;
1320                                 }
1321                         }
1322                 }
1323
1324                 set (last_when, true);
1325         }
1326 }
1327
1328 bool
1329 AudioClock::on_key_press_event (GdkEventKey* ev)
1330 {
1331         if (!editing) {
1332                 return false;
1333         }
1334
1335         string new_text;
1336         char new_char = 0;
1337         int highlight_length;
1338         framepos_t pos;
1339
1340         switch (ev->keyval) {
1341         case GDK_0:
1342         case GDK_KP_0:
1343                 new_char = '0';
1344                 break;
1345         case GDK_1:
1346         case GDK_KP_1:
1347                 new_char = '1';
1348                 break;
1349         case GDK_2:
1350         case GDK_KP_2:
1351                 new_char = '2';
1352                 break;
1353         case GDK_3:
1354         case GDK_KP_3:
1355                 new_char = '3';
1356                 break;
1357         case GDK_4:
1358         case GDK_KP_4:
1359                 new_char = '4';
1360                 break;
1361         case GDK_5:
1362         case GDK_KP_5:
1363                 new_char = '5';
1364                 break;
1365         case GDK_6:
1366         case GDK_KP_6:
1367                 new_char = '6';
1368                 break;
1369         case GDK_7:
1370         case GDK_KP_7:
1371                 new_char = '7';
1372                 break;
1373         case GDK_8:
1374         case GDK_KP_8:
1375                 new_char = '8';
1376                 break;
1377         case GDK_9:
1378         case GDK_KP_9:
1379                 new_char = '9';
1380                 break;
1381
1382         case GDK_minus:
1383         case GDK_KP_Subtract:
1384                 if (_negative_allowed && input_string.empty()) {
1385                                 edit_is_negative = true;
1386                                 edit_string.replace(0,1,"-");
1387                                 _layout->set_text (edit_string);
1388                                 queue_draw ();
1389                 } else {
1390                         end_edit_relative (false);
1391                 }
1392                 return true;
1393                 break;
1394
1395         case GDK_plus:
1396                 end_edit_relative (true);
1397                 return true;
1398                 break;
1399
1400         case GDK_Tab:
1401         case GDK_Return:
1402         case GDK_KP_Enter:
1403                 end_edit (true);
1404                 return true;
1405                 break;
1406
1407         case GDK_Escape:
1408                 end_edit (false);
1409                 ChangeAborted();  /*  EMIT SIGNAL  */
1410                 return true;
1411
1412         case GDK_Delete:
1413         case GDK_BackSpace:
1414                 if (!input_string.empty()) {
1415                         /* delete the last key entered
1416                         */
1417                         input_string = input_string.substr (0, input_string.length() - 1);
1418                 }
1419                 goto use_input_string;
1420
1421         default:
1422                 /* do not allow other keys to passthru to the rest of the GUI
1423                    when editing.
1424                 */
1425                 return true;
1426         }
1427
1428         if (!insert_map.empty() && (input_string.length() >= insert_map.size())) {
1429                 /* too many digits: eat the key event, but do nothing with it */
1430                 return true;
1431         }
1432
1433         input_string.push_back (new_char);
1434
1435   use_input_string:
1436
1437         switch (_mode) {
1438         case Frames:
1439                 /* get this one in the right order, and to the right width */
1440                 if (ev->keyval == GDK_Delete || ev->keyval == GDK_BackSpace) {
1441                         edit_string = edit_string.substr (0, edit_string.length() - 1);
1442                 } else {
1443                         edit_string.push_back (new_char);
1444                 }
1445                 if (!edit_string.empty()) {
1446                         char buf[32];
1447                         sscanf (edit_string.c_str(), "%" PRId64, &pos);
1448                         snprintf (buf, sizeof (buf), " %10" PRId64, pos);
1449                         edit_string = buf;
1450                 }
1451                 /* highlight the whole thing */
1452                 highlight_length = edit_string.length();
1453                 break;
1454
1455         default:
1456                 highlight_length = merge_input_and_edit_string ();
1457         }
1458
1459         if (edit_is_negative) {
1460                 edit_string.replace(0,1,"-");
1461         } else {
1462                 if (!pre_edit_string.empty() && (pre_edit_string.at(0) == '-')) {
1463                         edit_string.replace(0,1,"_");
1464                 } else {
1465                         edit_string.replace(0,1," ");
1466                 }
1467         }
1468
1469         show_edit_status (highlight_length);
1470         _layout->set_text (edit_string);
1471         queue_draw ();
1472
1473         return true;
1474 }
1475
1476 int
1477 AudioClock::merge_input_and_edit_string ()
1478 {
1479         /* merge with pre-edit-string into edit string */
1480
1481         edit_string = pre_edit_string;
1482
1483         if (input_string.empty()) {
1484                 return 0;
1485         }
1486
1487         string::size_type target;
1488         for (string::size_type i = 0; i < input_string.length(); ++i) {
1489                 target = insert_map[input_string.length() - 1 - i];
1490                 edit_string[target] = input_string[i];
1491         }
1492         /* highlight from end to wherever the last character was added */
1493         return edit_string.length() - insert_map[input_string.length()-1];
1494 }
1495
1496
1497 bool
1498 AudioClock::on_key_release_event (GdkEventKey *ev)
1499 {
1500         if (!editing) {
1501                 return false;
1502         }
1503
1504         /* return true for keys that we used on press
1505            so that they cannot possibly do double-duty
1506         */
1507         switch (ev->keyval) {
1508         case GDK_0:
1509         case GDK_KP_0:
1510         case GDK_1:
1511         case GDK_KP_1:
1512         case GDK_2:
1513         case GDK_KP_2:
1514         case GDK_3:
1515         case GDK_KP_3:
1516         case GDK_4:
1517         case GDK_KP_4:
1518         case GDK_5:
1519         case GDK_KP_5:
1520         case GDK_6:
1521         case GDK_KP_6:
1522         case GDK_7:
1523         case GDK_KP_7:
1524         case GDK_8:
1525         case GDK_KP_8:
1526         case GDK_9:
1527         case GDK_KP_9:
1528         case GDK_period:
1529         case GDK_comma:
1530         case GDK_KP_Decimal:
1531         case GDK_Tab:
1532         case GDK_Return:
1533         case GDK_KP_Enter:
1534         case GDK_Escape:
1535         case GDK_minus:
1536         case GDK_plus:
1537         case GDK_KP_Add:
1538         case GDK_KP_Subtract:
1539                 return true;
1540         default:
1541                 return false;
1542         }
1543 }
1544
1545 AudioClock::Field
1546 AudioClock::index_to_field (int index) const
1547 {
1548         switch (_mode) {
1549         case Timecode:
1550                 if (index < 4) {
1551                         return Timecode_Hours;
1552                 } else if (index < 7) {
1553                         return Timecode_Minutes;
1554                 } else if (index < 10) {
1555                         return Timecode_Seconds;
1556                 } else {
1557                         return Timecode_Frames;
1558                 }
1559                 break;
1560         case BBT:
1561                 if (index < 5) {
1562                         return Bars;
1563                 } else if (index < 7) {
1564                         return Beats;
1565                 } else {
1566                         return Ticks;
1567                 }
1568                 break;
1569         case MinSec:
1570                 if (index < 3) {
1571                         return Timecode_Hours;
1572                 } else if (index < 6) {
1573                         return MS_Minutes;
1574                 } else if (index < 9) {
1575                         return MS_Seconds;
1576                 } else {
1577                         return MS_Milliseconds;
1578                 }
1579                 break;
1580         case Frames:
1581                 return AudioFrames;
1582                 break;
1583         }
1584
1585         return Field (0);
1586 }
1587
1588 bool
1589 AudioClock::on_button_press_event (GdkEventButton *ev)
1590 {
1591         switch (ev->button) {
1592         case 1:
1593                 if (editable && !_off) {
1594                         int index;
1595                         int trailing;
1596                         int y;
1597                         int x;
1598
1599                         /* the text has been centered vertically, so adjust
1600                          * x and y.
1601                          */
1602                         int xcenter = (get_width() - layout_width) /2;
1603
1604                         y = ev->y - ((upper_height - layout_height)/2);
1605                         x = ev->x - xcenter;
1606
1607                         if (!_layout->xy_to_index (x * PANGO_SCALE, y * PANGO_SCALE, index, trailing)) {
1608                                 /* pretend it is a character on the far right */
1609                                 index = 99;
1610                         }
1611                         drag_field = index_to_field (index);
1612                         dragging = true;
1613                         /* make absolutely sure that the pointer is grabbed */
1614                         gdk_pointer_grab(ev->window,false ,
1615                                          GdkEventMask( Gdk::POINTER_MOTION_MASK | Gdk::BUTTON_PRESS_MASK |Gdk::BUTTON_RELEASE_MASK),
1616                                          NULL,NULL,ev->time);
1617                         drag_accum = 0;
1618                         drag_start_y = ev->y;
1619                         drag_y = ev->y;
1620                 }
1621                 break;
1622
1623         default:
1624                 return false;
1625                 break;
1626         }
1627
1628         return true;
1629 }
1630
1631 bool
1632 AudioClock::on_button_release_event (GdkEventButton *ev)
1633 {
1634         if (editable && !_off) {
1635                 if (dragging) {
1636                         gdk_pointer_ungrab (GDK_CURRENT_TIME);
1637                         dragging = false;
1638                         if (ev->y > drag_start_y+1 || ev->y < drag_start_y-1 || Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)){
1639                                 // we actually dragged so return without
1640                                 // setting editing focus, or we shift clicked
1641                                 return true;
1642                         } else {
1643                                 if (ev->button == 1) {
1644
1645                                         if (_edit_by_click_field) {
1646
1647                                                 int xcenter = (get_width() - layout_width) /2;
1648                                                 int index = 0;
1649                                                 int trailing;
1650                                                 int y = ev->y - ((upper_height - layout_height)/2);
1651                                                 int x = ev->x - xcenter;
1652                                                 Field f;
1653
1654                                                 if (!_layout->xy_to_index (x * PANGO_SCALE, y * PANGO_SCALE, index, trailing)) {
1655                                                         return true;
1656                                                 }
1657
1658                                                 f = index_to_field (index);
1659
1660                                                 switch (f) {
1661                                                 case Timecode_Frames:
1662                                                 case MS_Milliseconds:
1663                                                 case Ticks:
1664                                                         f = Field (0);
1665                                                         break;
1666                                                 default:
1667                                                         break;
1668                                                 }
1669                                                 start_edit (f);
1670                                         } else {
1671                                                 start_edit ();
1672                                         }
1673                                 }
1674                         }
1675                 }
1676         }
1677
1678         if (Keyboard::is_context_menu_event (ev)) {
1679                 if (ops_menu == 0) {
1680                         build_ops_menu ();
1681                 }
1682                 ops_menu->popup (1, ev->time);
1683                 return true;
1684         }
1685
1686         return false;
1687 }
1688
1689 bool
1690 AudioClock::on_focus_out_event (GdkEventFocus* ev)
1691 {
1692         bool ret = CairoWidget::on_focus_out_event (ev);
1693
1694         if (editing) {
1695                 end_edit (_accept_on_focus_out);
1696         }
1697
1698         return ret;
1699 }
1700
1701 bool
1702 AudioClock::on_scroll_event (GdkEventScroll *ev)
1703 {
1704         int index;
1705         int trailing;
1706
1707         if (editing || _session == 0 || !editable || _off) {
1708                 return false;
1709         }
1710
1711         int y;
1712         int x;
1713
1714         /* the text has been centered vertically, so adjust
1715          * x and y.
1716          */
1717
1718         int xcenter = (get_width() - layout_width) /2;
1719         y = ev->y - ((upper_height - layout_height)/2);
1720         x = ev->x - xcenter;
1721
1722         if (!_layout->xy_to_index (x * PANGO_SCALE, y * PANGO_SCALE, index, trailing)) {
1723                 /* not in the main layout */
1724                 return false;
1725         }
1726
1727         Field f = index_to_field (index);
1728         framepos_t frames = 0;
1729
1730         switch (ev->direction) {
1731
1732         case GDK_SCROLL_UP:
1733                 frames = get_frame_step (f);
1734                 if (frames != 0) {
1735                         if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
1736                                 frames *= 10;
1737                         }
1738                         set (current_time() + frames, true);
1739                         ValueChanged (); /* EMIT_SIGNAL */
1740                 }
1741                 break;
1742
1743         case GDK_SCROLL_DOWN:
1744                 frames = get_frame_step (f);
1745                 if (frames != 0) {
1746                         if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
1747                                 frames *= 10;
1748                         }
1749
1750                         if (!_negative_allowed && (double)current_time() - (double)frames < 0.0) {
1751                                 set (0, true);
1752                         } else {
1753                                 set (current_time() - frames, true);
1754                         }
1755
1756                         ValueChanged (); /* EMIT_SIGNAL */
1757                 }
1758                 break;
1759
1760         default:
1761                 return false;
1762                 break;
1763         }
1764
1765         return true;
1766 }
1767
1768 bool
1769 AudioClock::on_motion_notify_event (GdkEventMotion *ev)
1770 {
1771         if (editing || _session == 0 || !dragging) {
1772                 return false;
1773         }
1774
1775         float pixel_frame_scale_factor = 0.2f;
1776
1777         if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier))  {
1778                 pixel_frame_scale_factor = 0.1f;
1779         }
1780
1781
1782         if (Keyboard::modifier_state_contains (ev->state,
1783                                                Keyboard::PrimaryModifier|Keyboard::SecondaryModifier)) {
1784
1785                 pixel_frame_scale_factor = 0.025f;
1786         }
1787
1788         double y_delta = ev->y - drag_y;
1789
1790         drag_accum +=  y_delta*pixel_frame_scale_factor;
1791
1792         drag_y = ev->y;
1793
1794         if (floor (drag_accum) != 0) {
1795
1796                 framepos_t frames;
1797                 framepos_t pos;
1798                 int dir;
1799                 dir = (drag_accum < 0 ? 1:-1);
1800                 pos = current_time();
1801                 frames = get_frame_step (drag_field, pos, dir);
1802
1803                 if (frames  != 0 &&  frames * drag_accum < current_time()) {
1804                         set ((framepos_t) floor (pos - drag_accum * frames), false); // minus because up is negative in GTK
1805                 } else {
1806                         set (0 , false);
1807                 }
1808
1809                 drag_accum= 0;
1810                 ValueChanged();  /* EMIT_SIGNAL */
1811         }
1812
1813         return true;
1814 }
1815
1816 framepos_t
1817 AudioClock::get_frame_step (Field field, framepos_t pos, int dir)
1818 {
1819         framecnt_t f = 0;
1820         Timecode::BBT_Time BBT;
1821         switch (field) {
1822         case Timecode_Hours:
1823                 f = (framecnt_t) floor (3600.0 * _session->frame_rate());
1824                 break;
1825         case Timecode_Minutes:
1826                 f = (framecnt_t) floor (60.0 * _session->frame_rate());
1827                 break;
1828         case Timecode_Seconds:
1829                 f = _session->frame_rate();
1830                 break;
1831         case Timecode_Frames:
1832                 f = (framecnt_t) floor (_session->frame_rate() / _session->timecode_frames_per_second());
1833                 break;
1834
1835         case AudioFrames:
1836                 f = 1;
1837                 break;
1838
1839         case MS_Hours:
1840                 f = (framecnt_t) floor (3600.0 * _session->frame_rate());
1841                 break;
1842         case MS_Minutes:
1843                 f = (framecnt_t) floor (60.0 * _session->frame_rate());
1844                 break;
1845         case MS_Seconds:
1846                 f = (framecnt_t) _session->frame_rate();
1847                 break;
1848         case MS_Milliseconds:
1849                 f = (framecnt_t) floor (_session->frame_rate() / 1000.0);
1850                 break;
1851
1852         case Bars:
1853                 BBT.bars = 1;
1854                 BBT.beats = 0;
1855                 BBT.ticks = 0;
1856                 f = _session->tempo_map().bbt_duration_at (pos,BBT,dir);
1857                 break;
1858         case Beats:
1859                 BBT.bars = 0;
1860                 BBT.beats = 1;
1861                 BBT.ticks = 0;
1862                 f = _session->tempo_map().bbt_duration_at(pos,BBT,dir);
1863                 break;
1864         case Ticks:
1865                 BBT.bars = 0;
1866                 BBT.beats = 0;
1867                 BBT.ticks = 1;
1868                 f = _session->tempo_map().bbt_duration_at(pos,BBT,dir);
1869                 break;
1870         default:
1871                 error << string_compose (_("programming error: %1"), "attempt to get frames from non-text field!") << endmsg;
1872                 f = 0;
1873                 break;
1874         }
1875
1876         return f;
1877 }
1878
1879 framepos_t
1880 AudioClock::current_time (framepos_t) const
1881 {
1882         return last_when;
1883 }
1884
1885 framepos_t
1886 AudioClock::current_duration (framepos_t pos) const
1887 {
1888         framepos_t ret = 0;
1889
1890         switch (_mode) {
1891         case Timecode:
1892                 ret = last_when;
1893                 break;
1894         case BBT:
1895                 ret = frame_duration_from_bbt_string (pos, _layout->get_text());
1896                 break;
1897
1898         case MinSec:
1899                 ret = last_when;
1900                 break;
1901
1902         case Frames:
1903                 ret = last_when;
1904                 break;
1905         }
1906
1907         return ret;
1908 }
1909
1910 bool
1911 AudioClock::bbt_validate_edit (const string& str)
1912 {
1913         AnyTime any;
1914
1915         if (sscanf (str.c_str(), BBT_SCANF_FORMAT, &any.bbt.bars, &any.bbt.beats, &any.bbt.ticks) != 3) {
1916                 return false;
1917         }
1918
1919         if (any.bbt.ticks > Timecode::BBT_Time::ticks_per_beat) {
1920                 return false;
1921         }
1922
1923         if (!is_duration && any.bbt.bars == 0) {
1924                 return false;
1925         }
1926
1927         if (!is_duration && any.bbt.beats == 0) {
1928                 return false;
1929         }
1930
1931         return true;
1932 }
1933
1934 bool
1935 AudioClock::timecode_validate_edit (const string&)
1936 {
1937         Timecode::Time TC;
1938         int hours;
1939         char ignored[2];
1940
1941         if (sscanf (_layout->get_text().c_str(), "%[- _]%" PRId32 ":%" PRId32 ":%" PRId32 "%[:;]%" PRId32,
1942                     ignored, &hours, &TC.minutes, &TC.seconds, ignored, &TC.frames) != 6) {
1943                 return false;
1944         }
1945
1946         if (hours < 0) {
1947                 TC.hours = hours * -1;
1948                 TC.negative = true;
1949         } else {
1950                 TC.hours = hours;
1951                 TC.negative = false;
1952         }
1953
1954         if (TC.negative && !_negative_allowed) {
1955                 return false;
1956         }
1957
1958         if (TC.hours > 23U || TC.minutes > 59U || TC.seconds > 59U) {
1959                 return false;
1960         }
1961
1962         if (TC.frames > (uint32_t) rint (_session->timecode_frames_per_second()) - 1) {
1963                 return false;
1964         }
1965
1966         if (_session->timecode_drop_frames()) {
1967                 if (TC.minutes % 10 && TC.seconds == 0U && TC.frames < 2U) {
1968                         return false;
1969                 }
1970         }
1971
1972         return true;
1973 }
1974
1975 bool
1976 AudioClock::minsec_validate_edit (const string& str)
1977 {
1978         int hrs, mins, secs, millisecs;
1979
1980         if (sscanf (str.c_str(), "%d:%d:%d.%d", &hrs, &mins, &secs, &millisecs) != 4) {
1981                 return false;
1982         }
1983
1984         if (hrs > 23 || mins > 59 || secs > 59 || millisecs > 999) {
1985                 return false;
1986         }
1987
1988         return true;
1989 }
1990
1991 framepos_t
1992 AudioClock::frames_from_timecode_string (const string& str) const
1993 {
1994         if (_session == 0) {
1995                 return 0;
1996         }
1997
1998         Timecode::Time TC;
1999         framepos_t sample;
2000         char ignored[2];
2001         int hours;
2002
2003         if (sscanf (str.c_str(), "%[- _]%d:%d:%d%[:;]%d", ignored, &hours, &TC.minutes, &TC.seconds, ignored, &TC.frames) != 6) {
2004                 error << string_compose (_("programming error: %1 %2"), "badly formatted timecode clock string", str) << endmsg;
2005                 return 0;
2006         }
2007         TC.hours = abs(hours);
2008         TC.rate = _session->timecode_frames_per_second();
2009         TC.drop= _session->timecode_drop_frames();
2010
2011         _session->timecode_to_sample (TC, sample, false /* use_offset */, false /* use_subframes */ );
2012
2013         // timecode_tester ();
2014         if (edit_is_negative) {
2015                 sample = - sample;
2016         }
2017
2018         return sample;
2019 }
2020
2021 framepos_t
2022 AudioClock::frames_from_minsec_string (const string& str) const
2023 {
2024         if (_session == 0) {
2025                 return 0;
2026         }
2027
2028         int hrs, mins, secs, millisecs;
2029         framecnt_t sr = _session->frame_rate();
2030
2031         if (sscanf (str.c_str(), "%d:%d:%d.%d", &hrs, &mins, &secs, &millisecs) != 4) {
2032                 error << string_compose (_("programming error: %1 %2"), "badly formatted minsec clock string", str) << endmsg;
2033                 return 0;
2034         }
2035
2036         return (framepos_t) floor ((hrs * 60.0f * 60.0f * sr) + (mins * 60.0f * sr) + (secs * sr) + (millisecs * sr / 1000.0));
2037 }
2038
2039 framepos_t
2040 AudioClock::frames_from_bbt_string (framepos_t pos, const string& str) const
2041 {
2042         if (_session == 0) {
2043                 error << "AudioClock::current_time() called with BBT mode but without session!" << endmsg;
2044                 return 0;
2045         }
2046
2047         AnyTime any;
2048         any.type = AnyTime::BBT;
2049
2050         if (sscanf (str.c_str(), BBT_SCANF_FORMAT, &any.bbt.bars, &any.bbt.beats, &any.bbt.ticks) != 3) {
2051                 return 0;
2052         }
2053
2054         if (is_duration) {
2055                 any.bbt.bars++;
2056                 any.bbt.beats++;
2057                 return _session->any_duration_to_frames (pos, any);
2058         } else {
2059                 return _session->convert_to_frames (any);
2060         }
2061 }
2062
2063
2064 framepos_t
2065 AudioClock::frame_duration_from_bbt_string (framepos_t pos, const string& str) const
2066 {
2067         if (_session == 0) {
2068                 error << "AudioClock::frame_duration_from_bbt_string() called with BBT mode but without session!" << endmsg;
2069                 return 0;
2070         }
2071
2072         Timecode::BBT_Time bbt;
2073
2074         if (sscanf (str.c_str(), BBT_SCANF_FORMAT, &bbt.bars, &bbt.beats, &bbt.ticks) != 3) {
2075                 return 0;
2076         }
2077
2078         return _session->tempo_map().bbt_duration_at(pos,bbt,1);
2079 }
2080
2081 framepos_t
2082 AudioClock::frames_from_audioframes_string (const string& str) const
2083 {
2084         framepos_t f;
2085         sscanf (str.c_str(), "%" PRId64, &f);
2086         return f;
2087 }
2088
2089 void
2090 AudioClock::copy_text_to_clipboard () const
2091 {
2092         string val;
2093         if (editing) {
2094                 val = pre_edit_string;
2095         } else {
2096                 val = _layout->get_text ();
2097         }
2098         const size_t trim = val.find_first_not_of(" ");
2099         if (trim == string::npos) {
2100                 assert(0); // empty clock, can't be right.
2101                 return;
2102         }
2103         Glib::RefPtr<Clipboard> cl = Gtk::Clipboard::get();
2104         cl->set_text (val.substr(trim));
2105 }
2106
2107 void
2108 AudioClock::build_ops_menu ()
2109 {
2110         using namespace Menu_Helpers;
2111         ops_menu = new Menu;
2112         MenuList& ops_items = ops_menu->items();
2113         ops_menu->set_name ("ArdourContextMenu");
2114
2115         if (!Profile->get_sae()) {
2116                 ops_items.push_back (MenuElem (_("Timecode"), sigc::bind (sigc::mem_fun(*this, &AudioClock::set_mode), Timecode)));
2117         }
2118         ops_items.push_back (MenuElem (_("Bars:Beats"), sigc::bind (sigc::mem_fun(*this, &AudioClock::set_mode), BBT)));
2119         ops_items.push_back (MenuElem (_("Minutes:Seconds"), sigc::bind (sigc::mem_fun(*this, &AudioClock::set_mode), MinSec)));
2120         ops_items.push_back (MenuElem (_("Samples"), sigc::bind (sigc::mem_fun(*this, &AudioClock::set_mode), Frames)));
2121
2122         if (editable && !_off && !is_duration && !_follows_playhead) {
2123                 ops_items.push_back (SeparatorElem());
2124                 ops_items.push_back (MenuElem (_("Set From Playhead"), sigc::mem_fun(*this, &AudioClock::set_from_playhead)));
2125                 ops_items.push_back (MenuElem (_("Locate to This Time"), sigc::mem_fun(*this, &AudioClock::locate)));
2126         }
2127         ops_items.push_back (SeparatorElem());
2128         ops_items.push_back (MenuElem (_("Copy to clipboard"), sigc::mem_fun(*this, &AudioClock::copy_text_to_clipboard)));
2129 }
2130
2131 void
2132 AudioClock::set_from_playhead ()
2133 {
2134         if (!_session) {
2135                 return;
2136         }
2137
2138         set (_session->transport_frame());
2139         ValueChanged ();
2140 }
2141
2142 void
2143 AudioClock::locate ()
2144 {
2145         if (!_session || is_duration) {
2146                 return;
2147         }
2148
2149         _session->request_locate (current_time(), _session->transport_rolling ());
2150 }
2151
2152 void
2153 AudioClock::set_mode (Mode m)
2154 {
2155         if (_mode == m) {
2156                 return;
2157         }
2158
2159         _mode = m;
2160
2161         insert_map.clear();
2162
2163         _layout->set_text ("");
2164
2165         Gtk::Requisition req;
2166         set_clock_dimensions (req);
2167
2168         if (_left_layout) {
2169
2170                 _left_layout->set_attributes (info_attributes);
2171                 _right_layout->set_attributes (info_attributes);
2172                 /* adjust info_height according to font size */
2173                 int ignored;
2174                 _left_layout->set_text (" 1234567890");
2175                 _left_layout->get_pixel_size (ignored, info_height);
2176
2177                 _left_layout->set_text ("");
2178                 _right_layout->set_text ("");
2179         }
2180
2181         switch (_mode) {
2182         case Timecode:
2183                 mode_based_info_ratio = 0.6;
2184                 insert_map.push_back (11);
2185                 insert_map.push_back (10);
2186                 insert_map.push_back (8);
2187                 insert_map.push_back (7);
2188                 insert_map.push_back (5);
2189                 insert_map.push_back (4);
2190                 insert_map.push_back (2);
2191                 insert_map.push_back (1);
2192                 break;
2193
2194         case BBT:
2195                 mode_based_info_ratio = 0.5;
2196                 insert_map.push_back (11);
2197                 insert_map.push_back (10);
2198                 insert_map.push_back (9);
2199                 insert_map.push_back (8);
2200                 insert_map.push_back (6);
2201                 insert_map.push_back (5);
2202                 insert_map.push_back (3);
2203                 insert_map.push_back (2);
2204                 insert_map.push_back (1);
2205                 break;
2206
2207         case MinSec:
2208                 mode_based_info_ratio = 0.6;
2209                 insert_map.push_back (12);
2210                 insert_map.push_back (11);
2211                 insert_map.push_back (10);
2212                 insert_map.push_back (8);
2213                 insert_map.push_back (7);
2214                 insert_map.push_back (5);
2215                 insert_map.push_back (4);
2216                 insert_map.push_back (2);
2217                 insert_map.push_back (1);
2218                 break;
2219
2220         case Frames:
2221                 mode_based_info_ratio = 0.45;
2222                 break;
2223         }
2224
2225         set (last_when, true);
2226
2227         if (!is_transient) {
2228                 ModeChanged (); /* EMIT SIGNAL (the static one)*/
2229         }
2230
2231         mode_changed (); /* EMIT SIGNAL (the member one) */
2232 }
2233
2234 void
2235 AudioClock::set_bbt_reference (framepos_t pos)
2236 {
2237         bbt_reference_time = pos;
2238 }
2239
2240 void
2241 AudioClock::on_style_changed (const Glib::RefPtr<Gtk::Style>& old_style)
2242 {
2243         CairoWidget::on_style_changed (old_style);
2244
2245         Gtk::Requisition req;
2246         set_clock_dimensions (req);
2247
2248         /* XXXX fix me ... we shouldn't be using GTK styles anyway */
2249         // set_font ();
2250         set_colors ();
2251 }
2252
2253 void
2254 AudioClock::set_editable (bool yn)
2255 {
2256         editable = yn;
2257 }
2258
2259 void
2260 AudioClock::set_is_duration (bool yn)
2261 {
2262         if (yn == is_duration) {
2263                 return;
2264         }
2265
2266         is_duration = yn;
2267         set (last_when, true);
2268 }
2269
2270 void
2271 AudioClock::set_off (bool yn)
2272 {
2273         if (_off == yn) {
2274                 return;
2275         }
2276
2277         _off = yn;
2278
2279         /* force a redraw. last_when will be preserved, but the clock text will
2280          * change
2281          */
2282
2283         set (last_when, true);
2284 }
2285
2286 void
2287 AudioClock::focus ()
2288 {
2289         start_edit (Field (0));
2290 }
2291
2292 void
2293 AudioClock::set_corner_radius (double r)
2294 {
2295         corner_radius = r;
2296         first_width = 0;
2297         first_height = 0;
2298         queue_resize ();
2299 }
2300
2301 void
2302 AudioClock::dpi_reset ()
2303 {
2304         /* force recomputation of size even if we are fixed width
2305          */
2306         first_width = 0;
2307         first_height = 0;
2308         queue_resize ();
2309 }
2310
2311 void
2312 AudioClock::set_negative_allowed (bool yn)
2313 {
2314         _negative_allowed = yn;
2315 }