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