4299a74075be3ae780e0099720bb3c622dfba41e
[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
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         , font_size (10240)
87         , editing (false)
88         , bbt_reference_time (-1)
89         , last_when(0)
90         , last_pdelta (0)
91         , last_sdelta (0)
92         , dragging (false)
93         , drag_field (Field (0))
94
95 {
96         set_flags (CAN_FOCUS);
97
98         _layout = Pango::Layout::create (get_pango_context());
99         _layout->set_attributes (normal_attributes);
100
101         if (with_info) {
102                 _left_layout = Pango::Layout::create (get_pango_context());
103                 _right_layout = Pango::Layout::create (get_pango_context());
104         }
105
106         set_widget_name (widget_name);
107
108         _mode = BBT; /* lie to force mode switch */
109         set_mode (Timecode);
110         set (last_when, true);
111
112         if (!is_transient) {
113                 clocks.push_back (this);
114         }
115
116         ColorsChanged.connect (sigc::mem_fun (*this, &AudioClock::set_colors));
117         DPIReset.connect (sigc::mem_fun (*this, &AudioClock::dpi_reset));
118 }
119
120 AudioClock::~AudioClock ()
121 {
122         delete foreground_attr;
123         delete editing_attr;
124 }
125
126 void
127 AudioClock::set_widget_name (const string& str)
128 {
129         if (str.empty()) {
130                 set_name ("clock");
131         } else {
132                 set_name (str + " clock");
133         }
134
135         if (is_realized()) {
136                 set_colors ();
137         }
138 }
139
140
141 void
142 AudioClock::on_realize ()
143 {
144         CairoWidget::on_realize ();
145         set_font ();
146         set_colors ();
147 }
148
149 void
150 AudioClock::set_font ()
151 {
152         Glib::RefPtr<Gtk::Style> style = get_style ();
153         Pango::FontDescription font; 
154         Pango::AttrFontDesc* font_attr;
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
182         /* get the figure width for the font. This doesn't have to super
183          * accurate since we only use it to measure the (roughly 1 character)
184          * offset from the position Pango tells us for the "cursor"
185          */
186
187         Glib::RefPtr<Pango::Layout> tmp = Pango::Layout::create (get_pango_context());
188         int ignore_height;
189
190         tmp->set_text ("8");
191         tmp->get_pixel_size (em_width, ignore_height);
192 }
193
194 void
195 AudioClock::set_active_state (Gtkmm2ext::ActiveState s)
196 {
197         CairoWidget::set_active_state (s);
198         set_colors ();
199 }
200
201 void
202 AudioClock::set_colors ()
203 {
204         int r, g, b, a;
205
206         uint32_t bg_color;
207         uint32_t text_color;
208         uint32_t editing_color;
209         uint32_t cursor_color;
210
211         if (active_state()) {
212                 bg_color = ARDOUR_UI::config()->color_by_name (string_compose ("%1 active: background", get_name()));
213                 text_color = ARDOUR_UI::config()->color_by_name (string_compose ("%1 active: text", get_name()));
214                 editing_color = ARDOUR_UI::config()->color_by_name (string_compose ("%1 active: edited text", get_name()));
215                 cursor_color = ARDOUR_UI::config()->color_by_name (string_compose ("%1 active: cursor", get_name()));
216         } else {
217                 bg_color = ARDOUR_UI::config()->color_by_name (string_compose ("%1: background", get_name()));
218                 text_color = ARDOUR_UI::config()->color_by_name (string_compose ("%1: text", get_name()));
219                 editing_color = ARDOUR_UI::config()->color_by_name (string_compose ("%1: edited text", get_name()));
220                 cursor_color = ARDOUR_UI::config()->color_by_name (string_compose ("%1: cursor", get_name()));
221         }
222
223         /* store for bg and cursor in render() */
224
225         UINT_TO_RGBA (bg_color, &r, &g, &b, &a);
226
227         bg_r = r/255.0;
228         bg_g = g/255.0;
229         bg_b = b/255.0;
230         bg_a = a/255.0;
231
232         UINT_TO_RGBA (cursor_color, &r, &g, &b, &a);
233
234         cursor_r = r/255.0;
235         cursor_g = g/255.0;
236         cursor_b = b/255.0;
237         cursor_a = a/255.0;
238
239         /* rescale for Pango colors ... sigh */
240
241         r = lrint (r * 65535.0);
242         g = lrint (g * 65535.0);
243         b = lrint (b * 65535.0);
244
245         UINT_TO_RGBA (text_color, &r, &g, &b, &a);
246         r = lrint ((r/255.0) * 65535.0);
247         g = lrint ((g/255.0) * 65535.0);
248         b = lrint ((b/255.0) * 65535.0);
249         foreground_attr = new Pango::AttrColor (Pango::Attribute::create_attr_foreground (r, g, b));
250
251         UINT_TO_RGBA (editing_color, &r, &g, &b, &a);
252         r = lrint ((r/255.0) * 65535.0);
253         g = lrint ((g/255.0) * 65535.0);
254         b = lrint ((b/255.0) * 65535.0);
255         editing_attr = new Pango::AttrColor (Pango::Attribute::create_attr_foreground (r, g, b));
256         
257         normal_attributes.change (*foreground_attr);
258         info_attributes.change (*foreground_attr);
259         editing_attributes.change (*foreground_attr);
260         editing_attributes.change (*editing_attr);
261
262         if (!editing) {
263                 _layout->set_attributes (normal_attributes);
264         } else {
265                 _layout->set_attributes (editing_attributes);
266         }
267
268         if (_left_layout) {
269                 _left_layout->set_attributes (info_attributes);
270                 _right_layout->set_attributes (info_attributes);
271         }
272
273         queue_draw ();
274 }
275
276 void
277 AudioClock::render (cairo_t* cr)
278 {
279         /* main layout: rounded rect, plus the text */
280         
281         if (_need_bg) {
282                 cairo_set_source_rgba (cr, bg_r, bg_g, bg_b, bg_a);
283                 if (corner_radius) {
284                         if (_left_layout) {
285                                 Gtkmm2ext::rounded_top_half_rectangle (cr, 0, 0, get_width(), upper_height, corner_radius);
286                         } else {
287                                 Gtkmm2ext::rounded_rectangle (cr, 0, 0, get_width(), upper_height, corner_radius);
288                         }
289                 } else {
290                         cairo_rectangle (cr, 0, 0, get_width(), upper_height);
291                 }
292                 cairo_fill (cr);
293         }
294
295         if (!_fixed_width) {
296                 cairo_move_to (cr, layout_x_offset, 0);
297         } else {
298                 int xcenter = layout_x_offset != 0 ? 0 : (get_width() - _mode_width[_mode]) /2;
299                 cairo_move_to (cr, layout_x_offset + xcenter, (upper_height - layout_height) / 2.0);
300         }
301
302         pango_cairo_show_layout (cr, _layout->gobj());
303
304         if (_left_layout) {
305
306                 double h = get_height() - upper_height - separator_height;
307
308                 if (_need_bg) {
309                         cairo_set_source_rgba (cr, bg_r, bg_g, bg_b, bg_a);
310                 }
311
312                 if (mode_based_info_ratio != 1.0) {
313
314                         double left_rect_width = round (((get_width() - separator_height) * mode_based_info_ratio) + 0.5);
315
316                         if (_need_bg) {
317                                 if (corner_radius) {
318                                         Gtkmm2ext::rounded_bottom_half_rectangle (cr, 0, upper_height + separator_height, left_rect_width, h, corner_radius);
319                                 } else {
320                                         cairo_rectangle (cr, 0, upper_height + separator_height, left_rect_width, h);
321                                 }
322                                 cairo_fill (cr);
323                         }
324
325                         cairo_move_to (cr, x_leading_padding, upper_height + separator_height + ((h - info_height)/2.0));
326                         pango_cairo_show_layout (cr, _left_layout->gobj());
327                         
328                         if (_need_bg) {
329                                 if (corner_radius) {
330                                         Gtkmm2ext::rounded_bottom_half_rectangle (cr, left_rect_width + separator_height, upper_height + separator_height, 
331                                                                                   get_width() - separator_height - left_rect_width, h, 
332                                                                                   corner_radius);
333                                 } else {
334                                         cairo_rectangle (cr, left_rect_width + separator_height, upper_height + separator_height, 
335                                                          get_width() - separator_height - left_rect_width, h);
336                                 }
337                                 cairo_fill (cr);        
338                         }
339
340
341                         if (_right_layout->get_alignment() == Pango::ALIGN_RIGHT) {
342                                 /* right-align does not work per se beacuse layout width is unset.
343                                  * Using _right_layout->set_width([value >=0]) would also enable
344                                  * word-wrapping which is not wanted here.
345                                  * The solution is to custom align the layout depending on its size.
346                                  * if it is larger than the available space it will be cropped on the
347                                  * right edge rather than override text on the left side.
348                                  */
349                                 int x, rw, rh;
350                                 _right_layout->get_pixel_size(rw, rh);
351                                 x = get_width() - rw- separator_height - x_leading_padding;
352                                 if (x < x_leading_padding + left_rect_width + separator_height) {
353                                         x = x_leading_padding + left_rect_width + separator_height;
354                                 }
355                                 cairo_move_to (cr, x, upper_height + separator_height + ((h - rh)/2.0));
356                         } else {
357                                 cairo_move_to (cr, x_leading_padding + left_rect_width + separator_height, upper_height + separator_height + ((h - info_height)/2.0));
358                         }
359                         pango_cairo_show_layout (cr, _right_layout->gobj());
360
361                 } else {
362                         /* no info to display, or just one */
363
364                         if (_need_bg) {
365                                 if (corner_radius) {
366                                         Gtkmm2ext::rounded_bottom_half_rectangle (cr, 0, upper_height + separator_height, get_width(), h, corner_radius);
367                                 } else {
368                                         cairo_rectangle (cr, 0, upper_height + separator_height, get_width(), h);
369                                 }
370                                 cairo_fill (cr);
371                         }
372                 }
373         }
374
375         if (editing) {
376                 if (!insert_map.empty()) {
377
378                         int xcenter = layout_x_offset != 0 ? 0 : (get_width() - _mode_width[_mode]) /2;
379
380                         if (input_string.length() < insert_map.size()) {
381                                 Pango::Rectangle cursor;
382                                 
383                                 if (input_string.empty()) {
384                                         /* nothing entered yet, put cursor at the end
385                                            of string
386                                         */
387                                         cursor = _layout->get_cursor_strong_pos (edit_string.length() - 1);
388                                 } else {
389                                         cursor = _layout->get_cursor_strong_pos (insert_map[input_string.length()]);
390                                 }
391                                 
392                                 cairo_set_source_rgba (cr, cursor_r, cursor_g, cursor_b, cursor_a);
393                                 if (!_fixed_width) {
394                                         cairo_rectangle (cr, 
395                                                          min (get_width() - 2.0, 
396                                                               (double) cursor.get_x()/PANGO_SCALE + layout_x_offset + xcenter + em_width), 0,
397                                                          2.0, cursor.get_height()/PANGO_SCALE);
398                                 } else {
399                                         cairo_rectangle (cr, 
400                                                          min (get_width() - 2.0, 
401                                                               (double) layout_x_offset + xcenter + cursor.get_x()/PANGO_SCALE + em_width),
402                                                          (upper_height - layout_height)/2.0, 
403                                                          2.0, cursor.get_height()/PANGO_SCALE);
404                                 }
405                                 cairo_fill (cr);        
406                         } else {
407                                 /* we've entered all possible digits, no cursor */
408                         }
409
410                 } else {
411                         if (input_string.empty()) {
412                                 cairo_set_source_rgba (cr, cursor_r, cursor_g, cursor_b, cursor_a);
413                                 if (!_fixed_width) {
414                                         cairo_rectangle (cr, 
415                                                          (get_width()/2.0),
416                                                          0,
417                                                          2.0, upper_height);
418                                 } else {
419                                         cairo_rectangle (cr, 
420                                                          (get_width()/2.0),
421                                                          (upper_height - layout_height)/2.0, 
422                                                          2.0, upper_height);
423                                 }
424                                 cairo_fill (cr);
425                         }
426                 }
427         }
428 }
429
430 void
431 AudioClock::on_size_allocate (Gtk::Allocation& alloc)
432 {
433         CairoWidget::on_size_allocate (alloc);
434         
435         if (_left_layout) {
436                 upper_height = (get_height()/2.0) - 1.0;
437         } else {
438                 upper_height = get_height();
439         }
440
441         if (_fixed_width) {
442                 /* center display in available space
443                  * NB. this only works if the containing widget is not the
444                  * layout itself (eg. the session->property dialog)
445                  */
446                 layout_x_offset = (get_width() - layout_width)/2.0;
447         } else {
448                 /* left justify */
449                 layout_x_offset = 0;
450         }
451 }
452
453 void
454 AudioClock::on_size_request (Gtk::Requisition* req)
455 {
456         /* even for non fixed width clocks, the size we *ask* for never changes, 
457            even though the size we receive might. so once we've computed it,
458            just return it.
459         */
460         
461         if (first_width) {
462                 req->width = first_width;
463                 req->height = first_height;
464                 return;
465         }
466
467         Glib::RefPtr<Pango::Layout> tmp;
468         Glib::RefPtr<Gtk::Style> style = get_style ();
469         Pango::FontDescription font; 
470
471         tmp = Pango::Layout::create (get_pango_context());
472
473         if (!is_realized()) {
474                 font = get_font_for_style (get_name());
475         } else {
476                 font = style->get_font();
477         }
478
479         tmp->set_font_description (font);
480
481         if (_fixed_width) {
482                 int ignored;
483                 tmp->set_text ("-88:88:88:88");
484                 tmp->get_pixel_size (_mode_width[Timecode], ignored);
485                 tmp->set_text (" 8888|88|8888");
486                 tmp->get_pixel_size (_mode_width[BBT], ignored);
487                 tmp->set_text (" 88:88:88,888");
488                 tmp->get_pixel_size (_mode_width[MinSec], ignored);
489                 tmp->set_text (" 8888888888");
490                 tmp->get_pixel_size (_mode_width[Frames], ignored);
491
492                 /* this string is the longest thing we will ever display,
493                    and also includes the BBT bar char that may descends below
494                    the baseline a bit, and a comma for the minsecs mode
495                    where we printf a fractional value (XXX or should)
496                 */
497                 
498                 tmp->set_text (" 8888888888:,|"); 
499         } else {
500                 switch (_mode) {
501                 case Timecode:
502                         tmp->set_text ("-88:88:88:88");
503                         break;
504                 case BBT:
505                         tmp->set_text (" 8888|88|8888");
506                         break;
507                 case MinSec:
508                         tmp->set_text (" 88:88:88,888");
509                         break;
510                 case Frames:
511                         tmp->set_text (" 8888888888");
512                         break;
513                 }
514         }
515
516         tmp->get_pixel_size (req->width, req->height);
517
518         layout_height = req->height;
519         layout_width = req->width;
520
521         /* now tackle height, for which we need to know the height of the lower
522          * layout
523          */
524
525         if (_left_layout) {
526
527                 int w;
528
529                 font.set_size ((int) lrint (font.get_size() * info_font_scale_factor));
530                 font.set_weight (Pango::WEIGHT_NORMAL);
531                 tmp->set_font_description (font);
532
533                 /* we only care about height, so put as much stuff in here
534                    as possible that might change the height.
535                 */
536                 tmp->set_text ("qyhH|"); /* one ascender, one descender */
537                 
538                 tmp->get_pixel_size (w, info_height);
539                 
540                 /* silly extra padding that seems necessary to correct the info
541                  * that pango just gave us. I have no idea why.
542                  */
543
544                 info_height += 4;
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, "SR %.1fkHz", rate/1000.0);
1039                 } else {
1040                         sprintf (buf, "SR %" PRId64, rate);
1041                 }
1042
1043                 _left_layout->set_text (buf);
1044
1045                 float vid_pullup = _session->config.get_video_pullup();
1046
1047                 if (vid_pullup == 0.0) {
1048                         _right_layout->set_text (_("pullup: \u2012"));
1049                 } else {
1050                         sprintf (buf, _("%+-6.4f%%"), vid_pullup);
1051                         _right_layout->set_text (buf);
1052                 }
1053         }
1054 }
1055
1056 void
1057 AudioClock::set_minsec (framepos_t when, bool /*force*/)
1058 {
1059         char buf[32];
1060         framecnt_t left;
1061         int hrs;
1062         int mins;
1063         int secs;
1064         int millisecs;
1065         bool negative = false;
1066
1067         if (_off) {
1068                 _layout->set_text ("\u2012\u2012:\u2012\u2012:\u2012\u2012.\u2012\u2012\u2012");
1069
1070                 if (_left_layout) {
1071                         _left_layout->set_text ("");
1072                         _right_layout->set_text ("");
1073                 }
1074                 
1075                 return;
1076         }       
1077
1078         if (when < 0) {
1079                 when = -when;
1080                 negative = true;
1081         }
1082
1083         left = when;
1084         hrs = (int) floor (left / (_session->frame_rate() * 60.0f * 60.0f));
1085         left -= (framecnt_t) floor (hrs * _session->frame_rate() * 60.0f * 60.0f);
1086         mins = (int) floor (left / (_session->frame_rate() * 60.0f));
1087         left -= (framecnt_t) floor (mins * _session->frame_rate() * 60.0f);
1088         secs = (int) floor (left / (float) _session->frame_rate());
1089         left -= (framecnt_t) floor (secs * _session->frame_rate());
1090         millisecs = floor (left * 1000.0 / (float) _session->frame_rate());
1091         
1092         if (negative) {
1093                 snprintf (buf, sizeof (buf), "-%02" PRId32 ":%02" PRId32 ":%02" PRId32 ".%03" PRId32, hrs, mins, secs, millisecs);
1094         } else {
1095                 snprintf (buf, sizeof (buf), " %02" PRId32 ":%02" PRId32 ":%02" PRId32 ".%03" PRId32, hrs, mins, secs, millisecs);
1096         }
1097
1098         _layout->set_text (buf);
1099 }
1100
1101 void
1102 AudioClock::set_timecode (framepos_t when, bool /*force*/)
1103 {
1104         Timecode::Time TC;
1105         bool negative = false;
1106
1107         if (_off) {
1108                 _layout->set_text ("\u2012\u2012:\u2012\u2012:\u2012\u2012:\u2012\u2012");
1109                 if (_left_layout) {
1110                         _left_layout->set_text ("");
1111                         _right_layout->set_text ("");
1112                 }
1113                 
1114                 return;
1115         }
1116
1117         if (when < 0) {
1118                 when = -when;
1119                 negative = true;
1120         }
1121
1122         if (is_duration) {
1123                 _session->timecode_duration (when, TC);
1124         } else {
1125                 _session->timecode_time (when, TC);
1126         }
1127
1128         TC.negative = TC.negative || negative;
1129
1130         _layout->set_text (Timecode::timecode_format_time(TC));
1131
1132         if (_left_layout && _right_layout) {
1133
1134                 SyncSource sync_src = Config->get_sync_source();
1135
1136                 if (_session->config.get_external_sync()) {
1137                         Slave* slave = _session->slave();
1138
1139                         switch (sync_src) {
1140                         case JACK:
1141                                 _left_layout->set_text (string_compose ("%1",
1142                                                         sync_source_to_string(sync_src, true)));
1143                                 _right_layout->set_text ("");
1144                                 break;
1145                         case MTC:
1146                                 if (slave) {
1147                                         _left_layout->set_text (string_compose ("%1",
1148                                                                 dynamic_cast<TimecodeSlave*>(slave)->approximate_current_position()));
1149                                         _right_layout->set_markup (string_compose ("<span size=\"%1\" foreground=\"white\">%2</span>",
1150                                                                 (int)round(font_size * info_font_scale_factor * .9), slave->approximate_current_delta()));
1151                                 } else {
1152                                         _left_layout->set_text (string_compose ("--pending--",
1153                                                                 sync_source_to_string(sync_src, true)));
1154                                         _right_layout->set_text ("");
1155                                 }
1156                                 break;
1157                         case MIDIClock:
1158                                 if (slave) {
1159                                         _left_layout->set_text (string_compose ("%1",
1160                                                                 sync_source_to_string(sync_src, true)));
1161                                         _right_layout->set_markup (string_compose ("<span size=\"%1\" foreground=\"white\">%2</span>",
1162                                                                 (int)round(font_size * info_font_scale_factor * .9), slave->approximate_current_delta()));
1163                                 } else {
1164                                         _left_layout->set_text (string_compose ("%1 --pending--",
1165                                                                 sync_source_to_string(sync_src, true)));
1166                                         _right_layout->set_text ("");
1167                                 }
1168                                 break;
1169                         case LTC:
1170                                 if (slave) {
1171                                         _left_layout->set_text (string_compose ("%1",
1172                                                                 dynamic_cast<TimecodeSlave*>(slave)->approximate_current_position()));
1173                                         _right_layout->set_markup (string_compose ("<span size=\"%1\" foreground=\"white\">%2</span>",
1174                                                                 (int)round(font_size * info_font_scale_factor * .9), slave->approximate_current_delta()));
1175                                 } else {
1176                                         _left_layout->set_text (string_compose ("%1 --pending--",
1177                                                                 sync_source_to_string(sync_src, true)));
1178                                         _right_layout->set_text ("");
1179                                 }
1180                                 break;
1181                         }
1182                 } else {
1183                         _left_layout->set_text (string_compose (_("INT/%1"),
1184                                                 sync_source_to_string(sync_src, true)));
1185                         _right_layout->set_text ("");
1186                 }
1187         }
1188 }
1189
1190 void
1191 AudioClock::set_bbt (framepos_t when, bool /*force*/)
1192 {
1193         char buf[16];
1194         Timecode::BBT_Time BBT;
1195         bool negative = false;
1196
1197         if (_off) {
1198                 _layout->set_text ("\u2012\u2012\u2012|\u2012\u2012|\u2012\u2012\u2012\u2012");
1199                 if (_left_layout) {
1200                         _left_layout->set_text ("");
1201                         _right_layout->set_text ("");
1202                 }
1203                 return;
1204         }
1205
1206         if (when < 0) {
1207                 when = -when;
1208                 negative = true;
1209         }
1210
1211         /* handle a common case */
1212         if (is_duration) {
1213                 if (when == 0) {
1214                         BBT.bars = 0;
1215                         BBT.beats = 0;
1216                         BBT.ticks = 0;
1217                 } else {
1218                         _session->tempo_map().bbt_time (when, BBT);
1219                         BBT.bars--;
1220                         BBT.beats--;
1221                 }
1222         } else {
1223                 _session->tempo_map().bbt_time (when, BBT);
1224         }
1225
1226         if (negative) {
1227                 snprintf (buf, sizeof (buf), "-%03" PRIu32 BBT_BAR_CHAR "%02" PRIu32 BBT_BAR_CHAR "%04" PRIu32, 
1228                           BBT.bars, BBT.beats, BBT.ticks);
1229         } else {
1230                 snprintf (buf, sizeof (buf), " %03" PRIu32 BBT_BAR_CHAR "%02" PRIu32 BBT_BAR_CHAR "%04" PRIu32, 
1231                           BBT.bars, BBT.beats, BBT.ticks);
1232         }
1233
1234         _layout->set_text (buf);
1235                  
1236         if (_right_layout) {
1237                 framepos_t pos;
1238
1239                 if (bbt_reference_time < 0) {
1240                         pos = when;
1241                 } else {
1242                         pos = bbt_reference_time;
1243                 }
1244
1245                 TempoMetric m (_session->tempo_map().metric_at (pos));
1246
1247                 sprintf (buf, "%-5.2f", m.tempo().beats_per_minute());
1248                 _left_layout->set_text (buf);
1249
1250                 sprintf (buf, "%g/%g", m.meter().divisions_per_bar(), m.meter().note_divisor());
1251                 _right_layout->set_text (buf);
1252         }
1253 }
1254
1255 void
1256 AudioClock::set_session (Session *s)
1257 {
1258         SessionHandlePtr::set_session (s);
1259
1260         if (_session) {
1261
1262                 _session->config.ParameterChanged.connect (_session_connections, invalidator (*this), boost::bind (&AudioClock::session_configuration_changed, this, _1), gui_context());
1263
1264                 const XMLProperty* prop;
1265                 XMLNode* node = _session->extra_xml (X_("ClockModes"));
1266                 AudioClock::Mode amode;
1267
1268                 if (node) {
1269                         for (XMLNodeList::const_iterator i = node->children().begin(); i != node->children().end(); ++i) {
1270                                 if ((prop = (*i)->property (X_("name"))) && prop->value() == _name) {
1271
1272                                         if ((prop = (*i)->property (X_("mode"))) != 0) {
1273                                                 amode = AudioClock::Mode (string_2_enum (prop->value(), amode));
1274                                                 set_mode (amode);
1275                                         }
1276                                         if ((prop = (*i)->property (X_("on"))) != 0) {
1277                                                 set_off (!string_is_affirmative (prop->value()));
1278                                         }
1279                                         break;
1280                                 }
1281                         }
1282                 }
1283
1284                 set (last_when, true);
1285         }
1286 }
1287
1288 bool
1289 AudioClock::on_key_press_event (GdkEventKey* ev)
1290 {
1291         if (!editing) {
1292                 return false;
1293         }
1294
1295         string new_text;
1296         char new_char = 0;
1297         int highlight_length;
1298         framepos_t pos;
1299
1300         switch (ev->keyval) {
1301         case GDK_0:
1302         case GDK_KP_0:
1303                 new_char = '0';
1304                 break;
1305         case GDK_1:
1306         case GDK_KP_1:
1307                 new_char = '1';
1308                 break;
1309         case GDK_2:
1310         case GDK_KP_2:
1311                 new_char = '2';
1312                 break;
1313         case GDK_3:
1314         case GDK_KP_3:
1315                 new_char = '3';
1316                 break;
1317         case GDK_4:
1318         case GDK_KP_4:
1319                 new_char = '4';
1320                 break;
1321         case GDK_5:
1322         case GDK_KP_5:
1323                 new_char = '5';
1324                 break;
1325         case GDK_6:
1326         case GDK_KP_6:
1327                 new_char = '6';
1328                 break;
1329         case GDK_7:
1330         case GDK_KP_7:
1331                 new_char = '7';
1332                 break;
1333         case GDK_8:
1334         case GDK_KP_8:
1335                 new_char = '8';
1336                 break;
1337         case GDK_9:
1338         case GDK_KP_9:
1339                 new_char = '9';
1340                 break;
1341
1342         case GDK_minus:
1343         case GDK_KP_Subtract:
1344                 end_edit_relative (false);
1345                 return true;
1346                 break;
1347
1348         case GDK_plus:
1349         case GDK_KP_Add:
1350                 end_edit_relative (true);
1351                 return true;
1352                 break;
1353
1354         case GDK_Tab:
1355         case GDK_Return:
1356         case GDK_KP_Enter:
1357                 end_edit (true);
1358                 return true;
1359                 break;
1360
1361         case GDK_Escape:
1362                 end_edit (false);
1363                 ChangeAborted();  /*  EMIT SIGNAL  */
1364                 return true;
1365
1366         case GDK_Delete:
1367         case GDK_BackSpace:
1368                 if (!input_string.empty()) {
1369                         /* delete the last key entered
1370                         */
1371                         input_string = input_string.substr (0, input_string.length() - 1);
1372                 }
1373                 goto use_input_string;
1374
1375         default:
1376                 return false;
1377         }
1378
1379         if (!insert_map.empty() && (input_string.length() >= insert_map.size())) {
1380                 /* too many digits: eat the key event, but do nothing with it */
1381                 return true;
1382         }
1383
1384         input_string.push_back (new_char);
1385
1386   use_input_string:
1387
1388         switch (_mode) {
1389         case Frames:
1390                 /* get this one in the right order, and to the right width */
1391                 if (ev->keyval == GDK_Delete || ev->keyval == GDK_BackSpace) {
1392                         edit_string = edit_string.substr (0, edit_string.length() - 1);
1393                 } else {
1394                         edit_string.push_back (new_char);
1395                 }
1396                 if (!edit_string.empty()) {
1397                         char buf[32];
1398                         sscanf (edit_string.c_str(), "%" PRId64, &pos);
1399                         snprintf (buf, sizeof (buf), " %10" PRId64, pos);
1400                         edit_string = buf;
1401                 }
1402                 /* highlight the whole thing */
1403                 highlight_length = edit_string.length();
1404                 break;
1405
1406         default:
1407                 highlight_length = merge_input_and_edit_string ();
1408         }
1409
1410         show_edit_status (highlight_length);
1411         _layout->set_text (edit_string);
1412         queue_draw ();
1413
1414         return true;
1415 }
1416
1417 int
1418 AudioClock::merge_input_and_edit_string ()
1419 {
1420         /* merge with pre-edit-string into edit string */
1421         
1422         edit_string = pre_edit_string;
1423         
1424         if (input_string.empty()) {
1425                 return 0;
1426         } 
1427
1428         string::size_type target;
1429         for (string::size_type i = 0; i < input_string.length(); ++i) {
1430                 target = insert_map[input_string.length() - 1 - i];
1431                 edit_string[target] = input_string[i];
1432         }
1433         /* highlight from end to wherever the last character was added */
1434         return edit_string.length() - insert_map[input_string.length()-1];
1435 }
1436
1437
1438 bool
1439 AudioClock::on_key_release_event (GdkEventKey *ev)
1440 {
1441         if (!editing) {
1442                 return false;
1443         }
1444         
1445         /* return true for keys that we used on press
1446            so that they cannot possibly do double-duty
1447         */
1448         switch (ev->keyval) {
1449         case GDK_0:
1450         case GDK_KP_0:
1451         case GDK_1:
1452         case GDK_KP_1:
1453         case GDK_2:
1454         case GDK_KP_2:
1455         case GDK_3:
1456         case GDK_KP_3:
1457         case GDK_4:
1458         case GDK_KP_4:
1459         case GDK_5:
1460         case GDK_KP_5:
1461         case GDK_6:
1462         case GDK_KP_6:
1463         case GDK_7:
1464         case GDK_KP_7:
1465         case GDK_8:
1466         case GDK_KP_8:
1467         case GDK_9:
1468         case GDK_KP_9:
1469         case GDK_period:
1470         case GDK_comma:
1471         case GDK_KP_Decimal:
1472         case GDK_Tab:
1473         case GDK_Return:
1474         case GDK_KP_Enter:
1475         case GDK_Escape:
1476         case GDK_minus:
1477         case GDK_plus:
1478         case GDK_KP_Add:
1479         case GDK_KP_Subtract:
1480                 return true;
1481         default:
1482                 return false;
1483         }
1484 }
1485
1486 AudioClock::Field
1487 AudioClock::index_to_field (int index) const
1488 {
1489         switch (_mode) {
1490         case Timecode:
1491                 if (index < 4) {
1492                         return Timecode_Hours;
1493                 } else if (index < 7) {
1494                         return Timecode_Minutes;
1495                 } else if (index < 10) {
1496                         return Timecode_Seconds;
1497                 } else {
1498                         return Timecode_Frames;
1499                 }
1500                 break;
1501         case BBT:
1502                 if (index < 5) {
1503                         return Bars;
1504                 } else if (index < 7) {
1505                         return Beats;
1506                 } else {
1507                         return Ticks;
1508                 }
1509                 break;
1510         case MinSec:
1511                 if (index < 3) {
1512                         return Timecode_Hours;
1513                 } else if (index < 6) {
1514                         return MS_Minutes;
1515                 } else if (index < 9) {
1516                         return MS_Seconds;
1517                 } else {
1518                         return MS_Milliseconds;
1519                 }
1520                 break;
1521         case Frames:
1522                 return AudioFrames;
1523                 break;
1524         }
1525
1526         return Field (0);
1527 }
1528
1529 bool
1530 AudioClock::on_button_press_event (GdkEventButton *ev)
1531 {
1532         switch (ev->button) {
1533         case 1:
1534                 if (editable && !_off) {
1535                         int index;
1536                         int trailing;
1537                         int y;
1538                         int x;
1539
1540                         /* the text has been centered vertically, so adjust
1541                          * x and y. 
1542                          */
1543
1544                         y = ev->y - ((upper_height - layout_height)/2);
1545                         x = ev->x - layout_x_offset;
1546                         
1547                         if (!_layout->xy_to_index (x * PANGO_SCALE, y * PANGO_SCALE, index, trailing)) {
1548                                 /* pretend it is a character on the far right */
1549                                 index = 99;
1550                         }
1551                         drag_field = index_to_field (index);
1552                         dragging = true;
1553                         /* make absolutely sure that the pointer is grabbed */
1554                         gdk_pointer_grab(ev->window,false ,
1555                                          GdkEventMask( Gdk::POINTER_MOTION_MASK | Gdk::BUTTON_PRESS_MASK |Gdk::BUTTON_RELEASE_MASK),
1556                                          NULL,NULL,ev->time);
1557                         drag_accum = 0;
1558                         drag_start_y = ev->y;
1559                         drag_y = ev->y;
1560                 }
1561                 break;
1562                 
1563         default:
1564                 return false;
1565                 break;
1566         }
1567
1568         return true;
1569 }
1570
1571 bool
1572 AudioClock::on_button_release_event (GdkEventButton *ev)
1573 {
1574         if (editable && !_off) {
1575                 if (dragging) {
1576                         gdk_pointer_ungrab (GDK_CURRENT_TIME);
1577                         dragging = false;
1578                         if (ev->y > drag_start_y+1 || ev->y < drag_start_y-1 || Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)){
1579                                 // we actually dragged so return without
1580                                 // setting editing focus, or we shift clicked
1581                                 return true;
1582                         } else {
1583                                 if (ev->button == 1) {
1584                                         
1585                                         if (_edit_by_click_field) {
1586
1587                                                 int index = 0;
1588                                                 int trailing;
1589                                                 int y = ev->y - ((upper_height - layout_height)/2);
1590                                                 int x = ev->x - layout_x_offset;
1591                                                 Field f;
1592
1593                                                 if (!_layout->xy_to_index (x * PANGO_SCALE, y * PANGO_SCALE, index, trailing)) {
1594                                                         return true;
1595                                                 }
1596                                                 
1597                                                 f = index_to_field (index);
1598                                                 
1599                                                 switch (f) {
1600                                                 case Timecode_Frames:
1601                                                 case MS_Milliseconds:
1602                                                 case Ticks:
1603                                                         f = Field (0);
1604                                                         break;
1605                                                 default:
1606                                                         break;
1607                                                 }
1608                                                 start_edit (f);
1609                                         } else {
1610                                                 start_edit ();
1611                                         }
1612                                 }
1613                         }
1614                 }
1615         }
1616
1617         if (Keyboard::is_context_menu_event (ev)) {
1618                 if (ops_menu == 0) {
1619                         build_ops_menu ();
1620                 }
1621                 ops_menu->popup (1, ev->time);
1622                 return true;
1623         }
1624
1625         return false;
1626 }
1627
1628 bool
1629 AudioClock::on_focus_out_event (GdkEventFocus* ev)
1630 {
1631         bool ret = CairoWidget::on_focus_out_event (ev);
1632
1633         if (editing) {
1634                 end_edit (false);
1635         }
1636
1637         return ret;
1638 }
1639
1640 bool
1641 AudioClock::on_scroll_event (GdkEventScroll *ev)
1642 {
1643         int index;
1644         int trailing;
1645
1646         if (editing || _session == 0 || !editable || _off) {
1647                 return false;
1648         }
1649
1650         int y;
1651         int x;
1652         
1653         /* the text has been centered vertically, so adjust
1654          * x and y. 
1655          */
1656
1657         y = ev->y - ((upper_height - layout_height)/2);
1658         x = ev->x - layout_x_offset;
1659
1660         if (!_layout->xy_to_index (x * PANGO_SCALE, y * PANGO_SCALE, index, trailing)) {
1661                 /* not in the main layout */
1662                 return false;
1663         }
1664         
1665         Field f = index_to_field (index);
1666         framepos_t frames = 0;
1667
1668         switch (ev->direction) {
1669
1670         case GDK_SCROLL_UP:
1671                 frames = get_frame_step (f);
1672                 if (frames != 0) {
1673                         if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
1674                                 frames *= 10;
1675                         }
1676                         set (current_time() + frames, true);
1677                         ValueChanged (); /* EMIT_SIGNAL */
1678                 }
1679                 break;
1680                 
1681         case GDK_SCROLL_DOWN:
1682                 frames = get_frame_step (f);
1683                 if (frames != 0) {
1684                         if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
1685                                 frames *= 10;
1686                         }
1687                         
1688                         if ((double)current_time() - (double)frames < 0.0) {
1689                                 set (0, true);
1690                         } else {
1691                                 set (current_time() - frames, true);
1692                         }
1693                         
1694                         ValueChanged (); /* EMIT_SIGNAL */
1695                 }
1696                 break;
1697                 
1698         default:
1699                 return false;
1700                 break;
1701         }
1702         
1703         return true;
1704 }
1705
1706 bool
1707 AudioClock::on_motion_notify_event (GdkEventMotion *ev)
1708 {
1709         if (editing || _session == 0 || !dragging) {
1710                 return false;
1711         }
1712
1713         float pixel_frame_scale_factor = 0.2f;
1714
1715         if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier))  {
1716                 pixel_frame_scale_factor = 0.1f;
1717         }
1718
1719
1720         if (Keyboard::modifier_state_contains (ev->state,
1721                                                Keyboard::PrimaryModifier|Keyboard::SecondaryModifier)) {
1722
1723                 pixel_frame_scale_factor = 0.025f;
1724         }
1725
1726         double y_delta = ev->y - drag_y;
1727
1728         drag_accum +=  y_delta*pixel_frame_scale_factor;
1729
1730         drag_y = ev->y;
1731
1732         if (trunc (drag_accum) != 0) {
1733
1734                 framepos_t frames;
1735                 framepos_t pos;
1736                 int dir;
1737                 dir = (drag_accum < 0 ? 1:-1);
1738                 pos = current_time();
1739                 frames = get_frame_step (drag_field, pos, dir);
1740
1741                 if (frames  != 0 &&  frames * drag_accum < current_time()) {
1742                         set ((framepos_t) floor (pos - drag_accum * frames), false); // minus because up is negative in GTK
1743                 } else {
1744                         set (0 , false);
1745                 }
1746
1747                 drag_accum= 0;
1748                 ValueChanged();  /* EMIT_SIGNAL */
1749         }
1750
1751         return true;
1752 }
1753
1754 framepos_t
1755 AudioClock::get_frame_step (Field field, framepos_t pos, int dir)
1756 {
1757         framecnt_t f = 0;
1758         Timecode::BBT_Time BBT;
1759         switch (field) {
1760         case Timecode_Hours:
1761                 f = (framecnt_t) floor (3600.0 * _session->frame_rate());
1762                 break;
1763         case Timecode_Minutes:
1764                 f = (framecnt_t) floor (60.0 * _session->frame_rate());
1765                 break;
1766         case Timecode_Seconds:
1767                 f = _session->frame_rate();
1768                 break;
1769         case Timecode_Frames:
1770                 f = (framecnt_t) floor (_session->frame_rate() / _session->timecode_frames_per_second());
1771                 break;
1772
1773         case AudioFrames:
1774                 f = 1;
1775                 break;
1776
1777         case MS_Hours:
1778                 f = (framecnt_t) floor (3600.0 * _session->frame_rate());
1779                 break;
1780         case MS_Minutes:
1781                 f = (framecnt_t) floor (60.0 * _session->frame_rate());
1782                 break;
1783         case MS_Seconds:
1784                 f = (framecnt_t) _session->frame_rate();
1785                 break;
1786         case MS_Milliseconds:
1787                 f = (framecnt_t) floor (_session->frame_rate() / 1000.0);
1788                 break;
1789
1790         case Bars:
1791                 BBT.bars = 1;
1792                 BBT.beats = 0;
1793                 BBT.ticks = 0;
1794                 f = _session->tempo_map().bbt_duration_at (pos,BBT,dir);
1795                 break;
1796         case Beats:
1797                 BBT.bars = 0;
1798                 BBT.beats = 1;
1799                 BBT.ticks = 0;
1800                 f = _session->tempo_map().bbt_duration_at(pos,BBT,dir);
1801                 break;
1802         case Ticks:
1803                 BBT.bars = 0;
1804                 BBT.beats = 0;
1805                 BBT.ticks = 1;
1806                 f = _session->tempo_map().bbt_duration_at(pos,BBT,dir);
1807                 break;
1808         default:
1809                 error << string_compose (_("programming error: %1"), "attempt to get frames from non-text field!") << endmsg;
1810                 f = 0;
1811                 break;
1812         }
1813
1814         return f;
1815 }
1816
1817 framepos_t
1818 AudioClock::current_time (framepos_t) const
1819 {
1820         return last_when;
1821 }
1822
1823 framepos_t
1824 AudioClock::current_duration (framepos_t pos) const
1825 {
1826         framepos_t ret = 0;
1827
1828         switch (_mode) {
1829         case Timecode:
1830                 ret = last_when;
1831                 break;
1832         case BBT:
1833                 ret = frame_duration_from_bbt_string (pos, _layout->get_text());
1834                 break;
1835
1836         case MinSec:
1837                 ret = last_when;
1838                 break;
1839
1840         case Frames:
1841                 ret = last_when;
1842                 break;
1843         }
1844
1845         return ret;
1846 }
1847
1848 bool
1849 AudioClock::bbt_validate_edit (const string& str)
1850 {
1851         AnyTime any;
1852
1853         if (sscanf (str.c_str(), BBT_SCANF_FORMAT, &any.bbt.bars, &any.bbt.beats, &any.bbt.ticks) != 3) {
1854                 return false;
1855         }
1856
1857         if (any.bbt.ticks > Timecode::BBT_Time::ticks_per_beat) {
1858                 return false;
1859         }
1860
1861         if (!is_duration && any.bbt.bars == 0) {
1862                 return false;
1863         }
1864
1865         if (!is_duration && any.bbt.beats == 0) {
1866                 return false;
1867         }
1868
1869         return true;
1870 }
1871
1872 bool
1873 AudioClock::timecode_validate_edit (const string&)
1874 {
1875         Timecode::Time TC;
1876
1877         if (sscanf (_layout->get_text().c_str(), "%" PRId32 ":%" PRId32 ":%" PRId32 ":%" PRId32, 
1878                     &TC.hours, &TC.minutes, &TC.seconds, &TC.frames) != 4) {
1879                 return false;
1880         }
1881
1882         if (TC.hours > 23U || TC.minutes > 59U || TC.seconds > 59U) {
1883                 return false;
1884         }
1885
1886         if (TC.frames > (uint32_t) rint (_session->timecode_frames_per_second()) - 1) {
1887                 return false;
1888         }
1889
1890         if (_session->timecode_drop_frames()) {
1891                 if (TC.minutes % 10 && TC.seconds == 0U && TC.frames < 2U) {
1892                         return false;
1893                 }
1894         }
1895
1896         return true;
1897 }
1898
1899 bool
1900 AudioClock::minsec_validate_edit (const string& str)
1901 {
1902         int hrs, mins, secs, millisecs;
1903
1904         if (sscanf (str.c_str(), "%d:%d:%d.%d", &hrs, &mins, &secs, &millisecs) != 4) {
1905                 return false;
1906         }
1907         
1908         if (hrs > 23 || mins > 59 || secs > 59 || millisecs > 999) {
1909                 return false;
1910         }
1911
1912         return true;
1913 }
1914
1915 framepos_t
1916 AudioClock::frames_from_timecode_string (const string& str) const
1917 {
1918         if (_session == 0) {
1919                 return 0;
1920         }
1921
1922         Timecode::Time TC;
1923         framepos_t sample;
1924
1925         if (sscanf (str.c_str(), "%d:%d:%d:%d", &TC.hours, &TC.minutes, &TC.seconds, &TC.frames) != 4) {
1926                 error << string_compose (_("programming error: %1 %2"), "badly formatted timecode clock string", str) << endmsg;
1927                 return 0;
1928         }
1929
1930         TC.rate = _session->timecode_frames_per_second();
1931         TC.drop= _session->timecode_drop_frames();
1932
1933         _session->timecode_to_sample (TC, sample, false /* use_offset */, false /* use_subframes */ );
1934         
1935         // timecode_tester ();
1936
1937         return sample;
1938 }
1939
1940 framepos_t
1941 AudioClock::frames_from_minsec_string (const string& str) const
1942 {
1943         if (_session == 0) {
1944                 return 0;
1945         }
1946
1947         int hrs, mins, secs, millisecs;
1948         framecnt_t sr = _session->frame_rate();
1949
1950         if (sscanf (str.c_str(), "%d:%d:%d.%d", &hrs, &mins, &secs, &millisecs) != 4) {
1951                 error << string_compose (_("programming error: %1 %2"), "badly formatted minsec clock string", str) << endmsg;
1952                 return 0;
1953         }
1954
1955         return (framepos_t) floor ((hrs * 60.0f * 60.0f * sr) + (mins * 60.0f * sr) + (secs * sr) + (millisecs * sr / 1000.0));
1956 }
1957
1958 framepos_t
1959 AudioClock::frames_from_bbt_string (framepos_t pos, const string& str) const
1960 {
1961         if (_session == 0) {
1962                 error << "AudioClock::current_time() called with BBT mode but without session!" << endmsg;
1963                 return 0;
1964         }
1965
1966         AnyTime any;
1967         any.type = AnyTime::BBT;
1968
1969         if (sscanf (str.c_str(), BBT_SCANF_FORMAT, &any.bbt.bars, &any.bbt.beats, &any.bbt.ticks) != 3) {
1970                 return 0;
1971         }
1972         
1973         if (is_duration) {
1974                 any.bbt.bars++;
1975                 any.bbt.beats++;
1976                 return _session->any_duration_to_frames (pos, any);
1977         } else {
1978                 return _session->convert_to_frames (any);
1979         }
1980 }
1981
1982
1983 framepos_t
1984 AudioClock::frame_duration_from_bbt_string (framepos_t pos, const string& str) const
1985 {
1986         if (_session == 0) {
1987                 error << "AudioClock::current_time() called with BBT mode but without session!" << endmsg;
1988                 return 0;
1989         }
1990
1991         Timecode::BBT_Time bbt;
1992
1993         if (sscanf (str.c_str(), BBT_SCANF_FORMAT, &bbt.bars, &bbt.beats, &bbt.ticks) != 3) {
1994                 return 0;
1995         }
1996
1997         return _session->tempo_map().bbt_duration_at(pos,bbt,1);
1998 }
1999
2000 framepos_t
2001 AudioClock::frames_from_audioframes_string (const string& str) const
2002 {
2003         framepos_t f;
2004         sscanf (str.c_str(), "%" PRId64, &f);
2005         return f;
2006 }
2007
2008 void
2009 AudioClock::build_ops_menu ()
2010 {
2011         using namespace Menu_Helpers;
2012         ops_menu = new Menu;
2013         MenuList& ops_items = ops_menu->items();
2014         ops_menu->set_name ("ArdourContextMenu");
2015
2016         if (!Profile->get_sae()) {
2017                 ops_items.push_back (MenuElem (_("Timecode"), sigc::bind (sigc::mem_fun(*this, &AudioClock::set_mode), Timecode)));
2018         }
2019         ops_items.push_back (MenuElem (_("Bars:Beats"), sigc::bind (sigc::mem_fun(*this, &AudioClock::set_mode), BBT)));
2020         ops_items.push_back (MenuElem (_("Minutes:Seconds"), sigc::bind (sigc::mem_fun(*this, &AudioClock::set_mode), MinSec)));
2021         ops_items.push_back (MenuElem (_("Samples"), sigc::bind (sigc::mem_fun(*this, &AudioClock::set_mode), Frames)));
2022
2023         if (editable && !_off && !is_duration && !_follows_playhead) {
2024                 ops_items.push_back (SeparatorElem());
2025                 ops_items.push_back (MenuElem (_("Set From Playhead"), sigc::mem_fun(*this, &AudioClock::set_from_playhead)));
2026                 ops_items.push_back (MenuElem (_("Locate to This Time"), sigc::mem_fun(*this, &AudioClock::locate)));
2027         }
2028 }
2029
2030 void
2031 AudioClock::set_from_playhead ()
2032 {
2033         if (!_session) {
2034                 return;
2035         }
2036
2037         set (_session->transport_frame());
2038         ValueChanged ();
2039 }
2040
2041 void
2042 AudioClock::locate ()
2043 {
2044         if (!_session || is_duration) {
2045                 return;
2046         }
2047
2048         _session->request_locate (current_time(), _session->transport_rolling ());
2049 }
2050
2051 void
2052 AudioClock::set_mode (Mode m)
2053 {
2054         if (_mode == m) {
2055                 return;
2056         }
2057
2058         _mode = m;
2059
2060         insert_map.clear();
2061
2062         _layout->set_text ("");
2063
2064         if (_left_layout) {
2065
2066                 _left_layout->set_attributes (info_attributes);
2067                 _right_layout->set_attributes (info_attributes);
2068                 /* adjust info_height according to font size */
2069                 int ignored;
2070                 _left_layout->set_text (" 1234567890");
2071                 _left_layout->get_pixel_size (ignored, info_height);
2072                 info_height += 4;
2073
2074                 _left_layout->set_text ("");
2075                 _right_layout->set_text ("");
2076         }
2077
2078         switch (_mode) {
2079         case Timecode:
2080                 mode_based_info_ratio = 0.5;
2081                 insert_map.push_back (11);
2082                 insert_map.push_back (10);
2083                 insert_map.push_back (8);
2084                 insert_map.push_back (7);
2085                 insert_map.push_back (5);
2086                 insert_map.push_back (4);
2087                 insert_map.push_back (2);
2088                 insert_map.push_back (1);
2089                 break;
2090                 
2091         case BBT:
2092                 mode_based_info_ratio = 0.5;
2093                 insert_map.push_back (11);
2094                 insert_map.push_back (10);
2095                 insert_map.push_back (9);
2096                 insert_map.push_back (8);
2097                 insert_map.push_back (6);
2098                 insert_map.push_back (5);       
2099                 insert_map.push_back (3);       
2100                 insert_map.push_back (2);       
2101                 insert_map.push_back (1);       
2102                 break;
2103                 
2104         case MinSec:
2105                 mode_based_info_ratio = 1.0;
2106                 insert_map.push_back (12);
2107                 insert_map.push_back (11);
2108                 insert_map.push_back (10);
2109                 insert_map.push_back (8);
2110                 insert_map.push_back (7);
2111                 insert_map.push_back (5);
2112                 insert_map.push_back (4);
2113                 insert_map.push_back (2);       
2114                 insert_map.push_back (1);       
2115                 break;
2116                 
2117         case Frames:
2118                 mode_based_info_ratio = 0.5;
2119                 break;
2120         }
2121
2122         set (last_when, true);
2123
2124         if (!is_transient) {
2125                 ModeChanged (); /* EMIT SIGNAL (the static one)*/
2126         }
2127
2128         if (!_fixed_width) {
2129                 /* display is different, allow us to resize */
2130                 first_width = 0;
2131                 first_height = 0;
2132                 queue_resize ();
2133         }
2134
2135         mode_changed (); /* EMIT SIGNAL (the member one) */
2136 }
2137
2138 void
2139 AudioClock::set_bbt_reference (framepos_t pos)
2140 {
2141         bbt_reference_time = pos;
2142 }
2143
2144 void
2145 AudioClock::on_style_changed (const Glib::RefPtr<Gtk::Style>& old_style)
2146 {
2147         CairoWidget::on_style_changed (old_style);
2148         first_width = 0;
2149         first_height = 0;
2150         set_font ();
2151         set_colors ();
2152 }
2153
2154 void
2155 AudioClock::set_editable (bool yn)
2156 {
2157         editable = yn;
2158 }
2159
2160 void
2161 AudioClock::set_is_duration (bool yn)
2162 {
2163         if (yn == is_duration) {
2164                 return;
2165         }
2166
2167         is_duration = yn;
2168         set (last_when, true);
2169 }
2170
2171 void
2172 AudioClock::set_off (bool yn) 
2173 {
2174         if (_off == yn) {
2175                 return;
2176         }
2177
2178         _off = yn;
2179
2180         /* force a redraw. last_when will be preserved, but the clock text will
2181          * change 
2182          */
2183         
2184         set (last_when, true);
2185 }
2186
2187 void
2188 AudioClock::focus ()
2189 {
2190         start_edit (Field (0));
2191 }
2192
2193 void
2194 AudioClock::set_corner_radius (double r)
2195 {
2196         corner_radius = r;
2197         queue_draw ();
2198 }
2199
2200 void
2201 AudioClock::set_fixed_width (bool yn)
2202 {
2203         _fixed_width = yn;
2204 }
2205
2206 void
2207 AudioClock::dpi_reset ()
2208 {
2209         /* force recomputation of size even if we are fixed width 
2210          */
2211         first_width = 0;
2212         first_height = 0;
2213         queue_resize ();
2214 }