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