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