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