X-Git-Url: https://git.carlh.net/gitweb/?a=blobdiff_plain;f=gtk2_ardour%2Faudio_clock.cc;h=a11ae1c42ec4598a2f7a57f6f912b00ced2a2b1b;hb=b47bfc0121705d7268839357bbc3eab679ae32db;hp=9423d3847ffefca2c1b9997b518af81c8a911dfa;hpb=e3a0c3dfb79e2b989070707ca8eb1039566f26d7;p=ardour.git diff --git a/gtk2_ardour/audio_clock.cc b/gtk2_ardour/audio_clock.cc index 9423d3847f..a11ae1c42e 100644 --- a/gtk2_ardour/audio_clock.cc +++ b/gtk2_ardour/audio_clock.cc @@ -24,27 +24,31 @@ #include "pbd/enumwriter.h" #include +#include #include "gtkmm2ext/cairocell.h" #include "gtkmm2ext/utils.h" #include "gtkmm2ext/rgb_macros.h" -#include "ardour/ardour.h" -#include "ardour/session.h" -#include "ardour/tempo.h" #include "ardour/profile.h" +#include "ardour/lmath.h" +#include "ardour/session.h" #include "ardour/slave.h" -#include +#include "ardour/tempo.h" +#include "ardour/types.h" #include "ardour_ui.h" #include "audio_clock.h" -#include "global_signals.h" -#include "utils.h" -#include "keyboard.h" #include "gui_thread.h" -#include "i18n.h" +#include "keyboard.h" +#include "tooltips.h" +#include "ui_config.h" +#include "utils.h" + +#include "pbd/i18n.h" using namespace ARDOUR; +using namespace ARDOUR_UI_UTILS; using namespace PBD; using namespace Gtk; using namespace std; @@ -53,34 +57,35 @@ using Gtkmm2ext::Keyboard; sigc::signal AudioClock::ModeChanged; vector AudioClock::clocks; -const double AudioClock::info_font_scale_factor = 0.6; -const double AudioClock::separator_height = 2.0; -const double AudioClock::x_leading_padding = 6.0; #define BBT_BAR_CHAR "|" #define BBT_SCANF_FORMAT "%" PRIu32 "%*c%" PRIu32 "%*c%" PRIu32 AudioClock::AudioClock (const string& clock_name, bool transient, const string& widget_name, - bool allow_edit, bool follows_playhead, bool duration, bool with_info) - : _name (clock_name) + bool allow_edit, bool follows_playhead, bool duration, bool with_info, + bool accept_on_focus_out) + : ops_menu (0) + , _name (clock_name) , is_transient (transient) , is_duration (duration) , editable (allow_edit) , _follows_playhead (follows_playhead) + , _accept_on_focus_out (accept_on_focus_out) , _off (false) - , _fixed_width (true) - , layout_x_offset (0) - , ops_menu (0) + , em_width (0) + , _edit_by_click_field (false) + , _negative_allowed (false) + , edit_is_negative (false) + , _with_info (with_info) , editing_attr (0) , foreground_attr (0) , first_height (0) , first_width (0) + , style_resets_first (true) , layout_height (0) , layout_width (0) - , info_height (0) - , upper_height (0) - , mode_based_info_ratio (1.0) - , corner_radius (9) + , corner_radius (4) + , font_size (10240) , editing (false) , bbt_reference_time (-1) , last_when(0) @@ -88,18 +93,14 @@ AudioClock::AudioClock (const string& clock_name, bool transient, const string& , last_sdelta (0) , dragging (false) , drag_field (Field (0)) - + , xscale (1.0) + , yscale (1.0) { set_flags (CAN_FOCUS); _layout = Pango::Layout::create (get_pango_context()); _layout->set_attributes (normal_attributes); - if (with_info) { - _left_layout = Pango::Layout::create (get_pango_context()); - _right_layout = Pango::Layout::create (get_pango_context()); - } - set_widget_name (widget_name); _mode = BBT; /* lie to force mode switch */ @@ -110,8 +111,14 @@ AudioClock::AudioClock (const string& clock_name, bool transient, const string& clocks.push_back (this); } - ColorsChanged.connect (sigc::mem_fun (*this, &AudioClock::set_colors)); - DPIReset.connect (sigc::mem_fun (*this, &AudioClock::dpi_reset)); + _left_btn.set_sizing_text (_("0000000000000")); + // NB right_btn is in a size-group + + _left_btn.set_layout_font (UIConfiguration::instance().get_SmallFont()); + _right_btn.set_layout_font (UIConfiguration::instance().get_SmallFont()); + + UIConfiguration::instance().ColorsChanged.connect (sigc::mem_fun (*this, &AudioClock::set_colors)); + UIConfiguration::instance().DPIReset.connect (sigc::mem_fun (*this, &AudioClock::dpi_reset)); } AudioClock::~AudioClock () @@ -138,39 +145,46 @@ AudioClock::set_widget_name (const string& str) void AudioClock::on_realize () { + Gtk::Requisition req; + CairoWidget::on_realize (); - set_font (); + + set_clock_dimensions (req); + + first_width = req.width; + first_height = req.height; + + // XXX FIX ME: define font based on ... ??? + // set_font (); set_colors (); } void -AudioClock::set_font () +AudioClock::set_font (Pango::FontDescription font) { Glib::RefPtr style = get_style (); - Pango::FontDescription font; Pango::AttrFontDesc* font_attr; - if (!is_realized()) { - font = get_font_for_style (get_name()); - } else { - font = style->get_font(); - } - + font_size = font.get_size(); font_attr = new Pango::AttrFontDesc (Pango::Attribute::create_attr_font_desc (font)); normal_attributes.change (*font_attr); editing_attributes.change (*font_attr); + delete font_attr; - /* now a smaller version of the same font */ + /* get the figure width for the font. This doesn't have to super + * accurate since we only use it to measure the (roughly 1 character) + * offset from the position Pango tells us for the "cursor" + */ - delete font_attr; - font.set_size ((int) lrint (font.get_size() * info_font_scale_factor)); - font.set_weight (Pango::WEIGHT_NORMAL); - font_attr = new Pango::AttrFontDesc (Pango::Attribute::create_attr_font_desc (font)); - - info_attributes.change (*font_attr); - - delete font_attr; + Glib::RefPtr tmp = Pango::Layout::create (get_pango_context()); + int ignore_height; + + tmp->set_text ("8"); + tmp->get_pixel_size (em_width, ignore_height); + + /* force redraw of markup with new font-size */ + set (last_when, true); } void @@ -191,15 +205,15 @@ AudioClock::set_colors () uint32_t cursor_color; if (active_state()) { - bg_color = ARDOUR_UI::config()->color_by_name (string_compose ("%1 active: background", get_name())); - text_color = ARDOUR_UI::config()->color_by_name (string_compose ("%1 active: text", get_name())); - editing_color = ARDOUR_UI::config()->color_by_name (string_compose ("%1 active: edited text", get_name())); - cursor_color = ARDOUR_UI::config()->color_by_name (string_compose ("%1 active: cursor", get_name())); + bg_color = UIConfiguration::instance().color (string_compose ("%1 active: background", get_name())); + text_color = UIConfiguration::instance().color (string_compose ("%1 active: text", get_name())); + editing_color = UIConfiguration::instance().color (string_compose ("%1 active: edited text", get_name())); + cursor_color = UIConfiguration::instance().color (string_compose ("%1 active: cursor", get_name())); } else { - bg_color = ARDOUR_UI::config()->color_by_name (string_compose ("%1: background", get_name())); - text_color = ARDOUR_UI::config()->color_by_name (string_compose ("%1: text", get_name())); - editing_color = ARDOUR_UI::config()->color_by_name (string_compose ("%1: edited text", get_name())); - cursor_color = ARDOUR_UI::config()->color_by_name (string_compose ("%1: cursor", get_name())); + bg_color = UIConfiguration::instance().color (string_compose ("%1: background", get_name())); + text_color = UIConfiguration::instance().color (string_compose ("%1: text", get_name())); + editing_color = UIConfiguration::instance().color (string_compose ("%1: edited text", get_name())); + cursor_color = UIConfiguration::instance().color (string_compose ("%1: cursor", get_name())); } /* store for bg and cursor in render() */ @@ -228,16 +242,17 @@ AudioClock::set_colors () r = lrint ((r/255.0) * 65535.0); g = lrint ((g/255.0) * 65535.0); b = lrint ((b/255.0) * 65535.0); + delete foreground_attr; foreground_attr = new Pango::AttrColor (Pango::Attribute::create_attr_foreground (r, g, b)); UINT_TO_RGBA (editing_color, &r, &g, &b, &a); r = lrint ((r/255.0) * 65535.0); g = lrint ((g/255.0) * 65535.0); b = lrint ((b/255.0) * 65535.0); + delete editing_attr; editing_attr = new Pango::AttrColor (Pango::Attribute::create_attr_foreground (r, g, b)); - + normal_attributes.change (*foreground_attr); - info_attributes.change (*foreground_attr); editing_attributes.change (*foreground_attr); editing_attributes.change (*editing_attr); @@ -247,133 +262,84 @@ AudioClock::set_colors () _layout->set_attributes (editing_attributes); } - if (_left_layout) { - _left_layout->set_attributes (info_attributes); - _right_layout->set_attributes (info_attributes); - } + queue_draw (); +} + +void +AudioClock::set_scale (double x, double y) +{ + xscale = x; + yscale = y; queue_draw (); } void -AudioClock::render (cairo_t* cr) +AudioClock::render (cairo_t* cr, cairo_rectangle_t*) { /* main layout: rounded rect, plus the text */ - + if (_need_bg) { cairo_set_source_rgba (cr, bg_r, bg_g, bg_b, bg_a); if (corner_radius) { - Gtkmm2ext::rounded_rectangle (cr, 0, 0, get_width(), upper_height, corner_radius); + Gtkmm2ext::rounded_rectangle (cr, 0, 0, get_width(), get_height(), corner_radius); } else { - cairo_rectangle (cr, 0, 0, get_width(), upper_height); + cairo_rectangle (cr, 0, 0, get_width(), get_height()); } cairo_fill (cr); } - if (!_fixed_width) { - cairo_move_to (cr, layout_x_offset, 0); - } else { - cairo_move_to (cr, layout_x_offset, (upper_height - layout_height) / 2.0); - } - - pango_cairo_show_layout (cr, _layout->gobj()); + double lw = layout_width * xscale; + double lh = layout_height * yscale; - if (_left_layout) { - - double h = get_height() - upper_height - separator_height; - - if (_need_bg) { - cairo_set_source_rgba (cr, bg_r, bg_g, bg_b, bg_a); - } + cairo_move_to (cr, (get_width() - lw) / 2.0, (get_height() - lh) / 2.0); - if (mode_based_info_ratio != 1.0) { + if (xscale != 1.0 || yscale != 1.0) { + cairo_save (cr); + cairo_scale (cr, xscale, yscale); + } - double left_rect_width = round (((get_width() - separator_height) * mode_based_info_ratio) + 0.5); + pango_cairo_show_layout (cr, _layout->gobj()); - if (_need_bg) { - if (corner_radius) { - Gtkmm2ext::rounded_rectangle (cr, 0, upper_height + separator_height, left_rect_width, h, corner_radius); - } else { - cairo_rectangle (cr, 0, upper_height + separator_height, left_rect_width, h); - } - cairo_fill (cr); - } + if (xscale != 1.0 || yscale != 1.0) { + cairo_restore (cr); + } - cairo_move_to (cr, x_leading_padding, upper_height + separator_height + ((h - info_height)/2.0)); - pango_cairo_show_layout (cr, _left_layout->gobj()); - - if (_need_bg) { - if (corner_radius) { - Gtkmm2ext::rounded_rectangle (cr, left_rect_width + separator_height, upper_height + separator_height, - get_width() - separator_height - left_rect_width, h, - corner_radius); - } else { - cairo_rectangle (cr, left_rect_width + separator_height, upper_height + separator_height, - get_width() - separator_height - left_rect_width, h); - } - cairo_fill (cr); - } + if (editing) { + if (!insert_map.empty()) { - cairo_move_to (cr, x_leading_padding + left_rect_width + separator_height, upper_height + separator_height + ((h - info_height)/2.0)); - pango_cairo_show_layout (cr, _right_layout->gobj()); + int xcenter = (get_width() - layout_width) /2; - } else { - /* no info to display, or just one */ + if (input_string.length() < insert_map.size()) { + Pango::Rectangle cursor; - if (_need_bg) { - if (corner_radius) { - Gtkmm2ext::rounded_rectangle (cr, 0, upper_height + separator_height, get_width(), h, corner_radius); + if (input_string.empty()) { + /* nothing entered yet, put cursor at the end + of string + */ + cursor = _layout->get_cursor_strong_pos (edit_string.length() - 1); } else { - cairo_rectangle (cr, 0, upper_height + separator_height, get_width(), h); + cursor = _layout->get_cursor_strong_pos (insert_map[input_string.length()]); } - cairo_fill (cr); - } - } - } - - if (editing) { - const double cursor_width = 12; /* need em width here, not 16 */ - if (!insert_map.empty()) { - - Pango::Rectangle cursor; - - if (input_string.empty()) { - /* nothing entered yet, put cursor at the end - of string - */ - cursor = _layout->get_cursor_strong_pos (edit_string.length() - 1); - } else { - cursor = _layout->get_cursor_strong_pos (insert_map[input_string.length()-1]); - } - - cairo_set_source_rgba (cr, cursor_r, cursor_g, cursor_b, cursor_a); - if (!_fixed_width) { - cairo_rectangle (cr, - layout_x_offset + cursor.get_x()/PANGO_SCALE + cursor_width, - 0, + cairo_set_source_rgba (cr, cursor_r, cursor_g, cursor_b, cursor_a); + cairo_rectangle (cr, + min (get_width() - 2.0, + (double) xcenter + cursor.get_x()/PANGO_SCALE + em_width), + (get_height() - layout_height)/2.0, 2.0, cursor.get_height()/PANGO_SCALE); + cairo_fill (cr); } else { - cairo_rectangle (cr, - layout_x_offset + cursor.get_x()/PANGO_SCALE + cursor_width, - (upper_height - layout_height)/2.0, - 2.0, cursor.get_height()/PANGO_SCALE); + /* we've entered all possible digits, no cursor */ } - cairo_fill (cr); + } else { if (input_string.empty()) { cairo_set_source_rgba (cr, cursor_r, cursor_g, cursor_b, cursor_a); - if (!_fixed_width) { - cairo_rectangle (cr, - (get_width()/2.0), - 0, - 2.0, upper_height); - } else { - cairo_rectangle (cr, - (get_width()/2.0), - (upper_height - layout_height)/2.0, - 2.0, upper_height); - } + cairo_rectangle (cr, + (get_width()/2.0), + (get_height() - layout_height)/2.0, + 2.0, get_height()); cairo_fill (cr); } } @@ -381,115 +347,52 @@ AudioClock::render (cairo_t* cr) } void -AudioClock::on_size_allocate (Gtk::Allocation& alloc) +AudioClock::set_clock_dimensions (Gtk::Requisition& req) { - CairoWidget::on_size_allocate (alloc); - - if (_left_layout) { - upper_height = (get_height()/2.0) - 1.0; - } else { - upper_height = get_height(); - } + Glib::RefPtr tmp; + Glib::RefPtr style = get_style (); + Pango::FontDescription font; + + tmp = Pango::Layout::create (get_pango_context()); - if (_fixed_width) { - /* center display in available space */ - layout_x_offset = (get_width() - layout_width)/2.0; + if (!is_realized()) { + font = get_font_for_style (get_name()); } else { - /* left justify */ - layout_x_offset = 0; + font = style->get_font(); } + tmp->set_font_description (font); + + /* this string is the longest thing we will ever display */ + if (_mode == MinSec) + tmp->set_text (" 88:88:88,888 "); + else + tmp->set_text (" 88:88:88,88 "); + tmp->get_pixel_size (req.width, req.height); + + layout_height = req.height; + layout_width = req.width; } void AudioClock::on_size_request (Gtk::Requisition* req) { - /* even for non fixed width clocks, the size we *ask* for never changes, + /* even for non fixed width clocks, the size we *ask* for never changes, even though the size we receive might. so once we've computed it, just return it. */ - + if (first_width) { req->width = first_width; req->height = first_height; return; } - Glib::RefPtr tmp; - Glib::RefPtr style = get_style (); - Pango::FontDescription font; - - tmp = Pango::Layout::create (get_pango_context()); - - if (!is_realized()) { - font = get_font_for_style (get_name()); - } else { - font = style->get_font(); - } - - tmp->set_font_description (font); - - if (_fixed_width) { - /* this string is the longest thing we will ever display, - and also includes the BBT bar char that may descends below - the baseline a bit, and a comma for the minsecs mode - where we printf a fractional value (XXX or should) - */ - - tmp->set_text (" 8888888888:,|"); - } else { - switch (_mode) { - case Timecode: - tmp->set_text (" 88:88:88:88"); - break; - case BBT: - tmp->set_text (" 888|88|8888"); - break; - case MinSec: - tmp->set_text (" 88:88:88,888"); - break; - case Frames: - tmp->set_text (" 8888888888"); - break; - } - } - - tmp->get_pixel_size (req->width, req->height); - - layout_height = req->height; - layout_width = req->width; + set_clock_dimensions (*req); /* now tackle height, for which we need to know the height of the lower * layout */ - - if (_left_layout) { - - int w; - - font.set_size ((int) lrint (font.get_size() * info_font_scale_factor)); - font.set_weight (Pango::WEIGHT_NORMAL); - tmp->set_font_description (font); - - /* we only care about height, so put as much stuff in here - as possible that might change the height. - */ - tmp->set_text ("qyhH|"); /* one ascender, one descender */ - - tmp->get_pixel_size (w, info_height); - - /* silly extra padding that seems necessary to correct the info - * that pango just gave us. I have no idea why. - */ - - info_height += 4; - - req->height += info_height; - req->height += separator_height; - } - - first_height = req->height; - first_width = req->width; } void @@ -497,7 +400,7 @@ AudioClock::show_edit_status (int length) { editing_attr->set_start_index (edit_string.length() - length); editing_attr->set_end_index (edit_string.length()); - + editing_attributes.change (*foreground_attr); editing_attributes.change (*editing_attr); @@ -505,22 +408,76 @@ AudioClock::show_edit_status (int length) } void -AudioClock::start_edit () +AudioClock::start_edit (Field f) { - pre_edit_string = _layout->get_text (); - if (!insert_map.empty()) { - edit_string = pre_edit_string; - } else { - edit_string.clear (); - _layout->set_text (""); - } - input_string.clear (); - editing = true; + if (!editing) { + pre_edit_string = _layout->get_text (); + if (!insert_map.empty()) { + edit_string = pre_edit_string; + } else { + edit_string.clear (); + _layout->set_text (""); + } - queue_draw (); + input_string.clear (); + editing = true; + edit_is_negative = false; + + if (f) { + input_string = get_field (f); + show_edit_status (merge_input_and_edit_string ()); + _layout->set_text (edit_string); + } - Keyboard::magic_widget_grab_focus (); - grab_focus (); + queue_draw (); + + Keyboard::magic_widget_grab_focus (); + grab_focus (); + } +} + +string +AudioClock::get_field (Field f) +{ + switch (f) { + case Timecode_Hours: + return edit_string.substr (1, 2); + break; + case Timecode_Minutes: + return edit_string.substr (4, 2); + break; + case Timecode_Seconds: + return edit_string.substr (7, 2); + break; + case Timecode_Frames: + return edit_string.substr (10, 2); + break; + case MS_Hours: + return edit_string.substr (1, 2); + break; + case MS_Minutes: + return edit_string.substr (4, 2); + break; + case MS_Seconds: + return edit_string.substr (7, 2); + break; + case MS_Milliseconds: + return edit_string.substr (10, 3); + break; + case Bars: + return edit_string.substr (1, 3); + break; + case Beats: + return edit_string.substr (5, 2); + break; + case Ticks: + return edit_string.substr (8, 4); + break; + case AudioFrames: + return edit_string; + break; + } + return ""; } void @@ -529,23 +486,27 @@ AudioClock::end_edit (bool modify) if (modify) { bool ok = true; - + switch (_mode) { case Timecode: ok = timecode_validate_edit (edit_string); break; - + case BBT: ok = bbt_validate_edit (edit_string); break; - + case MinSec: + ok = minsec_validate_edit (edit_string); break; - + case Frames: + if (edit_string.length() < 1) { + edit_string = pre_edit_string; + } break; } - + if (!ok) { edit_string = pre_edit_string; input_string.clear (); @@ -561,19 +522,19 @@ AudioClock::end_edit (bool modify) case Timecode: pos = frames_from_timecode_string (edit_string); break; - + case BBT: if (is_duration) { - pos = frame_duration_from_bbt_string (0, edit_string); + pos = frame_duration_from_bbt_string (bbt_reference_time, edit_string); } else { pos = frames_from_bbt_string (0, edit_string); } break; - + case MinSec: pos = frames_from_minsec_string (edit_string); break; - + case Frames: pos = frames_from_audioframes_string (edit_string); break; @@ -587,6 +548,7 @@ AudioClock::end_edit (bool modify) } else { editing = false; + edit_is_negative = false; _layout->set_attributes (normal_attributes); _layout->set_text (pre_edit_string); } @@ -604,19 +566,12 @@ AudioClock::drop_focus () Keyboard::magic_widget_drop_focus (); if (has_focus()) { - /* move focus back to the default widget in the top level window */ - - Widget* top = get_toplevel(); - - if (top->is_toplevel ()) { - Window* win = dynamic_cast (top); - win->grab_focus (); - } + ARDOUR_UI::instance()->reset_focus (this); } } -framecnt_t +framecnt_t AudioClock::parse_as_frames_distance (const std::string& str) { framecnt_t f; @@ -628,7 +583,7 @@ AudioClock::parse_as_frames_distance (const std::string& str) return 0; } -framecnt_t +framecnt_t AudioClock::parse_as_minsec_distance (const std::string& str) { framecnt_t sr = _session->frame_rate(); @@ -646,7 +601,7 @@ AudioClock::parse_as_minsec_distance (const std::string& str) case 4: sscanf (str.c_str(), "%" PRId32, &msecs); return msecs * (sr / 1000); - + case 5: sscanf (str.c_str(), "%1" PRId32 "%" PRId32, &secs, &msecs); return (secs * sr) + (msecs * (sr/1000)); @@ -670,7 +625,7 @@ AudioClock::parse_as_minsec_distance (const std::string& str) case 10: sscanf (str.c_str(), "%1" PRId32 "%2" PRId32 "%2" PRId32 "%" PRId32, &hrs, &mins, &secs, &msecs); return (hrs * 3600 * sr) + (mins * 60 * sr) + (secs * sr) + (msecs * (sr/1000)); - + default: break; } @@ -678,7 +633,7 @@ AudioClock::parse_as_minsec_distance (const std::string& str) return 0; } -framecnt_t +framecnt_t AudioClock::parse_as_timecode_distance (const std::string& str) { double fps = _session->timecode_frames_per_second(); @@ -687,39 +642,39 @@ AudioClock::parse_as_timecode_distance (const std::string& str) int secs; int mins; int hrs; - + switch (str.length()) { case 0: return 0; case 1: case 2: sscanf (str.c_str(), "%" PRId32, &frames); - return lrint ((frames/(float)fps) * sr); + return llrint ((frames/(float)fps) * sr); case 3: sscanf (str.c_str(), "%1" PRId32 "%" PRId32, &secs, &frames); - return (secs * sr) + lrint ((frames/(float)fps) * sr); + return (secs * sr) + llrint ((frames/(float)fps) * sr); case 4: sscanf (str.c_str(), "%2" PRId32 "%" PRId32, &secs, &frames); - return (secs * sr) + lrint ((frames/(float)fps) * sr); - + return (secs * sr) + llrint ((frames/(float)fps) * sr); + case 5: sscanf (str.c_str(), "%1" PRId32 "%2" PRId32 "%" PRId32, &mins, &secs, &frames); - return (mins * 60 * sr) + (secs * sr) + lrint ((frames/(float)fps) * sr); + return (mins * 60 * sr) + (secs * sr) + llrint ((frames/(float)fps) * sr); case 6: sscanf (str.c_str(), "%2" PRId32 "%2" PRId32 "%" PRId32, &mins, &secs, &frames); - return (mins * 60 * sr) + (secs * sr) + lrint ((frames/(float)fps) * sr); + return (mins * 60 * sr) + (secs * sr) + llrint ((frames/(float)fps) * sr); case 7: sscanf (str.c_str(), "%1" PRId32 "%2" PRId32 "%2" PRId32 "%" PRId32, &hrs, &mins, &secs, &frames); - return (hrs * 3600 * sr) + (mins * 60 * sr) + (secs * sr) + lrint ((frames/(float)fps) * sr); + return (hrs * 3600 * sr) + (mins * 60 * sr) + (secs * sr) + llrint ((frames/(float)fps) * sr); case 8: sscanf (str.c_str(), "%2" PRId32 "%2" PRId32 "%2" PRId32 "%" PRId32, &hrs, &mins, &secs, &frames); - return (hrs * 3600 * sr) + (mins * 60 * sr) + (secs * sr) + lrint ((frames/(float)fps) * sr); - + return (hrs * 3600 * sr) + (mins * 60 * sr) + (secs * sr) + llrint ((frames/(float)fps) * sr); + default: break; } @@ -727,33 +682,27 @@ AudioClock::parse_as_timecode_distance (const std::string& str) return 0; } -framecnt_t -AudioClock::parse_as_bbt_distance (const std::string& str) +framecnt_t +AudioClock::parse_as_bbt_distance (const std::string&) { return 0; } -framecnt_t +framecnt_t AudioClock::parse_as_distance (const std::string& instr) { - string str = instr; - - /* the input string is in reverse order */ - - std::reverse (str.begin(), str.end()); - switch (_mode) { case Timecode: - return parse_as_timecode_distance (str); + return parse_as_timecode_distance (instr); break; case Frames: - return parse_as_frames_distance (str); + return parse_as_frames_distance (instr); break; case BBT: - return parse_as_bbt_distance (str); + return parse_as_bbt_distance (instr); break; case MinSec: - return parse_as_minsec_distance (str); + return parse_as_minsec_distance (instr); break; } return 0; @@ -762,6 +711,35 @@ AudioClock::parse_as_distance (const std::string& instr) void AudioClock::end_edit_relative (bool add) { + bool ok = true; + + switch (_mode) { + case Timecode: + ok = timecode_validate_edit (edit_string); + break; + + case BBT: + ok = bbt_validate_edit (edit_string); + break; + + case MinSec: + ok = minsec_validate_edit (edit_string); + break; + + case Frames: + break; + } + + if (!ok) { + edit_string = pre_edit_string; + input_string.clear (); + _layout->set_text (edit_string); + show_edit_status (0); + /* edit attributes remain in use */ + queue_draw (); + return; + } + framecnt_t frames = parse_as_distance (input_string); editing = false; @@ -775,7 +753,7 @@ AudioClock::end_edit_relative (bool add) } else { framepos_t c = current_time(); - if (c > frames) { + if (c > frames || _negative_allowed) { set (c - frames, true); } else { set (0, true); @@ -789,9 +767,26 @@ AudioClock::end_edit_relative (bool add) drop_focus (); } +void +AudioClock::session_property_changed (const PropertyChange&) +{ + set (last_when, true); +} + +void +AudioClock::metric_position_changed () +{ + set (last_when, true); +} + void AudioClock::session_configuration_changed (std::string p) { + if (_negative_allowed) { + /* session option editor clock */ + return; + } + if (p == "sync-source" || p == "external-sync") { set (current_time(), true); return; @@ -826,37 +821,132 @@ AudioClock::set (framepos_t when, bool force, framecnt_t offset) if (is_duration) { when = when - offset; - } + } if (when == last_when && !force) { +#if 0 // XXX return if no change and no change forced. verify Aug/2014 + if (_mode != Timecode && _mode != MinSec) { + /* may need to force display of TC source + * time, so don't return early. + */ + /* ^^ Why was that?, delta times? + * Timecode FPS, pull-up/down, etc changes + * trigger a 'session_property_changed' which + * eventually calls set(last_when, true) + * + * re-rendering the clock every 40ms or so just + * because we can is not ideal. + */ + return; + } +#else return; +#endif } + bool btn_en = false; + if (!editing) { switch (_mode) { case Timecode: set_timecode (when, force); break; - + case BBT: - set_bbt (when, force); + set_bbt (when, offset, force); + btn_en = true; break; - + case MinSec: set_minsec (when, force); break; - + case Frames: set_frames (when, force); break; } } + if (_with_info) { + _left_btn.set_sensitive (btn_en); + _right_btn.set_sensitive (btn_en); + _left_btn.set_visual_state (Gtkmm2ext::NoVisualState); + _right_btn.set_visual_state (Gtkmm2ext::NoVisualState); + if (btn_en) { + _left_btn.set_elements (ArdourButton::Element(ArdourButton::Edge|ArdourButton::Body|ArdourButton::Text)); + _right_btn.set_elements (ArdourButton::Element(ArdourButton::Edge|ArdourButton::Body|ArdourButton::Text)); + _left_btn.set_alignment (.5, .5); + _right_btn.set_alignment (.5, .5); + set_tooltip (_left_btn, _("Change current tempo")); + set_tooltip (_right_btn, _("Change current time signature")); + } else { + _left_btn.set_elements (ArdourButton::Text); + _right_btn.set_elements (ArdourButton::Text); + _left_btn.set_alignment (0, .5); + _right_btn.set_alignment (1, .5); + set_tooltip (_left_btn, _("")); + set_tooltip (_right_btn, _("")); + } + } + queue_draw (); last_when = when; } +void +AudioClock::set_slave_info () +{ + if (!_with_info) { + return; + } + + SyncSource sync_src = Config->get_sync_source(); + + if (_session->config.get_external_sync()) { + Slave* slave = _session->slave(); + + switch (sync_src) { + case Engine: + _left_btn.set_text (sync_source_to_string (sync_src, true)); + _right_btn.set_text (""); + break; + case MIDIClock: + if (slave) { + _left_btn.set_text (sync_source_to_string (sync_src, true)); + _right_btn.set_text (slave->approximate_current_delta (), true); + } else { + _left_btn.set_text (_("--pending--")); + _right_btn.set_text (""); + } + break; + case LTC: + case MTC: + if (slave) { + bool matching; + TimecodeSlave* tcslave; + if ((tcslave = dynamic_cast(_session->slave())) != 0) { + matching = (tcslave->apparent_timecode_format() == _session->config.get_timecode_format()); + _left_btn.set_text (string_compose ("%1 %2", + sync_source_to_string(sync_src, true)[0], + dynamic_cast(slave)->approximate_current_position (), + matching ? "#66ff66" : "#ff3333" + ), true); + _right_btn.set_text (slave->approximate_current_delta (), true); + } + } else { + _left_btn.set_text (_("--pending--")); + _right_btn.set_text (""); + } + break; + } + } else { + _left_btn.set_text (string_compose ("%1/%2", + _("INT"), sync_source_to_string(sync_src, true))); + _right_btn.set_text (""); + } +} + void AudioClock::set_frames (framepos_t when, bool /*force*/) { @@ -864,16 +954,12 @@ AudioClock::set_frames (framepos_t when, bool /*force*/) bool negative = false; if (_off) { - _layout->set_text ("\u2012\u2012\u2012\u2012\u2012\u2012\u2012\u2012\u2012\u2012"); - - if (_left_layout) { - _left_layout->set_text (""); - _right_layout->set_text (""); - } - + _layout->set_text (" ----------"); + _left_btn.set_text (""); + _right_btn.set_text (""); return; } - + if (when < 0) { when = -when; negative = true; @@ -887,87 +973,91 @@ AudioClock::set_frames (framepos_t when, bool /*force*/) _layout->set_text (buf); - if (_left_layout) { + if (_with_info) { framecnt_t rate = _session->frame_rate(); if (fmod (rate, 100.0) == 0.0) { - sprintf (buf, "SR %.1fkHz", rate/1000.0); + sprintf (buf, "%.1fkHz", rate/1000.0); } else { - sprintf (buf, "SR %" PRId64, rate); + sprintf (buf, "%" PRId64 "Hz", rate); } - _left_layout->set_text (buf); + _left_btn.set_text (string_compose ("%1 %2", _("SR"), buf)); float vid_pullup = _session->config.get_video_pullup(); if (vid_pullup == 0.0) { - _right_layout->set_text (_("pullup: \u2012")); + _right_btn.set_text (string_compose ("%1 off", _("Pull"))); } else { - sprintf (buf, _("pullup %-6.4f"), vid_pullup); - _right_layout->set_text (buf); + sprintf (buf, _("%+.4f%%"), vid_pullup); + _right_btn.set_text (string_compose ("%1 %2", _("Pull"), buf)); } } } void -AudioClock::set_minsec (framepos_t when, bool force) +AudioClock::print_minsec (framepos_t when, char* buf, size_t bufsize, float frame_rate) { - char buf[32]; framecnt_t left; int hrs; int mins; int secs; int millisecs; - bool negative = false; - - if (_off) { - _layout->set_text ("\u2012\u2012:\u2012\u2012:\u2012\u2012.\u2012\u2012\u2012"); - - if (_left_layout) { - _left_layout->set_text (""); - _right_layout->set_text (""); - } - - return; - } + bool negative; if (when < 0) { when = -when; negative = true; + } else { + negative = false; } left = when; - hrs = (int) floor (left / (_session->frame_rate() * 60.0f * 60.0f)); - left -= (framecnt_t) floor (hrs * _session->frame_rate() * 60.0f * 60.0f); - mins = (int) floor (left / (_session->frame_rate() * 60.0f)); - left -= (framecnt_t) floor (mins * _session->frame_rate() * 60.0f); - secs = (int) floor (left / (float) _session->frame_rate()); - left -= (framecnt_t) floor (secs * _session->frame_rate()); - millisecs = floor (left * 1000.0 / (float) _session->frame_rate()); - + hrs = (int) floor (left / (frame_rate * 60.0f * 60.0f)); + left -= (framecnt_t) floor (hrs * frame_rate * 60.0f * 60.0f); + mins = (int) floor (left / (frame_rate * 60.0f)); + left -= (framecnt_t) floor (mins * frame_rate * 60.0f); + secs = (int) floor (left / (float) frame_rate); + left -= (framecnt_t) floor ((double)(secs * frame_rate)); + millisecs = floor (left * 1000.0 / (float) frame_rate); + if (negative) { - snprintf (buf, sizeof (buf), "-%02" PRId32 ":%02" PRId32 ":%02" PRId32 ".%03" PRId32, hrs, mins, secs, millisecs); + snprintf (buf, bufsize, "-%02" PRId32 ":%02" PRId32 ":%02" PRId32 ".%03" PRId32, hrs, mins, secs, millisecs); } else { - snprintf (buf, sizeof (buf), " %02" PRId32 ":%02" PRId32 ":%02" PRId32 ".%03" PRId32, hrs, mins, secs, millisecs); + snprintf (buf, bufsize, " %02" PRId32 ":%02" PRId32 ":%02" PRId32 ".%03" PRId32, hrs, mins, secs, millisecs); } - _layout->set_text (buf); } void -AudioClock::set_timecode (framepos_t when, bool force) +AudioClock::set_minsec (framepos_t when, bool /*force*/) { char buf[32]; + + if (_off) { + _layout->set_text (" --:--:--.---"); + _left_btn.set_text (""); + _right_btn.set_text (""); + + return; + } + + print_minsec (when, buf, sizeof (buf), _session->frame_rate()); + + _layout->set_text (buf); + set_slave_info(); +} + +void +AudioClock::set_timecode (framepos_t when, bool /*force*/) +{ Timecode::Time TC; bool negative = false; if (_off) { - _layout->set_text ("\u2012\u2012:\u2012\u2012:\u2012\u2012:\u2012\u2012"); - if (_left_layout) { - _left_layout->set_text (""); - _right_layout->set_text (""); - } - + _layout->set_text (" --:--:--:--"); + _left_btn.set_text (""); + _right_btn.set_text (""); return; } @@ -981,58 +1071,25 @@ AudioClock::set_timecode (framepos_t when, bool force) } else { _session->timecode_time (when, TC); } - - if (TC.negative || negative) { - snprintf (buf, sizeof (buf), "-%02" PRIu32 ":%02" PRIu32 ":%02" PRIu32 ":%02" PRIu32, TC.hours, TC.minutes, TC.seconds, TC.frames); - } else { - snprintf (buf, sizeof (buf), " %02" PRIu32 ":%02" PRIu32 ":%02" PRIu32 ":%02" PRIu32, TC.hours, TC.minutes, TC.seconds, TC.frames); - } - - _layout->set_text (buf); - if (_left_layout) { + TC.negative = TC.negative || negative; - if (_session->config.get_external_sync()) { - switch (_session->config.get_sync_source()) { - case JACK: - _left_layout->set_text ("JACK"); - break; - case MTC: - _left_layout->set_text ("MTC"); - break; - case MIDIClock: - _left_layout->set_text ("M-Clock"); - break; - } - } else { - _left_layout->set_text ("INT"); - } + _layout->set_text (Timecode::timecode_format_time(TC)); - double timecode_frames = _session->timecode_frames_per_second(); - - if (fmod(timecode_frames, 1.0) == 0.0) { - sprintf (buf, "FPS %u %s", int (timecode_frames), (_session->timecode_drop_frames() ? "D" : "")); - } else { - sprintf (buf, "%.2f %s", timecode_frames, (_session->timecode_drop_frames() ? "D" : "")); - } - - _right_layout->set_text (buf); - } + set_slave_info(); } void -AudioClock::set_bbt (framepos_t when, bool force) +AudioClock::set_bbt (framepos_t when, framecnt_t offset, bool /*force*/) { - char buf[16]; + char buf[64]; Timecode::BBT_Time BBT; bool negative = false; if (_off) { - _layout->set_text ("\u2012\u2012\u2012|\u2012\u2012|\u2012\u2012\u2012\u2012"); - if (_left_layout) { - _left_layout->set_text (""); - _right_layout->set_text (""); - } + _layout->set_text (" ---|--|----"); + _left_btn.set_text (""); + _right_btn.set_text (""); return; } @@ -1048,25 +1105,59 @@ AudioClock::set_bbt (framepos_t when, bool force) BBT.beats = 0; BBT.ticks = 0; } else { - _session->tempo_map().bbt_time (when, BBT); - BBT.bars--; - BBT.beats--; + TempoMap& tmap (_session->tempo_map()); + + if (offset == 0) { + offset = bbt_reference_time; + } + + const double divisions = tmap.meter_section_at_frame (offset).divisions_per_bar(); + Timecode::BBT_Time sub_bbt; + + if (negative) { + BBT = tmap.bbt_at_beat (tmap.beat_at_frame (offset)); + sub_bbt = tmap.bbt_at_frame (offset - when); + } else { + BBT = tmap.bbt_at_beat (tmap.beat_at_frame (when + offset)); + sub_bbt = tmap.bbt_at_frame (offset); + } + + BBT.bars -= sub_bbt.bars; + + if (BBT.ticks < sub_bbt.ticks) { + if (BBT.beats == 1) { + BBT.bars--; + BBT.beats = divisions; + } else { + BBT.beats--; + } + BBT.ticks = Timecode::BBT_Time::ticks_per_beat - (sub_bbt.ticks - BBT.ticks); + } else { + BBT.ticks -= sub_bbt.ticks; + } + + if (BBT.beats < sub_bbt.beats) { + BBT.bars--; + BBT.beats = divisions - (sub_bbt.beats - BBT.beats); + } else { + BBT.beats -= sub_bbt.beats; + } } } else { - _session->tempo_map().bbt_time (when, BBT); + BBT = _session->tempo_map().bbt_at_frame (when); } if (negative) { - snprintf (buf, sizeof (buf), "-%03" PRIu32 BBT_BAR_CHAR "%02" PRIu32 BBT_BAR_CHAR "%04" PRIu32, + snprintf (buf, sizeof (buf), "-%03" PRIu32 BBT_BAR_CHAR "%02" PRIu32 BBT_BAR_CHAR "%04" PRIu32, BBT.bars, BBT.beats, BBT.ticks); } else { - snprintf (buf, sizeof (buf), " %03" PRIu32 BBT_BAR_CHAR "%02" PRIu32 BBT_BAR_CHAR "%04" PRIu32, + snprintf (buf, sizeof (buf), " %03" PRIu32 BBT_BAR_CHAR "%02" PRIu32 BBT_BAR_CHAR "%04" PRIu32, BBT.bars, BBT.beats, BBT.ticks); } _layout->set_text (buf); - - if (_right_layout) { + + if (_with_info) { framepos_t pos; if (bbt_reference_time < 0) { @@ -1077,11 +1168,19 @@ AudioClock::set_bbt (framepos_t when, bool force) TempoMetric m (_session->tempo_map().metric_at (pos)); - sprintf (buf, "%-5.2f", m.tempo().beats_per_minute()); - _left_layout->set_text (buf); + if (m.tempo().note_type() == 4) { + snprintf (buf, sizeof(buf), "\u2669 = %.3f", _session->tempo_map().tempo_at_frame (pos).note_types_per_minute()); + _left_btn.set_text (string_compose ("%1", buf)); + } else if (m.tempo().note_type() == 8) { + snprintf (buf, sizeof(buf), "\u266a = %.3f", _session->tempo_map().tempo_at_frame (pos).note_types_per_minute()); + _left_btn.set_text (string_compose ("%1", buf)); + } else { + snprintf (buf, sizeof(buf), "%.1f = %.3f", m.tempo().note_type(), _session->tempo_map().tempo_at_frame (pos).note_types_per_minute()); + _left_btn.set_text (string_compose ("%1: %2", S_("Tempo|T"), buf)); + } - sprintf (buf, "%g/%g", m.meter().beats_per_bar(), m.meter().note_divisor()); - _right_layout->set_text (buf); + snprintf (buf, sizeof(buf), "%g/%g", m.meter().divisions_per_bar(), m.meter().note_divisor()); + _right_btn.set_text (string_compose ("%1: %2", S_("TimeSignature|TS"), buf)); } } @@ -1092,9 +1191,12 @@ AudioClock::set_session (Session *s) if (_session) { + Config->ParameterChanged.connect (_session_connections, invalidator (*this), boost::bind (&AudioClock::session_configuration_changed, this, _1), gui_context()); _session->config.ParameterChanged.connect (_session_connections, invalidator (*this), boost::bind (&AudioClock::session_configuration_changed, this, _1), gui_context()); + _session->tempo_map().PropertyChanged.connect (_session_connections, invalidator (*this), boost::bind (&AudioClock::session_property_changed, this, _1), gui_context()); + _session->tempo_map().MetricPositionChanged.connect (_session_connections, invalidator (*this), boost::bind (&AudioClock::metric_position_changed, this), gui_context()); - const XMLProperty* prop; + XMLProperty const * prop; XMLNode* node = _session->extra_xml (X_("ClockModes")); AudioClock::Mode amode; @@ -1104,7 +1206,7 @@ AudioClock::set_session (Session *s) if ((prop = (*i)->property (X_("mode"))) != 0) { amode = AudioClock::Mode (string_2_enum (prop->value(), amode)); - set_mode (amode); + set_mode (amode, true); } if ((prop = (*i)->property (X_("on"))) != 0) { set_off (!string_is_affirmative (prop->value())); @@ -1127,6 +1229,8 @@ AudioClock::on_key_press_event (GdkEventKey* ev) string new_text; char new_char = 0; + int highlight_length; + framepos_t pos; switch (ev->keyval) { case GDK_0: @@ -1172,12 +1276,18 @@ AudioClock::on_key_press_event (GdkEventKey* ev) case GDK_minus: case GDK_KP_Subtract: - end_edit_relative (false); + if (_negative_allowed && input_string.empty()) { + edit_is_negative = true; + edit_string.replace(0,1,"-"); + _layout->set_text (edit_string); + queue_draw (); + } else { + end_edit_relative (false); + } return true; break; case GDK_plus: - case GDK_KP_Add: end_edit_relative (true); return true; break; @@ -1197,15 +1307,17 @@ AudioClock::on_key_press_event (GdkEventKey* ev) case GDK_Delete: case GDK_BackSpace: if (!input_string.empty()) { - /* delete the last key entered, which is at the FRONT - of the input_string + /* delete the last key entered */ - input_string = input_string.substr (1, input_string.length() - 1); + input_string = input_string.substr (0, input_string.length() - 1); } goto use_input_string; default: - return false; + /* do not allow other keys to passthru to the rest of the GUI + when editing. + */ + return true; } if (!insert_map.empty() && (input_string.length() >= insert_map.size())) { @@ -1213,77 +1325,69 @@ AudioClock::on_key_press_event (GdkEventKey* ev) return true; } - input_string.insert (input_string.begin(), new_char); + input_string.push_back (new_char); use_input_string: - string::reverse_iterator ri; - vector insert_at; - int highlight_length = 0; - char buf[32]; - framepos_t pos; - - /* merge with pre-edit-string into edit string */ - switch (_mode) { case Frames: /* get this one in the right order, and to the right width */ - if (ev->keyval != GDK_Delete && ev->keyval != GDK_BackSpace) { - edit_string.push_back (new_char); - } else { + if (ev->keyval == GDK_Delete || ev->keyval == GDK_BackSpace) { edit_string = edit_string.substr (0, edit_string.length() - 1); + } else { + edit_string.push_back (new_char); } if (!edit_string.empty()) { + char buf[32]; sscanf (edit_string.c_str(), "%" PRId64, &pos); snprintf (buf, sizeof (buf), " %10" PRId64, pos); edit_string = buf; } + /* highlight the whole thing */ highlight_length = edit_string.length(); break; - + default: - if (!input_string.empty()) { - edit_string = pre_edit_string; - - /* backup through the original string, till we have - * enough digits locations to put all the digits from - * the input string. - */ - - for (ri = edit_string.rbegin(); ri != edit_string.rend(); ++ri) { - if (isdigit (*ri)) { - insert_at.push_back (edit_string.length() - (ri - edit_string.rbegin()) - 1); - if (insert_at.size() == input_string.length()) { - break; - } - } - } - - if (insert_at.size() != input_string.length()) { - error << "something went wrong, insert at = " << insert_at.size() << " is.len() = " << input_string.length() << endmsg; - } else { - for (int i = input_string.length() - 1; i >= 0; --i) { - edit_string[insert_at[i]] = input_string[i]; - } - - highlight_length = edit_string.length() - insert_at.back(); - } + highlight_length = merge_input_and_edit_string (); + } + + if (edit_is_negative) { + edit_string.replace(0,1,"-"); + } else { + if (!pre_edit_string.empty() && (pre_edit_string.at(0) == '-')) { + edit_string.replace(0,1,"_"); } else { - edit_string = pre_edit_string; - highlight_length = 0; + edit_string.replace(0,1," "); } - break; } - - if (edit_string != _layout->get_text()) { - show_edit_status (highlight_length); - _layout->set_text (edit_string); - queue_draw (); - } + + show_edit_status (highlight_length); + _layout->set_text (edit_string); + queue_draw (); return true; } +int +AudioClock::merge_input_and_edit_string () +{ + /* merge with pre-edit-string into edit string */ + + edit_string = pre_edit_string; + + if (input_string.empty()) { + return 0; + } + + string::size_type target; + for (string::size_type i = 0; i < input_string.length(); ++i) { + target = insert_map[input_string.length() - 1 - i]; + edit_string[target] = input_string[i]; + } + /* highlight from end to wherever the last character was added */ + return edit_string.length() - insert_map[input_string.length()-1]; +} + bool AudioClock::on_key_release_event (GdkEventKey *ev) @@ -1291,7 +1395,7 @@ AudioClock::on_key_release_event (GdkEventKey *ev) if (!editing) { return false; } - + /* return true for keys that we used on press so that they cannot possibly do double-duty */ @@ -1382,35 +1486,35 @@ AudioClock::on_button_press_event (GdkEventButton *ev) switch (ev->button) { case 1: if (editable && !_off) { - dragging = true; - /* make absolutely sure that the pointer is grabbed */ - gdk_pointer_grab(ev->window,false , - GdkEventMask( Gdk::POINTER_MOTION_MASK | Gdk::BUTTON_PRESS_MASK |Gdk::BUTTON_RELEASE_MASK), - NULL,NULL,ev->time); - drag_accum = 0; - drag_start_y = ev->y; - drag_y = ev->y; - int index; int trailing; int y; int x; /* the text has been centered vertically, so adjust - * x and y. + * x and y. */ + int xcenter = (get_width() - layout_width) /2; - y = ev->y - ((upper_height - layout_height)/2); - x = ev->x - layout_x_offset; - - if (_layout->xy_to_index (x * PANGO_SCALE, y * PANGO_SCALE, index, trailing)) { - drag_field = index_to_field (index); - } else { - drag_field = Field (0); + y = ev->y - ((get_height() - layout_height)/2); + x = ev->x - xcenter; + + if (!_layout->xy_to_index (x * PANGO_SCALE, y * PANGO_SCALE, index, trailing)) { + /* pretend it is a character on the far right */ + index = 99; } + drag_field = index_to_field (index); + dragging = true; + /* make absolutely sure that the pointer is grabbed */ + gdk_pointer_grab(ev->window,false , + GdkEventMask( Gdk::POINTER_MOTION_MASK | Gdk::BUTTON_PRESS_MASK |Gdk::BUTTON_RELEASE_MASK), + NULL,NULL,ev->time); + drag_accum = 0; + drag_start_y = ev->y; + drag_y = ev->y; } break; - + default: return false; break; @@ -1432,10 +1536,37 @@ AudioClock::on_button_release_event (GdkEventButton *ev) return true; } else { if (ev->button == 1) { - start_edit (); + + if (_edit_by_click_field) { + + int xcenter = (get_width() - layout_width) /2; + int index = 0; + int trailing; + int y = ev->y - ((get_height() - layout_height)/2); + int x = ev->x - xcenter; + Field f; + + if (!_layout->xy_to_index (x * PANGO_SCALE, y * PANGO_SCALE, index, trailing)) { + return true; + } + + f = index_to_field (index); + + switch (f) { + case Timecode_Frames: + case MS_Milliseconds: + case Ticks: + f = Field (0); + break; + default: + break; + } + start_edit (f); + } else { + start_edit (); + } } } - } } @@ -1456,7 +1587,7 @@ AudioClock::on_focus_out_event (GdkEventFocus* ev) bool ret = CairoWidget::on_focus_out_event (ev); if (editing) { - end_edit (false); + end_edit (_accept_on_focus_out); } return ret; @@ -1474,26 +1605,27 @@ AudioClock::on_scroll_event (GdkEventScroll *ev) int y; int x; - + /* the text has been centered vertically, so adjust - * x and y. + * x and y. */ - y = ev->y - ((upper_height - layout_height)/2); - x = ev->x - layout_x_offset; + int xcenter = (get_width() - layout_width) /2; + y = ev->y - ((get_height() - layout_height)/2); + x = ev->x - xcenter; if (!_layout->xy_to_index (x * PANGO_SCALE, y * PANGO_SCALE, index, trailing)) { /* not in the main layout */ return false; } - + Field f = index_to_field (index); framepos_t frames = 0; switch (ev->direction) { case GDK_SCROLL_UP: - frames = get_frame_step (f); + frames = get_frame_step (f, current_time(), 1); if (frames != 0) { if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) { frames *= 10; @@ -1502,29 +1634,29 @@ AudioClock::on_scroll_event (GdkEventScroll *ev) ValueChanged (); /* EMIT_SIGNAL */ } break; - + case GDK_SCROLL_DOWN: - frames = get_frame_step (f); + frames = get_frame_step (f, current_time(), -1); if (frames != 0) { if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) { frames *= 10; } - - if ((double)current_time() - (double)frames < 0.0) { + + if (!_negative_allowed && (double)current_time() - (double)frames < 0.0) { set (0, true); } else { set (current_time() - frames, true); } - + ValueChanged (); /* EMIT_SIGNAL */ } break; - + default: return false; break; } - + return true; } @@ -1554,14 +1686,14 @@ AudioClock::on_motion_notify_event (GdkEventMotion *ev) drag_y = ev->y; - if (trunc (drag_accum) != 0) { + if (floor (drag_accum) != 0) { framepos_t frames; framepos_t pos; int dir; dir = (drag_accum < 0 ? 1:-1); pos = current_time(); - frames = get_frame_step (drag_field,pos,dir); + frames = get_frame_step (drag_field, pos, dir); if (frames != 0 && frames * drag_accum < current_time()) { set ((framepos_t) floor (pos - drag_accum * frames), false); // minus because up is negative in GTK @@ -1640,7 +1772,7 @@ AudioClock::get_frame_step (Field field, framepos_t pos, int dir) } framepos_t -AudioClock::current_time (framepos_t pos) const +AudioClock::current_time (framepos_t) const { return last_when; } @@ -1678,7 +1810,11 @@ AudioClock::bbt_validate_edit (const string& str) if (sscanf (str.c_str(), BBT_SCANF_FORMAT, &any.bbt.bars, &any.bbt.beats, &any.bbt.ticks) != 3) { return false; } - + + if (any.bbt.ticks > Timecode::BBT_Time::ticks_per_beat) { + return false; + } + if (!is_duration && any.bbt.bars == 0) { return false; } @@ -1691,25 +1827,39 @@ AudioClock::bbt_validate_edit (const string& str) } bool -AudioClock::timecode_validate_edit (const string& str) +AudioClock::timecode_validate_edit (const string&) { Timecode::Time TC; + int hours; + char ignored[2]; + + if (sscanf (_layout->get_text().c_str(), "%[- _]%" PRId32 ":%" PRId32 ":%" PRId32 "%[:;]%" PRId32, + ignored, &hours, &TC.minutes, &TC.seconds, ignored, &TC.frames) != 6) { + return false; + } - if (sscanf (_layout->get_text().c_str(), "%" PRId32 ":%" PRId32 ":%" PRId32 ":%" PRId32, - &TC.hours, &TC.minutes, &TC.seconds, &TC.frames) != 4) { + if (hours < 0) { + TC.hours = hours * -1; + TC.negative = true; + } else { + TC.hours = hours; + TC.negative = false; + } + + if (TC.negative && !_negative_allowed) { return false; } - if (TC.minutes > 59 || TC.seconds > 59) { + if (TC.hours > 23U || TC.minutes > 59U || TC.seconds > 59U) { return false; } - if (TC.frames > (long)rint(_session->timecode_frames_per_second()) - 1) { + if (TC.frames > (uint32_t) rint (_session->timecode_frames_per_second()) - 1) { return false; } if (_session->timecode_drop_frames()) { - if (TC.minutes % 10 && TC.seconds == 0 && TC.frames < 2) { + if (TC.minutes % 10 && TC.seconds == 0U && TC.frames < 2U) { return false; } } @@ -1717,6 +1867,22 @@ AudioClock::timecode_validate_edit (const string& str) return true; } +bool +AudioClock::minsec_validate_edit (const string& str) +{ + int hrs, mins, secs, millisecs; + + if (sscanf (str.c_str(), "%d:%d:%d.%d", &hrs, &mins, &secs, &millisecs) != 4) { + return false; + } + + if (hrs > 23 || mins > 59 || secs > 59 || millisecs > 999) { + return false; + } + + return true; +} + framepos_t AudioClock::frames_from_timecode_string (const string& str) const { @@ -1726,18 +1892,23 @@ AudioClock::frames_from_timecode_string (const string& str) const Timecode::Time TC; framepos_t sample; + char ignored[2]; + int hours; - if (sscanf (str.c_str(), "%d:%d:%d:%d", &TC.hours, &TC.minutes, &TC.seconds, &TC.frames) != 4) { + if (sscanf (str.c_str(), "%[- _]%d:%d:%d%[:;]%d", ignored, &hours, &TC.minutes, &TC.seconds, ignored, &TC.frames) != 6) { error << string_compose (_("programming error: %1 %2"), "badly formatted timecode clock string", str) << endmsg; return 0; } - + TC.hours = abs(hours); TC.rate = _session->timecode_frames_per_second(); TC.drop= _session->timecode_drop_frames(); _session->timecode_to_sample (TC, sample, false /* use_offset */, false /* use_subframes */ ); - + // timecode_tester (); + if (edit_is_negative) { + sample = - sample; + } return sample; } @@ -1774,7 +1945,7 @@ AudioClock::frames_from_bbt_string (framepos_t pos, const string& str) const if (sscanf (str.c_str(), BBT_SCANF_FORMAT, &any.bbt.bars, &any.bbt.beats, &any.bbt.ticks) != 3) { return 0; } - + if (is_duration) { any.bbt.bars++; any.bbt.beats++; @@ -1789,13 +1960,13 @@ framepos_t AudioClock::frame_duration_from_bbt_string (framepos_t pos, const string& str) const { if (_session == 0) { - error << "AudioClock::current_time() called with BBT mode but without session!" << endmsg; + error << "AudioClock::frame_duration_from_bbt_string() called with BBT mode but without session!" << endmsg; return 0; } Timecode::BBT_Time bbt; - if (sscanf (str.c_str(), BBT_SCANF_FORMAT, &bbt.bars, &bbt.beats, &bbt.ticks) != 0) { + if (sscanf (str.c_str(), BBT_SCANF_FORMAT, &bbt.bars, &bbt.beats, &bbt.ticks) != 3) { return 0; } @@ -1810,6 +1981,24 @@ AudioClock::frames_from_audioframes_string (const string& str) const return f; } +void +AudioClock::copy_text_to_clipboard () const +{ + string val; + if (editing) { + val = pre_edit_string; + } else { + val = _layout->get_text (); + } + const size_t trim = val.find_first_not_of(" "); + if (trim == string::npos) { + assert(0); // empty clock, can't be right. + return; + } + Glib::RefPtr cl = Gtk::Clipboard::get(); + cl->set_text (val.substr(trim)); +} + void AudioClock::build_ops_menu () { @@ -1818,18 +2007,18 @@ AudioClock::build_ops_menu () MenuList& ops_items = ops_menu->items(); ops_menu->set_name ("ArdourContextMenu"); - if (!Profile->get_sae()) { - ops_items.push_back (MenuElem (_("Timecode"), sigc::bind (sigc::mem_fun(*this, &AudioClock::set_mode), Timecode))); - } - ops_items.push_back (MenuElem (_("Bars:Beats"), sigc::bind (sigc::mem_fun(*this, &AudioClock::set_mode), BBT))); - ops_items.push_back (MenuElem (_("Minutes:Seconds"), sigc::bind (sigc::mem_fun(*this, &AudioClock::set_mode), MinSec))); - ops_items.push_back (MenuElem (_("Samples"), sigc::bind (sigc::mem_fun(*this, &AudioClock::set_mode), Frames))); + ops_items.push_back (MenuElem (_("Timecode"), sigc::bind (sigc::mem_fun(*this, &AudioClock::set_mode), Timecode, false))); + ops_items.push_back (MenuElem (_("Bars:Beats"), sigc::bind (sigc::mem_fun(*this, &AudioClock::set_mode), BBT, false))); + ops_items.push_back (MenuElem (_("Minutes:Seconds"), sigc::bind (sigc::mem_fun(*this, &AudioClock::set_mode), MinSec, false))); + ops_items.push_back (MenuElem (_("Samples"), sigc::bind (sigc::mem_fun(*this, &AudioClock::set_mode), Frames, false))); if (editable && !_off && !is_duration && !_follows_playhead) { ops_items.push_back (SeparatorElem()); - ops_items.push_back (MenuElem (_("Set From Playhead"), sigc::mem_fun(*this, &AudioClock::set_from_playhead))); + ops_items.push_back (MenuElem (_("Set from Playhead"), sigc::mem_fun(*this, &AudioClock::set_from_playhead))); ops_items.push_back (MenuElem (_("Locate to This Time"), sigc::mem_fun(*this, &AudioClock::locate))); } + ops_items.push_back (SeparatorElem()); + ops_items.push_back (MenuElem (_("Copy to clipboard"), sigc::mem_fun(*this, &AudioClock::copy_text_to_clipboard))); } void @@ -1854,7 +2043,7 @@ AudioClock::locate () } void -AudioClock::set_mode (Mode m) +AudioClock::set_mode (Mode m, bool noemit) { if (_mode == m) { return; @@ -1866,39 +2055,34 @@ AudioClock::set_mode (Mode m) _layout->set_text (""); - if (_left_layout) { - _left_layout->set_text (""); - _right_layout->set_text (""); - } + Gtk::Requisition req; + set_clock_dimensions (req); switch (_mode) { case Timecode: - mode_based_info_ratio = 0.5; + insert_map.push_back (11); insert_map.push_back (10); - insert_map.push_back (9); + insert_map.push_back (8); insert_map.push_back (7); - insert_map.push_back (6); + insert_map.push_back (5); insert_map.push_back (4); - insert_map.push_back (3); + insert_map.push_back (2); insert_map.push_back (1); - insert_map.push_back (0); break; - + case BBT: - mode_based_info_ratio = 0.5; insert_map.push_back (11); insert_map.push_back (10); insert_map.push_back (9); insert_map.push_back (8); insert_map.push_back (6); - insert_map.push_back (5); - insert_map.push_back (3); - insert_map.push_back (2); - insert_map.push_back (1); + insert_map.push_back (5); + insert_map.push_back (3); + insert_map.push_back (2); + insert_map.push_back (1); break; - + case MinSec: - mode_based_info_ratio = 1.0; insert_map.push_back (12); insert_map.push_back (11); insert_map.push_back (10); @@ -1906,29 +2090,21 @@ AudioClock::set_mode (Mode m) insert_map.push_back (7); insert_map.push_back (5); insert_map.push_back (4); - insert_map.push_back (2); - insert_map.push_back (1); + insert_map.push_back (2); + insert_map.push_back (1); break; - + case Frames: - mode_based_info_ratio = 0.5; break; } set (last_when, true); - if (!is_transient) { - ModeChanged (); /* EMIT SIGNAL (the static one)*/ - } - - if (!_fixed_width) { - /* display is different, allow us to resize */ - first_width = 0; - first_height = 0; - queue_resize (); + if (!is_transient && !noemit) { + ModeChanged (); /* EMIT SIGNAL (the static one)*/ } - mode_changed (); /* EMIT SIGNAL (the member one) */ + mode_changed (); /* EMIT SIGNAL (the member one) */ } void @@ -1941,7 +2117,12 @@ void AudioClock::on_style_changed (const Glib::RefPtr& old_style) { CairoWidget::on_style_changed (old_style); - set_font (); + + Gtk::Requisition req; + set_clock_dimensions (req); + + /* XXXX fix me ... we shouldn't be using GTK styles anyway */ + // set_font (); set_colors (); } @@ -1963,7 +2144,7 @@ AudioClock::set_is_duration (bool yn) } void -AudioClock::set_off (bool yn) +AudioClock::set_off (bool yn) { if (_off == yn) { return; @@ -1972,37 +2153,39 @@ AudioClock::set_off (bool yn) _off = yn; /* force a redraw. last_when will be preserved, but the clock text will - * change + * change */ - + set (last_when, true); } void AudioClock::focus () { - start_edit (); + start_edit (Field (0)); } void AudioClock::set_corner_radius (double r) { corner_radius = r; - queue_draw (); -} - -void -AudioClock::set_fixed_width (bool yn) -{ - _fixed_width = yn; + first_width = 0; + first_height = 0; + queue_resize (); } void AudioClock::dpi_reset () { - /* force recomputation of size even if we are fixed width + /* force recomputation of size even if we are fixed width */ first_width = 0; first_height = 0; queue_resize (); } + +void +AudioClock::set_negative_allowed (bool yn) +{ + _negative_allowed = yn; +}