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