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