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