Merge branch 'clang' of https://github.com/axetota/ardour
[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                         break;
624                 }
625
626                 if (!ok) {
627                         edit_string = pre_edit_string;
628                         input_string.clear ();
629                         _layout->set_text (edit_string);
630                         show_edit_status (0);
631                         /* edit attributes remain in use */
632                 } else {
633
634                         editing = false;
635                         framepos_t pos = 0; /* stupid gcc */
636
637                         switch (_mode) {
638                         case Timecode:
639                                 pos = frames_from_timecode_string (edit_string);
640                                 break;
641
642                         case BBT:
643                                 if (is_duration) {
644                                         pos = frame_duration_from_bbt_string (0, edit_string);
645                                 } else {
646                                         pos = frames_from_bbt_string (0, edit_string);
647                                 }
648                                 break;
649
650                         case MinSec:
651                                 pos = frames_from_minsec_string (edit_string);
652                                 break;
653
654                         case Frames:
655                                 pos = frames_from_audioframes_string (edit_string);
656                                 break;
657                         }
658
659                         set (pos, true);
660                         _layout->set_attributes (normal_attributes);
661                         ValueChanged(); /* EMIT_SIGNAL */
662                 }
663
664         } else {
665
666                 editing = false;
667                 edit_is_negative = false;
668                 _layout->set_attributes (normal_attributes);
669                 _layout->set_text (pre_edit_string);
670         }
671
672         queue_draw ();
673
674         if (!editing) {
675                 drop_focus ();
676         }
677 }
678
679 void
680 AudioClock::drop_focus ()
681 {
682         Keyboard::magic_widget_drop_focus ();
683
684         if (has_focus()) {
685
686                 /* move focus back to the default widget in the top level window */
687
688                 Widget* top = get_toplevel();
689
690                 if (top->is_toplevel ()) {
691                         Window* win = dynamic_cast<Window*> (top);
692                         win->grab_focus ();
693                 }
694         }
695 }
696
697 framecnt_t
698 AudioClock::parse_as_frames_distance (const std::string& str)
699 {
700         framecnt_t f;
701
702         if (sscanf (str.c_str(), "%" PRId64, &f) == 1) {
703                 return f;
704         }
705
706         return 0;
707 }
708
709 framecnt_t
710 AudioClock::parse_as_minsec_distance (const std::string& str)
711 {
712         framecnt_t sr = _session->frame_rate();
713         int msecs;
714         int secs;
715         int mins;
716         int hrs;
717
718         switch (str.length()) {
719         case 0:
720                 return 0;
721         case 1:
722         case 2:
723         case 3:
724         case 4:
725                 sscanf (str.c_str(), "%" PRId32, &msecs);
726                 return msecs * (sr / 1000);
727
728         case 5:
729                 sscanf (str.c_str(), "%1" PRId32 "%" PRId32, &secs, &msecs);
730                 return (secs * sr) + (msecs * (sr/1000));
731
732         case 6:
733                 sscanf (str.c_str(), "%2" PRId32 "%" PRId32, &secs, &msecs);
734                 return (secs * sr) + (msecs * (sr/1000));
735
736         case 7:
737                 sscanf (str.c_str(), "%1" PRId32 "%2" PRId32 "%" PRId32, &mins, &secs, &msecs);
738                 return (mins * 60 * sr) + (secs * sr) + (msecs * (sr/1000));
739
740         case 8:
741                 sscanf (str.c_str(), "%2" PRId32 "%2" PRId32 "%" PRId32, &mins, &secs, &msecs);
742                 return (mins * 60 * sr) + (secs * sr) + (msecs * (sr/1000));
743
744         case 9:
745                 sscanf (str.c_str(), "%1" PRId32 "%2" PRId32 "%2" PRId32 "%" PRId32, &hrs, &mins, &secs, &msecs);
746                 return (hrs * 3600 * sr) + (mins * 60 * sr) + (secs * sr) + (msecs * (sr/1000));
747
748         case 10:
749                 sscanf (str.c_str(), "%1" PRId32 "%2" PRId32 "%2" PRId32 "%" PRId32, &hrs, &mins, &secs, &msecs);
750                 return (hrs * 3600 * sr) + (mins * 60 * sr) + (secs * sr) + (msecs * (sr/1000));
751
752         default:
753                 break;
754         }
755
756         return 0;
757 }
758
759 framecnt_t
760 AudioClock::parse_as_timecode_distance (const std::string& str)
761 {
762         double fps = _session->timecode_frames_per_second();
763         framecnt_t sr = _session->frame_rate();
764         int frames;
765         int secs;
766         int mins;
767         int hrs;
768
769         switch (str.length()) {
770         case 0:
771                 return 0;
772         case 1:
773         case 2:
774                 sscanf (str.c_str(), "%" PRId32, &frames);
775                 return lrint ((frames/(float)fps) * sr);
776
777         case 3:
778                 sscanf (str.c_str(), "%1" PRId32 "%" PRId32, &secs, &frames);
779                 return (secs * sr) + lrint ((frames/(float)fps) * sr);
780
781         case 4:
782                 sscanf (str.c_str(), "%2" PRId32 "%" PRId32, &secs, &frames);
783                 return (secs * sr) + lrint ((frames/(float)fps) * sr);
784
785         case 5:
786                 sscanf (str.c_str(), "%1" PRId32 "%2" PRId32 "%" PRId32, &mins, &secs, &frames);
787                 return (mins * 60 * sr) + (secs * sr) + lrint ((frames/(float)fps) * sr);
788
789         case 6:
790                 sscanf (str.c_str(), "%2" PRId32 "%2" PRId32 "%" PRId32, &mins, &secs, &frames);
791                 return (mins * 60 * sr) + (secs * sr) + lrint ((frames/(float)fps) * sr);
792
793         case 7:
794                 sscanf (str.c_str(), "%1" PRId32 "%2" PRId32 "%2" PRId32 "%" PRId32, &hrs, &mins, &secs, &frames);
795                 return (hrs * 3600 * sr) + (mins * 60 * sr) + (secs * sr) + lrint ((frames/(float)fps) * sr);
796
797         case 8:
798                 sscanf (str.c_str(), "%2" PRId32 "%2" PRId32 "%2" PRId32 "%" PRId32, &hrs, &mins, &secs, &frames);
799                 return (hrs * 3600 * sr) + (mins * 60 * sr) + (secs * sr) + lrint ((frames/(float)fps) * sr);
800
801         default:
802                 break;
803         }
804
805         return 0;
806 }
807
808 framecnt_t
809 AudioClock::parse_as_bbt_distance (const std::string&)
810 {
811         return 0;
812 }
813
814 framecnt_t
815 AudioClock::parse_as_distance (const std::string& instr)
816 {
817         switch (_mode) {
818         case Timecode:
819                 return parse_as_timecode_distance (instr);
820                 break;
821         case Frames:
822                 return parse_as_frames_distance (instr);
823                 break;
824         case BBT:
825                 return parse_as_bbt_distance (instr);
826                 break;
827         case MinSec:
828                 return parse_as_minsec_distance (instr);
829                 break;
830         }
831         return 0;
832 }
833
834 void
835 AudioClock::end_edit_relative (bool add)
836 {
837         bool ok = true;
838
839         switch (_mode) {
840         case Timecode:
841                 ok = timecode_validate_edit (edit_string);
842                 break;
843
844         case BBT:
845                 ok = bbt_validate_edit (edit_string);
846                 break;
847
848         case MinSec:
849                 ok = minsec_validate_edit (edit_string);
850                 break;
851
852         case Frames:
853                 break;
854         }
855
856         if (!ok) {
857                 edit_string = pre_edit_string;
858                 input_string.clear ();
859                 _layout->set_text (edit_string);
860                 show_edit_status (0);
861                 /* edit attributes remain in use */
862                 queue_draw ();
863                 return;
864         }
865
866         framecnt_t frames = parse_as_distance (input_string);
867
868         editing = false;
869
870         editing = false;
871         _layout->set_attributes (normal_attributes);
872
873         if (frames != 0) {
874                 if (add) {
875                         set (current_time() + frames, true);
876                 } else {
877                         framepos_t c = current_time();
878
879                         if (c > frames || _negative_allowed) {
880                                 set (c - frames, true);
881                         } else {
882                                 set (0, true);
883                         }
884                 }
885                 ValueChanged (); /* EMIT SIGNAL */
886         }
887
888         input_string.clear ();
889         queue_draw ();
890         drop_focus ();
891 }
892
893 void
894 AudioClock::session_property_changed (const PropertyChange&)
895 {
896         set (last_when, true);
897 }
898
899 void
900 AudioClock::session_configuration_changed (std::string p)
901 {
902         if (_negative_allowed) {
903                 /* session option editor clock */
904                 return;
905         }
906
907         if (p == "sync-source" || p == "external-sync") {
908                 set (current_time(), true);
909                 return;
910         }
911
912         if (p != "timecode-offset" && p != "timecode-offset-negative") {
913                 return;
914         }
915
916         framecnt_t current;
917
918         switch (_mode) {
919         case Timecode:
920                 if (is_duration) {
921                         current = current_duration ();
922                 } else {
923                         current = current_time ();
924                 }
925                 set (current, true);
926                 break;
927         default:
928                 break;
929         }
930 }
931
932 void
933 AudioClock::set (framepos_t when, bool force, framecnt_t offset)
934 {
935         if ((!force && !is_visible()) || _session == 0) {
936                 return;
937         }
938
939         if (is_duration) {
940                 when = when - offset;
941         }
942
943         if (when == last_when && !force) {
944                 if (_mode != Timecode && _mode != MinSec) {
945                         /* may need to force display of TC source
946                          * time, so don't return early.
947                          */
948                         return;
949                 }
950         }
951
952         if (!editing) {
953                 if (_right_layout) {
954                         _right_layout->set_alignment(Pango::ALIGN_LEFT);
955                 }
956
957                 switch (_mode) {
958                 case Timecode:
959                         if (_right_layout) {
960                                 _right_layout->set_alignment(Pango::ALIGN_RIGHT);
961                         }
962                         set_timecode (when, force);
963                         break;
964
965                 case BBT:
966                         set_bbt (when, force);
967                         break;
968
969                 case MinSec:
970                         if (_right_layout) {
971                                 _right_layout->set_alignment(Pango::ALIGN_RIGHT);
972                         }
973                         set_minsec (when, force);
974                         break;
975
976                 case Frames:
977                         set_frames (when, force);
978                         break;
979                 }
980         }
981
982         queue_draw ();
983         last_when = when;
984 }
985
986 void
987 AudioClock::set_slave_info ()
988 {
989         if (!_left_layout || !_right_layout) {
990                 return;
991         }
992
993         SyncSource sync_src = Config->get_sync_source();
994
995         if (_session->config.get_external_sync()) {
996                 Slave* slave = _session->slave();
997
998                 switch (sync_src) {
999                 case JACK:
1000                         _left_layout->set_markup (string_compose ("<span size=\"%1\">" TXTSPAN "%2</span></span>",
1001                                                 INFO_FONT_SIZE, sync_source_to_string(sync_src, true)));
1002                         _right_layout->set_text ("");
1003                         break;
1004                 case MIDIClock:
1005                         if (slave) {
1006                                 _left_layout->set_markup (string_compose ("<span size=\"%1\">" TXTSPAN "%2</span></span>",
1007                                                         INFO_FONT_SIZE, sync_source_to_string(sync_src, true)));
1008                                 _right_layout->set_markup (string_compose ("<span size=\"%1\">" TXTSPAN "%2</span></span>",
1009                                                         INFO_FONT_SIZE, slave->approximate_current_delta()));
1010                         } else {
1011                                 _left_layout->set_markup (string_compose ("<span size=\"%1\">" TXTSPAN "%2</span></span>",
1012                                                         INFO_FONT_SIZE, _("--pending--")));
1013                                 _right_layout->set_text ("");
1014                         }
1015                         break;
1016                 case LTC:
1017                 case MTC:
1018                         if (slave) {
1019                                 bool matching;
1020                                 TimecodeSlave* tcslave;
1021                                 if ((tcslave = dynamic_cast<TimecodeSlave*>(_session->slave())) != 0) {
1022                                         matching = (tcslave->apparent_timecode_format() == _session->config.get_timecode_format());
1023                                         _left_layout->set_markup (string_compose ("<span size=\"%1\">" TXTSPAN "%2</span><span foreground=\"%3\">%4</span></span>",
1024                                                                                   INFO_FONT_SIZE, sync_source_to_string(sync_src, true)[0], (matching?"green":"red"),
1025                                                                                   dynamic_cast<TimecodeSlave*>(slave)->approximate_current_position()));
1026                                         _right_layout->set_markup (string_compose ("<span size=\"%1\">" TXTSPAN "%2</span></span>",
1027                                                                                    INFO_FONT_SIZE, slave->approximate_current_delta()));
1028                                 }
1029                         } else {
1030                                 _left_layout->set_markup (string_compose ("<span size=\"%1\">" TXTSPAN "%2</span></span>",
1031                                                         INFO_FONT_SIZE, _("--pending--")));
1032                                 _right_layout->set_text ("");
1033                         }
1034                         break;
1035                 }
1036         } else {
1037                 _left_layout->set_markup (string_compose ("<span size=\"%1\">" TXTSPAN "INT/%2</span></span>",
1038                                         INFO_FONT_SIZE, sync_source_to_string(sync_src, true)));
1039                 _right_layout->set_text ("");
1040         }
1041 }
1042
1043 void
1044 AudioClock::set_frames (framepos_t when, bool /*force*/)
1045 {
1046         char buf[32];
1047         bool negative = false;
1048
1049         if (_off) {
1050                 _layout->set_text (" ----------");
1051
1052                 if (_left_layout) {
1053                         _left_layout->set_text ("");
1054                         _right_layout->set_text ("");
1055                 }
1056
1057                 return;
1058         }
1059
1060         if (when < 0) {
1061                 when = -when;
1062                 negative = true;
1063         }
1064
1065         if (negative) {
1066                 snprintf (buf, sizeof (buf), "-%10" PRId64, when);
1067         } else {
1068                 snprintf (buf, sizeof (buf), " %10" PRId64, when);
1069         }
1070
1071         _layout->set_text (buf);
1072
1073         if (_left_layout) {
1074                 framecnt_t rate = _session->frame_rate();
1075
1076                 if (fmod (rate, 100.0) == 0.0) {
1077                         sprintf (buf, "%.1fkHz", rate/1000.0);
1078                 } else {
1079                         sprintf (buf, "%" PRId64 "Hz", rate);
1080                 }
1081
1082                 _left_layout->set_markup (string_compose ("<span size=\"%1\">" TXTSPAN "%2 </span><span foreground=\"green\">%3</span></span>",
1083                                 INFO_FONT_SIZE, _("SR"), buf));
1084
1085                 float vid_pullup = _session->config.get_video_pullup();
1086
1087                 if (vid_pullup == 0.0) {
1088                         _right_layout->set_markup (string_compose ("<span size=\"%1\">" TXTSPAN "%2 </span><span foreground=\"green\">off</span></span>",
1089                                         INFO_FONT_SIZE, _("Pull")));
1090                 } else {
1091                         sprintf (buf, _("%+.4f%%"), vid_pullup);
1092                         _right_layout->set_markup (string_compose ("<span size=\"%1\">" TXTSPAN "%2 </span><span foreground=\"green\">%3</span></span>",
1093                                         INFO_FONT_SIZE, _("Pull"), buf));
1094                 }
1095         }
1096 }
1097
1098 void
1099 AudioClock::set_minsec (framepos_t when, bool /*force*/)
1100 {
1101         char buf[32];
1102         framecnt_t left;
1103         int hrs;
1104         int mins;
1105         int secs;
1106         int millisecs;
1107         bool negative = false;
1108
1109         if (_off) {
1110                 _layout->set_text (" --:--:--.---");
1111
1112                 if (_left_layout) {
1113                         _left_layout->set_text ("");
1114                         _right_layout->set_text ("");
1115                 }
1116
1117                 return;
1118         }
1119
1120         if (when < 0) {
1121                 when = -when;
1122                 negative = true;
1123         }
1124
1125         left = when;
1126         hrs = (int) floor (left / (_session->frame_rate() * 60.0f * 60.0f));
1127         left -= (framecnt_t) floor (hrs * _session->frame_rate() * 60.0f * 60.0f);
1128         mins = (int) floor (left / (_session->frame_rate() * 60.0f));
1129         left -= (framecnt_t) floor (mins * _session->frame_rate() * 60.0f);
1130         secs = (int) floor (left / (float) _session->frame_rate());
1131         left -= (framecnt_t) floor (secs * _session->frame_rate());
1132         millisecs = floor (left * 1000.0 / (float) _session->frame_rate());
1133
1134         if (negative) {
1135                 snprintf (buf, sizeof (buf), "-%02" PRId32 ":%02" PRId32 ":%02" PRId32 ".%03" PRId32, hrs, mins, secs, millisecs);
1136         } else {
1137                 snprintf (buf, sizeof (buf), " %02" PRId32 ":%02" PRId32 ":%02" PRId32 ".%03" PRId32, hrs, mins, secs, millisecs);
1138         }
1139
1140         _layout->set_text (buf);
1141         set_slave_info();
1142 }
1143
1144 void
1145 AudioClock::set_timecode (framepos_t when, bool /*force*/)
1146 {
1147         Timecode::Time TC;
1148         bool negative = false;
1149
1150         if (_off) {
1151                 _layout->set_text (" --:--:--:--");
1152                 if (_left_layout) {
1153                         _left_layout->set_text ("");
1154                         _right_layout->set_text ("");
1155                 }
1156
1157                 return;
1158         }
1159
1160         if (when < 0) {
1161                 when = -when;
1162                 negative = true;
1163         }
1164
1165         if (is_duration) {
1166                 _session->timecode_duration (when, TC);
1167         } else {
1168                 _session->timecode_time (when, TC);
1169         }
1170
1171         TC.negative = TC.negative || negative;
1172
1173         _layout->set_text (Timecode::timecode_format_time(TC));
1174
1175         set_slave_info();
1176 }
1177
1178 void
1179 AudioClock::set_bbt (framepos_t when, bool /*force*/)
1180 {
1181         char buf[16];
1182         Timecode::BBT_Time BBT;
1183         bool negative = false;
1184
1185         if (_off) {
1186                 _layout->set_text (" ---|--|----");
1187                 if (_left_layout) {
1188                         _left_layout->set_text ("");
1189                         _right_layout->set_text ("");
1190                 }
1191                 return;
1192         }
1193
1194         if (when < 0) {
1195                 when = -when;
1196                 negative = true;
1197         }
1198
1199         /* handle a common case */
1200         if (is_duration) {
1201                 if (when == 0) {
1202                         BBT.bars = 0;
1203                         BBT.beats = 0;
1204                         BBT.ticks = 0;
1205                 } else {
1206                         _session->tempo_map().bbt_time (when, BBT);
1207                         BBT.bars--;
1208                         BBT.beats--;
1209                 }
1210         } else {
1211                 _session->tempo_map().bbt_time (when, BBT);
1212         }
1213
1214         if (negative) {
1215                 snprintf (buf, sizeof (buf), "-%03" PRIu32 BBT_BAR_CHAR "%02" PRIu32 BBT_BAR_CHAR "%04" PRIu32,
1216                           BBT.bars, BBT.beats, BBT.ticks);
1217         } else {
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         }
1221
1222         _layout->set_text (buf);
1223
1224         if (_right_layout) {
1225                 framepos_t pos;
1226
1227                 if (bbt_reference_time < 0) {
1228                         pos = when;
1229                 } else {
1230                         pos = bbt_reference_time;
1231                 }
1232
1233                 TempoMetric m (_session->tempo_map().metric_at (pos));
1234
1235                 sprintf (buf, "%-5.1f", m.tempo().beats_per_minute());
1236                 _left_layout->set_markup (string_compose ("<span size=\"%1\">" TXTSPAN "%3</span> <span foreground=\"green\">%2</span></span>",
1237                                                           INFO_FONT_SIZE, buf, _("Tempo")));
1238
1239                 sprintf (buf, "%g/%g", m.meter().divisions_per_bar(), m.meter().note_divisor());
1240                 _right_layout->set_markup (string_compose ("<span size=\"%1\">" TXTSPAN "%3</span> <span foreground=\"green\">%2</span></span>",
1241                                                            INFO_FONT_SIZE, buf, _("Meter")));
1242         }
1243 }
1244
1245 void
1246 AudioClock::set_session (Session *s)
1247 {
1248         SessionHandlePtr::set_session (s);
1249
1250         if (_session) {
1251
1252                 _session->config.ParameterChanged.connect (_session_connections, invalidator (*this), boost::bind (&AudioClock::session_configuration_changed, this, _1), gui_context());
1253                 _session->tempo_map().PropertyChanged.connect (_session_connections, invalidator (*this), boost::bind (&AudioClock::session_property_changed, this, _1), gui_context());
1254
1255                 const XMLProperty* prop;
1256                 XMLNode* node = _session->extra_xml (X_("ClockModes"));
1257                 AudioClock::Mode amode;
1258
1259                 if (node) {
1260                         for (XMLNodeList::const_iterator i = node->children().begin(); i != node->children().end(); ++i) {
1261                                 if ((prop = (*i)->property (X_("name"))) && prop->value() == _name) {
1262
1263                                         if ((prop = (*i)->property (X_("mode"))) != 0) {
1264                                                 amode = AudioClock::Mode (string_2_enum (prop->value(), amode));
1265                                                 set_mode (amode);
1266                                         }
1267                                         if ((prop = (*i)->property (X_("on"))) != 0) {
1268                                                 set_off (!string_is_affirmative (prop->value()));
1269                                         }
1270                                         break;
1271                                 }
1272                         }
1273                 }
1274
1275                 set (last_when, true);
1276         }
1277 }
1278
1279 bool
1280 AudioClock::on_key_press_event (GdkEventKey* ev)
1281 {
1282         if (!editing) {
1283                 return false;
1284         }
1285
1286         string new_text;
1287         char new_char = 0;
1288         int highlight_length;
1289         framepos_t pos;
1290
1291         switch (ev->keyval) {
1292         case GDK_0:
1293         case GDK_KP_0:
1294                 new_char = '0';
1295                 break;
1296         case GDK_1:
1297         case GDK_KP_1:
1298                 new_char = '1';
1299                 break;
1300         case GDK_2:
1301         case GDK_KP_2:
1302                 new_char = '2';
1303                 break;
1304         case GDK_3:
1305         case GDK_KP_3:
1306                 new_char = '3';
1307                 break;
1308         case GDK_4:
1309         case GDK_KP_4:
1310                 new_char = '4';
1311                 break;
1312         case GDK_5:
1313         case GDK_KP_5:
1314                 new_char = '5';
1315                 break;
1316         case GDK_6:
1317         case GDK_KP_6:
1318                 new_char = '6';
1319                 break;
1320         case GDK_7:
1321         case GDK_KP_7:
1322                 new_char = '7';
1323                 break;
1324         case GDK_8:
1325         case GDK_KP_8:
1326                 new_char = '8';
1327                 break;
1328         case GDK_9:
1329         case GDK_KP_9:
1330                 new_char = '9';
1331                 break;
1332
1333         case GDK_minus:
1334         case GDK_KP_Subtract:
1335                 if (_negative_allowed && input_string.empty()) {
1336                                 edit_is_negative = true;
1337                                 edit_string.replace(0,1,"-");
1338                                 _layout->set_text (edit_string);
1339                                 queue_draw ();
1340                 } else {
1341                         end_edit_relative (false);
1342                 }
1343                 return true;
1344                 break;
1345
1346         case GDK_plus:
1347                 end_edit_relative (true);
1348                 return true;
1349                 break;
1350
1351         case GDK_Tab:
1352         case GDK_Return:
1353         case GDK_KP_Enter:
1354                 end_edit (true);
1355                 return true;
1356                 break;
1357
1358         case GDK_Escape:
1359                 end_edit (false);
1360                 ChangeAborted();  /*  EMIT SIGNAL  */
1361                 return true;
1362
1363         case GDK_Delete:
1364         case GDK_BackSpace:
1365                 if (!input_string.empty()) {
1366                         /* delete the last key entered
1367                         */
1368                         input_string = input_string.substr (0, input_string.length() - 1);
1369                 }
1370                 goto use_input_string;
1371
1372         default:
1373                 return false;
1374         }
1375
1376         if (!insert_map.empty() && (input_string.length() >= insert_map.size())) {
1377                 /* too many digits: eat the key event, but do nothing with it */
1378                 return true;
1379         }
1380
1381         input_string.push_back (new_char);
1382
1383   use_input_string:
1384
1385         switch (_mode) {
1386         case Frames:
1387                 /* get this one in the right order, and to the right width */
1388                 if (ev->keyval == GDK_Delete || ev->keyval == GDK_BackSpace) {
1389                         edit_string = edit_string.substr (0, edit_string.length() - 1);
1390                 } else {
1391                         edit_string.push_back (new_char);
1392                 }
1393                 if (!edit_string.empty()) {
1394                         char buf[32];
1395                         sscanf (edit_string.c_str(), "%" PRId64, &pos);
1396                         snprintf (buf, sizeof (buf), " %10" PRId64, pos);
1397                         edit_string = buf;
1398                 }
1399                 /* highlight the whole thing */
1400                 highlight_length = edit_string.length();
1401                 break;
1402
1403         default:
1404                 highlight_length = merge_input_and_edit_string ();
1405         }
1406
1407         if (edit_is_negative) {
1408                 edit_string.replace(0,1,"-");
1409         } else {
1410                 if (!pre_edit_string.empty() && (pre_edit_string.at(0) == '-')) {
1411                         edit_string.replace(0,1,"_");
1412                 } else {
1413                         edit_string.replace(0,1," ");
1414                 }
1415         }
1416
1417         show_edit_status (highlight_length);
1418         _layout->set_text (edit_string);
1419         queue_draw ();
1420
1421         return true;
1422 }
1423
1424 int
1425 AudioClock::merge_input_and_edit_string ()
1426 {
1427         /* merge with pre-edit-string into edit string */
1428
1429         edit_string = pre_edit_string;
1430
1431         if (input_string.empty()) {
1432                 return 0;
1433         }
1434
1435         string::size_type target;
1436         for (string::size_type i = 0; i < input_string.length(); ++i) {
1437                 target = insert_map[input_string.length() - 1 - i];
1438                 edit_string[target] = input_string[i];
1439         }
1440         /* highlight from end to wherever the last character was added */
1441         return edit_string.length() - insert_map[input_string.length()-1];
1442 }
1443
1444
1445 bool
1446 AudioClock::on_key_release_event (GdkEventKey *ev)
1447 {
1448         if (!editing) {
1449                 return false;
1450         }
1451
1452         /* return true for keys that we used on press
1453            so that they cannot possibly do double-duty
1454         */
1455         switch (ev->keyval) {
1456         case GDK_0:
1457         case GDK_KP_0:
1458         case GDK_1:
1459         case GDK_KP_1:
1460         case GDK_2:
1461         case GDK_KP_2:
1462         case GDK_3:
1463         case GDK_KP_3:
1464         case GDK_4:
1465         case GDK_KP_4:
1466         case GDK_5:
1467         case GDK_KP_5:
1468         case GDK_6:
1469         case GDK_KP_6:
1470         case GDK_7:
1471         case GDK_KP_7:
1472         case GDK_8:
1473         case GDK_KP_8:
1474         case GDK_9:
1475         case GDK_KP_9:
1476         case GDK_period:
1477         case GDK_comma:
1478         case GDK_KP_Decimal:
1479         case GDK_Tab:
1480         case GDK_Return:
1481         case GDK_KP_Enter:
1482         case GDK_Escape:
1483         case GDK_minus:
1484         case GDK_plus:
1485         case GDK_KP_Add:
1486         case GDK_KP_Subtract:
1487                 return true;
1488         default:
1489                 return false;
1490         }
1491 }
1492
1493 AudioClock::Field
1494 AudioClock::index_to_field (int index) const
1495 {
1496         switch (_mode) {
1497         case Timecode:
1498                 if (index < 4) {
1499                         return Timecode_Hours;
1500                 } else if (index < 7) {
1501                         return Timecode_Minutes;
1502                 } else if (index < 10) {
1503                         return Timecode_Seconds;
1504                 } else {
1505                         return Timecode_Frames;
1506                 }
1507                 break;
1508         case BBT:
1509                 if (index < 5) {
1510                         return Bars;
1511                 } else if (index < 7) {
1512                         return Beats;
1513                 } else {
1514                         return Ticks;
1515                 }
1516                 break;
1517         case MinSec:
1518                 if (index < 3) {
1519                         return Timecode_Hours;
1520                 } else if (index < 6) {
1521                         return MS_Minutes;
1522                 } else if (index < 9) {
1523                         return MS_Seconds;
1524                 } else {
1525                         return MS_Milliseconds;
1526                 }
1527                 break;
1528         case Frames:
1529                 return AudioFrames;
1530                 break;
1531         }
1532
1533         return Field (0);
1534 }
1535
1536 bool
1537 AudioClock::on_button_press_event (GdkEventButton *ev)
1538 {
1539         switch (ev->button) {
1540         case 1:
1541                 if (editable && !_off) {
1542                         int index;
1543                         int trailing;
1544                         int y;
1545                         int x;
1546
1547                         /* the text has been centered vertically, so adjust
1548                          * x and y.
1549                          */
1550                         int xcenter = (get_width() - layout_width) /2;
1551
1552                         y = ev->y - ((upper_height - layout_height)/2);
1553                         x = ev->x - xcenter;
1554
1555                         if (!_layout->xy_to_index (x * PANGO_SCALE, y * PANGO_SCALE, index, trailing)) {
1556                                 /* pretend it is a character on the far right */
1557                                 index = 99;
1558                         }
1559                         drag_field = index_to_field (index);
1560                         dragging = true;
1561                         /* make absolutely sure that the pointer is grabbed */
1562                         gdk_pointer_grab(ev->window,false ,
1563                                          GdkEventMask( Gdk::POINTER_MOTION_MASK | Gdk::BUTTON_PRESS_MASK |Gdk::BUTTON_RELEASE_MASK),
1564                                          NULL,NULL,ev->time);
1565                         drag_accum = 0;
1566                         drag_start_y = ev->y;
1567                         drag_y = ev->y;
1568                 }
1569                 break;
1570
1571         default:
1572                 return false;
1573                 break;
1574         }
1575
1576         return true;
1577 }
1578
1579 bool
1580 AudioClock::on_button_release_event (GdkEventButton *ev)
1581 {
1582         if (editable && !_off) {
1583                 if (dragging) {
1584                         gdk_pointer_ungrab (GDK_CURRENT_TIME);
1585                         dragging = false;
1586                         if (ev->y > drag_start_y+1 || ev->y < drag_start_y-1 || Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)){
1587                                 // we actually dragged so return without
1588                                 // setting editing focus, or we shift clicked
1589                                 return true;
1590                         } else {
1591                                 if (ev->button == 1) {
1592
1593                                         if (_edit_by_click_field) {
1594
1595                                                 int xcenter = (get_width() - layout_width) /2;
1596                                                 int index = 0;
1597                                                 int trailing;
1598                                                 int y = ev->y - ((upper_height - layout_height)/2);
1599                                                 int x = ev->x - xcenter;
1600                                                 Field f;
1601
1602                                                 if (!_layout->xy_to_index (x * PANGO_SCALE, y * PANGO_SCALE, index, trailing)) {
1603                                                         return true;
1604                                                 }
1605
1606                                                 f = index_to_field (index);
1607
1608                                                 switch (f) {
1609                                                 case Timecode_Frames:
1610                                                 case MS_Milliseconds:
1611                                                 case Ticks:
1612                                                         f = Field (0);
1613                                                         break;
1614                                                 default:
1615                                                         break;
1616                                                 }
1617                                                 start_edit (f);
1618                                         } else {
1619                                                 start_edit ();
1620                                         }
1621                                 }
1622                         }
1623                 }
1624         }
1625
1626         if (Keyboard::is_context_menu_event (ev)) {
1627                 if (ops_menu == 0) {
1628                         build_ops_menu ();
1629                 }
1630                 ops_menu->popup (1, ev->time);
1631                 return true;
1632         }
1633
1634         return false;
1635 }
1636
1637 bool
1638 AudioClock::on_focus_out_event (GdkEventFocus* ev)
1639 {
1640         bool ret = CairoWidget::on_focus_out_event (ev);
1641
1642         if (editing) {
1643                 end_edit (false);
1644         }
1645
1646         return ret;
1647 }
1648
1649 bool
1650 AudioClock::on_scroll_event (GdkEventScroll *ev)
1651 {
1652         int index;
1653         int trailing;
1654
1655         if (editing || _session == 0 || !editable || _off) {
1656                 return false;
1657         }
1658
1659         int y;
1660         int x;
1661
1662         /* the text has been centered vertically, so adjust
1663          * x and y.
1664          */
1665
1666         int xcenter = (get_width() - layout_width) /2;
1667         y = ev->y - ((upper_height - layout_height)/2);
1668         x = ev->x - xcenter;
1669
1670         if (!_layout->xy_to_index (x * PANGO_SCALE, y * PANGO_SCALE, index, trailing)) {
1671                 /* not in the main layout */
1672                 return false;
1673         }
1674
1675         Field f = index_to_field (index);
1676         framepos_t frames = 0;
1677
1678         switch (ev->direction) {
1679
1680         case GDK_SCROLL_UP:
1681                 frames = get_frame_step (f);
1682                 if (frames != 0) {
1683                         if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
1684                                 frames *= 10;
1685                         }
1686                         set (current_time() + frames, true);
1687                         ValueChanged (); /* EMIT_SIGNAL */
1688                 }
1689                 break;
1690
1691         case GDK_SCROLL_DOWN:
1692                 frames = get_frame_step (f);
1693                 if (frames != 0) {
1694                         if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
1695                                 frames *= 10;
1696                         }
1697
1698                         if (!_negative_allowed && (double)current_time() - (double)frames < 0.0) {
1699                                 set (0, true);
1700                         } else {
1701                                 set (current_time() - frames, true);
1702                         }
1703
1704                         ValueChanged (); /* EMIT_SIGNAL */
1705                 }
1706                 break;
1707
1708         default:
1709                 return false;
1710                 break;
1711         }
1712
1713         return true;
1714 }
1715
1716 bool
1717 AudioClock::on_motion_notify_event (GdkEventMotion *ev)
1718 {
1719         if (editing || _session == 0 || !dragging) {
1720                 return false;
1721         }
1722
1723         float pixel_frame_scale_factor = 0.2f;
1724
1725         if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier))  {
1726                 pixel_frame_scale_factor = 0.1f;
1727         }
1728
1729
1730         if (Keyboard::modifier_state_contains (ev->state,
1731                                                Keyboard::PrimaryModifier|Keyboard::SecondaryModifier)) {
1732
1733                 pixel_frame_scale_factor = 0.025f;
1734         }
1735
1736         double y_delta = ev->y - drag_y;
1737
1738         drag_accum +=  y_delta*pixel_frame_scale_factor;
1739
1740         drag_y = ev->y;
1741
1742         if (trunc (drag_accum) != 0) {
1743
1744                 framepos_t frames;
1745                 framepos_t pos;
1746                 int dir;
1747                 dir = (drag_accum < 0 ? 1:-1);
1748                 pos = current_time();
1749                 frames = get_frame_step (drag_field, pos, dir);
1750
1751                 if (frames  != 0 &&  frames * drag_accum < current_time()) {
1752                         set ((framepos_t) floor (pos - drag_accum * frames), false); // minus because up is negative in GTK
1753                 } else {
1754                         set (0 , false);
1755                 }
1756
1757                 drag_accum= 0;
1758                 ValueChanged();  /* EMIT_SIGNAL */
1759         }
1760
1761         return true;
1762 }
1763
1764 framepos_t
1765 AudioClock::get_frame_step (Field field, framepos_t pos, int dir)
1766 {
1767         framecnt_t f = 0;
1768         Timecode::BBT_Time BBT;
1769         switch (field) {
1770         case Timecode_Hours:
1771                 f = (framecnt_t) floor (3600.0 * _session->frame_rate());
1772                 break;
1773         case Timecode_Minutes:
1774                 f = (framecnt_t) floor (60.0 * _session->frame_rate());
1775                 break;
1776         case Timecode_Seconds:
1777                 f = _session->frame_rate();
1778                 break;
1779         case Timecode_Frames:
1780                 f = (framecnt_t) floor (_session->frame_rate() / _session->timecode_frames_per_second());
1781                 break;
1782
1783         case AudioFrames:
1784                 f = 1;
1785                 break;
1786
1787         case MS_Hours:
1788                 f = (framecnt_t) floor (3600.0 * _session->frame_rate());
1789                 break;
1790         case MS_Minutes:
1791                 f = (framecnt_t) floor (60.0 * _session->frame_rate());
1792                 break;
1793         case MS_Seconds:
1794                 f = (framecnt_t) _session->frame_rate();
1795                 break;
1796         case MS_Milliseconds:
1797                 f = (framecnt_t) floor (_session->frame_rate() / 1000.0);
1798                 break;
1799
1800         case Bars:
1801                 BBT.bars = 1;
1802                 BBT.beats = 0;
1803                 BBT.ticks = 0;
1804                 f = _session->tempo_map().bbt_duration_at (pos,BBT,dir);
1805                 break;
1806         case Beats:
1807                 BBT.bars = 0;
1808                 BBT.beats = 1;
1809                 BBT.ticks = 0;
1810                 f = _session->tempo_map().bbt_duration_at(pos,BBT,dir);
1811                 break;
1812         case Ticks:
1813                 BBT.bars = 0;
1814                 BBT.beats = 0;
1815                 BBT.ticks = 1;
1816                 f = _session->tempo_map().bbt_duration_at(pos,BBT,dir);
1817                 break;
1818         default:
1819                 error << string_compose (_("programming error: %1"), "attempt to get frames from non-text field!") << endmsg;
1820                 f = 0;
1821                 break;
1822         }
1823
1824         return f;
1825 }
1826
1827 framepos_t
1828 AudioClock::current_time (framepos_t) const
1829 {
1830         return last_when;
1831 }
1832
1833 framepos_t
1834 AudioClock::current_duration (framepos_t pos) const
1835 {
1836         framepos_t ret = 0;
1837
1838         switch (_mode) {
1839         case Timecode:
1840                 ret = last_when;
1841                 break;
1842         case BBT:
1843                 ret = frame_duration_from_bbt_string (pos, _layout->get_text());
1844                 break;
1845
1846         case MinSec:
1847                 ret = last_when;
1848                 break;
1849
1850         case Frames:
1851                 ret = last_when;
1852                 break;
1853         }
1854
1855         return ret;
1856 }
1857
1858 bool
1859 AudioClock::bbt_validate_edit (const string& str)
1860 {
1861         AnyTime any;
1862
1863         if (sscanf (str.c_str(), BBT_SCANF_FORMAT, &any.bbt.bars, &any.bbt.beats, &any.bbt.ticks) != 3) {
1864                 return false;
1865         }
1866
1867         if (any.bbt.ticks > Timecode::BBT_Time::ticks_per_beat) {
1868                 return false;
1869         }
1870
1871         if (!is_duration && any.bbt.bars == 0) {
1872                 return false;
1873         }
1874
1875         if (!is_duration && any.bbt.beats == 0) {
1876                 return false;
1877         }
1878
1879         return true;
1880 }
1881
1882 bool
1883 AudioClock::timecode_validate_edit (const string&)
1884 {
1885         Timecode::Time TC;
1886         int hours;
1887         char ignored[2];
1888
1889         if (sscanf (_layout->get_text().c_str(), "%[- _]%" PRId32 ":%" PRId32 ":%" PRId32 "%[:;]%" PRId32,
1890                     ignored, &hours, &TC.minutes, &TC.seconds, ignored, &TC.frames) != 6) {
1891                 return false;
1892         }
1893
1894         if (hours < 0) {
1895                 TC.hours = hours * -1;
1896                 TC.negative = true;
1897         } else {
1898                 TC.hours = hours;
1899                 TC.negative = false;
1900         }
1901
1902         if (TC.negative && !_negative_allowed) {
1903                 return false;
1904         }
1905
1906         if (TC.hours > 23U || TC.minutes > 59U || TC.seconds > 59U) {
1907                 return false;
1908         }
1909
1910         if (TC.frames > (uint32_t) rint (_session->timecode_frames_per_second()) - 1) {
1911                 return false;
1912         }
1913
1914         if (_session->timecode_drop_frames()) {
1915                 if (TC.minutes % 10 && TC.seconds == 0U && TC.frames < 2U) {
1916                         return false;
1917                 }
1918         }
1919
1920         return true;
1921 }
1922
1923 bool
1924 AudioClock::minsec_validate_edit (const string& str)
1925 {
1926         int hrs, mins, secs, millisecs;
1927
1928         if (sscanf (str.c_str(), "%d:%d:%d.%d", &hrs, &mins, &secs, &millisecs) != 4) {
1929                 return false;
1930         }
1931
1932         if (hrs > 23 || mins > 59 || secs > 59 || millisecs > 999) {
1933                 return false;
1934         }
1935
1936         return true;
1937 }
1938
1939 framepos_t
1940 AudioClock::frames_from_timecode_string (const string& str) const
1941 {
1942         if (_session == 0) {
1943                 return 0;
1944         }
1945
1946         Timecode::Time TC;
1947         framepos_t sample;
1948         char ignored[2];
1949         int hours;
1950
1951         if (sscanf (str.c_str(), "%[- _]%d:%d:%d%[:;]%d", ignored, &hours, &TC.minutes, &TC.seconds, ignored, &TC.frames) != 6) {
1952                 error << string_compose (_("programming error: %1 %2"), "badly formatted timecode clock string", str) << endmsg;
1953                 return 0;
1954         }
1955         TC.hours = abs(hours);
1956         TC.rate = _session->timecode_frames_per_second();
1957         TC.drop= _session->timecode_drop_frames();
1958
1959         _session->timecode_to_sample (TC, sample, false /* use_offset */, false /* use_subframes */ );
1960
1961         // timecode_tester ();
1962         if (edit_is_negative) {
1963                 sample = - sample;
1964         }
1965
1966         return sample;
1967 }
1968
1969 framepos_t
1970 AudioClock::frames_from_minsec_string (const string& str) const
1971 {
1972         if (_session == 0) {
1973                 return 0;
1974         }
1975
1976         int hrs, mins, secs, millisecs;
1977         framecnt_t sr = _session->frame_rate();
1978
1979         if (sscanf (str.c_str(), "%d:%d:%d.%d", &hrs, &mins, &secs, &millisecs) != 4) {
1980                 error << string_compose (_("programming error: %1 %2"), "badly formatted minsec clock string", str) << endmsg;
1981                 return 0;
1982         }
1983
1984         return (framepos_t) floor ((hrs * 60.0f * 60.0f * sr) + (mins * 60.0f * sr) + (secs * sr) + (millisecs * sr / 1000.0));
1985 }
1986
1987 framepos_t
1988 AudioClock::frames_from_bbt_string (framepos_t pos, const string& str) const
1989 {
1990         if (_session == 0) {
1991                 error << "AudioClock::current_time() called with BBT mode but without session!" << endmsg;
1992                 return 0;
1993         }
1994
1995         AnyTime any;
1996         any.type = AnyTime::BBT;
1997
1998         if (sscanf (str.c_str(), BBT_SCANF_FORMAT, &any.bbt.bars, &any.bbt.beats, &any.bbt.ticks) != 3) {
1999                 return 0;
2000         }
2001
2002         if (is_duration) {
2003                 any.bbt.bars++;
2004                 any.bbt.beats++;
2005                 return _session->any_duration_to_frames (pos, any);
2006         } else {
2007                 return _session->convert_to_frames (any);
2008         }
2009 }
2010
2011
2012 framepos_t
2013 AudioClock::frame_duration_from_bbt_string (framepos_t pos, const string& str) const
2014 {
2015         if (_session == 0) {
2016                 error << "AudioClock::current_time() called with BBT mode but without session!" << endmsg;
2017                 return 0;
2018         }
2019
2020         Timecode::BBT_Time bbt;
2021
2022         if (sscanf (str.c_str(), BBT_SCANF_FORMAT, &bbt.bars, &bbt.beats, &bbt.ticks) != 3) {
2023                 return 0;
2024         }
2025
2026         return _session->tempo_map().bbt_duration_at(pos,bbt,1);
2027 }
2028
2029 framepos_t
2030 AudioClock::frames_from_audioframes_string (const string& str) const
2031 {
2032         framepos_t f;
2033         sscanf (str.c_str(), "%" PRId64, &f);
2034         return f;
2035 }
2036
2037 void
2038 AudioClock::build_ops_menu ()
2039 {
2040         using namespace Menu_Helpers;
2041         ops_menu = new Menu;
2042         MenuList& ops_items = ops_menu->items();
2043         ops_menu->set_name ("ArdourContextMenu");
2044
2045         if (!Profile->get_sae()) {
2046                 ops_items.push_back (MenuElem (_("Timecode"), sigc::bind (sigc::mem_fun(*this, &AudioClock::set_mode), Timecode)));
2047         }
2048         ops_items.push_back (MenuElem (_("Bars:Beats"), sigc::bind (sigc::mem_fun(*this, &AudioClock::set_mode), BBT)));
2049         ops_items.push_back (MenuElem (_("Minutes:Seconds"), sigc::bind (sigc::mem_fun(*this, &AudioClock::set_mode), MinSec)));
2050         ops_items.push_back (MenuElem (_("Samples"), sigc::bind (sigc::mem_fun(*this, &AudioClock::set_mode), Frames)));
2051
2052         if (editable && !_off && !is_duration && !_follows_playhead) {
2053                 ops_items.push_back (SeparatorElem());
2054                 ops_items.push_back (MenuElem (_("Set From Playhead"), sigc::mem_fun(*this, &AudioClock::set_from_playhead)));
2055                 ops_items.push_back (MenuElem (_("Locate to This Time"), sigc::mem_fun(*this, &AudioClock::locate)));
2056         }
2057 }
2058
2059 void
2060 AudioClock::set_from_playhead ()
2061 {
2062         if (!_session) {
2063                 return;
2064         }
2065
2066         set (_session->transport_frame());
2067         ValueChanged ();
2068 }
2069
2070 void
2071 AudioClock::locate ()
2072 {
2073         if (!_session || is_duration) {
2074                 return;
2075         }
2076
2077         _session->request_locate (current_time(), _session->transport_rolling ());
2078 }
2079
2080 void
2081 AudioClock::set_mode (Mode m)
2082 {
2083         if (_mode == m) {
2084                 return;
2085         }
2086
2087         _mode = m;
2088
2089         insert_map.clear();
2090
2091         _layout->set_text ("");
2092
2093         if (_left_layout) {
2094
2095                 _left_layout->set_attributes (info_attributes);
2096                 _right_layout->set_attributes (info_attributes);
2097                 /* adjust info_height according to font size */
2098                 int ignored;
2099                 _left_layout->set_text (" 1234567890");
2100                 _left_layout->get_pixel_size (ignored, info_height);
2101
2102                 _left_layout->set_text ("");
2103                 _right_layout->set_text ("");
2104         }
2105
2106         switch (_mode) {
2107         case Timecode:
2108                 mode_based_info_ratio = 0.6;
2109                 insert_map.push_back (11);
2110                 insert_map.push_back (10);
2111                 insert_map.push_back (8);
2112                 insert_map.push_back (7);
2113                 insert_map.push_back (5);
2114                 insert_map.push_back (4);
2115                 insert_map.push_back (2);
2116                 insert_map.push_back (1);
2117                 break;
2118
2119         case BBT:
2120                 mode_based_info_ratio = 0.5;
2121                 insert_map.push_back (11);
2122                 insert_map.push_back (10);
2123                 insert_map.push_back (9);
2124                 insert_map.push_back (8);
2125                 insert_map.push_back (6);
2126                 insert_map.push_back (5);
2127                 insert_map.push_back (3);
2128                 insert_map.push_back (2);
2129                 insert_map.push_back (1);
2130                 break;
2131
2132         case MinSec:
2133                 mode_based_info_ratio = 0.6;
2134                 insert_map.push_back (12);
2135                 insert_map.push_back (11);
2136                 insert_map.push_back (10);
2137                 insert_map.push_back (8);
2138                 insert_map.push_back (7);
2139                 insert_map.push_back (5);
2140                 insert_map.push_back (4);
2141                 insert_map.push_back (2);
2142                 insert_map.push_back (1);
2143                 break;
2144
2145         case Frames:
2146                 mode_based_info_ratio = 0.45;
2147                 break;
2148         }
2149
2150         set (last_when, true);
2151
2152         if (!is_transient) {
2153                 ModeChanged (); /* EMIT SIGNAL (the static one)*/
2154         }
2155
2156         mode_changed (); /* EMIT SIGNAL (the member one) */
2157 }
2158
2159 void
2160 AudioClock::set_bbt_reference (framepos_t pos)
2161 {
2162         bbt_reference_time = pos;
2163 }
2164
2165 void
2166 AudioClock::on_style_changed (const Glib::RefPtr<Gtk::Style>& old_style)
2167 {
2168         CairoWidget::on_style_changed (old_style);
2169
2170         Gtk::Requisition req;
2171         set_clock_dimensions (req);
2172
2173         set_font ();
2174         set_colors ();
2175 }
2176
2177 void
2178 AudioClock::set_editable (bool yn)
2179 {
2180         editable = yn;
2181 }
2182
2183 void
2184 AudioClock::set_is_duration (bool yn)
2185 {
2186         if (yn == is_duration) {
2187                 return;
2188         }
2189
2190         is_duration = yn;
2191         set (last_when, true);
2192 }
2193
2194 void
2195 AudioClock::set_off (bool yn)
2196 {
2197         if (_off == yn) {
2198                 return;
2199         }
2200
2201         _off = yn;
2202
2203         /* force a redraw. last_when will be preserved, but the clock text will
2204          * change
2205          */
2206
2207         set (last_when, true);
2208 }
2209
2210 void
2211 AudioClock::focus ()
2212 {
2213         start_edit (Field (0));
2214 }
2215
2216 void
2217 AudioClock::set_corner_radius (double r)
2218 {
2219         corner_radius = r;
2220         first_width = 0;
2221         first_height = 0;
2222         queue_resize ();
2223 }
2224
2225 void
2226 AudioClock::dpi_reset ()
2227 {
2228         /* force recomputation of size even if we are fixed width
2229          */
2230         first_width = 0;
2231         first_height = 0;
2232         queue_resize ();
2233 }
2234
2235 void
2236 AudioClock::set_negative_allowed (bool yn)
2237 {
2238         _negative_allowed = yn;
2239 }