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