2 Copyright (C) 1999 Paul Davis
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.
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.
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.
20 #include <cstdio> // for sprintf
23 #include "pbd/convert.h"
24 #include "pbd/enumwriter.h"
26 #include <gtkmm/style.h>
28 #include "gtkmm2ext/cairocell.h"
29 #include "gtkmm2ext/utils.h"
30 #include "gtkmm2ext/rgb_macros.h"
32 #include "ardour/ardour.h"
33 #include "ardour/session.h"
34 #include "ardour/tempo.h"
35 #include "ardour/profile.h"
36 #include <sigc++/bind.h>
38 #include "ardour_ui.h"
39 #include "audio_clock.h"
42 #include "gui_thread.h"
45 using namespace ARDOUR;
50 using Gtkmm2ext::Keyboard;
52 sigc::signal<void> AudioClock::ModeChanged;
53 vector<AudioClock*> AudioClock::clocks;
54 const double AudioClock::info_font_scale_factor = 0.6;
55 const double AudioClock::separator_height = 2.0;
57 AudioClock::AudioClock (const string& clock_name, bool transient, const string& widget_name,
58 bool allow_edit, bool follows_playhead, bool duration, bool with_info)
60 , is_transient (transient)
61 , is_duration (duration)
62 , editable (allow_edit)
63 , _follows_playhead (follows_playhead)
70 , mode_based_info_ratio (1.0)
72 , bbt_reference_time (-1)
77 , drag_field (Field (0))
80 set_flags (CAN_FOCUS);
82 _layout = Pango::Layout::create (get_pango_context());
83 _layout->set_attributes (normal_attributes);
86 _left_layout = Pango::Layout::create (get_pango_context());
87 _right_layout = Pango::Layout::create (get_pango_context());
90 set_widget_name (widget_name);
92 _mode = BBT; /* lie to force mode switch */
94 set (last_when, true);
97 clocks.push_back (this);
101 AudioClock::~AudioClock ()
103 delete background_attr;
104 delete foreground_attr;
109 AudioClock::set_widget_name (const string& str)
114 set_name (str + " clock");
120 AudioClock::on_realize ()
122 CairoWidget::on_realize ();
128 AudioClock::set_font ()
130 Glib::RefPtr<Gtk::Style> style = get_style ();
131 Pango::FontDescription font;
132 Pango::AttrFontDesc* font_attr;
134 if (!is_realized()) {
135 font = get_font_for_style (get_name());
137 font = style->get_font();
140 font_attr = new Pango::AttrFontDesc (Pango::Attribute::create_attr_font_desc (font));
142 normal_attributes.change (*font_attr);
143 editing_attributes.change (*font_attr);
145 /* now a smaller version of the same font */
148 font.set_size ((int) lrint (font.get_size() * info_font_scale_factor));
149 font.set_weight (Pango::WEIGHT_NORMAL);
150 font_attr = new Pango::AttrFontDesc (Pango::Attribute::create_attr_font_desc (font));
152 info_attributes.change (*font_attr);
158 AudioClock::set_active_state (Gtkmm2ext::ActiveState s)
160 CairoWidget::set_active_state (s);
165 AudioClock::set_colors ()
171 uint32_t editing_color;
173 if (active_state()) {
174 bg_color = ARDOUR_UI::config()->color_by_name (string_compose ("%1 active: background", get_name()));
175 text_color = ARDOUR_UI::config()->color_by_name (string_compose ("%1 active: text", get_name()));
176 editing_color = ARDOUR_UI::config()->color_by_name (string_compose ("%1 active: edited text", get_name()));
178 bg_color = ARDOUR_UI::config()->color_by_name (string_compose ("%1: background", get_name()));
179 text_color = ARDOUR_UI::config()->color_by_name (string_compose ("%1: text", get_name()));
180 editing_color = ARDOUR_UI::config()->color_by_name (string_compose ("%1: edited text", get_name()));
183 UINT_TO_RGBA (bg_color, &r, &g, &b, &a);
184 r = lrint ((r/256.0) * 65535.0);
185 g = lrint ((g/256.0) * 65535.0);
186 b = lrint ((b/256.0) * 65535.0);
187 background_attr = new Pango::AttrColor (Pango::Attribute::create_attr_background (r, g, b));
189 /* store for bg in ::render() */
195 UINT_TO_RGBA (text_color, &r, &g, &b, &a);
196 r = lrint ((r/256.0) * 65535.0);
197 g = lrint ((g/256.0) * 65535.0);
198 b = lrint ((b/256.0) * 65535.0);
199 foreground_attr = new Pango::AttrColor (Pango::Attribute::create_attr_foreground (r, g, b));
201 UINT_TO_RGBA (editing_color, &r, &g, &b, &a);
202 r = lrint ((r/256.0) * 65535.0);
203 g = lrint ((g/256.0) * 65535.0);
204 b = lrint ((b/256.0) * 65535.0);
205 editing_attr = new Pango::AttrColor (Pango::Attribute::create_attr_foreground (r, g, b));
207 normal_attributes.change (*background_attr);
208 normal_attributes.change (*foreground_attr);
210 info_attributes.change (*background_attr);
211 info_attributes.change (*foreground_attr);
213 editing_attributes.change (*background_attr);
214 editing_attributes.change (*foreground_attr);
215 editing_attributes.change (*editing_attr);
218 _layout->set_attributes (normal_attributes);
220 _layout->set_attributes (editing_attributes);
224 _left_layout->set_attributes (info_attributes);
225 _right_layout->set_attributes (info_attributes);
232 AudioClock::render (cairo_t* cr)
235 /* paint entire area the color of the parent window bg
237 XXX try to optimize this so that we just paint the corners and
238 other areas that may be exposed by rounded corners.
241 Gdk::Color bg (get_parent_bg());
242 cairo_rectangle (cr, 0, 0, _width, _height);
243 cairo_stroke_preserve (cr);
244 cairo_set_source_rgb (cr, bg.get_red_p(), bg.get_green_p(), bg.get_blue_p());
248 /* main layout: rounded rect, plus the text */
250 cairo_set_source_rgba (cr, bg_r, bg_g, bg_b, bg_a);
251 Gtkmm2ext::rounded_rectangle (cr, 0, 0, _width, upper_height, 9);
253 cairo_move_to (cr, 6, (upper_height - layout_height) / 2.0);
254 pango_cairo_show_layout (cr, _layout->gobj());
258 double h = _height - upper_height - separator_height;
260 if (mode_based_info_ratio != 1.0) {
262 double left_rect_width = round (((_width - separator_height) * mode_based_info_ratio) + 0.5);
264 cairo_set_source_rgba (cr, bg_r, bg_g, bg_b, bg_a);
265 Gtkmm2ext::rounded_rectangle (cr, 0, upper_height + separator_height, left_rect_width, h, 9);
267 cairo_move_to (cr, 6, upper_height + separator_height + ((h - info_height)/2.0));
268 pango_cairo_show_layout (cr, _left_layout->gobj());
270 Gtkmm2ext::rounded_rectangle (cr, left_rect_width + separator_height, upper_height + separator_height,
271 _width - separator_height - left_rect_width, h, 9);
273 cairo_move_to (cr, 6 + left_rect_width + separator_height, upper_height + separator_height + ((h - info_height)/2.0));
274 pango_cairo_show_layout (cr, _right_layout->gobj());
277 /* no info to display, or just one */
279 cairo_set_source_rgba (cr, bg_r, bg_g, bg_b, bg_a);
280 Gtkmm2ext::rounded_rectangle (cr, 0, upper_height + separator_height, _width, h, 9);
286 AudioClock::on_size_allocate (Gtk::Allocation& alloc)
288 CairoWidget::on_size_allocate (alloc);
291 upper_height = (_height/2.0) - 1.0;
293 upper_height = _height;
298 AudioClock::on_size_request (Gtk::Requisition* req)
300 Glib::RefPtr<Pango::Layout> tmp;
301 Glib::RefPtr<Gtk::Style> style = get_style ();
302 Pango::FontDescription font;
304 tmp = Pango::Layout::create (get_pango_context());
306 if (!is_realized()) {
307 font = get_font_for_style (get_name());
309 font = style->get_font();
312 tmp->set_font_description (font);
314 /* this string is the longest thing we will ever display,
315 and also includes the BBT "|" that may descends below
316 the baseline a bit, and a comma for the minsecs mode
317 where we printf a fractional value (XXX or should)
320 tmp->set_text (" 88|88:88:88,88");
322 tmp->get_pixel_size (req->width, req->height);
324 layout_height = req->height;
325 layout_width = req->width;
327 /* now tackle height, for which we need to know the height of the lower
335 font.set_size ((int) lrint (font.get_size() * info_font_scale_factor));
336 font.set_weight (Pango::WEIGHT_NORMAL);
337 tmp->set_font_description (font);
339 /* we only care about height, so put as much stuff in here
340 as possible that might change the height.
342 tmp->set_text ("qyhH|"); /* one ascender, one descender */
344 tmp->get_pixel_size (w, info_height);
348 req->height += info_height;
349 req->height += separator_height;
354 AudioClock::show_edit_status (int length)
356 editing_attr->set_start_index (edit_string.length() - length);
357 editing_attr->set_end_index (edit_string.length());
359 editing_attributes.change (*background_attr);
360 editing_attributes.change (*foreground_attr);
361 editing_attributes.change (*editing_attr);
363 _layout->set_attributes (editing_attributes);
367 AudioClock::start_edit ()
369 edit_string = _layout->get_text ();
370 pre_edit_string = edit_string;
371 input_string.clear ();
374 show_edit_status (1);
377 Keyboard::magic_widget_grab_focus ();
382 AudioClock::end_edit (bool modify)
390 ok = timecode_validate_edit (edit_string);
394 ok = bbt_validate_edit (edit_string);
405 edit_string = pre_edit_string;
406 input_string.clear ();
407 _layout->set_text (edit_string);
408 show_edit_status (1);
409 /* edit attributes remain in use */
417 pos = frames_from_timecode_string (edit_string);
422 pos = frame_duration_from_bbt_string (0, edit_string);
424 pos = frames_from_bbt_string (0, edit_string);
429 pos = frames_from_minsec_string (edit_string);
433 pos = frames_from_audioframes_string (edit_string);
438 _layout->set_attributes (normal_attributes);
439 ValueChanged(); /* EMIT_SIGNAL */
444 _layout->set_attributes (normal_attributes);
445 _layout->set_text (pre_edit_string);
456 AudioClock::drop_focus ()
458 /* move focus back to the default widget in the top level window */
460 Keyboard::magic_widget_drop_focus ();
461 Widget* top = get_toplevel();
462 if (top->is_toplevel ()) {
463 Window* win = dynamic_cast<Window*> (top);
469 AudioClock::parse_as_frames_distance (const std::string& str)
473 if (sscanf (str.c_str(), "%" PRId64, &f) == 1) {
481 AudioClock::parse_as_minsec_distance (const std::string& str)
483 framecnt_t sr = _session->frame_rate();
489 switch (str.length()) {
496 sscanf (str.c_str(), "%" PRId32, &msecs);
497 return msecs * (sr / 1000);
500 sscanf (str.c_str(), "%1" PRId32 "%" PRId32, &secs, &msecs);
501 return (secs * sr) + (msecs * (sr/1000));
504 sscanf (str.c_str(), "%2" PRId32 "%" PRId32, &secs, &msecs);
505 return (secs * sr) + (msecs * (sr/1000));
508 sscanf (str.c_str(), "%1" PRId32 "%2" PRId32 "%" PRId32, &mins, &secs, &msecs);
509 return (mins * 60 * sr) + (secs * sr) + (msecs * (sr/1000));
512 sscanf (str.c_str(), "%2" PRId32 "%2" PRId32 "%" PRId32, &mins, &secs, &msecs);
513 return (mins * 60 * sr) + (secs * sr) + (msecs * (sr/1000));
516 sscanf (str.c_str(), "%1" PRId32 "%2" PRId32 "%2" PRId32 "%" PRId32, &hrs, &mins, &secs, &msecs);
517 return (hrs * 3600 * sr) + (mins * 60 * sr) + (secs * sr) + (msecs * (sr/1000));
520 sscanf (str.c_str(), "%1" PRId32 "%2" PRId32 "%2" PRId32 "%" PRId32, &hrs, &mins, &secs, &msecs);
521 return (hrs * 3600 * sr) + (mins * 60 * sr) + (secs * sr) + (msecs * (sr/1000));
531 AudioClock::parse_as_timecode_distance (const std::string& str)
533 double fps = _session->timecode_frames_per_second();
534 framecnt_t sr = _session->frame_rate();
540 switch (str.length()) {
545 sscanf (str.c_str(), "%" PRId32, &frames);
546 return lrint ((frames/(float)fps) * sr);
549 sscanf (str.c_str(), "%1" PRId32 "%" PRId32, &secs, &frames);
550 return (secs * sr) + lrint ((frames/(float)fps) * sr);
553 sscanf (str.c_str(), "%2" PRId32 "%" PRId32, &secs, &frames);
554 return (secs * sr) + lrint ((frames/(float)fps) * sr);
557 sscanf (str.c_str(), "%1" PRId32 "%2" PRId32 "%" PRId32, &mins, &secs, &frames);
558 return (mins * 60 * sr) + (secs * sr) + lrint ((frames/(float)fps) * sr);
561 sscanf (str.c_str(), "%2" PRId32 "%2" PRId32 "%" PRId32, &mins, &secs, &frames);
562 return (mins * 60 * sr) + (secs * sr) + lrint ((frames/(float)fps) * sr);
565 sscanf (str.c_str(), "%1" PRId32 "%2" PRId32 "%2" PRId32 "%" PRId32, &hrs, &mins, &secs, &frames);
566 return (hrs * 3600 * sr) + (mins * 60 * sr) + (secs * sr) + lrint ((frames/(float)fps) * sr);
569 sscanf (str.c_str(), "%2" PRId32 "%2" PRId32 "%2" PRId32 "%" PRId32, &hrs, &mins, &secs, &frames);
570 return (hrs * 3600 * sr) + (mins * 60 * sr) + (secs * sr) + lrint ((frames/(float)fps) * sr);
580 AudioClock::parse_as_bbt_distance (const std::string& str)
586 AudioClock::parse_as_distance (const std::string& instr)
590 /* the input string is in reverse order */
592 std::reverse (str.begin(), str.end());
596 return parse_as_timecode_distance (str);
599 return parse_as_frames_distance (str);
602 return parse_as_bbt_distance (str);
605 return parse_as_minsec_distance (str);
612 AudioClock::end_edit_relative (bool add)
614 framecnt_t frames = parse_as_distance (input_string);
619 _layout->set_attributes (normal_attributes);
623 set (current_time() + frames, true);
625 framepos_t c = current_time();
628 set (c - frames, true);
633 ValueChanged (); /* EMIT SIGNAL */
636 input_string.clear ();
642 AudioClock::session_configuration_changed (std::string p)
644 if (p != "timecode-offset" && p != "timecode-offset-negative") {
653 current = current_duration ();
655 current = current_time ();
665 AudioClock::set (framepos_t when, bool force, framecnt_t offset, char which)
667 if ((!force && !is_visible()) || _session == 0) {
671 bool const pdelta = Config->get_primary_clock_delta_edit_cursor ();
672 bool const sdelta = Config->get_secondary_clock_delta_edit_cursor ();
674 if (offset && which == 'p' && pdelta) {
675 when = (when > offset) ? when - offset : offset - when;
676 } else if (offset && which == 's' && sdelta) {
677 when = (when > offset) ? when - offset : offset - when;
680 if (when == last_when && !force) {
684 if (which == 'p' && pdelta && !last_pdelta) {
685 set_name("TransportClockDisplayDelta");
687 } else if (which == 'p' && !pdelta && last_pdelta) {
688 set_name("TransportClockDisplay");
690 } else if (which == 's' && sdelta && !last_sdelta) {
691 set_name("SecondaryClockDisplayDelta");
693 } else if (which == 's' && !sdelta && last_sdelta) {
694 set_name("SecondaryClockDisplay");
702 set_timecode (when, force);
706 set_bbt (when, force);
710 set_minsec (when, force);
714 set_frames (when, force);
719 if (when != last_when || force) {
727 AudioClock::set_frames (framepos_t when, bool /*force*/)
732 /* XXX with cairo, we can do better, surely? */
734 _layout->set_text ("-----------");
737 _left_layout->set_text ("");
738 _right_layout->set_text ("");
744 snprintf (buf, sizeof (buf), "%10" PRId64, when);
745 _layout->set_text (buf);
748 framecnt_t rate = _session->frame_rate();
750 if (fmod (rate, 1000.0) == 0.000) {
751 sprintf (buf, "%" PRId64 "K", rate/1000);
753 sprintf (buf, "%" PRId64, rate);
756 _left_layout->set_text (buf);
758 float vid_pullup = _session->config.get_video_pullup();
760 if (vid_pullup == 0.0) {
761 _right_layout->set_text (_("none"));
763 sprintf (buf, "%-6.4f", vid_pullup);
764 _right_layout->set_text (buf);
770 AudioClock::set_minsec (framepos_t when, bool force)
780 _layout->set_text ("--:--:--");
783 _left_layout->set_text ("");
784 _right_layout->set_text ("");
791 hrs = (int) floor (left / (_session->frame_rate() * 60.0f * 60.0f));
792 left -= (framecnt_t) floor (hrs * _session->frame_rate() * 60.0f * 60.0f);
793 mins = (int) floor (left / (_session->frame_rate() * 60.0f));
794 left -= (framecnt_t) floor (mins * _session->frame_rate() * 60.0f);
795 secs = (int) floor (left / (float) _session->frame_rate());
796 left -= (framecnt_t) floor (secs * _session->frame_rate());
797 millisecs = floor (left * 1000.0 / (float) _session->frame_rate());
799 snprintf (buf, sizeof (buf), "%02" PRIu32 ":%02" PRIu32 ":%02" PRIu32 ".%03" PRIu32, hrs, mins, secs, millisecs);
800 _layout->set_text (buf);
804 AudioClock::set_timecode (framepos_t when, bool force)
810 _layout->set_text ("--:--:--:--");
812 _left_layout->set_text ("");
813 _right_layout->set_text ("");
820 _session->timecode_duration (when, TC);
822 _session->timecode_time (when, TC);
826 snprintf (buf, sizeof (buf), "-%02" PRIu32 ":%02" PRIu32 ":%02" PRIu32 ":%02" PRIu32, TC.hours, TC.minutes, TC.seconds, TC.frames);
828 snprintf (buf, sizeof (buf), " %02" PRIu32 ":%02" PRIu32 ":%02" PRIu32 ":%02" PRIu32, TC.hours, TC.minutes, TC.seconds, TC.frames);
831 _layout->set_text (buf);
834 double timecode_frames = _session->timecode_frames_per_second();
836 if (fmod(timecode_frames, 1.0) == 0.0) {
837 sprintf (buf, "FPS %u %s", int (timecode_frames), (_session->timecode_drop_frames() ? "D" : ""));
839 sprintf (buf, "%.2f %s", timecode_frames, (_session->timecode_drop_frames() ? "D" : ""));
842 _right_layout->set_text (buf);
847 AudioClock::set_bbt (framepos_t when, bool force)
850 Timecode::BBT_Time BBT;
853 _layout->set_text ("--|--|--");
855 _left_layout->set_text ("");
856 _right_layout->set_text ("");
861 /* handle a common case */
868 _session->tempo_map().bbt_time (when, BBT);
873 _session->tempo_map().bbt_time (when, BBT);
876 snprintf (buf, sizeof (buf), "%02" PRIu32 "|%02" PRIu32 "|%04" PRIu32, BBT.bars, BBT.beats, BBT.ticks);
877 _layout->set_text (buf);
882 if (bbt_reference_time < 0) {
885 pos = bbt_reference_time;
888 TempoMetric m (_session->tempo_map().metric_at (pos));
890 sprintf (buf, "%-5.2f", m.tempo().beats_per_minute());
891 _left_layout->set_text (buf);
893 sprintf (buf, "%g|%g", m.meter().beats_per_bar(), m.meter().note_divisor());
894 _right_layout->set_text (buf);
899 AudioClock::set_session (Session *s)
901 SessionHandlePtr::set_session (s);
905 _session->config.ParameterChanged.connect (_session_connections, invalidator (*this), boost::bind (&AudioClock::session_configuration_changed, this, _1), gui_context());
907 const XMLProperty* prop;
908 XMLNode* node = _session->extra_xml (X_("ClockModes"));
909 AudioClock::Mode amode;
912 for (XMLNodeList::const_iterator i = node->children().begin(); i != node->children().end(); ++i) {
913 if ((prop = (*i)->property (X_("name"))) && prop->value() == _name) {
915 if ((prop = (*i)->property (X_("mode"))) != 0) {
916 amode = AudioClock::Mode (string_2_enum (prop->value(), amode));
919 if ((prop = (*i)->property (X_("on"))) != 0) {
920 set_off (!string_is_affirmative (prop->value()));
927 set (last_when, true);
932 AudioClock::on_key_press_event (GdkEventKey* ev)
938 /* return true for keys that we MIGHT use
941 switch (ev->keyval) {
976 AudioClock::on_key_release_event (GdkEventKey *ev)
985 switch (ev->keyval) {
1028 case GDK_KP_Subtract:
1029 end_edit_relative (false);
1035 end_edit_relative (true);
1048 ChangeAborted(); /* EMIT SIGNAL */
1055 if (input_string.length() >= insert_max) {
1056 /* eat the key event, but do no nothing with it */
1060 input_string.insert (input_string.begin(), new_char);
1062 string::reverse_iterator ri;
1063 vector<int> insert_at;
1064 int highlight_length;
1066 /* merge with pre-edit-string into edit string */
1070 edit_string = input_string;
1071 highlight_length = edit_string.length();
1075 edit_string = pre_edit_string;
1077 /* backup through the original string, till we have
1078 * enough digits locations to put all the digits from
1082 for (ri = edit_string.rbegin(); ri != edit_string.rend(); ++ri) {
1083 if (isdigit (*ri)) {
1084 insert_at.push_back (edit_string.length() - (ri - edit_string.rbegin()) - 1);
1085 if (insert_at.size() == input_string.length()) {
1091 if (insert_at.size() != input_string.length()) {
1092 error << "something went wrong " << endmsg;
1094 for (int i = input_string.length() - 1; i >= 0; --i) {
1095 edit_string[insert_at[i]] = input_string[i];
1098 highlight_length = edit_string.length() - insert_at.back();
1104 if (edit_string != _layout->get_text()) {
1105 show_edit_status (highlight_length);
1106 _layout->set_text (edit_string);
1114 AudioClock::index_to_field (int index) const
1119 return Timecode_Hours;
1120 } else if (index < 7) {
1121 return Timecode_Minutes;
1122 } else if (index < 10) {
1123 return Timecode_Seconds;
1124 } else if (index < 13) {
1125 return Timecode_Frames;
1133 } else if (index < 7) {
1135 } else if (index < 12) {
1144 return Timecode_Hours;
1145 } else if (index < 6) {
1147 } else if (index < 9) {
1149 } else if (index < 12) {
1150 return MS_Milliseconds;
1163 AudioClock::on_button_press_event (GdkEventButton *ev)
1165 switch (ev->button) {
1169 /* make absolutely sure that the pointer is grabbed */
1170 gdk_pointer_grab(ev->window,false ,
1171 GdkEventMask( Gdk::POINTER_MOTION_MASK | Gdk::BUTTON_PRESS_MASK |Gdk::BUTTON_RELEASE_MASK),
1172 NULL,NULL,ev->time);
1174 drag_start_y = ev->y;
1180 if (_layout->xy_to_index (ev->x * PANGO_SCALE, ev->y * PANGO_SCALE, index, trailing)) {
1181 drag_field = index_to_field (index);
1183 drag_field = Field (0);
1197 AudioClock::on_button_release_event (GdkEventButton *ev)
1201 gdk_pointer_ungrab (GDK_CURRENT_TIME);
1203 if (ev->y > drag_start_y+1 || ev->y < drag_start_y-1 || Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)){
1204 // we actually dragged so return without
1205 // setting editing focus, or we shift clicked
1208 if (ev->button == 1) {
1216 if (Keyboard::is_context_menu_event (ev)) {
1217 if (ops_menu == 0) {
1220 ops_menu->popup (1, ev->time);
1228 AudioClock::on_focus_out_event (GdkEventFocus* ev)
1230 bool ret = CairoWidget::on_focus_out_event (ev);
1238 AudioClock::on_scroll_event (GdkEventScroll *ev)
1243 if (_session == 0 || !editable) {
1247 if (!_layout->xy_to_index (ev->x * PANGO_SCALE, ev->y * PANGO_SCALE, index, trailing)) {
1248 /* not in the main layout */
1252 Field f = index_to_field (index);
1253 framepos_t frames = 0;
1255 switch (ev->direction) {
1258 frames = get_frame_step (f);
1260 if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
1263 set (current_time() + frames, true);
1264 ValueChanged (); /* EMIT_SIGNAL */
1268 case GDK_SCROLL_DOWN:
1269 frames = get_frame_step (f);
1271 if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
1275 if ((double)current_time() - (double)frames < 0.0) {
1278 set (current_time() - frames, true);
1281 ValueChanged (); /* EMIT_SIGNAL */
1294 AudioClock::on_motion_notify_event (GdkEventMotion *ev)
1296 if (_session == 0 || !dragging) {
1300 float pixel_frame_scale_factor = 0.2f;
1302 if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
1303 pixel_frame_scale_factor = 0.1f;
1307 if (Keyboard::modifier_state_contains (ev->state,
1308 Keyboard::PrimaryModifier|Keyboard::SecondaryModifier)) {
1310 pixel_frame_scale_factor = 0.025f;
1313 double y_delta = ev->y - drag_y;
1315 drag_accum += y_delta*pixel_frame_scale_factor;
1319 if (trunc (drag_accum) != 0) {
1324 dir = (drag_accum < 0 ? 1:-1);
1325 pos = current_time();
1326 frames = get_frame_step (drag_field,pos,dir);
1328 if (frames != 0 && frames * drag_accum < current_time()) {
1329 set ((framepos_t) floor (pos - drag_accum * frames), false); // minus because up is negative in GTK
1335 ValueChanged(); /* EMIT_SIGNAL */
1342 AudioClock::get_frame_step (Field field, framepos_t pos, int dir)
1345 Timecode::BBT_Time BBT;
1347 case Timecode_Hours:
1348 f = (framecnt_t) floor (3600.0 * _session->frame_rate());
1350 case Timecode_Minutes:
1351 f = (framecnt_t) floor (60.0 * _session->frame_rate());
1353 case Timecode_Seconds:
1354 f = _session->frame_rate();
1356 case Timecode_Frames:
1357 f = (framecnt_t) floor (_session->frame_rate() / _session->timecode_frames_per_second());
1365 f = (framecnt_t) floor (3600.0 * _session->frame_rate());
1368 f = (framecnt_t) floor (60.0 * _session->frame_rate());
1371 f = (framecnt_t) _session->frame_rate();
1373 case MS_Milliseconds:
1374 f = (framecnt_t) floor (_session->frame_rate() / 1000.0);
1381 f = _session->tempo_map().bbt_duration_at (pos,BBT,dir);
1387 f = _session->tempo_map().bbt_duration_at(pos,BBT,dir);
1393 f = _session->tempo_map().bbt_duration_at(pos,BBT,dir);
1396 error << string_compose (_("programming error: %1"), "attempt to get frames from non-text field!") << endmsg;
1405 AudioClock::current_time (framepos_t pos) const
1411 AudioClock::current_duration (framepos_t pos) const
1420 ret = frame_duration_from_bbt_string (pos, _layout->get_text());
1436 AudioClock::bbt_validate_edit (const string& str)
1440 sscanf (str.c_str(), "%" PRIu32 "|%" PRIu32 "|%" PRIu32, &any.bbt.bars, &any.bbt.beats, &any.bbt.ticks);
1442 if (!is_duration && any.bbt.bars == 0) {
1446 if (!is_duration && any.bbt.beats == 0) {
1454 AudioClock::timecode_validate_edit (const string& str)
1458 if (sscanf (_layout->get_text().c_str(), "%" PRId32 ":%" PRId32 ":%" PRId32 ":%" PRId32,
1459 &TC.hours, &TC.minutes, &TC.seconds, &TC.frames) != 4) {
1463 if (TC.minutes > 59 || TC.seconds > 59) {
1467 if (TC.frames > (long)rint(_session->timecode_frames_per_second()) - 1) {
1471 if (_session->timecode_drop_frames()) {
1472 if (TC.minutes % 10 && TC.seconds == 0 && TC.frames < 2) {
1481 AudioClock::frames_from_timecode_string (const string& str) const
1483 if (_session == 0) {
1490 sscanf (str.c_str(), "%d:%d:%d:%d", &TC.hours, &TC.minutes, &TC.seconds, &TC.frames);
1492 TC.rate = _session->timecode_frames_per_second();
1493 TC.drop= _session->timecode_drop_frames();
1495 _session->timecode_to_sample (TC, sample, false /* use_offset */, false /* use_subframes */ );
1497 // timecode_tester ();
1503 AudioClock::frames_from_minsec_string (const string& str) const
1505 if (_session == 0) {
1509 int hrs, mins, secs, millisecs;
1510 framecnt_t sr = _session->frame_rate();
1512 sscanf (str.c_str(), "%d:%d:%d:%d", &hrs, &mins, &secs, &millisecs);
1513 return (framepos_t) floor ((hrs * 60.0f * 60.0f * sr) + (mins * 60.0f * sr) + (secs * sr) + (millisecs * sr / 1000.0));
1517 AudioClock::frames_from_bbt_string (framepos_t pos, const string& str) const
1519 if (_session == 0) {
1520 error << "AudioClock::current_time() called with BBT mode but without session!" << endmsg;
1525 any.type = AnyTime::BBT;
1527 sscanf (str.c_str(), "%" PRId32 "|%" PRId32 "|%" PRId32, &any.bbt.bars, &any.bbt.beats, &any.bbt.ticks);
1532 return _session->any_duration_to_frames (pos, any);
1534 return _session->convert_to_frames (any);
1540 AudioClock::frame_duration_from_bbt_string (framepos_t pos, const string& str) const
1542 if (_session == 0) {
1543 error << "AudioClock::current_time() called with BBT mode but without session!" << endmsg;
1547 Timecode::BBT_Time bbt;
1549 sscanf (str.c_str(), "%" PRIu32 "|%" PRIu32 "|%" PRIu32, &bbt.bars, &bbt.beats, &bbt.ticks);
1551 return _session->tempo_map().bbt_duration_at(pos,bbt,1);
1555 AudioClock::frames_from_audioframes_string (const string& str) const
1558 sscanf (str.c_str(), "%" PRId64, &f);
1563 AudioClock::build_ops_menu ()
1565 using namespace Menu_Helpers;
1566 ops_menu = new Menu;
1567 MenuList& ops_items = ops_menu->items();
1568 ops_menu->set_name ("ArdourContextMenu");
1570 if (!Profile->get_sae()) {
1571 ops_items.push_back (MenuElem (_("Timecode"), sigc::bind (sigc::mem_fun(*this, &AudioClock::set_mode), Timecode)));
1573 ops_items.push_back (MenuElem (_("Bars:Beats"), sigc::bind (sigc::mem_fun(*this, &AudioClock::set_mode), BBT)));
1574 ops_items.push_back (MenuElem (_("Minutes:Seconds"), sigc::bind (sigc::mem_fun(*this, &AudioClock::set_mode), MinSec)));
1575 ops_items.push_back (MenuElem (_("Samples"), sigc::bind (sigc::mem_fun(*this, &AudioClock::set_mode), Frames)));
1577 if (editable && !is_duration && !_follows_playhead) {
1578 ops_items.push_back (SeparatorElem());
1579 ops_items.push_back (MenuElem (_("Set From Playhead"), sigc::mem_fun(*this, &AudioClock::set_from_playhead)));
1580 ops_items.push_back (MenuElem (_("Locate to This Time"), sigc::mem_fun(*this, &AudioClock::locate)));
1585 AudioClock::set_from_playhead ()
1591 set (_session->transport_frame());
1596 AudioClock::locate ()
1598 if (!_session || is_duration) {
1602 _session->request_locate (current_time(), _session->transport_rolling ());
1606 AudioClock::set_mode (Mode m)
1616 insert_max = 9; // 8 digits + sign [-]2:2:2:2
1617 mode_based_info_ratio = 0.5;
1621 insert_max = 8; // 8 digits, 2|2|4
1622 mode_based_info_ratio = 0.5;
1626 insert_max = 9; // 7 digits 2:2:2.3
1627 mode_based_info_ratio = 1.0;
1631 insert_max = INT_MAX;
1632 mode_based_info_ratio = 1.0;
1636 set (last_when, true);
1638 if (!is_transient) {
1639 ModeChanged (); /* EMIT SIGNAL (the static one)*/
1642 mode_changed (); /* EMIT SIGNAL (the member one) */
1646 AudioClock::set_bbt_reference (framepos_t pos)
1648 bbt_reference_time = pos;
1652 AudioClock::on_style_changed (const Glib::RefPtr<Gtk::Style>& old_style)
1654 CairoWidget::on_style_changed (old_style);
1660 AudioClock::set_is_duration (bool yn)
1662 if (yn == is_duration) {
1667 set (last_when, true, 0, 's');
1671 AudioClock::set_off (bool yn)
1679 /* force a redraw. last_when will be preserved, but the clock text will
1683 set (last_when, true);
1687 AudioClock::focus ()
1693 AudioClock::set_draw_background (bool yn)