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