NO-OP whitespace (updated GH PR #357)
[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 ((double)(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         if (!_session || _session->actively_recording()) {
1524                 /* swallow event, do nothing */
1525                 return true;
1526         }
1527
1528         switch (ev->button) {
1529         case 1:
1530                 if (editable && !_off) {
1531                         int index;
1532                         int trailing;
1533                         int y;
1534                         int x;
1535
1536                         /* the text has been centered vertically, so adjust
1537                          * x and y.
1538                          */
1539                         int xcenter = (get_width() - layout_width) /2;
1540
1541                         y = ev->y - ((get_height() - layout_height)/2);
1542                         x = ev->x - xcenter;
1543
1544                         if (!_layout->xy_to_index (x * PANGO_SCALE, y * PANGO_SCALE, index, trailing)) {
1545                                 /* pretend it is a character on the far right */
1546                                 index = 99;
1547                         }
1548                         drag_field = index_to_field (index);
1549                         dragging = true;
1550                         /* make absolutely sure that the pointer is grabbed */
1551                         gdk_pointer_grab(ev->window,false ,
1552                                          GdkEventMask( Gdk::POINTER_MOTION_MASK | Gdk::BUTTON_PRESS_MASK |Gdk::BUTTON_RELEASE_MASK),
1553                                          NULL,NULL,ev->time);
1554                         drag_accum = 0;
1555                         drag_start_y = ev->y;
1556                         drag_y = ev->y;
1557                 }
1558                 break;
1559
1560         default:
1561                 return false;
1562                 break;
1563         }
1564
1565         return true;
1566 }
1567
1568 bool
1569 AudioClock::on_button_release_event (GdkEventButton *ev)
1570 {
1571         if (!_session || _session->actively_recording()) {
1572                 /* swallow event, do nothing */
1573                 return true;
1574         }
1575
1576         if (editable && !_off) {
1577                 if (dragging) {
1578                         gdk_pointer_ungrab (GDK_CURRENT_TIME);
1579                         dragging = false;
1580                         if (ev->y > drag_start_y+1 || ev->y < drag_start_y-1 || Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)){
1581                                 // we actually dragged so return without
1582                                 // setting editing focus, or we shift clicked
1583                                 return true;
1584                         } else {
1585                                 if (ev->button == 1) {
1586
1587                                         if (_edit_by_click_field) {
1588
1589                                                 int xcenter = (get_width() - layout_width) /2;
1590                                                 int index = 0;
1591                                                 int trailing;
1592                                                 int y = ev->y - ((get_height() - layout_height)/2);
1593                                                 int x = ev->x - xcenter;
1594                                                 Field f;
1595
1596                                                 if (!_layout->xy_to_index (x * PANGO_SCALE, y * PANGO_SCALE, index, trailing)) {
1597                                                         return true;
1598                                                 }
1599
1600                                                 f = index_to_field (index);
1601
1602                                                 switch (f) {
1603                                                 case Timecode_Frames:
1604                                                 case MS_Milliseconds:
1605                                                 case Ticks:
1606                                                         f = Field (0);
1607                                                         break;
1608                                                 default:
1609                                                         break;
1610                                                 }
1611                                                 start_edit (f);
1612                                         } else {
1613                                                 start_edit ();
1614                                         }
1615                                 }
1616                         }
1617                 }
1618         }
1619
1620         if (Keyboard::is_context_menu_event (ev)) {
1621                 if (ops_menu == 0) {
1622                         build_ops_menu ();
1623                 }
1624                 ops_menu->popup (1, ev->time);
1625                 return true;
1626         }
1627
1628         return false;
1629 }
1630
1631 bool
1632 AudioClock::on_focus_out_event (GdkEventFocus* ev)
1633 {
1634         bool ret = CairoWidget::on_focus_out_event (ev);
1635
1636         if (editing) {
1637                 end_edit (_accept_on_focus_out);
1638         }
1639
1640         return ret;
1641 }
1642
1643 bool
1644 AudioClock::on_scroll_event (GdkEventScroll *ev)
1645 {
1646         int index;
1647         int trailing;
1648
1649         if (editing || _session == 0 || !editable || _off || _session->actively_recording())  {
1650                 return false;
1651         }
1652
1653         int y;
1654         int x;
1655
1656         /* the text has been centered vertically, so adjust
1657          * x and y.
1658          */
1659
1660         int xcenter = (get_width() - layout_width) /2;
1661         y = ev->y - ((get_height() - layout_height)/2);
1662         x = ev->x - xcenter;
1663
1664         if (!_layout->xy_to_index (x * PANGO_SCALE, y * PANGO_SCALE, index, trailing)) {
1665                 /* not in the main layout */
1666                 return false;
1667         }
1668
1669         Field f = index_to_field (index);
1670         framepos_t frames = 0;
1671
1672         switch (ev->direction) {
1673
1674         case GDK_SCROLL_UP:
1675                 frames = get_frame_step (f, current_time(), 1);
1676                 if (frames != 0) {
1677                         if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
1678                                 frames *= 10;
1679                         }
1680                         set (current_time() + frames, true);
1681                         ValueChanged (); /* EMIT_SIGNAL */
1682                 }
1683                 break;
1684
1685         case GDK_SCROLL_DOWN:
1686                 frames = get_frame_step (f, current_time(), -1);
1687                 if (frames != 0) {
1688                         if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
1689                                 frames *= 10;
1690                         }
1691
1692                         if (!_negative_allowed && (double)current_time() - (double)frames < 0.0) {
1693                                 set (0, true);
1694                         } else {
1695                                 set (current_time() - frames, true);
1696                         }
1697
1698                         ValueChanged (); /* EMIT_SIGNAL */
1699                 }
1700                 break;
1701
1702         default:
1703                 return false;
1704                 break;
1705         }
1706
1707         return true;
1708 }
1709
1710 bool
1711 AudioClock::on_motion_notify_event (GdkEventMotion *ev)
1712 {
1713         if (editing || _session == 0 || !dragging || _session->actively_recording()) {
1714                 return false;
1715         }
1716
1717         float pixel_frame_scale_factor = 0.2f;
1718
1719         if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier))  {
1720                 pixel_frame_scale_factor = 0.1f;
1721         }
1722
1723
1724         if (Keyboard::modifier_state_contains (ev->state,
1725                                                Keyboard::PrimaryModifier|Keyboard::SecondaryModifier)) {
1726                 pixel_frame_scale_factor = 0.025f;
1727         }
1728
1729         double y_delta = ev->y - drag_y;
1730
1731         drag_accum +=  y_delta*pixel_frame_scale_factor;
1732
1733         drag_y = ev->y;
1734
1735         if (floor (drag_accum) != 0) {
1736
1737                 framepos_t frames;
1738                 framepos_t pos;
1739                 int dir;
1740                 dir = (drag_accum < 0 ? 1:-1);
1741                 pos = current_time();
1742                 frames = get_frame_step (drag_field, pos, dir);
1743
1744                 if (frames  != 0 &&  frames * drag_accum < current_time()) {
1745                         set ((framepos_t) floor (pos - drag_accum * frames), false); // minus because up is negative in GTK
1746                 } else {
1747                         set (0 , false);
1748                 }
1749
1750                 drag_accum= 0;
1751                 ValueChanged();  /* EMIT_SIGNAL */
1752         }
1753
1754         return true;
1755 }
1756
1757 framepos_t
1758 AudioClock::get_frame_step (Field field, framepos_t pos, int dir)
1759 {
1760         framecnt_t f = 0;
1761         Timecode::BBT_Time BBT;
1762         switch (field) {
1763         case Timecode_Hours:
1764                 f = (framecnt_t) floor (3600.0 * _session->frame_rate());
1765                 break;
1766         case Timecode_Minutes:
1767                 f = (framecnt_t) floor (60.0 * _session->frame_rate());
1768                 break;
1769         case Timecode_Seconds:
1770                 f = _session->frame_rate();
1771                 break;
1772         case Timecode_Frames:
1773                 f = (framecnt_t) floor (_session->frame_rate() / _session->timecode_frames_per_second());
1774                 break;
1775
1776         case AudioFrames:
1777                 f = 1;
1778                 break;
1779
1780         case MS_Hours:
1781                 f = (framecnt_t) floor (3600.0 * _session->frame_rate());
1782                 break;
1783         case MS_Minutes:
1784                 f = (framecnt_t) floor (60.0 * _session->frame_rate());
1785                 break;
1786         case MS_Seconds:
1787                 f = (framecnt_t) _session->frame_rate();
1788                 break;
1789         case MS_Milliseconds:
1790                 f = (framecnt_t) floor (_session->frame_rate() / 1000.0);
1791                 break;
1792
1793         case Bars:
1794                 BBT.bars = 1;
1795                 BBT.beats = 0;
1796                 BBT.ticks = 0;
1797                 f = _session->tempo_map().bbt_duration_at (pos,BBT,dir);
1798                 break;
1799         case Beats:
1800                 BBT.bars = 0;
1801                 BBT.beats = 1;
1802                 BBT.ticks = 0;
1803                 f = _session->tempo_map().bbt_duration_at(pos,BBT,dir);
1804                 break;
1805         case Ticks:
1806                 BBT.bars = 0;
1807                 BBT.beats = 0;
1808                 BBT.ticks = 1;
1809                 f = _session->tempo_map().bbt_duration_at(pos,BBT,dir);
1810                 break;
1811         default:
1812                 error << string_compose (_("programming error: %1"), "attempt to get frames from non-text field!") << endmsg;
1813                 f = 0;
1814                 break;
1815         }
1816
1817         return f;
1818 }
1819
1820 framepos_t
1821 AudioClock::current_time (framepos_t) const
1822 {
1823         return last_when;
1824 }
1825
1826 framepos_t
1827 AudioClock::current_duration (framepos_t pos) const
1828 {
1829         framepos_t ret = 0;
1830
1831         switch (_mode) {
1832         case Timecode:
1833                 ret = last_when;
1834                 break;
1835         case BBT:
1836                 ret = frame_duration_from_bbt_string (pos, _layout->get_text());
1837                 break;
1838
1839         case MinSec:
1840                 ret = last_when;
1841                 break;
1842
1843         case Frames:
1844                 ret = last_when;
1845                 break;
1846         }
1847
1848         return ret;
1849 }
1850
1851 bool
1852 AudioClock::bbt_validate_edit (const string& str)
1853 {
1854         AnyTime any;
1855
1856         if (sscanf (str.c_str(), BBT_SCANF_FORMAT, &any.bbt.bars, &any.bbt.beats, &any.bbt.ticks) != 3) {
1857                 return false;
1858         }
1859
1860         if (any.bbt.ticks > Timecode::BBT_Time::ticks_per_beat) {
1861                 return false;
1862         }
1863
1864         if (!is_duration && any.bbt.bars == 0) {
1865                 return false;
1866         }
1867
1868         if (!is_duration && any.bbt.beats == 0) {
1869                 return false;
1870         }
1871
1872         return true;
1873 }
1874
1875 bool
1876 AudioClock::timecode_validate_edit (const string&)
1877 {
1878         Timecode::Time TC;
1879         int hours;
1880         char ignored[2];
1881
1882         if (sscanf (_layout->get_text().c_str(), "%[- _]%" PRId32 ":%" PRId32 ":%" PRId32 "%[:;]%" PRId32,
1883                     ignored, &hours, &TC.minutes, &TC.seconds, ignored, &TC.frames) != 6) {
1884                 return false;
1885         }
1886
1887         if (hours < 0) {
1888                 TC.hours = hours * -1;
1889                 TC.negative = true;
1890         } else {
1891                 TC.hours = hours;
1892                 TC.negative = false;
1893         }
1894
1895         if (TC.negative && !_negative_allowed) {
1896                 return false;
1897         }
1898
1899         if (TC.hours > 23U || TC.minutes > 59U || TC.seconds > 59U) {
1900                 return false;
1901         }
1902
1903         if (TC.frames > (uint32_t) rint (_session->timecode_frames_per_second()) - 1) {
1904                 return false;
1905         }
1906
1907         if (_session->timecode_drop_frames()) {
1908                 if (TC.minutes % 10 && TC.seconds == 0U && TC.frames < 2U) {
1909                         return false;
1910                 }
1911         }
1912
1913         return true;
1914 }
1915
1916 bool
1917 AudioClock::minsec_validate_edit (const string& str)
1918 {
1919         int hrs, mins, secs, millisecs;
1920
1921         if (sscanf (str.c_str(), "%d:%d:%d.%d", &hrs, &mins, &secs, &millisecs) != 4) {
1922                 return false;
1923         }
1924
1925         if (hrs > 23 || mins > 59 || secs > 59 || millisecs > 999) {
1926                 return false;
1927         }
1928
1929         return true;
1930 }
1931
1932 framepos_t
1933 AudioClock::frames_from_timecode_string (const string& str) const
1934 {
1935         if (_session == 0) {
1936                 return 0;
1937         }
1938
1939         Timecode::Time TC;
1940         framepos_t sample;
1941         char ignored[2];
1942         int hours;
1943
1944         if (sscanf (str.c_str(), "%[- _]%d:%d:%d%[:;]%d", ignored, &hours, &TC.minutes, &TC.seconds, ignored, &TC.frames) != 6) {
1945                 error << string_compose (_("programming error: %1 %2"), "badly formatted timecode clock string", str) << endmsg;
1946                 return 0;
1947         }
1948         TC.hours = abs(hours);
1949         TC.rate = _session->timecode_frames_per_second();
1950         TC.drop= _session->timecode_drop_frames();
1951
1952         _session->timecode_to_sample (TC, sample, false /* use_offset */, false /* use_subframes */ );
1953
1954         // timecode_tester ();
1955         if (edit_is_negative) {
1956                 sample = - sample;
1957         }
1958
1959         return sample;
1960 }
1961
1962 framepos_t
1963 AudioClock::frames_from_minsec_string (const string& str) const
1964 {
1965         if (_session == 0) {
1966                 return 0;
1967         }
1968
1969         int hrs, mins, secs, millisecs;
1970         framecnt_t sr = _session->frame_rate();
1971
1972         if (sscanf (str.c_str(), "%d:%d:%d.%d", &hrs, &mins, &secs, &millisecs) != 4) {
1973                 error << string_compose (_("programming error: %1 %2"), "badly formatted minsec clock string", str) << endmsg;
1974                 return 0;
1975         }
1976
1977         return (framepos_t) floor ((hrs * 60.0f * 60.0f * sr) + (mins * 60.0f * sr) + (secs * sr) + (millisecs * sr / 1000.0));
1978 }
1979
1980 framepos_t
1981 AudioClock::frames_from_bbt_string (framepos_t pos, const string& str) const
1982 {
1983         if (_session == 0) {
1984                 error << "AudioClock::current_time() called with BBT mode but without session!" << endmsg;
1985                 return 0;
1986         }
1987
1988         AnyTime any;
1989         any.type = AnyTime::BBT;
1990
1991         if (sscanf (str.c_str(), BBT_SCANF_FORMAT, &any.bbt.bars, &any.bbt.beats, &any.bbt.ticks) != 3) {
1992                 return 0;
1993         }
1994
1995         if (is_duration) {
1996                 any.bbt.bars++;
1997                 any.bbt.beats++;
1998                 return _session->any_duration_to_frames (pos, any);
1999         } else {
2000                 return _session->convert_to_frames (any);
2001         }
2002 }
2003
2004
2005 framepos_t
2006 AudioClock::frame_duration_from_bbt_string (framepos_t pos, const string& str) const
2007 {
2008         if (_session == 0) {
2009                 error << "AudioClock::frame_duration_from_bbt_string() called with BBT mode but without session!" << endmsg;
2010                 return 0;
2011         }
2012
2013         Timecode::BBT_Time bbt;
2014
2015         if (sscanf (str.c_str(), BBT_SCANF_FORMAT, &bbt.bars, &bbt.beats, &bbt.ticks) != 3) {
2016                 return 0;
2017         }
2018
2019         return _session->tempo_map().bbt_duration_at(pos,bbt,1);
2020 }
2021
2022 framepos_t
2023 AudioClock::frames_from_audioframes_string (const string& str) const
2024 {
2025         framepos_t f;
2026         sscanf (str.c_str(), "%" PRId64, &f);
2027         return f;
2028 }
2029
2030 void
2031 AudioClock::copy_text_to_clipboard () const
2032 {
2033         string val;
2034         if (editing) {
2035                 val = pre_edit_string;
2036         } else {
2037                 val = _layout->get_text ();
2038         }
2039         const size_t trim = val.find_first_not_of(" ");
2040         if (trim == string::npos) {
2041                 assert(0); // empty clock, can't be right.
2042                 return;
2043         }
2044         Glib::RefPtr<Clipboard> cl = Gtk::Clipboard::get();
2045         cl->set_text (val.substr(trim));
2046 }
2047
2048 void
2049 AudioClock::build_ops_menu ()
2050 {
2051         using namespace Menu_Helpers;
2052         ops_menu = new Menu;
2053         MenuList& ops_items = ops_menu->items();
2054         ops_menu->set_name ("ArdourContextMenu");
2055
2056         ops_items.push_back (MenuElem (_("Timecode"), sigc::bind (sigc::mem_fun(*this, &AudioClock::set_mode), Timecode, false)));
2057         ops_items.push_back (MenuElem (_("Bars:Beats"), sigc::bind (sigc::mem_fun(*this, &AudioClock::set_mode), BBT, false)));
2058         ops_items.push_back (MenuElem (_("Minutes:Seconds"), sigc::bind (sigc::mem_fun(*this, &AudioClock::set_mode), MinSec, false)));
2059         ops_items.push_back (MenuElem (_("Samples"), sigc::bind (sigc::mem_fun(*this, &AudioClock::set_mode), Frames, false)));
2060
2061         if (editable && !_off && !is_duration && !_follows_playhead) {
2062                 ops_items.push_back (SeparatorElem());
2063                 ops_items.push_back (MenuElem (_("Set from Playhead"), sigc::mem_fun(*this, &AudioClock::set_from_playhead)));
2064                 ops_items.push_back (MenuElem (_("Locate to This Time"), sigc::mem_fun(*this, &AudioClock::locate)));
2065         }
2066         ops_items.push_back (SeparatorElem());
2067         ops_items.push_back (MenuElem (_("Copy to clipboard"), sigc::mem_fun(*this, &AudioClock::copy_text_to_clipboard)));
2068 }
2069
2070 void
2071 AudioClock::set_from_playhead ()
2072 {
2073         if (!_session) {
2074                 return;
2075         }
2076
2077         set (_session->transport_frame());
2078         ValueChanged ();
2079 }
2080
2081 void
2082 AudioClock::locate ()
2083 {
2084         if (!_session || is_duration) {
2085                 return;
2086         }
2087
2088         _session->request_locate (current_time(), _session->transport_rolling ());
2089 }
2090
2091 void
2092 AudioClock::set_mode (Mode m, bool noemit)
2093 {
2094         if (_mode == m) {
2095                 return;
2096         }
2097
2098         _mode = m;
2099
2100         insert_map.clear();
2101
2102         _layout->set_text ("");
2103
2104         Gtk::Requisition req;
2105         set_clock_dimensions (req);
2106
2107         switch (_mode) {
2108         case Timecode:
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                 insert_map.push_back (11);
2121                 insert_map.push_back (10);
2122                 insert_map.push_back (9);
2123                 insert_map.push_back (8);
2124                 insert_map.push_back (6);
2125                 insert_map.push_back (5);
2126                 insert_map.push_back (3);
2127                 insert_map.push_back (2);
2128                 insert_map.push_back (1);
2129                 break;
2130
2131         case MinSec:
2132                 insert_map.push_back (12);
2133                 insert_map.push_back (11);
2134                 insert_map.push_back (10);
2135                 insert_map.push_back (8);
2136                 insert_map.push_back (7);
2137                 insert_map.push_back (5);
2138                 insert_map.push_back (4);
2139                 insert_map.push_back (2);
2140                 insert_map.push_back (1);
2141                 break;
2142
2143         case Frames:
2144                 break;
2145         }
2146
2147         set (last_when, true);
2148
2149         if (!is_transient && !noemit) {
2150                 ModeChanged (); /* EMIT SIGNAL (the static one)*/
2151         }
2152
2153         mode_changed (); /* EMIT SIGNAL (the member one) */
2154 }
2155
2156 void
2157 AudioClock::set_bbt_reference (framepos_t pos)
2158 {
2159         bbt_reference_time = pos;
2160 }
2161
2162 void
2163 AudioClock::on_style_changed (const Glib::RefPtr<Gtk::Style>& old_style)
2164 {
2165         CairoWidget::on_style_changed (old_style);
2166
2167         Gtk::Requisition req;
2168         set_clock_dimensions (req);
2169
2170         /* XXXX fix me ... we shouldn't be using GTK styles anyway */
2171         // set_font ();
2172         set_colors ();
2173 }
2174
2175 void
2176 AudioClock::set_editable (bool yn)
2177 {
2178         editable = yn;
2179 }
2180
2181 void
2182 AudioClock::set_is_duration (bool yn)
2183 {
2184         if (yn == is_duration) {
2185                 return;
2186         }
2187
2188         is_duration = yn;
2189         set (last_when, true);
2190 }
2191
2192 void
2193 AudioClock::set_off (bool yn)
2194 {
2195         if (_off == yn) {
2196                 return;
2197         }
2198
2199         _off = yn;
2200
2201         /* force a redraw. last_when will be preserved, but the clock text will
2202          * change
2203          */
2204
2205         set (last_when, true);
2206 }
2207
2208 void
2209 AudioClock::focus ()
2210 {
2211         start_edit (Field (0));
2212 }
2213
2214 void
2215 AudioClock::set_corner_radius (double r)
2216 {
2217         corner_radius = r;
2218         first_width = 0;
2219         first_height = 0;
2220         queue_resize ();
2221 }
2222
2223 void
2224 AudioClock::dpi_reset ()
2225 {
2226         /* force recomputation of size even if we are fixed width
2227          */
2228         first_width = 0;
2229         first_height = 0;
2230         queue_resize ();
2231 }
2232
2233 void
2234 AudioClock::set_negative_allowed (bool yn)
2235 {
2236         _negative_allowed = yn;
2237 }