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