X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;f=gtk2_ardour%2Fautomation_line.cc;h=92ea8cf952850b6f2a72190ba2f10bd46a038468;hb=0f1f7ca4fdaa0a186c37592ed429388a5060f624;hp=f06cbee510c636336eeb5b1166f4ddf76a78c811;hpb=042997e5ed35778c5309b6119ca1dcf8f696c3cc;p=ardour.git diff --git a/gtk2_ardour/automation_line.cc b/gtk2_ardour/automation_line.cc index f06cbee510..92ea8cf952 100644 --- a/gtk2_ardour/automation_line.cc +++ b/gtk2_ardour/automation_line.cc @@ -1,5 +1,5 @@ /* - Copyright (C) 2002-2003 Paul Davis + Copyright (C) 2002-2003 Paul Davis This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -22,17 +22,23 @@ #include #include -#include -#include -#include +#include "boost/shared_ptr.hpp" -#include -#include -#include +#include "pbd/floating.h" +#include "pbd/memento_command.h" +#include "pbd/stl_delete.h" +#include "pbd/stacktrace.h" + +#include "ardour/automation_list.h" +#include "ardour/dB.h" +#include "ardour/debug.h" + +#include "evoral/Curve.hpp" #include "simplerect.h" #include "automation_line.h" #include "control_point.h" +#include "gui_thread.h" #include "rgb_macros.h" #include "ardour_ui.h" #include "public_editor.h" @@ -40,33 +46,48 @@ #include "selection.h" #include "time_axis_view.h" #include "point_selection.h" -#include "automation_selectable.h" #include "automation_time_axis.h" -#include "public_editor.h" -#include +#include "ardour/event_type_map.h" +#include "ardour/session.h" #include "i18n.h" using namespace std; -using namespace sigc; using namespace ARDOUR; using namespace PBD; using namespace Editing; using namespace Gnome; // for Canvas -AutomationLine::AutomationLine (const string& name, TimeAxisView& tv, ArdourCanvas::Group& parent, boost::shared_ptr al) - : trackview (tv), - _name (name), - alist (al), - _parent_group (parent) +/** @param converter A TimeConverter whose origin_b is the start time of the AutomationList in session frames. + * This will not be deleted by AutomationLine. + */ +AutomationLine::AutomationLine (const string& name, TimeAxisView& tv, ArdourCanvas::Group& parent, + boost::shared_ptr al, + Evoral::TimeConverter* converter) + : trackview (tv) + , _name (name) + , alist (al) + , _time_converter (converter ? converter : new Evoral::IdentityConverter) + , _parent_group (parent) + , _offset (0) + , _maximum_time (max_framepos) { - _interpolation = al->interpolation(); - points_visible = false; + if (converter) { + _time_converter = converter; + _our_time_converter = false; + } else { + _time_converter = new Evoral::IdentityConverter; + _our_time_converter = true; + } + + _visible = Line; + update_pending = false; + have_timeout = false; _uses_gain_mapping = false; no_draw = false; - _visible = true; + _is_boolean = false; terminal_points_can_slide = true; _height = 0; @@ -78,24 +99,28 @@ AutomationLine::AutomationLine (const string& name, TimeAxisView& tv, ArdourCanv line->property_width_pixels() = (guint)1; line->set_data ("line", this); - line->signal_event().connect (mem_fun (*this, &AutomationLine::event_handler)); + line->signal_event().connect (sigc::mem_fun (*this, &AutomationLine::event_handler)); - alist->StateChanged.connect (mem_fun(*this, &AutomationLine::list_changed)); - - trackview.session().register_with_memento_command_factory(alist->id(), this); + trackview.session()->register_with_memento_command_factory(alist->id(), this); if (alist->parameter().type() == GainAutomation || alist->parameter().type() == EnvelopeAutomation) { set_uses_gain_mapping (true); } - set_interpolation(alist->interpolation()); + interpolation_changed (alist->interpolation ()); + + connect_to_list (); } AutomationLine::~AutomationLine () { vector_delete (&control_points); delete group; + + if (_our_time_converter) { + delete _time_converter; + } } bool @@ -105,52 +130,57 @@ AutomationLine::event_handler (GdkEvent* event) } void -AutomationLine::queue_reset () +AutomationLine::show () { - if (!update_pending) { - update_pending = true; - Gtkmm2ext::UI::instance()->call_slot (mem_fun(*this, &AutomationLine::reset)); + if (_visible & Line) { + /* Only show the line there are some points, otherwise we may show an out-of-date line + when automation points have been removed (the line will still follow the shape of the + old points). + */ + if (alist->interpolation() != AutomationList::Discrete && control_points.size() >= 2) { + line->show(); + } else { + line->hide (); + } + } else { + line->hide(); } -} - -void -AutomationLine::show () -{ - if (_interpolation != AutomationList::Discrete) - line->show(); - if (points_visible) { + if (_visible & ControlPoints) { for (vector::iterator i = control_points.begin(); i != control_points.end(); ++i) { + (*i)->set_visible (true); (*i)->show (); } + } else if (_visible & SelectedControlPoints) { + for (vector::iterator i = control_points.begin(); i != control_points.end(); ++i) { + (*i)->set_visible ((*i)->get_selected()); + } + } else { + for (vector::iterator i = control_points.begin(); i != control_points.end(); ++i) { + (*i)->set_visible (false); + } } - - _visible = true; } void -AutomationLine::hide () +AutomationLine::hide () { - line->hide(); - for (vector::iterator i = control_points.begin(); i != control_points.end(); ++i) { - (*i)->hide(); - } - _visible = false; + set_visibility (VisibleAspects (0)); } double AutomationLine::control_point_box_size () { - if (_interpolation == AutomationList::Discrete) { + if (alist->interpolation() == AutomationList::Discrete) { return max((_height*4.0) / (double)(alist->parameter().max() - alist->parameter().min()), 4.0); } - if (_height > TimeAxisView::hLarger) { + if (_height > TimeAxisView::preset_height (HeightLarger)) { return 8.0; - } else if (_height > (guint32) TimeAxisView::hNormal) { + } else if (_height > (guint32) TimeAxisView::preset_height (HeightNormal)) { return 6.0; - } + } return 4.0; } @@ -196,6 +226,16 @@ AutomationLine::nth (uint32_t n) } } +ControlPoint const * +AutomationLine::nth (uint32_t n) const +{ + if (n < control_points.size()) { + return control_points[n]; + } else { + return 0; + } +} + void AutomationLine::modify_point_y (ControlPoint& cp, double y) { @@ -207,12 +247,15 @@ AutomationLine::modify_point_y (ControlPoint& cp, double y) y = min (1.0, y); y = _height - (y * _height); - double const x = trackview.editor().frame_to_unit ((*cp.model())->when); + double const x = trackview.editor().frame_to_unit_unrounded (_time_converter->to((*cp.model())->when) - _offset); - trackview.editor().current_session()->begin_reversible_command (_("automation event move")); - trackview.editor().current_session()->add_command (new MementoCommand(*alist.get(), &get_state(), 0)); + trackview.editor().session()->begin_reversible_command (_("automation event move")); + trackview.editor().session()->add_command ( + new MementoCommand (memento_command_binder(), &get_state(), 0) + ); cp.move_to (x, y, ControlPoint::Full); + reset_line_coords (cp); if (line_points.size() > 1) { @@ -220,687 +263,517 @@ AutomationLine::modify_point_y (ControlPoint& cp, double y) } alist->freeze (); - sync_model_with_view_point (cp, false, 0); + sync_model_with_view_point (cp); alist->thaw (); update_pending = false; - trackview.editor().current_session()->add_command (new MementoCommand(*alist.get(), 0, &alist->get_state())); - trackview.editor().current_session()->commit_reversible_command (); - trackview.editor().current_session()->set_dirty (); + trackview.editor().session()->add_command ( + new MementoCommand (memento_command_binder(), 0, &alist->get_state()) + ); + + trackview.editor().session()->commit_reversible_command (); + trackview.editor().session()->set_dirty (); } +void +AutomationLine::reset_line_coords (ControlPoint& cp) +{ + if (cp.view_index() < line_points.size()) { + line_points[cp.view_index()].set_x (cp.get_x()); + line_points[cp.view_index()].set_y (cp.get_y()); + } +} void -AutomationLine::modify_view_point (ControlPoint& cp, double x, double y, bool with_push) +AutomationLine::sync_model_with_view_points (list cp) { - double delta = 0.0; - uint32_t last_movable = UINT_MAX; - double x_limit = DBL_MAX; + update_pending = true; - /* this just changes the current view. it does not alter - the model in any way at all. - */ + for (list::iterator i = cp.begin(); i != cp.end(); ++i) { + sync_model_with_view_point (**i); + } +} - /* clamp y-coord appropriately. y is supposed to be a normalized fraction (0.0-1.0), - and needs to be converted to a canvas unit distance. - */ +string +AutomationLine::get_verbose_cursor_string (double fraction) const +{ + std::string s = fraction_to_string (fraction); + if (_uses_gain_mapping) { + s += " dB"; + } - y = max (0.0, y); - y = min (1.0, y); - y = _height - (y * _height); + return s; +} - if (cp.can_slide()) { +string +AutomationLine::get_verbose_cursor_relative_string (double original, double fraction) const +{ + std::string s = fraction_to_string (fraction); + if (_uses_gain_mapping) { + s += " dB"; + } - /* x-coord cannot move beyond adjacent points or the start/end, and is - already in frames. it needs to be converted to canvas units. - */ - - x = trackview.editor().frame_to_unit (x); + std::string d = fraction_to_relative_string (original, fraction); - /* clamp x position using view coordinates */ + if (!d.empty()) { - ControlPoint *before; - ControlPoint *after; + s += " (\u0394"; + s += d; - if (cp.view_index()) { - before = nth (cp.view_index() - 1); - x = max (x, before->get_x()+1.0); - } else { - before = &cp; + if (_uses_gain_mapping) { + s += " dB"; } + s += ')'; + } - if (!with_push) { - if (cp.view_index() < control_points.size() - 1) { - - after = nth (cp.view_index() + 1); - - /*if it is a "spike" leave the x alone */ - - if (after->get_x() - before->get_x() < 2) { - x = cp.get_x(); - - } else { - x = min (x, after->get_x()-1.0); - } - } else { - after = &cp; - } + return s; +} - } else { +/** + * @param fraction y fraction + * @return string representation of this value, using dB if appropriate. + */ +string +AutomationLine::fraction_to_string (double fraction) const +{ + char buf[32]; - ControlPoint* after; - - /* find the first point that can't move */ - - for (uint32_t n = cp.view_index() + 1; (after = nth (n)) != 0; ++n) { - if (!after->can_slide()) { - x_limit = after->get_x() - 1.0; - last_movable = after->view_index(); - break; - } - } - - delta = x - cp.get_x(); + if (_uses_gain_mapping) { + if (fraction == 0.0) { + snprintf (buf, sizeof (buf), "-\u221e"); + } else { + snprintf (buf, sizeof (buf), "%.1f", accurate_coefficient_to_dB (slider_position_to_gain_with_max (fraction, Config->get_max_gain()))); } - } else { + view_to_model_coord_y (fraction); + if (EventTypeMap::instance().is_integer (alist->parameter())) { + snprintf (buf, sizeof (buf), "%d", (int)fraction); + } else { + snprintf (buf, sizeof (buf), "%.2f", fraction); + } + } + + return buf; +} - /* leave the x-coordinate alone */ +/** + * @param original an old y-axis fraction + * @param fraction the new y fraction + * @return string representation of the difference between original and fraction, using dB if appropriate. + */ +string +AutomationLine::fraction_to_relative_string (double original, double fraction) const +{ + char buf[32]; - x = trackview.editor().frame_to_unit ((*cp.model())->when); + if (original == fraction) { + return "0"; + } + if (_uses_gain_mapping) { + if (original == 0.0) { + /* there is no sensible representation of a relative + change from -inf dB, so return an empty string. + */ + return ""; + } else if (fraction == 0.0) { + snprintf (buf, sizeof (buf), "-\u221e"); + } else { + double old_db = accurate_coefficient_to_dB (slider_position_to_gain_with_max (original, Config->get_max_gain())); + double new_db = accurate_coefficient_to_dB (slider_position_to_gain_with_max (fraction, Config->get_max_gain())); + snprintf (buf, sizeof (buf), "%.1f", new_db - old_db); + } + } else { + view_to_model_coord_y (original); + view_to_model_coord_y (fraction); + if (EventTypeMap::instance().is_integer (alist->parameter())) { + snprintf (buf, sizeof (buf), "%d", (int)fraction - (int)original); + } else { + snprintf (buf, sizeof (buf), "%.2f", fraction - original); + } } - if (!with_push) { + return buf; +} + +/** + * @param s Value string in the form as returned by fraction_to_string. + * @return Corresponding y fraction. + */ +double +AutomationLine::string_to_fraction (string const & s) const +{ + if (s == "-\u221e") { + return 0; + } - cp.move_to (x, y, ControlPoint::Full); - reset_line_coords (cp); + double v; + sscanf (s.c_str(), "%lf", &v); + if (_uses_gain_mapping) { + v = gain_to_slider_position_with_max (dB_to_coefficient (v), Config->get_max_gain()); } else { + double dummy = 0.0; + model_to_view_coord (dummy, v); + } - uint32_t limit = min (control_points.size(), (size_t)last_movable); - - /* move the current point to wherever the user told it to go, subject - to x_limit. - */ - - cp.move_to (min (x, x_limit), y, ControlPoint::Full); - reset_line_coords (cp); - - /* now move all subsequent control points, to reflect the motion. - */ - - for (uint32_t i = cp.view_index() + 1; i < limit; ++i) { - ControlPoint *p = nth (i); - double new_x; + return v; +} - if (p->can_slide()) { - new_x = min (p->get_x() + delta, x_limit); - p->move_to (new_x, p->get_y(), ControlPoint::Full); - reset_line_coords (*p); +/** Start dragging a single point, possibly adding others if the supplied point is selected and there + * are other selected points. + * + * @param cp Point to drag. + * @param x Initial x position (units). + * @param fraction Initial y position (as a fraction of the track height, where 0 is the bottom and 1 the top) + */ +void +AutomationLine::start_drag_single (ControlPoint* cp, double x, float fraction) +{ + trackview.editor().session()->begin_reversible_command (_("automation event move")); + trackview.editor().session()->add_command ( + new MementoCommand (memento_command_binder(), &get_state(), 0) + ); + + _drag_points.clear (); + _drag_points.push_back (cp); + + if (cp->get_selected ()) { + for (vector::iterator i = control_points.begin(); i != control_points.end(); ++i) { + if (*i != cp && (*i)->get_selected()) { + _drag_points.push_back (*i); } } } -} -void -AutomationLine::reset_line_coords (ControlPoint& cp) -{ - if (cp.view_index() < line_points.size()) { - line_points[cp.view_index()].set_x (cp.get_x()); - line_points[cp.view_index()].set_y (cp.get_y()); - } + start_drag_common (x, fraction); } +/** Start dragging a line vertically (with no change in x) + * @param i1 Control point index of the `left' point on the line. + * @param i2 Control point index of the `right' point on the line. + * @param fraction Initial y position (as a fraction of the track height, where 0 is the bottom and 1 the top) + */ void -AutomationLine::sync_model_with_view_line (uint32_t start, uint32_t end) +AutomationLine::start_drag_line (uint32_t i1, uint32_t i2, float fraction) { + trackview.editor().session()->begin_reversible_command (_("automation range move")); + trackview.editor().session()->add_command ( + new MementoCommand (memento_command_binder (), &get_state(), 0) + ); - ControlPoint *p; - - update_pending = true; + _drag_points.clear (); - for (uint32_t i = start; i <= end; ++i) { - p = nth(i); - sync_model_with_view_point (*p, false, 0); + for (uint32_t i = i1; i <= i2; i++) { + _drag_points.push_back (nth (i)); } + + start_drag_common (0, fraction); } +/** Start dragging multiple points (with no change in x) + * @param cp Points to drag. + * @param fraction Initial y position (as a fraction of the track height, where 0 is the bottom and 1 the top) + */ void -AutomationLine::model_representation (ControlPoint& cp, ModelRepresentation& mr) +AutomationLine::start_drag_multiple (list cp, float fraction, XMLNode* state) { - /* part one: find out where the visual control point is. - initial results are in canvas units. ask the - line to convert them to something relevant. - */ - - mr.xval = (nframes_t) floor (cp.get_x()); - mr.yval = 1.0 - (cp.get_y() / _height); + trackview.editor().session()->begin_reversible_command (_("automation range move")); + trackview.editor().session()->add_command ( + new MementoCommand (memento_command_binder(), state, 0) + ); - /* if xval has not changed, set it directly from the model to avoid rounding errors */ + _drag_points = cp; + start_drag_common (0, fraction); +} - if (mr.xval == trackview.editor().frame_to_unit((*cp.model())->when)) { - mr.xval = (nframes_t) (*cp.model())->when; - } else { - mr.xval = trackview.editor().unit_to_frame (mr.xval); +struct ControlPointSorter +{ + bool operator() (ControlPoint const * a, ControlPoint const * b) const { + if (floateq (a->get_x(), b->get_x(), 1)) { + return a->view_index() < b->view_index(); + } + return a->get_x() < b->get_x(); } +}; - /* virtual call: this will do the right thing - for whatever particular type of line we are. - */ - - view_to_model_y (mr.yval); - - /* part 2: find out where the model point is now - */ - - mr.xpos = (nframes_t) (*cp.model())->when; - mr.ypos = (*cp.model())->value; +AutomationLine::ContiguousControlPoints::ContiguousControlPoints (AutomationLine& al) + : line (al), before_x (0), after_x (DBL_MAX) +{ +} - /* part 3: get the position of the visual control - points before and after us. - */ +void +AutomationLine::ContiguousControlPoints::compute_x_bounds () +{ + uint32_t sz = size(); - ControlPoint* before; - ControlPoint* after; + if (sz > 0 && sz < line.npoints()) { - if (cp.view_index()) { - before = nth (cp.view_index() - 1); - } else { - before = 0; - } + /* determine the limits on x-axis motion for this + contiguous range of control points + */ - after = nth (cp.view_index() + 1); + if (front()->view_index() > 0) { + before_x = line.nth (front()->view_index() - 1)->get_x(); + } - if (before) { - mr.xmin = (nframes_t) (*before->model())->when; - mr.ymin = (*before->model())->value; - mr.start = before->model(); - ++mr.start; - } else { - mr.xmin = mr.xpos; - mr.ymin = mr.ypos; - mr.start = cp.model(); - } + /* if our last point has a point after it in the line, + we have an "after" bound + */ - if (after) { - mr.end = after->model(); - } else { - mr.xmax = mr.xpos; - mr.ymax = mr.ypos; - mr.end = cp.model(); - ++mr.end; + if (back()->view_index() < (line.npoints() - 2)) { + after_x = line.nth (back()->view_index() + 1)->get_x(); + } } } -void -AutomationLine::determine_visible_control_points (ALPoints& points) -{ - uint32_t view_index, pi, n; - AutomationList::iterator model; - uint32_t npoints; - double last_control_point_x = 0.0; - double last_control_point_y = 0.0; - uint32_t this_rx = 0; - uint32_t prev_rx = 0; - uint32_t this_ry = 0; - uint32_t prev_ry = 0; - double* slope; - uint32_t box_size; - uint32_t cpsize; - - /* hide all existing points, and the line */ +double +AutomationLine::ContiguousControlPoints::clamp_dx (double dx) +{ + if (empty()) { + return dx; + } - cpsize = 0; + /* get the maximum distance we can move any of these points along the x-axis + */ - for (vector::iterator i = control_points.begin(); i != control_points.end(); ++i) { - (*i)->hide(); - ++cpsize; + double tx; /* possible position a point would move to, given dx */ + ControlPoint* cp; + + if (dx > 0) { + /* check the last point, since we're moving later in time */ + cp = back(); + } else { + /* check the first point, since we're moving earlier in time */ + cp = front(); } - line->hide (); + tx = cp->get_x() + dx; // new possible position if we just add the motion + tx = max (tx, before_x); // can't move later than following point + tx = min (tx, after_x); // can't move earlier than preceeding point + return tx - cp->get_x (); +} - if (points.empty()) { - return; +void +AutomationLine::ContiguousControlPoints::move (double dx, double dy) +{ + for (std::list::iterator i = begin(); i != end(); ++i) { + (*i)->move_to ((*i)->get_x() + dx, (*i)->get_y() - line.height() * dy, ControlPoint::Full); + line.reset_line_coords (**i); } +} - npoints = points.size(); - - /* compute derivative/slope for the entire line */ +/** Common parts of starting a drag. + * @param x Starting x position in units, or 0 if x is being ignored. + * @param fraction Starting y position (as a fraction of the track height, where 0 is the bottom and 1 the top) + */ +void +AutomationLine::start_drag_common (double x, float fraction) +{ + _drag_x = x; + _drag_distance = 0; + _last_drag_fraction = fraction; + _drag_had_movement = false; + did_push = false; - slope = new double[npoints]; + /* they are probably ordered already, but we have to make sure */ - for (n = 0; n < npoints - 1; ++n) { - double xdelta = points[n+1].x - points[n].x; - double ydelta = points[n+1].y - points[n].y; - slope[n] = ydelta/xdelta; - } + _drag_points.sort (ControlPointSorter()); +} - box_size = (uint32_t) control_point_box_size (); - /* read all points and decide which ones to show as control points */ +/** Should be called to indicate motion during a drag. + * @param x New x position of the drag in canvas units, or undefined if ignore_x == true. + * @param fraction New y fraction. + * @return x position and y fraction that were actually used (once clamped). + */ +pair +AutomationLine::drag_motion (double const x, float fraction, bool ignore_x, bool with_push, uint32_t& final_index) +{ + if (_drag_points.empty()) { + return pair (x,fraction); + } - view_index = 0; + double dx = ignore_x ? 0 : (x - _drag_x); + double dy = fraction - _last_drag_fraction; - for (model = alist->begin(), pi = 0; pi < npoints; ++model, ++pi) { + if (!_drag_had_movement) { - double tx = points[pi].x; - double ty = points[pi].y; + /* "first move" ... do some stuff that we don't want to do if + no motion ever took place, but need to do before we handle + motion. + */ + + /* partition the points we are dragging into (potentially several) + * set(s) of contiguous points. this will not happen with a normal + * drag, but if the user does a discontiguous selection, it can. + */ - if (isnan (tx) || isnan (ty)) { - warning << string_compose (_("Ignoring illegal points on AutomationLine \"%1\""), - _name) << endmsg; - continue; + uint32_t expected_view_index = 0; + CCP contig; + + for (list::iterator i = _drag_points.begin(); i != _drag_points.end(); ++i) { + if (i == _drag_points.begin() || (*i)->view_index() != expected_view_index) { + contig.reset (new ContiguousControlPoints (*this)); + contiguous_points.push_back (contig); + } + contig->push_back (*i); + expected_view_index = (*i)->view_index() + 1; } - /* now ensure that the control_points vector reflects the current curve - state, but don't plot control points too close together. also, don't - plot a series of points all with the same value. - - always plot the first and last points, of course. - */ - - if (invalid_point (points, pi)) { - /* for some reason, we are supposed to ignore this point, - but still keep track of the model index. - */ - continue; + if (contiguous_points.back()->empty()) { + contiguous_points.pop_back (); } - if (pi > 0 && pi < npoints - 1) { - if (slope[pi] == slope[pi-1]) { + for (vector::iterator ccp = contiguous_points.begin(); ccp != contiguous_points.end(); ++ccp) { + (*ccp)->compute_x_bounds (); + } + } + + /* OK, now on to the stuff related to *this* motion event. First, for + * each contiguous range, figure out the maximum x-axis motion we are + * allowed (because of neighbouring points that are not moving. + * + * if we are moving forwards with push, we don't need to do this, + * since all later points will move too. + */ - /* no reason to display this point */ - - continue; + if (dx < 0 || ((dx > 0) && !with_push)) { + for (vector::iterator ccp = contiguous_points.begin(); ccp != contiguous_points.end(); ++ccp) { + double dxt = (*ccp)->clamp_dx (dx); + if (fabs (dxt) < fabs (dx)) { + dx = dxt; } } - - /* need to round here. the ultimate coordinates are integer - pixels, so tiny deltas in the coords will be eliminated - and we end up with "colinear" line segments. since the - line rendering code in libart doesn't like this very - much, we eliminate them here. don't do this for the first and last - points. - */ - - this_rx = (uint32_t) rint (tx); - this_ry = (uint32_t) rint (ty); - - if (view_index && pi != npoints && /* not the first, not the last */ - (((this_rx == prev_rx) && (this_ry == prev_ry)) || /* same point */ - (((this_rx - prev_rx) < (box_size + 2)) && /* not identical, but still too close horizontally */ - (abs ((int)(this_ry - prev_ry)) < (int) (box_size + 2))))) { /* too close vertically */ - continue; - } + } - /* ok, we should display this point */ + /* clamp y */ + + for (list::iterator i = _drag_points.begin(); i != _drag_points.end(); ++i) { + double const y = ((_height - (*i)->get_y()) / _height) + dy; + if (y < 0) { + dy -= y; + } + if (y > 1) { + dy -= (y - 1); + } + } - if (view_index >= cpsize) { + if (dx || dy) { - /* make sure we have enough control points */ + /* and now move each section */ + + for (vector::iterator ccp = contiguous_points.begin(); ccp != contiguous_points.end(); ++ccp) { + (*ccp)->move (dx, dy); + } + if (with_push) { + final_index = contiguous_points.back()->back()->view_index () + 1; + ControlPoint* p; + uint32_t i = final_index; + while ((p = nth (i)) != 0 && p->can_slide()) { + p->move_to (p->get_x() + dx, p->get_y(), ControlPoint::Full); + reset_line_coords (*p); + ++i; + } + } - ControlPoint* ncp = new ControlPoint (*this); - - ncp->set_size (box_size); + /* update actual line coordinates (will queue a redraw) + */ - control_points.push_back (ncp); - ++cpsize; + if (line_points.size() > 1) { + line->property_points() = line_points; } - - ControlPoint::ShapeType shape; - - if (!terminal_points_can_slide) { - if (pi == 0) { - control_points[view_index]->set_can_slide(false); - if (tx == 0) { - shape = ControlPoint::Start; - } else { - shape = ControlPoint::Full; - } - } else if (pi == npoints - 1) { - control_points[view_index]->set_can_slide(false); - shape = ControlPoint::End; - } else { - control_points[view_index]->set_can_slide(true); - shape = ControlPoint::Full; - } - } else { - control_points[view_index]->set_can_slide(true); - shape = ControlPoint::Full; - } - - last_control_point_x = tx; - last_control_point_y = ty; - - control_points[view_index]->reset (tx, ty, model, view_index, shape); - - prev_rx = this_rx; - prev_ry = this_ry; - - /* finally, control visibility */ - - if (_visible && points_visible) { - control_points[view_index]->show (); - control_points[view_index]->set_visible (true); - } else { - if (!points_visible) { - control_points[view_index]->set_visible (false); - } - } - - view_index++; - } - - /* discard extra CP's to avoid confusing ourselves */ - - while (control_points.size() > view_index) { - ControlPoint* cp = control_points.back(); - control_points.pop_back (); - delete cp; - } - - if (!terminal_points_can_slide) { - control_points.back()->set_can_slide(false); - } - - delete [] slope; - - if (view_index > 1) { - - npoints = view_index; - - /* reset the line coordinates */ - - while (line_points.size() < npoints) { - line_points.push_back (Art::Point (0,0)); - } - - while (line_points.size() > npoints) { - line_points.pop_back (); - } - - for (view_index = 0; view_index < npoints; ++view_index) { - line_points[view_index].set_x (control_points[view_index]->get_x()); - line_points[view_index].set_y (control_points[view_index]->get_y()); - } - - line->property_points() = line_points; - - if (_visible && _interpolation != AutomationList::Discrete) - line->show(); - - } - - set_selected_points (trackview.editor().get_selection().points); - -} - -string -AutomationLine::get_verbose_cursor_string (double fraction) const -{ - std::string s = fraction_to_string (fraction); - if (_uses_gain_mapping) { - s += " dB"; - } - - return s; -} - -/** - * @param fraction y fraction - * @return string representation of this value, using dB if appropriate. - */ - -string -AutomationLine::fraction_to_string (double fraction) const -{ - char buf[32]; - - if (_uses_gain_mapping) { - if (fraction == 0.0) { - snprintf (buf, sizeof (buf), "-inf"); - } else { - snprintf (buf, sizeof (buf), "%.1f", coefficient_to_dB (slider_position_to_gain (fraction))); - } - } else { - view_to_model_y (fraction); - if (EventTypeMap::instance().is_integer (alist->parameter())) { - snprintf (buf, sizeof (buf), "%d", (int)fraction); - } else { - snprintf (buf, sizeof (buf), "%.2f", fraction); - } - } - - return buf; -} - - -/** - * @param s Value string in the form as returned by fraction_to_string. - * @return Corresponding y fraction. - */ - -double -AutomationLine::string_to_fraction (string const & s) const -{ - if (s == "-inf") { - return 0; - } - - double v; - sscanf (s.c_str(), "%lf", &v); - - if (_uses_gain_mapping) { - v = gain_to_slider_position (dB_to_coefficient (v)); - } else { - model_to_view_y (v); - } - - return v; -} - -bool -AutomationLine::invalid_point (ALPoints& p, uint32_t index) -{ - return p[index].x == max_frames && p[index].y == DBL_MAX; -} - -void -AutomationLine::invalidate_point (ALPoints& p, uint32_t index) -{ - p[index].x = max_frames; - p[index].y = DBL_MAX; -} - -void -AutomationLine::start_drag (ControlPoint* cp, nframes_t x, float fraction) -{ - if (trackview.editor().current_session() == 0) { /* how? */ - return; } - - string str; - - if (cp) { - str = _("automation event move"); - } else { - str = _("automation range drag"); - } - - trackview.editor().current_session()->begin_reversible_command (str); - trackview.editor().current_session()->add_command (new MementoCommand(*alist.get(), &get_state(), 0)); - drag_x = x; - drag_distance = 0; - first_drag_fraction = fraction; - last_drag_fraction = fraction; - drags = 0; - did_push = false; -} - -void -AutomationLine::point_drag (ControlPoint& cp, nframes_t x, float fraction, bool with_push) -{ - if (x > drag_x) { - drag_distance += (x - drag_x); - } else { - drag_distance -= (drag_x - x); - } - - drag_x = x; - - modify_view_point (cp, x, fraction, with_push); - - if (line_points.size() > 1) { - line->property_points() = line_points; - } - - drags++; - did_push = with_push; -} - -void -AutomationLine::line_drag (uint32_t i1, uint32_t i2, float fraction, bool with_push) -{ - double ydelta = fraction - last_drag_fraction; - + _drag_distance += dx; + _drag_x += dx; + _last_drag_fraction = fraction; + _drag_had_movement = true; did_push = with_push; - - last_drag_fraction = fraction; - - line_drag_cp1 = i1; - line_drag_cp2 = i2; - - ControlPoint *cp; - for (uint32_t i = i1 ; i <= i2; i++) { - cp = nth (i); - modify_view_point (*cp, trackview.editor().unit_to_frame (cp->get_x()), ((_height - cp->get_y()) /_height) + ydelta, with_push); - } - - if (line_points.size() > 1) { - line->property_points() = line_points; - } - - drags++; + return pair (_drag_x + dx, _last_drag_fraction + dy); } +/** Should be called to indicate the end of a drag */ void -AutomationLine::end_drag (ControlPoint* cp) +AutomationLine::end_drag (bool with_push, uint32_t final_index) { - if (!drags) { + if (!_drag_had_movement) { return; } alist->freeze (); - - if (cp) { - sync_model_with_view_point (*cp, did_push, drag_distance); - } else { - sync_model_with_view_line (line_drag_cp1, line_drag_cp2); + sync_model_with_view_points (_drag_points); + + if (with_push) { + ControlPoint* p; + uint32_t i = final_index; + while ((p = nth (i)) != 0 && p->can_slide()) { + sync_model_with_view_point (*p); + ++i; + } } - + alist->thaw (); update_pending = false; - trackview.editor().current_session()->add_command (new MementoCommand(*alist.get(), 0, &alist->get_state())); - trackview.editor().current_session()->commit_reversible_command (); - trackview.editor().current_session()->set_dirty (); -} + trackview.editor().session()->add_command ( + new MementoCommand(memento_command_binder (), 0, &alist->get_state()) + ); + trackview.editor().session()->set_dirty (); + did_push = false; + + contiguous_points.clear (); +} void -AutomationLine::sync_model_with_view_point (ControlPoint& cp, bool did_push, int64_t distance) +AutomationLine::sync_model_with_view_point (ControlPoint& cp) { - ModelRepresentation mr; - double ydelta; - - model_representation (cp, mr); - - /* how much are we changing the central point by */ - - ydelta = mr.yval - mr.ypos; - - /* - apply the full change to the central point, and interpolate - on both axes to cover all model points represented - by the control point. + /* find out where the visual control point is. + initial results are in canvas units. ask the + line to convert them to something relevant. */ - /* change all points before the primary point */ + double view_x = cp.get_x(); + double view_y = 1.0 - (cp.get_y() / _height); - for (AutomationList::iterator i = mr.start; i != cp.model(); ++i) { - - double fract = ((*i)->when - mr.xmin) / (mr.xpos - mr.xmin); - double y_delta = ydelta * fract; - double x_delta = distance * fract; + /* if xval has not changed, set it directly from the model to avoid rounding errors */ - /* interpolate */ - - if (y_delta || x_delta) { - alist->modify (i, (*i)->when + x_delta, mr.ymin + y_delta); - } + if (view_x == trackview.editor().frame_to_unit_unrounded (_time_converter->to ((*cp.model())->when)) - _offset) { + view_x = (*cp.model())->when - _offset; + } else { + view_x = trackview.editor().unit_to_frame (view_x); + view_x = _time_converter->from (view_x + _offset); } - /* change the primary point */ - update_pending = true; - alist->modify (cp.model(), mr.xval, mr.yval); + view_to_model_coord_y (view_y); - /* change later points */ - - AutomationList::iterator i = cp.model(); - - ++i; - - while (i != mr.end) { - - double delta = ydelta * (mr.xmax - (*i)->when) / (mr.xmax - mr.xpos); - - /* all later points move by the same distance along the x-axis as the main point */ - - if (delta) { - alist->modify (i, (*i)->when + distance, (*i)->value + delta); - } - - ++i; - } - - if (did_push) { - - /* move all points after the range represented by the view by the same distance - as the main point moved. - */ - - alist->slide (mr.end, drag_distance); - } - + alist->modify (cp.model(), view_x, view_y); } -bool +bool AutomationLine::control_points_adjacent (double xval, uint32_t & before, uint32_t& after) { ControlPoint *bcp = 0; ControlPoint *acp = 0; double unit_xval; - /* xval is in frames */ - - unit_xval = trackview.editor().frame_to_unit (xval); + unit_xval = trackview.editor().frame_to_unit_unrounded (xval); for (vector::iterator i = control_points.begin(); i != control_points.end(); ++i) { - + if ((*i)->get_x() <= unit_xval) { if (!bcp || (*i)->get_x() > bcp->get_x()) { bcp = *i; before = bcp->view_index(); - } + } } else if ((*i)->get_x() > unit_xval) { acp = *i; @@ -915,32 +788,31 @@ AutomationLine::control_points_adjacent (double xval, uint32_t & before, uint32_ bool AutomationLine::is_last_point (ControlPoint& cp) { - ModelRepresentation mr; + // If the list is not empty, and the point is the last point in the list - model_representation (cp, mr); + if (alist->empty()) { + return false; + } - // If the list is not empty, and the point is the last point in the list + AutomationList::const_iterator i = alist->end(); + --i; - if (!alist->empty() && mr.end == alist->end()) { + if (cp.model() == i) { return true; } - + return false; } bool AutomationLine::is_first_point (ControlPoint& cp) { - ModelRepresentation mr; - - model_representation (cp, mr); - // If the list is not empty, and the point is the first point in the list - if (!alist->empty() && mr.start == alist->begin()) { + if (!alist->empty() && cp.model() == alist->begin()) { return true; } - + return false; } @@ -948,180 +820,95 @@ AutomationLine::is_first_point (ControlPoint& cp) void AutomationLine::remove_point (ControlPoint& cp) { - ModelRepresentation mr; - - model_representation (cp, mr); - - trackview.editor().current_session()->begin_reversible_command (_("remove control point")); + trackview.editor().session()->begin_reversible_command (_("remove control point")); XMLNode &before = alist->get_state(); - alist->erase (mr.start, mr.end); + alist->erase (cp.model()); + + trackview.editor().session()->add_command( + new MementoCommand (memento_command_binder (), &before, &alist->get_state()) + ); - trackview.editor().current_session()->add_command(new MementoCommand( - *alist.get(), &before, &alist->get_state())); - trackview.editor().current_session()->commit_reversible_command (); - trackview.editor().current_session()->set_dirty (); + trackview.editor().session()->commit_reversible_command (); + trackview.editor().session()->set_dirty (); } +/** Get selectable points within an area. + * @param start Start position in session frames. + * @param end End position in session frames. + * @param bot Bottom y range, as a fraction of line height, where 0 is the bottom of the line. + * @param top Top y range, as a fraction of line height, where 0 is the bottom of the line. + * @param result Filled in with selectable things; in this case, ControlPoints. + */ void -AutomationLine::get_selectables (nframes_t& start, nframes_t& end, - double botfrac, double topfrac, list& results) +AutomationLine::get_selectables (framepos_t start, framepos_t end, double botfrac, double topfrac, list& results) { - - double top; - double bot; - nframes_t nstart; - nframes_t nend; - bool collecting = false; - - /* Curse X11 and its inverted coordinate system! */ - - bot = (1.0 - topfrac) * _height; - top = (1.0 - botfrac) * _height; - - nstart = max_frames; - nend = 0; + /* convert fractions to display coordinates with 0 at the top of the track */ + double const bot_track = (1 - topfrac) * trackview.current_height (); + double const top_track = (1 - botfrac) * trackview.current_height (); for (vector::iterator i = control_points.begin(); i != control_points.end(); ++i) { - - nframes_t when = (nframes_t) (*(*i)->model())->when; - - if (when >= start && when <= end) { - - if ((*i)->get_y() >= bot && (*i)->get_y() <= top) { + double const model_when = (*(*i)->model())->when; - (*i)->show(); - (*i)->set_visible(true); - collecting = true; - nstart = min (nstart, when); - nend = max (nend, when); + /* model_when is relative to the start of the source, so we just need to add on the origin_b here + (as it is the session frame position of the start of the source) + */ + + framepos_t const session_frames_when = _time_converter->to (model_when) + _time_converter->origin_b (); - } else { - - if (collecting) { - - results.push_back (new AutomationSelectable (nstart, nend, botfrac, topfrac, trackview)); - collecting = false; - nstart = max_frames; - nend = 0; - } - } + if (session_frames_when >= start && session_frames_when <= end && (*i)->get_y() >= bot_track && (*i)->get_y() <= top_track) { + results.push_back (*i); } } - - if (collecting) { - results.push_back (new AutomationSelectable (nstart, nend, botfrac, topfrac, trackview)); - } - } void -AutomationLine::get_inverted_selectables (Selection&, list& results) +AutomationLine::get_inverted_selectables (Selection&, list& /*results*/) { // hmmm .... } void -AutomationLine::set_selected_points (PointSelection& points) +AutomationLine::set_selected_points (PointSelection const & points) { - double top; - double bot; - for (vector::iterator i = control_points.begin(); i != control_points.end(); ++i) { - (*i)->set_selected(false); - } - - if (points.empty()) { - goto out; - } - - for (PointSelection::iterator r = points.begin(); r != points.end(); ++r) { - - if (&(*r).track != &trackview) { - continue; - } - - /* Curse X11 and its inverted coordinate system! */ - - bot = (1.0 - (*r).high_fract) * _height; - top = (1.0 - (*r).low_fract) * _height; - - for (vector::iterator i = control_points.begin(); i != control_points.end(); ++i) { - - double rstart, rend; - - rstart = trackview.editor().frame_to_unit ((*r).start); - rend = trackview.editor().frame_to_unit ((*r).end); - - if ((*i)->get_x() >= rstart && (*i)->get_x() <= rend) { - - if ((*i)->get_y() >= bot && (*i)->get_y() <= top) { - - (*i)->set_selected(true); - } - } - - } + (*i)->set_selected (false); } - out: - for (vector::iterator i = control_points.begin(); i != control_points.end(); ++i) { - (*i)->show_color (false, !points_visible); + for (PointSelection::const_iterator i = points.begin(); i != points.end(); ++i) { + (*i)->set_selected (true); } + set_colors (); } -void AutomationLine::set_colors() { - set_line_color( ARDOUR_UI::config()->canvasvar_AutomationLine.get() ); - for (vector::iterator i = control_points.begin(); i != control_points.end(); ++i) { - (*i)->show_color (false, !points_visible); - } -} - -void -AutomationLine::show_selection () +void AutomationLine::set_colors () { - TimeSelection& time (trackview.editor().get_selection().time); - + set_line_color (ARDOUR_UI::config()->canvasvar_AutomationLine.get()); for (vector::iterator i = control_points.begin(); i != control_points.end(); ++i) { - - (*i)->set_selected(false); - - for (list::iterator r = time.begin(); r != time.end(); ++r) { - double rstart, rend; - - rstart = trackview.editor().frame_to_unit ((*r).start); - rend = trackview.editor().frame_to_unit ((*r).end); - - if ((*i)->get_x() >= rstart && (*i)->get_x() <= rend) { - (*i)->set_selected(true); - break; - } - } - - (*i)->show_color (false, !points_visible); + (*i)->set_color (); } } -void -AutomationLine::hide_selection () -{ -// show_selection (); -} - void AutomationLine::list_changed () { - queue_reset (); + DEBUG_TRACE (DEBUG::Automation, string_compose ("\tline changed, existing update pending? %1\n", update_pending)); + + if (!update_pending) { + update_pending = true; + Gtkmm2ext::UI::instance()->call_slot (invalidator (*this), boost::bind (&AutomationLine::queue_reset, this)); + } } void AutomationLine::reset_callback (const Evoral::ControlList& events) { - ALPoints tmp_points; - uint32_t npoints = events.size(); + uint32_t vp = 0; + uint32_t pi = 0; + uint32_t np; - if (npoints == 0) { + if (events.empty()) { for (vector::iterator i = control_points.begin(); i != control_points.end(); ++i) { delete *i; } @@ -1130,31 +917,97 @@ AutomationLine::reset_callback (const Evoral::ControlList& events) return; } - AutomationList::const_iterator ai; + /* hide all existing points, and the line */ + + for (vector::iterator i = control_points.begin(); i != control_points.end(); ++i) { + (*i)->hide(); + } + + line->hide (); + np = events.size(); + + Evoral::ControlList& e = const_cast (events); + + for (AutomationList::iterator ai = e.begin(); ai != e.end(); ++ai, ++pi) { + + double tx = (*ai)->when; + double ty = (*ai)->value; + + /* convert from model coordinates to canonical view coordinates */ - for (ai = events.begin(); ai != events.end(); ++ai) { + model_to_view_coord (tx, ty); + + if (std::isnan (tx) || std::isnan (ty)) { + warning << string_compose (_("Ignoring illegal points on AutomationLine \"%1\""), + _name) << endmsg; + continue; + } - double translated_y = (*ai)->value; - model_to_view_y (translated_y); + if (tx >= max_framepos || tx < 0 || tx >= _maximum_time) { + continue; + } + + /* convert x-coordinate to a canvas unit coordinate (this takes + * zoom and scroll into account). + */ + + tx = trackview.editor().frame_to_unit_unrounded (tx); + + /* convert from canonical view height (0..1.0) to actual + * height coordinates (using X11's top-left rooted system) + */ + + ty = _height - (ty * _height); - add_model_point (tmp_points, (*ai)->when, translated_y); + add_visible_control_point (vp, pi, tx, ty, ai, np); + vp++; } - - determine_visible_control_points (tmp_points); -} + /* discard extra CP's to avoid confusing ourselves */ -void -AutomationLine::add_model_point (ALPoints& tmp_points, double frame, double yfract) -{ - tmp_points.push_back (ALPoint (trackview.editor().frame_to_unit (frame), - _height - (yfract * _height))); + while (control_points.size() > vp) { + ControlPoint* cp = control_points.back(); + control_points.pop_back (); + delete cp; + } + + if (!terminal_points_can_slide) { + control_points.back()->set_can_slide(false); + } + + if (vp > 1) { + + /* reset the line coordinates given to the CanvasLine */ + + while (line_points.size() < vp) { + line_points.push_back (Art::Point (0,0)); + } + + while (line_points.size() > vp) { + line_points.pop_back (); + } + + for (uint32_t n = 0; n < vp; ++n) { + line_points[n].set_x (control_points[n]->get_x()); + line_points[n].set_y (control_points[n]->get_y()); + } + + line->property_points() = line_points; + + if (_visible && alist->interpolation() != AutomationList::Discrete) { + line->show(); + } + } + + set_selected_points (trackview.editor().get_selection().points); } void AutomationLine::reset () { + DEBUG_TRACE (DEBUG::Automation, "\t\tLINE RESET\n"); update_pending = false; + have_timeout = false; if (no_draw) { return; @@ -1163,68 +1016,86 @@ AutomationLine::reset () alist->apply_to_points (*this, &AutomationLine::reset_callback); } +void +AutomationLine::queue_reset () +{ + /* this must be called from the GUI thread + */ + + if (trackview.editor().session()->transport_rolling() && alist->automation_write()) { + /* automation write pass ... defer to a timeout */ + /* redraw in 1/4 second */ + if (!have_timeout) { + DEBUG_TRACE (DEBUG::Automation, "\tqueue timeout\n"); + Glib::signal_timeout().connect (sigc::bind_return (sigc::mem_fun (*this, &AutomationLine::reset), false), 250); + have_timeout = true; + } else { + DEBUG_TRACE (DEBUG::Automation, "\ttimeout already queued, change ignored\n"); + } + } else { + reset (); + } +} + void AutomationLine::clear () { - /* parent must create command */ - XMLNode &before = get_state(); + /* parent must create and commit command */ + XMLNode &before = alist->get_state(); alist->clear(); - trackview.editor().current_session()->add_command (new MementoCommand(*this, &before, &get_state())); - trackview.editor().current_session()->commit_reversible_command (); - trackview.editor().current_session()->set_dirty (); + + trackview.editor().session()->add_command ( + new MementoCommand (memento_command_binder (), &before, &alist->get_state()) + ); } void -AutomationLine::change_model (AutomationList::iterator i, double x, double y) +AutomationLine::change_model (AutomationList::iterator /*i*/, double /*x*/, double /*y*/) { } void -AutomationLine::set_list(boost::shared_ptr list) +AutomationLine::set_list (boost::shared_ptr list) { alist = list; - queue_reset(); + queue_reset (); + connect_to_list (); } void -AutomationLine::show_all_control_points () +AutomationLine::add_visibility (VisibleAspects va) { - points_visible = true; - - for (vector::iterator i = control_points.begin(); i != control_points.end(); ++i) { - (*i)->show_color((_interpolation != AutomationList::Discrete), false); - (*i)->show (); - (*i)->set_visible (true); - } + _visible = VisibleAspects (_visible | va); + show (); } void -AutomationLine::hide_all_but_selected_control_points () +AutomationLine::set_visibility (VisibleAspects va) { - if (alist->interpolation() == AutomationList::Discrete) - return; - - points_visible = false; + _visible = va; + show (); +} - for (vector::iterator i = control_points.begin(); i != control_points.end(); ++i) { - if (!(*i)->selected()) { - (*i)->set_visible (false); - } - } +void +AutomationLine::remove_visibility (VisibleAspects va) +{ + _visible = VisibleAspects (_visible & ~va); + show (); } - + void AutomationLine::track_entered() { - if (alist->interpolation() != AutomationList::Discrete) - show_all_control_points(); + if (alist->interpolation() != AutomationList::Discrete) { + add_visibility (ControlPoints); + } } void AutomationLine::track_exited() { if (alist->interpolation() != AutomationList::Discrete) { - hide_all_but_selected_control_points(); + remove_visibility (ControlPoints); } } @@ -1235,40 +1106,50 @@ AutomationLine::get_state (void) return alist->get_state(); } -int -AutomationLine::set_state (const XMLNode &node) +int +AutomationLine::set_state (const XMLNode &node, int version) { /* function as a proxy for the model */ - return alist->set_state (node); + return alist->set_state (node, version); } void -AutomationLine::view_to_model_y (double& y) const +AutomationLine::view_to_model_coord (double& x, double& y) const +{ + x = _time_converter->from (x); + view_to_model_coord_y (y); +} + +void +AutomationLine::view_to_model_coord_y (double& y) const { /* TODO: This should be more generic ... */ if (alist->parameter().type() == GainAutomation || alist->parameter().type() == EnvelopeAutomation) { - y = slider_position_to_gain (y); + y = slider_position_to_gain_with_max (y, Config->get_max_gain()); y = max (0.0, y); y = min (2.0, y); - } else if (alist->parameter().type() == PanAutomation) { - // vertical coordinate axis reversal + } else if (alist->parameter().type() == PanAzimuthAutomation || + alist->parameter().type() == PanElevationAutomation || + alist->parameter().type() == PanWidthAutomation) { y = 1.0 - y; } else if (alist->parameter().type() == PluginAutomation) { y = y * (double)(alist->get_max_y()- alist->get_min_y()) + alist->get_min_y(); } else { - y = (int)(y * alist->parameter().max()); + y = rint (y * alist->parameter().max()); } } void -AutomationLine::model_to_view_y (double& y) const +AutomationLine::model_to_view_coord (double& x, double& y) const { /* TODO: This should be more generic ... */ if (alist->parameter().type() == GainAutomation || alist->parameter().type() == EnvelopeAutomation) { - y = gain_to_slider_position (y); - } else if (alist->parameter().type() == PanAutomation) { + y = gain_to_slider_position_with_max (y, Config->get_max_gain()); + } else if (alist->parameter().type() == PanAzimuthAutomation || + alist->parameter().type() == PanElevationAutomation || + alist->parameter().type() == PanWidthAutomation) { // vertical coordinate axis reversal y = 1.0 - y; } else if (alist->parameter().type() == PluginAutomation) { @@ -1276,20 +1157,130 @@ AutomationLine::model_to_view_y (double& y) const } else { y = y / (double)alist->parameter().max(); /* ... like this */ } + + x = _time_converter->to (x) - _offset; } - +/** Called when our list has announced that its interpolation style has changed */ void -AutomationLine::set_interpolation(AutomationList::InterpolationStyle style) +AutomationLine::interpolation_changed (AutomationList::InterpolationStyle style) { - _interpolation = style; - if (style == AutomationList::Discrete) { - show_all_control_points(); + set_visibility (ControlPoints); line->hide(); } else { - hide_all_but_selected_control_points(); - line->show(); + set_visibility (Line); + } +} + +void +AutomationLine::add_visible_control_point (uint32_t view_index, uint32_t pi, double tx, double ty, + AutomationList::iterator model, uint32_t npoints) +{ + ControlPoint::ShapeType shape; + + if (view_index >= control_points.size()) { + + /* make sure we have enough control points */ + + ControlPoint* ncp = new ControlPoint (*this); + ncp->set_size (control_point_box_size ()); + + control_points.push_back (ncp); + } + + if (!terminal_points_can_slide) { + if (pi == 0) { + control_points[view_index]->set_can_slide (false); + if (tx == 0) { + shape = ControlPoint::Start; + } else { + shape = ControlPoint::Full; + } + } else if (pi == npoints - 1) { + control_points[view_index]->set_can_slide (false); + shape = ControlPoint::End; + } else { + control_points[view_index]->set_can_slide (true); + shape = ControlPoint::Full; + } + } else { + control_points[view_index]->set_can_slide (true); + shape = ControlPoint::Full; + } + + control_points[view_index]->reset (tx, ty, model, view_index, shape); + + /* finally, control visibility */ + + if (_visible & ControlPoints) { + control_points[view_index]->show (); + control_points[view_index]->set_visible (true); + } else { + control_points[view_index]->set_visible (false); } } +void +AutomationLine::connect_to_list () +{ + _list_connections.drop_connections (); + + alist->StateChanged.connect (_list_connections, invalidator (*this), boost::bind (&AutomationLine::list_changed, this), gui_context()); + + alist->InterpolationChanged.connect ( + _list_connections, invalidator (*this), boost::bind (&AutomationLine::interpolation_changed, this, _1), gui_context() + ); +} + +MementoCommandBinder* +AutomationLine::memento_command_binder () +{ + return new SimpleMementoCommandBinder (*alist.get()); +} + +/** Set the maximum time that points on this line can be at, relative + * to the start of the track or region that it is on. + */ +void +AutomationLine::set_maximum_time (framecnt_t t) +{ + if (_maximum_time == t) { + return; + } + + _maximum_time = t; + reset (); +} + + +/** @return min and max x positions of points that are in the list, in session frames */ +pair +AutomationLine::get_point_x_range () const +{ + pair r (max_framepos, 0); + + for (AutomationList::const_iterator i = the_list()->begin(); i != the_list()->end(); ++i) { + r.first = min (r.first, session_position (i)); + r.second = max (r.second, session_position (i)); + } + + return r; +} + +framepos_t +AutomationLine::session_position (AutomationList::const_iterator p) const +{ + return _time_converter->to ((*p)->when) + _offset + _time_converter->origin_b (); +} + +void +AutomationLine::set_offset (framepos_t off) +{ + if (_offset == off) { + return; + } + + _offset = off; + reset (); +}