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