Implement clock delta modes
[ardour.git] / gtk2_ardour / audio_clock.cc
1 /*
2     Copyright (C) 1999 Paul Davis
3
4     This program is free software; you can redistribute it and/or modify
5     it under the terms of the GNU General Public License as published by
6     the Free Software Foundation; either version 2 of the License, or
7     (at your option) any later version.
8
9     This program is distributed in the hope that it will be useful,
10     but WITHOUT ANY WARRANTY; without even the implied warranty of
11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12     GNU General Public License for more details.
13
14     You should have received a copy of the GNU General Public License
15     along with this program; if not, write to the Free Software
16     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17
18 */
19
20 #include <cstdio> // for sprintf
21 #include <cmath>
22
23 #include "pbd/convert.h"
24 #include "pbd/enumwriter.h"
25
26 #include <gtkmm/style.h>
27 #include <sigc++/bind.h>
28
29 #include "gtkmm2ext/utils.h"
30 #include "gtkmm2ext/rgb_macros.h"
31
32 #include "widgets/tooltips.h"
33
34 #include "ardour/profile.h"
35 #include "ardour/lmath.h"
36 #include "ardour/session.h"
37 #include "ardour/slave.h"
38 #include "ardour/tempo.h"
39 #include "ardour/types.h"
40
41 #include "ardour_ui.h"
42 #include "audio_clock.h"
43 #include "enums_convert.h"
44 #include "gui_thread.h"
45 #include "keyboard.h"
46 #include "ui_config.h"
47 #include "utils.h"
48
49 #include "pbd/i18n.h"
50
51 using namespace ARDOUR;
52 using namespace ARDOUR_UI_UTILS;
53 using namespace ArdourWidgets;
54 using namespace PBD;
55 using namespace Gtk;
56 using namespace std;
57
58 using Gtkmm2ext::Keyboard;
59
60 sigc::signal<void> AudioClock::ModeChanged;
61 vector<AudioClock*> AudioClock::clocks;
62
63 #define BBT_BAR_CHAR "|"
64 #define BBT_SCANF_FORMAT "%" PRIu32 "%*c%" PRIu32 "%*c%" PRIu32
65
66 AudioClock::AudioClock (const string& clock_name, bool transient, const string& widget_name,
67                         bool allow_edit, bool follows_playhead, bool duration, bool with_info,
68                         bool accept_on_focus_out)
69         : ops_menu (0)
70         , _name (clock_name)
71         , is_transient (transient)
72         , is_duration (duration)
73         , editable (allow_edit)
74         , _follows_playhead (follows_playhead)
75         , _accept_on_focus_out (accept_on_focus_out)
76         , _off (false)
77         , em_width (0)
78         , _edit_by_click_field (false)
79         , _negative_allowed (false)
80         , edit_is_negative (false)
81         , _limit_pos (INT64_MAX - 1)
82         , _with_info (with_info)
83         , editing_attr (0)
84         , foreground_attr (0)
85         , first_height (0)
86         , first_width (0)
87         , style_resets_first (true)
88         , layout_height (0)
89         , layout_width (0)
90         , corner_radius (4)
91         , font_size (10240)
92         , editing (false)
93         , bbt_reference_time (-1)
94         , last_when(0)
95         , last_pdelta (0)
96         , last_sdelta (0)
97         , dragging (false)
98         , drag_field (Field (0))
99         , xscale (1.0)
100         , yscale (1.0)
101 {
102         set_flags (CAN_FOCUS);
103
104         _layout = Pango::Layout::create (get_pango_context());
105         _layout->set_attributes (normal_attributes);
106
107         set_widget_name (widget_name);
108
109         _mode = BBT; /* lie to force mode switch */
110         set_mode (Timecode);
111         AudioClock::set (last_when, true);
112
113         if (!is_transient) {
114                 clocks.push_back (this);
115         }
116
117         _left_btn.set_sizing_text (_("0000000000000"));
118         // NB right_btn is in a size-group
119
120         _left_btn.set_layout_font (UIConfiguration::instance().get_SmallFont());
121         _right_btn.set_layout_font (UIConfiguration::instance().get_SmallFont());
122
123         UIConfiguration::instance().ColorsChanged.connect (sigc::mem_fun (*this, &AudioClock::set_colors));
124         UIConfiguration::instance().DPIReset.connect (sigc::mem_fun (*this, &AudioClock::dpi_reset));
125 }
126
127 AudioClock::~AudioClock ()
128 {
129         delete foreground_attr;
130         delete editing_attr;
131 }
132
133 void
134 AudioClock::set_widget_name (const string& str)
135 {
136         if (str.empty()) {
137                 set_name ("clock");
138         } else {
139                 set_name (str + " clock");
140         }
141
142         if (is_realized()) {
143                 set_colors ();
144         }
145 }
146
147
148 void
149 AudioClock::on_realize ()
150 {
151         Gtk::Requisition req;
152
153         CairoWidget::on_realize ();
154
155         set_clock_dimensions (req);
156
157         first_width = req.width;
158         first_height = req.height;
159
160         // XXX FIX ME: define font based on ... ???
161         // set_font ();
162         set_colors ();
163 }
164
165 void
166 AudioClock::set_font (Pango::FontDescription font)
167 {
168         Glib::RefPtr<Gtk::Style> style = get_style ();
169         Pango::AttrFontDesc* font_attr;
170
171         font_size = font.get_size();
172         font_attr = new Pango::AttrFontDesc (Pango::Attribute::create_attr_font_desc (font));
173
174         normal_attributes.change (*font_attr);
175         editing_attributes.change (*font_attr);
176         delete font_attr;
177
178         /* get the figure width for the font. This doesn't have to super
179          * accurate since we only use it to measure the (roughly 1 character)
180          * offset from the position Pango tells us for the "cursor"
181          */
182
183         Glib::RefPtr<Pango::Layout> tmp = Pango::Layout::create (get_pango_context());
184         int ignore_height;
185
186         tmp->set_text ("8");
187         tmp->get_pixel_size (em_width, ignore_height);
188
189         /* force redraw of markup with new font-size */
190         AudioClock::set (last_when, true);
191 }
192
193 void
194 AudioClock::set_active_state (Gtkmm2ext::ActiveState s)
195 {
196         CairoWidget::set_active_state (s);
197         set_colors ();
198 }
199
200 void
201 AudioClock::set_colors ()
202 {
203         int r, g, b, a;
204
205         uint32_t bg_color;
206         uint32_t text_color;
207         uint32_t editing_color;
208         uint32_t cursor_color;
209
210         if (active_state()) {
211                 bg_color = UIConfiguration::instance().color (string_compose ("%1 active: background", get_name()));
212                 text_color = UIConfiguration::instance().color (string_compose ("%1 active: text", get_name()));
213                 editing_color = UIConfiguration::instance().color (string_compose ("%1 active: edited text", get_name()));
214                 cursor_color = UIConfiguration::instance().color (string_compose ("%1 active: cursor", get_name()));
215         } else {
216                 bg_color = UIConfiguration::instance().color (string_compose ("%1: background", get_name()));
217                 text_color = UIConfiguration::instance().color (string_compose ("%1: text", get_name()));
218                 editing_color = UIConfiguration::instance().color (string_compose ("%1: edited text", get_name()));
219                 cursor_color = UIConfiguration::instance().color (string_compose ("%1: cursor", get_name()));
220         }
221
222         /* store for bg and cursor in render() */
223
224         UINT_TO_RGBA (bg_color, &r, &g, &b, &a);
225
226         bg_r = r/255.0;
227         bg_g = g/255.0;
228         bg_b = b/255.0;
229         bg_a = a/255.0;
230
231         UINT_TO_RGBA (cursor_color, &r, &g, &b, &a);
232
233         cursor_r = r/255.0;
234         cursor_g = g/255.0;
235         cursor_b = b/255.0;
236         cursor_a = a/255.0;
237
238         /* rescale for Pango colors ... sigh */
239
240         r = lrint (r * 65535.0);
241         g = lrint (g * 65535.0);
242         b = lrint (b * 65535.0);
243
244         UINT_TO_RGBA (text_color, &r, &g, &b, &a);
245         r = lrint ((r/255.0) * 65535.0);
246         g = lrint ((g/255.0) * 65535.0);
247         b = lrint ((b/255.0) * 65535.0);
248         delete foreground_attr;
249         foreground_attr = new Pango::AttrColor (Pango::Attribute::create_attr_foreground (r, g, b));
250
251         UINT_TO_RGBA (editing_color, &r, &g, &b, &a);
252         r = lrint ((r/255.0) * 65535.0);
253         g = lrint ((g/255.0) * 65535.0);
254         b = lrint ((b/255.0) * 65535.0);
255         delete editing_attr;
256         editing_attr = new Pango::AttrColor (Pango::Attribute::create_attr_foreground (r, g, b));
257
258         normal_attributes.change (*foreground_attr);
259         editing_attributes.change (*foreground_attr);
260         editing_attributes.change (*editing_attr);
261
262         if (!editing) {
263                 _layout->set_attributes (normal_attributes);
264         } else {
265                 _layout->set_attributes (editing_attributes);
266         }
267
268         queue_draw ();
269 }
270
271 void
272 AudioClock::set_scale (double x, double y)
273 {
274         xscale = x;
275         yscale = y;
276
277         queue_draw ();
278 }
279
280 void
281 AudioClock::render (Cairo::RefPtr<Cairo::Context> const& ctx, cairo_rectangle_t*)
282 {
283         cairo_t* cr = ctx->cobj();
284         /* main layout: rounded rect, plus the text */
285
286         if (_need_bg) {
287                 cairo_set_source_rgba (cr, bg_r, bg_g, bg_b, bg_a);
288                 if (corner_radius) {
289                         Gtkmm2ext::rounded_rectangle (cr, 0, 0, get_width(), get_height(), corner_radius);
290                 } else {
291                         cairo_rectangle (cr, 0, 0, get_width(), get_height());
292                 }
293                 cairo_fill (cr);
294         }
295
296         double lw = layout_width * xscale;
297         double lh = layout_height * yscale;
298
299         cairo_move_to (cr, (get_width() - lw) / 2.0, (get_height() - lh) / 2.0);
300
301         if (xscale != 1.0 || yscale != 1.0) {
302                 cairo_save (cr);
303                 cairo_scale (cr, xscale, yscale);
304         }
305
306         pango_cairo_show_layout (cr, _layout->gobj());
307
308         if (xscale != 1.0 || yscale != 1.0) {
309                 cairo_restore (cr);
310         }
311
312         if (editing) {
313                 if (!insert_map.empty()) {
314
315                         int xcenter = (get_width() - layout_width) /2;
316
317                         if (input_string.length() < insert_map.size()) {
318                                 Pango::Rectangle cursor;
319
320                                 if (input_string.empty()) {
321                                         /* nothing entered yet, put cursor at the end
322                                            of string
323                                         */
324                                         cursor = _layout->get_cursor_strong_pos (edit_string.length() - 1);
325                                 } else {
326                                         cursor = _layout->get_cursor_strong_pos (insert_map[input_string.length()]);
327                                 }
328
329                                 cairo_set_source_rgba (cr, cursor_r, cursor_g, cursor_b, cursor_a);
330                                 cairo_rectangle (cr,
331                                                  min (get_width() - 2.0,
332                                                  (double) xcenter + cursor.get_x()/PANGO_SCALE + em_width),
333                                                  (get_height() - layout_height)/2.0,
334                                                  2.0, cursor.get_height()/PANGO_SCALE);
335                                 cairo_fill (cr);
336                         } else {
337                                 /* we've entered all possible digits, no cursor */
338                         }
339
340                 } else {
341                         if (input_string.empty()) {
342                                 cairo_set_source_rgba (cr, cursor_r, cursor_g, cursor_b, cursor_a);
343                                 cairo_rectangle (cr,
344                                                  (get_width()/2.0),
345                                                  (get_height() - layout_height)/2.0,
346                                                  2.0, get_height());
347                                 cairo_fill (cr);
348                         }
349                 }
350         }
351 }
352
353 void
354 AudioClock::set_clock_dimensions (Gtk::Requisition& req)
355 {
356         Glib::RefPtr<Pango::Layout> tmp;
357         Glib::RefPtr<Gtk::Style> style = get_style ();
358         Pango::FontDescription font;
359
360         tmp = Pango::Layout::create (get_pango_context());
361
362         if (!is_realized()) {
363                 font = get_font_for_style (get_name());
364         } else {
365                 font = style->get_font();
366         }
367
368         tmp->set_font_description (font);
369
370         /* this string is the longest thing we will ever display */
371         if (_mode == MinSec)
372                 tmp->set_text (" 88:88:88,888 ");
373         else
374                 tmp->set_text (" 88:88:88,88 ");
375         tmp->get_pixel_size (req.width, req.height);
376
377         layout_height = req.height;
378         layout_width = req.width;
379 }
380
381 void
382 AudioClock::on_size_request (Gtk::Requisition* req)
383 {
384         /* even for non fixed width clocks, the size we *ask* for never changes,
385            even though the size we receive might. so once we've computed it,
386            just return it.
387         */
388
389         if (first_width) {
390                 req->width = first_width;
391                 req->height = first_height;
392                 return;
393         }
394
395         set_clock_dimensions (*req);
396
397         /* now tackle height, for which we need to know the height of the lower
398          * layout
399          */
400 }
401
402 void
403 AudioClock::show_edit_status (int length)
404 {
405         editing_attr->set_start_index (edit_string.length() - length);
406         editing_attr->set_end_index (edit_string.length());
407
408         editing_attributes.change (*foreground_attr);
409         editing_attributes.change (*editing_attr);
410
411         _layout->set_attributes (editing_attributes);
412 }
413
414 void
415 AudioClock::start_edit (Field f)
416 {
417         if (!editing) {
418                 pre_edit_string = _layout->get_text ();
419                 if (!insert_map.empty()) {
420                         edit_string = pre_edit_string;
421                 } else {
422                         edit_string.clear ();
423                         _layout->set_text ("");
424                 }
425
426                 input_string.clear ();
427                 editing = true;
428                 edit_is_negative = false;
429
430                 if (f) {
431                         input_string = get_field (f);
432                         show_edit_status (merge_input_and_edit_string ());
433                         _layout->set_text (edit_string);
434                 }
435
436                 queue_draw ();
437
438                 Keyboard::magic_widget_grab_focus ();
439                 grab_focus ();
440         }
441 }
442
443 string
444 AudioClock::get_field (Field f)
445 {
446         switch (f) {
447         case Timecode_Hours:
448                 return edit_string.substr (1, 2);
449                 break;
450         case Timecode_Minutes:
451                 return edit_string.substr (4, 2);
452                 break;
453         case Timecode_Seconds:
454                 return edit_string.substr (7, 2);
455                 break;
456         case Timecode_frames:
457                 return edit_string.substr (10, 2);
458                 break;
459         case MS_Hours:
460                 return edit_string.substr (1, 2);
461                 break;
462         case MS_Minutes:
463                 return edit_string.substr (4, 2);
464                 break;
465         case MS_Seconds:
466                 return edit_string.substr (7, 2);
467                 break;
468         case MS_Milliseconds:
469                 return edit_string.substr (10, 3);
470                 break;
471         case Bars:
472                 return edit_string.substr (1, 3);
473                 break;
474         case Beats:
475                 return edit_string.substr (5, 2);
476                 break;
477         case Ticks:
478                 return edit_string.substr (8, 4);
479                 break;
480         case SS_Seconds:
481                 return edit_string.substr (0, 8);
482         case SS_Deciseconds:
483                 return edit_string.substr (9, 1);
484         case S_Samples:
485                 return edit_string;
486                 break;
487         }
488         return "";
489 }
490
491 void
492 AudioClock::end_edit (bool modify)
493 {
494         if (modify) {
495
496                 bool ok = true;
497
498                 switch (_mode) {
499                 case Timecode:
500                         ok = timecode_validate_edit (edit_string);
501                         break;
502
503                 case BBT:
504                         ok = bbt_validate_edit (edit_string);
505                         break;
506
507                 case MinSec:
508                         ok = minsec_validate_edit (edit_string);
509                         break;
510
511                 case Seconds:
512                         // no break
513                 case Samples:
514                         if (edit_string.length() < 1) {
515                                 edit_string = pre_edit_string;
516                         }
517                         break;
518                 }
519
520                 if (!ok) {
521                         edit_string = pre_edit_string;
522                         input_string.clear ();
523                         _layout->set_text (edit_string);
524                         show_edit_status (0);
525                         /* edit attributes remain in use */
526                 } else {
527
528                         editing = false;
529                         samplepos_t pos = 0; /* stupid gcc */
530
531                         switch (_mode) {
532                         case Timecode:
533                                 pos = samples_from_timecode_string (edit_string);
534                                 break;
535
536                         case BBT:
537                                 if (is_duration) {
538                                         pos = sample_duration_from_bbt_string (bbt_reference_time, edit_string);
539                                 } else {
540                                         pos = samples_from_bbt_string (0, edit_string);
541                                 }
542                                 break;
543
544                         case MinSec:
545                                 pos = samples_from_minsec_string (edit_string);
546                                 break;
547
548                         case Seconds:
549                                 pos = samples_from_seconds_string (edit_string);
550                                 break;
551
552                         case Samples:
553                                 pos = samples_from_audiosamples_string (edit_string);
554                                 break;
555                         }
556
557                         AudioClock::set (pos, true);
558                         _layout->set_attributes (normal_attributes);
559                         ValueChanged(); /* EMIT_SIGNAL */
560                 }
561
562         } else {
563
564                 editing = false;
565                 edit_is_negative = false;
566                 _layout->set_attributes (normal_attributes);
567                 _layout->set_text (pre_edit_string);
568         }
569
570         queue_draw ();
571
572         if (!editing) {
573                 drop_focus ();
574         }
575 }
576
577 void
578 AudioClock::drop_focus ()
579 {
580         Keyboard::magic_widget_drop_focus ();
581
582         if (has_focus()) {
583                 /* move focus back to the default widget in the top level window */
584                 ARDOUR_UI::instance()->reset_focus (this);
585         }
586 }
587
588 samplecnt_t
589 AudioClock::parse_as_seconds_distance (const std::string& str)
590 {
591         float f;
592
593         if (sscanf (str.c_str(), "%f", &f) == 1) {
594                 return f * _session->sample_rate();
595         }
596
597         return 0;
598 }
599
600 samplecnt_t
601 AudioClock::parse_as_samples_distance (const std::string& str)
602 {
603         samplecnt_t f;
604
605         if (sscanf (str.c_str(), "%" PRId64, &f) == 1) {
606                 return f;
607         }
608
609         return 0;
610 }
611
612 samplecnt_t
613 AudioClock::parse_as_minsec_distance (const std::string& str)
614 {
615         samplecnt_t sr = _session->sample_rate();
616         int msecs;
617         int secs;
618         int mins;
619         int hrs;
620
621         switch (str.length()) {
622         case 0:
623                 return 0;
624         case 1:
625         case 2:
626         case 3:
627         case 4:
628                 sscanf (str.c_str(), "%" PRId32, &msecs);
629                 return msecs * (sr / 1000);
630
631         case 5:
632                 sscanf (str.c_str(), "%1" PRId32 "%" PRId32, &secs, &msecs);
633                 return (secs * sr) + (msecs * (sr/1000));
634
635         case 6:
636                 sscanf (str.c_str(), "%2" PRId32 "%" PRId32, &secs, &msecs);
637                 return (secs * sr) + (msecs * (sr/1000));
638
639         case 7:
640                 sscanf (str.c_str(), "%1" PRId32 "%2" PRId32 "%" PRId32, &mins, &secs, &msecs);
641                 return (mins * 60 * sr) + (secs * sr) + (msecs * (sr/1000));
642
643         case 8:
644                 sscanf (str.c_str(), "%2" PRId32 "%2" PRId32 "%" PRId32, &mins, &secs, &msecs);
645                 return (mins * 60 * sr) + (secs * sr) + (msecs * (sr/1000));
646
647         case 9:
648                 sscanf (str.c_str(), "%1" PRId32 "%2" PRId32 "%2" PRId32 "%" PRId32, &hrs, &mins, &secs, &msecs);
649                 return (hrs * 3600 * sr) + (mins * 60 * sr) + (secs * sr) + (msecs * (sr/1000));
650
651         case 10:
652                 sscanf (str.c_str(), "%1" PRId32 "%2" PRId32 "%2" PRId32 "%" PRId32, &hrs, &mins, &secs, &msecs);
653                 return (hrs * 3600 * sr) + (mins * 60 * sr) + (secs * sr) + (msecs * (sr/1000));
654
655         default:
656                 break;
657         }
658
659         return 0;
660 }
661
662 samplecnt_t
663 AudioClock::parse_as_timecode_distance (const std::string& str)
664 {
665         double fps = _session->timecode_frames_per_second();
666         samplecnt_t sr = _session->sample_rate();
667         int samples;
668         int secs;
669         int mins;
670         int hrs;
671
672         switch (str.length()) {
673         case 0:
674                 return 0;
675         case 1:
676         case 2:
677                 sscanf (str.c_str(), "%" PRId32, &samples);
678                 return llrint ((samples/(float)fps) * sr);
679
680         case 3:
681                 sscanf (str.c_str(), "%1" PRId32 "%" PRId32, &secs, &samples);
682                 return (secs * sr) + llrint ((samples/(float)fps) * sr);
683
684         case 4:
685                 sscanf (str.c_str(), "%2" PRId32 "%" PRId32, &secs, &samples);
686                 return (secs * sr) + llrint ((samples/(float)fps) * sr);
687
688         case 5:
689                 sscanf (str.c_str(), "%1" PRId32 "%2" PRId32 "%" PRId32, &mins, &secs, &samples);
690                 return (mins * 60 * sr) + (secs * sr) + llrint ((samples/(float)fps) * sr);
691
692         case 6:
693                 sscanf (str.c_str(), "%2" PRId32 "%2" PRId32 "%" PRId32, &mins, &secs, &samples);
694                 return (mins * 60 * sr) + (secs * sr) + llrint ((samples/(float)fps) * sr);
695
696         case 7:
697                 sscanf (str.c_str(), "%1" PRId32 "%2" PRId32 "%2" PRId32 "%" PRId32, &hrs, &mins, &secs, &samples);
698                 return (hrs * 3600 * sr) + (mins * 60 * sr) + (secs * sr) + llrint ((samples/(float)fps) * sr);
699
700         case 8:
701                 sscanf (str.c_str(), "%2" PRId32 "%2" PRId32 "%2" PRId32 "%" PRId32, &hrs, &mins, &secs, &samples);
702                 return (hrs * 3600 * sr) + (mins * 60 * sr) + (secs * sr) + llrint ((samples/(float)fps) * sr);
703
704         default:
705                 break;
706         }
707
708         return 0;
709 }
710
711 samplecnt_t
712 AudioClock::parse_as_bbt_distance (const std::string&)
713 {
714         return 0;
715 }
716
717 samplecnt_t
718 AudioClock::parse_as_distance (const std::string& instr)
719 {
720         switch (_mode) {
721         case Timecode:
722                 return parse_as_timecode_distance (instr);
723                 break;
724         case Samples:
725                 return parse_as_samples_distance (instr);
726                 break;
727         case BBT:
728                 return parse_as_bbt_distance (instr);
729                 break;
730         case MinSec:
731                 return parse_as_minsec_distance (instr);
732                 break;
733         case Seconds:
734                 return parse_as_seconds_distance (instr);
735                 break;
736         }
737         return 0;
738 }
739
740 void
741 AudioClock::end_edit_relative (bool add)
742 {
743         bool ok = true;
744
745         switch (_mode) {
746         case Timecode:
747                 ok = timecode_validate_edit (edit_string);
748                 break;
749
750         case BBT:
751                 ok = bbt_validate_edit (edit_string);
752                 break;
753
754         case MinSec:
755                 ok = minsec_validate_edit (edit_string);
756                 break;
757
758         case Seconds:
759                 break;
760
761         case Samples:
762                 break;
763         }
764
765         if (!ok) {
766                 edit_string = pre_edit_string;
767                 input_string.clear ();
768                 _layout->set_text (edit_string);
769                 show_edit_status (0);
770                 /* edit attributes remain in use */
771                 queue_draw ();
772                 return;
773         }
774
775         samplecnt_t samples = parse_as_distance (input_string);
776
777         editing = false;
778
779         editing = false;
780         _layout->set_attributes (normal_attributes);
781
782         if (samples != 0) {
783                 if (add) {
784                         AudioClock::set (current_time() + samples, true);
785                 } else {
786                         samplepos_t c = current_time();
787
788                         if (c > samples || _negative_allowed) {
789                                 AudioClock::set (c - samples, true);
790                         } else {
791                                 AudioClock::set (0, true);
792                         }
793                 }
794                 ValueChanged (); /* EMIT SIGNAL */
795         }
796
797         input_string.clear ();
798         queue_draw ();
799         drop_focus ();
800 }
801
802 void
803 AudioClock::session_property_changed (const PropertyChange&)
804 {
805         AudioClock::set (last_when, true);
806 }
807
808 void
809 AudioClock::session_configuration_changed (std::string p)
810 {
811         if (_negative_allowed) {
812                 /* session option editor clock */
813                 return;
814         }
815
816         if (p == "sync-source" || p == "external-sync") {
817                 AudioClock::set (current_time(), true);
818                 return;
819         }
820
821         if (p != "timecode-offset" && p != "timecode-offset-negative") {
822                 return;
823         }
824
825         samplecnt_t current;
826
827         switch (_mode) {
828         case Timecode:
829                 if (is_duration) {
830                         current = current_duration ();
831                 } else {
832                         current = current_time ();
833                 }
834                 AudioClock::set (current, true);
835                 break;
836         default:
837                 break;
838         }
839 }
840
841 void
842 AudioClock::set (samplepos_t when, bool force, samplecnt_t offset)
843 {
844         if ((!force && !is_visible()) || _session == 0) {
845                 return;
846         }
847
848         _offset = offset;
849         if (is_duration) {
850                 when = when - offset;
851         }
852
853         if (when > _limit_pos) {
854                 when = _limit_pos;
855         } else if (when < -_limit_pos) {
856                 when = -_limit_pos;
857         }
858
859         if (when == last_when && !force) {
860 #if 0 // XXX return if no change and no change forced. verify Aug/2014
861                 if (_mode != Timecode && _mode != MinSec) {
862                         /* may need to force display of TC source
863                          * time, so don't return early.
864                          */
865                         /* ^^ Why was that?,  delta times?
866                          * Timecode FPS, pull-up/down, etc changes
867                          * trigger a 'session_property_changed' which
868                          * eventually calls set(last_when, true)
869                          *
870                          * re-rendering the clock every 40ms or so just
871                          * because we can is not ideal.
872                          */
873                         return;
874                 }
875 #else
876                 return;
877 #endif
878         }
879
880         bool btn_en = false;
881
882         if (!editing) {
883
884                 switch (_mode) {
885                 case Timecode:
886                         set_timecode (when, force);
887                         break;
888
889                 case BBT:
890                         set_bbt (when, offset, force);
891                         btn_en = true;
892                         break;
893
894                 case MinSec:
895                         set_minsec (when, force);
896                         break;
897
898                 case Seconds:
899                         set_seconds (when, force);
900                         break;
901
902                 case Samples:
903                         set_samples (when, force);
904                         break;
905                 }
906         }
907
908         if (_with_info) {
909                 _left_btn.set_sensitive (btn_en);
910                 _right_btn.set_sensitive (btn_en);
911                 _left_btn.set_visual_state (Gtkmm2ext::NoVisualState);
912                 _right_btn.set_visual_state (Gtkmm2ext::NoVisualState);
913                 if (btn_en) {
914                         _left_btn.set_elements (ArdourButton::Element(ArdourButton::Edge|ArdourButton::Body|ArdourButton::Text));
915                         _right_btn.set_elements (ArdourButton::Element(ArdourButton::Edge|ArdourButton::Body|ArdourButton::Text));
916                         _left_btn.set_alignment (.5, .5);
917                         _right_btn.set_alignment (.5, .5);
918                         set_tooltip (_left_btn, _("Change current tempo"));
919                         set_tooltip (_right_btn, _("Change current time signature"));
920                 } else {
921                         _left_btn.set_elements (ArdourButton::Text);
922                         _right_btn.set_elements (ArdourButton::Text);
923                         _left_btn.set_alignment (0, .5);
924                         _right_btn.set_alignment (1, .5);
925                         set_tooltip (_left_btn, _(""));
926                         set_tooltip (_right_btn, _(""));
927                 }
928         }
929
930         queue_draw ();
931         last_when = when;
932 }
933
934 void
935 AudioClock::set_slave_info ()
936 {
937         if (!_with_info) {
938                 return;
939         }
940
941         SyncSource sync_src = Config->get_sync_source();
942
943         if (_session->config.get_external_sync()) {
944                 Slave* slave = _session->slave();
945
946                 switch (sync_src) {
947                 case Engine:
948                         _left_btn.set_text (sync_source_to_string (sync_src, true), true);
949                         _right_btn.set_text ("", true);
950                         break;
951                 case MIDIClock:
952                         if (slave) {
953                                 _left_btn.set_text (sync_source_to_string (sync_src, true), true);
954                                 _right_btn.set_text (slave->approximate_current_delta (), 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 (slave) {
963                                 bool matching;
964                                 TimecodeSlave* tcslave;
965                                 if ((tcslave = dynamic_cast<TimecodeSlave*>(_session->slave())) != 0) {
966                                         matching = (tcslave->apparent_timecode_format() == _session->config.get_timecode_format());
967                                         _left_btn.set_text (string_compose ("%1<span face=\"monospace\" foreground=\"%3\">%2</span>",
968                                                                 sync_source_to_string(sync_src, true)[0],
969                                                                 dynamic_cast<TimecodeSlave*>(slave)->approximate_current_position (),
970                                                                 matching ? "#66ff66" : "#ff3333"
971                                                                 ), true);
972                                         _right_btn.set_text (slave->approximate_current_delta (), 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",
982                                         _("INT"), sync_source_to_string(sync_src, true)), 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                 ret = last_when;
1917                 break;
1918         }
1919
1920         return ret;
1921 }
1922
1923 bool
1924 AudioClock::bbt_validate_edit (const string& str)
1925 {
1926         AnyTime any;
1927
1928         if (sscanf (str.c_str(), BBT_SCANF_FORMAT, &any.bbt.bars, &any.bbt.beats, &any.bbt.ticks) != 3) {
1929                 return false;
1930         }
1931
1932         if (any.bbt.ticks > Timecode::BBT_Time::ticks_per_beat) {
1933                 return false;
1934         }
1935
1936         if (!is_duration && any.bbt.bars == 0) {
1937                 return false;
1938         }
1939
1940         if (!is_duration && any.bbt.beats == 0) {
1941                 return false;
1942         }
1943
1944         return true;
1945 }
1946
1947 bool
1948 AudioClock::timecode_validate_edit (const string&)
1949 {
1950         Timecode::Time TC;
1951         int hours;
1952         char ignored[2];
1953
1954         if (sscanf (_layout->get_text().c_str(), "%[- _]%" PRId32 ":%" PRId32 ":%" PRId32 "%[:;]%" PRId32,
1955                     ignored, &hours, &TC.minutes, &TC.seconds, ignored, &TC.frames) != 6) {
1956                 return false;
1957         }
1958
1959         if (hours < 0) {
1960                 TC.hours = hours * -1;
1961                 TC.negative = true;
1962         } else {
1963                 TC.hours = hours;
1964                 TC.negative = false;
1965         }
1966
1967         if (TC.negative && !_negative_allowed) {
1968                 return false;
1969         }
1970
1971         if (TC.hours > 23U || TC.minutes > 59U || TC.seconds > 59U) {
1972                 return false;
1973         }
1974
1975         if (TC.frames > (uint32_t) rint (_session->timecode_frames_per_second()) - 1) {
1976                 return false;
1977         }
1978
1979         if (_session->timecode_drop_frames()) {
1980                 if (TC.minutes % 10 && TC.seconds == 0U && TC.frames < 2U) {
1981                         return false;
1982                 }
1983         }
1984
1985         return true;
1986 }
1987
1988 bool
1989 AudioClock::minsec_validate_edit (const string& str)
1990 {
1991         int hrs, mins, secs, millisecs;
1992
1993         if (sscanf (str.c_str(), "%d:%d:%d.%d", &hrs, &mins, &secs, &millisecs) != 4) {
1994                 return false;
1995         }
1996
1997         if (hrs > 23 || mins > 59 || secs > 59 || millisecs > 999) {
1998                 return false;
1999         }
2000
2001         return true;
2002 }
2003
2004 samplepos_t
2005 AudioClock::samples_from_timecode_string (const string& str) const
2006 {
2007         if (_session == 0) {
2008                 return 0;
2009         }
2010
2011         Timecode::Time TC;
2012         samplepos_t sample;
2013         char ignored[2];
2014         int hours;
2015
2016         if (sscanf (str.c_str(), "%[- _]%d:%d:%d%[:;]%d", ignored, &hours, &TC.minutes, &TC.seconds, ignored, &TC.frames) != 6) {
2017                 error << string_compose (_("programming error: %1 %2"), "badly formatted timecode clock string", str) << endmsg;
2018                 return 0;
2019         }
2020         TC.hours = abs(hours);
2021         TC.rate = _session->timecode_frames_per_second();
2022         TC.drop= _session->timecode_drop_frames();
2023
2024         _session->timecode_to_sample (TC, sample, false /* use_offset */, false /* use_subframes */ );
2025
2026         // timecode_tester ();
2027         if (edit_is_negative) {
2028                 sample = - sample;
2029         }
2030
2031         return sample;
2032 }
2033
2034 samplepos_t
2035 AudioClock::samples_from_minsec_string (const string& str) const
2036 {
2037         if (_session == 0) {
2038                 return 0;
2039         }
2040
2041         int hrs, mins, secs, millisecs;
2042         samplecnt_t sr = _session->sample_rate();
2043
2044         if (sscanf (str.c_str(), "%d:%d:%d.%d", &hrs, &mins, &secs, &millisecs) != 4) {
2045                 error << string_compose (_("programming error: %1 %2"), "badly formatted minsec clock string", str) << endmsg;
2046                 return 0;
2047         }
2048
2049         return (samplepos_t) floor ((hrs * 60.0f * 60.0f * sr) + (mins * 60.0f * sr) + (secs * sr) + (millisecs * sr / 1000.0));
2050 }
2051
2052 samplepos_t
2053 AudioClock::samples_from_bbt_string (samplepos_t pos, const string& str) const
2054 {
2055         if (_session == 0) {
2056                 error << "AudioClock::current_time() called with BBT mode but without session!" << endmsg;
2057                 return 0;
2058         }
2059
2060         AnyTime any;
2061         any.type = AnyTime::BBT;
2062
2063         if (sscanf (str.c_str(), BBT_SCANF_FORMAT, &any.bbt.bars, &any.bbt.beats, &any.bbt.ticks) != 3) {
2064                 return 0;
2065         }
2066
2067         if (is_duration) {
2068                 any.bbt.bars++;
2069                 any.bbt.beats++;
2070                 return _session->any_duration_to_samples (pos, any);
2071         } else {
2072                 return _session->convert_to_samples (any);
2073         }
2074 }
2075
2076
2077 samplepos_t
2078 AudioClock::sample_duration_from_bbt_string (samplepos_t pos, const string& str) const
2079 {
2080         if (_session == 0) {
2081                 error << "AudioClock::sample_duration_from_bbt_string() called with BBT mode but without session!" << endmsg;
2082                 return 0;
2083         }
2084
2085         Timecode::BBT_Time bbt;
2086
2087         if (sscanf (str.c_str(), BBT_SCANF_FORMAT, &bbt.bars, &bbt.beats, &bbt.ticks) != 3) {
2088                 return 0;
2089         }
2090
2091         return _session->tempo_map().bbt_duration_at(pos,bbt,1);
2092 }
2093
2094 samplepos_t
2095 AudioClock::samples_from_seconds_string (const string& str) const
2096 {
2097         float f;
2098         sscanf (str.c_str(), "%f", &f);
2099         return f * _session->sample_rate();
2100 }
2101
2102 samplepos_t
2103 AudioClock::samples_from_audiosamples_string (const string& str) const
2104 {
2105         samplepos_t f;
2106         sscanf (str.c_str(), "%" PRId64, &f);
2107         return f;
2108 }
2109
2110 void
2111 AudioClock::copy_text_to_clipboard () const
2112 {
2113         string val;
2114         if (editing) {
2115                 val = pre_edit_string;
2116         } else {
2117                 val = _layout->get_text ();
2118         }
2119         const size_t trim = val.find_first_not_of(" ");
2120         if (trim == string::npos) {
2121                 assert(0); // empty clock, can't be right.
2122                 return;
2123         }
2124         Glib::RefPtr<Clipboard> cl = Gtk::Clipboard::get();
2125         cl->set_text (val.substr(trim));
2126 }
2127
2128 void
2129 AudioClock::build_ops_menu ()
2130 {
2131         using namespace Menu_Helpers;
2132         ops_menu = new Menu;
2133         MenuList& ops_items = ops_menu->items();
2134         ops_menu->set_name ("ArdourContextMenu");
2135
2136         ops_items.push_back (MenuElem (_("Timecode"), sigc::bind (sigc::mem_fun(*this, &AudioClock::set_mode), Timecode, false)));
2137         ops_items.push_back (MenuElem (_("Bars:Beats"), sigc::bind (sigc::mem_fun(*this, &AudioClock::set_mode), BBT, false)));
2138         ops_items.push_back (MenuElem (_("Minutes:Seconds"), sigc::bind (sigc::mem_fun(*this, &AudioClock::set_mode), MinSec, false)));
2139         ops_items.push_back (MenuElem (_("Seconds"), sigc::bind (sigc::mem_fun(*this, &AudioClock::set_mode), Seconds, false)));
2140         ops_items.push_back (MenuElem (_("Samples"), sigc::bind (sigc::mem_fun(*this, &AudioClock::set_mode), Samples, false)));
2141
2142         if (editable && !_off && !is_duration && !_follows_playhead) {
2143                 ops_items.push_back (SeparatorElem());
2144                 ops_items.push_back (MenuElem (_("Set from Playhead"), sigc::mem_fun(*this, &AudioClock::set_from_playhead)));
2145                 ops_items.push_back (MenuElem (_("Locate to This Time"), sigc::mem_fun(*this, &AudioClock::locate)));
2146         }
2147         ops_items.push_back (SeparatorElem());
2148         ops_items.push_back (MenuElem (_("Copy to clipboard"), sigc::mem_fun(*this, &AudioClock::copy_text_to_clipboard)));
2149 }
2150
2151 void
2152 AudioClock::set_from_playhead ()
2153 {
2154         if (!_session) {
2155                 return;
2156         }
2157
2158         AudioClock::set (_session->transport_sample());
2159         ValueChanged ();
2160 }
2161
2162 void
2163 AudioClock::locate ()
2164 {
2165         if (!_session || is_duration) {
2166                 return;
2167         }
2168
2169         _session->request_locate (current_time(), _session->transport_rolling ());
2170 }
2171
2172 void
2173 AudioClock::set_mode (Mode m, bool noemit)
2174 {
2175         if (_mode == m) {
2176                 return;
2177         }
2178
2179         _mode = m;
2180
2181         insert_map.clear();
2182
2183         _layout->set_text ("");
2184
2185         Gtk::Requisition req;
2186         set_clock_dimensions (req);
2187
2188         switch (_mode) {
2189         case Timecode:
2190                 insert_map.push_back (11);
2191                 insert_map.push_back (10);
2192                 insert_map.push_back (8);
2193                 insert_map.push_back (7);
2194                 insert_map.push_back (5);
2195                 insert_map.push_back (4);
2196                 insert_map.push_back (2);
2197                 insert_map.push_back (1);
2198                 break;
2199
2200         case BBT:
2201                 insert_map.push_back (11);
2202                 insert_map.push_back (10);
2203                 insert_map.push_back (9);
2204                 insert_map.push_back (8);
2205                 insert_map.push_back (6);
2206                 insert_map.push_back (5);
2207                 insert_map.push_back (3);
2208                 insert_map.push_back (2);
2209                 insert_map.push_back (1);
2210                 break;
2211
2212         case MinSec:
2213                 insert_map.push_back (12);
2214                 insert_map.push_back (11);
2215                 insert_map.push_back (10);
2216                 insert_map.push_back (8);
2217                 insert_map.push_back (7);
2218                 insert_map.push_back (5);
2219                 insert_map.push_back (4);
2220                 insert_map.push_back (2);
2221                 insert_map.push_back (1);
2222                 break;
2223
2224         case Seconds:
2225                 insert_map.push_back (11);
2226                 insert_map.push_back (9);
2227                 insert_map.push_back (8);
2228                 insert_map.push_back (7);
2229                 insert_map.push_back (6);
2230                 insert_map.push_back (5);
2231                 insert_map.push_back (4);
2232                 insert_map.push_back (3);
2233                 insert_map.push_back (2);
2234                 insert_map.push_back (1);
2235                 break;
2236
2237         case Samples:
2238                 break;
2239         }
2240
2241         AudioClock::set (last_when, true);
2242
2243         if (!is_transient && !noemit) {
2244                 ModeChanged (); /* EMIT SIGNAL (the static one)*/
2245         }
2246
2247         mode_changed (); /* EMIT SIGNAL (the member one) */
2248 }
2249
2250 void
2251 AudioClock::set_bbt_reference (samplepos_t pos)
2252 {
2253         bbt_reference_time = pos;
2254 }
2255
2256 void
2257 AudioClock::on_style_changed (const Glib::RefPtr<Gtk::Style>& old_style)
2258 {
2259         CairoWidget::on_style_changed (old_style);
2260
2261         Gtk::Requisition req;
2262         set_clock_dimensions (req);
2263
2264         /* XXXX fix me ... we shouldn't be using GTK styles anyway */
2265         // set_font ();
2266         set_colors ();
2267 }
2268
2269 void
2270 AudioClock::set_editable (bool yn)
2271 {
2272         editable = yn;
2273 }
2274
2275 void
2276 AudioClock::set_is_duration (bool yn)
2277 {
2278         if (yn == is_duration) {
2279                 return;
2280         }
2281
2282         is_duration = yn;
2283         AudioClock::set (last_when, true);
2284 }
2285
2286 void
2287 AudioClock::set_off (bool yn)
2288 {
2289         if (_off == yn) {
2290                 return;
2291         }
2292
2293         _off = yn;
2294
2295         /* force a redraw. last_when will be preserved, but the clock text will
2296          * change
2297          */
2298
2299         AudioClock::set (last_when, true);
2300 }
2301
2302 void
2303 AudioClock::focus ()
2304 {
2305         start_edit (Field (0));
2306 }
2307
2308 void
2309 AudioClock::set_corner_radius (double r)
2310 {
2311         corner_radius = r;
2312         first_width = 0;
2313         first_height = 0;
2314         queue_resize ();
2315 }
2316
2317 void
2318 AudioClock::dpi_reset ()
2319 {
2320         /* force recomputation of size even if we are fixed width
2321          */
2322         first_width = 0;
2323         first_height = 0;
2324         queue_resize ();
2325 }
2326
2327 void
2328 AudioClock::set_negative_allowed (bool yn)
2329 {
2330         _negative_allowed = yn;
2331 }