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