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