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