/*
- 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
#include <vector>
#include <fstream>
-#include <pbd/stl_delete.h>
-#include <pbd/memento_command.h>
-#include <pbd/stacktrace.h>
+#include "boost/shared_ptr.hpp"
-#include <ardour/automation_event.h>
-#include <ardour/curve.h>
-#include <ardour/dB.h>
+#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"
#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 <ardour/session.h>
+#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<AutomationList> 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<AutomationList> al,
+ Evoral::TimeConverter<double, framepos_t>* converter)
+ : trackview (tv)
+ , _name (name)
+ , alist (al)
+ , _time_converter (converter ? converter : new Evoral::IdentityConverter<double, framepos_t>)
+ , _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<double, framepos_t>;
+ _our_time_converter = true;
+ }
+
+ _visible = Line;
+
update_pending = false;
- _vc_uses_gain_mapping = false;
+ have_timeout = false;
+ _uses_gain_mapping = false;
no_draw = false;
- _visible = true;
+ _is_boolean = false;
terminal_points_can_slide = true;
- _y_position = 0;
_height = 0;
group = new ArdourCanvas::Group (parent);
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);
+ }
- if (alist->parameter().type() == GainAutomation)
- set_verbose_cursor_uses_gain_mapping (true);
+ interpolation_changed (alist->interpolation ());
- set_interpolation(alist->interpolation());
+ connect_to_list ();
}
AutomationLine::~AutomationLine ()
{
vector_delete (&control_points);
delete group;
+
+ if (_our_time_converter) {
+ delete _time_converter;
+ }
}
bool
}
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<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
+ (*i)->set_visible (true);
(*i)->show ();
}
+ } else if (_visible & SelectedControlPoints) {
+ for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
+ (*i)->set_visible ((*i)->get_selected());
+ }
+ } else {
+ for (vector<ControlPoint*>::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<ControlPoint*>::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;
}
void
-AutomationLine::set_y_position_and_height (guint32 y, guint32 h)
+AutomationLine::set_height (guint32 h)
{
- bool changed = false;
-
- if (y != _y_position) {
- _y_position = y;
- changed = true;
- }
-
if (h != _height) {
_height = h;
- double const bsz = control_point_box_size();
+ double bsz = control_point_box_size();
for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
(*i)->set_size (bsz);
}
- changed = true;
- }
- if (changed) {
reset ();
}
}
}
void
-AutomationLine::set_verbose_cursor_uses_gain_mapping (bool yn)
+AutomationLine::set_uses_gain_mapping (bool yn)
{
- if (yn != _vc_uses_gain_mapping) {
- _vc_uses_gain_mapping = yn;
+ if (yn != _uses_gain_mapping) {
+ _uses_gain_mapping = yn;
reset ();
}
}
}
}
-void
-AutomationLine::modify_view_point (ControlPoint& cp, double x, double y, bool with_push)
+ControlPoint const *
+AutomationLine::nth (uint32_t n) const
{
- double delta = 0.0;
- uint32_t last_movable = UINT_MAX;
- double x_limit = DBL_MAX;
-
- /* this just changes the current view. it does not alter
- the model in any way at all.
- */
+ if (n < control_points.size()) {
+ return control_points[n];
+ } else {
+ return 0;
+ }
+}
+void
+AutomationLine::modify_point_y (ControlPoint& cp, double y)
+{
/* 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.
*/
y = max (0.0, y);
y = min (1.0, y);
- y = _y_position + _height - (y * _height);
-
- if (cp.can_slide()) {
-
- /* 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);
-
- /* clamp x position using view coordinates */
-
- ControlPoint *before;
- ControlPoint *after;
-
- if (cp.view_index()) {
- before = nth (cp.view_index() - 1);
- x = max (x, before->get_x()+1.0);
- } else {
- before = &cp;
- }
-
-
- 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;
- }
+ y = _height - (y * _height);
- } else {
+ double const x = trackview.editor().frame_to_unit_unrounded (_time_converter->to((*cp.model())->when) - _offset);
- 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();
- }
-
- } else {
+ trackview.editor().session()->begin_reversible_command (_("automation event move"));
+ trackview.editor().session()->add_command (
+ new MementoCommand<AutomationList> (memento_command_binder(), &get_state(), 0)
+ );
- /* leave the x-coordinate alone */
+ cp.move_to (x, y, ControlPoint::Full);
- x = trackview.editor.frame_to_unit ((*cp.model())->when);
+ reset_line_coords (cp);
+ if (line_points.size() > 1) {
+ line->property_points() = line_points;
}
- if (!with_push) {
-
- cp.move_to (x, y, ControlPoint::Full);
- reset_line_coords (cp);
+ alist->freeze ();
+ sync_model_with_view_point (cp);
+ alist->thaw ();
- } else {
+ update_pending = false;
- 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;
+ trackview.editor().session()->add_command (
+ new MementoCommand<AutomationList> (memento_command_binder(), 0, &alist->get_state())
+ );
- 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);
- }
- }
- }
+ 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::sync_model_with_view_line (uint32_t start, uint32_t end)
+AutomationLine::sync_model_with_view_points (list<ControlPoint*> cp)
{
-
- ControlPoint *p;
-
update_pending = true;
- for (uint32_t i = start; i <= end; ++i) {
- p = nth(i);
- sync_model_with_view_point (*p, false, 0);
+ for (list<ControlPoint*>::iterator i = cp.begin(); i != cp.end(); ++i) {
+ sync_model_with_view_point (**i);
}
}
-void
-AutomationLine::model_representation (ControlPoint& cp, ModelRepresentation& mr)
+string
+AutomationLine::get_verbose_cursor_string (double fraction) const
{
- /* 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() - _y_position) / _height);
+ std::string s = fraction_to_string (fraction);
+ if (_uses_gain_mapping) {
+ s += " dB";
+ }
- /* if xval has not changed, set it directly from the model to avoid rounding errors */
+ return s;
+}
- 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);
+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";
}
- /* virtual call: this will do the right thing
- for whatever particular type of line we are.
- */
-
- view_to_model_y (mr.yval);
+ std::string d = fraction_to_relative_string (original, fraction);
- /* part 2: find out where the model point is now
- */
+ if (!d.empty()) {
- mr.xpos = (nframes_t) (*cp.model())->when;
- mr.ypos = (*cp.model())->value;
+ s += " (\u0394";
+ s += d;
- /* part 3: get the position of the visual control
- points before and after us.
- */
+ if (_uses_gain_mapping) {
+ s += " dB";
+ }
+
+ s += ')';
+ }
+
+ return s;
+}
- ControlPoint* before;
- ControlPoint* after;
+/**
+ * @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 (cp.view_index()) {
- before = nth (cp.view_index() - 1);
+ if (_uses_gain_mapping) {
+ if (fraction == 0.0) {
+ snprintf (buf, sizeof (buf), "-inf");
+ } else {
+ snprintf (buf, sizeof (buf), "%.1f", accurate_coefficient_to_dB (slider_position_to_gain_with_max (fraction, Config->get_max_gain())));
+ }
} else {
- before = 0;
+ 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);
+ }
}
- after = nth (cp.view_index() + 1);
+ return buf;
+}
- 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();
+/**
+ * @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];
+
+ if (original == fraction) {
+ return "0";
}
- if (after) {
- mr.end = after->model();
+ 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), "-inf");
+ } 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 {
- mr.xmax = mr.xpos;
- mr.ymax = mr.ypos;
- mr.end = cp.model();
- ++mr.end;
+ 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);
+ }
}
+
+ return buf;
}
-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;
+/**
+ * @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;
+ }
- /* hide all existing points, and the line */
+ double v;
+ sscanf (s.c_str(), "%lf", &v);
- cpsize = 0;
-
- for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
- (*i)->hide();
- ++cpsize;
+ 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);
}
- line->hide ();
+ return v;
+}
- if (points.empty()) {
- return;
+/** 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<AutomationList> (memento_command_binder(), &get_state(), 0)
+ );
+
+ _drag_points.clear ();
+ _drag_points.push_back (cp);
+
+ if (cp->get_selected ()) {
+ for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
+ if (*i != cp && (*i)->get_selected()) {
+ _drag_points.push_back (*i);
+ }
+ }
}
- npoints = points.size();
+ start_drag_common (x, fraction);
+}
- /* compute derivative/slope for the entire line */
+/** 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::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<AutomationList> (memento_command_binder (), &get_state(), 0)
+ );
- slope = new double[npoints];
+ _drag_points.clear ();
- 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;
+ for (uint32_t i = i1; i <= i2; i++) {
+ _drag_points.push_back (nth (i));
}
- box_size = (uint32_t) control_point_box_size ();
+ 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::start_drag_multiple (list<ControlPoint*> cp, float fraction, XMLNode* state)
+{
+ trackview.editor().session()->begin_reversible_command (_("automation range move"));
+ trackview.editor().session()->add_command (
+ new MementoCommand<AutomationList> (memento_command_binder(), state, 0)
+ );
- /* read all points and decide which ones to show as control points */
+ _drag_points = cp;
+ start_drag_common (0, fraction);
+}
- view_index = 0;
+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();
+ }
+};
- for (model = alist->begin(), pi = 0; pi < npoints; ++model, ++pi) {
+AutomationLine::ContiguousControlPoints::ContiguousControlPoints (AutomationLine& al)
+ : line (al), before_x (0), after_x (DBL_MAX)
+{
+}
- double tx = points[pi].x;
- double ty = points[pi].y;
-
- if (isnan (tx) || isnan (ty)) {
- warning << string_compose (_("Ignoring illegal points on AutomationLine \"%1\""),
- _name) << endmsg;
- continue;
- }
+void
+AutomationLine::ContiguousControlPoints::compute_x_bounds ()
+{
+ uint32_t sz = size();
- /* 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.
+ if (sz > 0 && sz < line.npoints()) {
- always plot the first and last points, of course.
+ /* determine the limits on x-axis motion for this
+ contiguous range of control points
*/
- 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 (front()->view_index() > 0) {
+ before_x = line.nth (front()->view_index() - 1)->get_x();
}
- if (pi > 0 && pi < npoints - 1) {
- if (slope[pi] == slope[pi-1]) {
+ /* if our last point has a point after it in the line,
+ we have an "after" bound
+ */
- /* no reason to display this point */
-
- continue;
- }
+ if (back()->view_index() < (line.npoints() - 2)) {
+ after_x = line.nth (back()->view_index() + 1)->get_x();
}
-
- /* 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;
- }
+double
+AutomationLine::ContiguousControlPoints::clamp_dx (double dx)
+{
+ if (empty()) {
+ return dx;
+ }
+
+ /* get the maximum distance we can move any of these points along the x-axis
+ */
+
+ 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();
+ }
- /* ok, we should display this point */
+ 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 (view_index >= cpsize) {
+void
+AutomationLine::ContiguousControlPoints::move (double dx, double dy)
+{
+ for (std::list<ControlPoint*>::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);
+ }
+}
- /* make sure we have enough control points */
+/** 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;
- ControlPoint* ncp = new ControlPoint (*this);
-
- ncp->set_size (box_size);
+ /* they are probably ordered already, but we have to make sure */
- control_points.push_back (ncp);
- ++cpsize;
- }
+ _drag_points.sort (ControlPointSorter());
+}
- 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;
+/** 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<double, float>
+AutomationLine::drag_motion (double const x, float fraction, bool ignore_x, bool with_push, uint32_t& final_index)
+{
+ if (_drag_points.empty()) {
+ return pair<double,float> (x,fraction);
+ }
- control_points[view_index]->reset (tx, ty, model, view_index, shape);
+ double dx = ignore_x ? 0 : (x - _drag_x);
+ double dy = fraction - _last_drag_fraction;
- prev_rx = this_rx;
- prev_ry = this_ry;
+ if (!_drag_had_movement) {
- /* finally, control visibility */
+ /* "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 (_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);
+ uint32_t expected_view_index = 0;
+ CCP contig;
+
+ for (list<ControlPoint*>::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;
}
- view_index++;
- }
-
- /* discard extra CP's to avoid confusing ourselves */
+ if (contiguous_points.back()->empty()) {
+ contiguous_points.pop_back ();
+ }
- while (control_points.size() > view_index) {
- ControlPoint* cp = control_points.back();
- control_points.pop_back ();
- delete cp;
- }
+ for (vector<CCP>::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.
+ */
- if (!terminal_points_can_slide) {
- control_points.back()->set_can_slide(false);
+ if (dx < 0 || ((dx > 0) && !with_push)) {
+ for (vector<CCP>::iterator ccp = contiguous_points.begin(); ccp != contiguous_points.end(); ++ccp) {
+ double dxt = (*ccp)->clamp_dx (dx);
+ if (fabs (dxt) < fabs (dx)) {
+ dx = dxt;
+ }
+ }
}
- delete [] slope;
+ /* clamp y */
+
+ for (list<ControlPoint*>::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 > 1) {
+ if (dx || dy) {
- npoints = view_index;
+ /* and now move each section */
- /* reset the line coordinates */
-
- while (line_points.size() < npoints) {
- line_points.push_back (Art::Point (0,0));
+ for (vector<CCP>::iterator ccp = contiguous_points.begin(); ccp != contiguous_points.end(); ++ccp) {
+ (*ccp)->move (dx, dy);
}
-
- 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());
+ 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;
+ }
}
-
- line->property_points() = line_points;
- if (_visible && _interpolation != AutomationList::Discrete)
- line->show();
-
- }
+ /* update actual line coordinates (will queue a redraw)
+ */
- set_selected_points (trackview.editor.get_selection().points);
+ if (line_points.size() > 1) {
+ line->property_points() = line_points;
+ }
+ }
+
+ _drag_distance += dx;
+ _drag_x += dx;
+ _last_drag_fraction = fraction;
+ _drag_had_movement = true;
+ did_push = with_push;
+ return pair<double, float> (_drag_x + dx, _last_drag_fraction + dy);
}
-string
-AutomationLine::get_verbose_cursor_string (double fraction)
+/** Should be called to indicate the end of a drag */
+void
+AutomationLine::end_drag (bool with_push, uint32_t final_index)
{
- char buf[32];
+ if (!_drag_had_movement) {
+ return;
+ }
- if (_vc_uses_gain_mapping) {
- if (fraction == 0.0) {
- snprintf (buf, sizeof (buf), "-inf dB");
- } else {
- snprintf (buf, sizeof (buf), "%.1fdB", coefficient_to_dB (slider_position_to_gain (fraction)));
+ alist->freeze ();
+ 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;
}
- } else {
- view_to_model_y(fraction);
- if (alist->parameter().type() == MidiCCAutomation)
- snprintf (buf, sizeof (buf), "%d", (int)fraction);
- else
- snprintf (buf, sizeof (buf), "%.2f", fraction);
}
- return buf;
-}
-
-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;
- }
+ alist->thaw ();
- string str;
+ update_pending = false;
- if (cp) {
- str = _("automation event move");
- } else {
- str = _("automation range drag");
- }
+ trackview.editor().session()->add_command (
+ new MementoCommand<AutomationList>(memento_command_binder (), 0, &alist->get_state())
+ );
- trackview.editor.current_session()->begin_reversible_command (str);
- trackview.editor.current_session()->add_command (new MementoCommand<AutomationList>(*alist.get(), &get_state(), 0));
-
- drag_x = x;
- drag_distance = 0;
- first_drag_fraction = fraction;
- last_drag_fraction = fraction;
- drags = 0;
+ trackview.editor().session()->set_dirty ();
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 const ydelta = fraction - last_drag_fraction;
-
- 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() + _y_position) /_height) + ydelta, with_push);
- }
-
- if (line_points.size() > 1) {
- line->property_points() = line_points;
- }
-
- drags++;
-}
-
-void
-AutomationLine::end_drag (ControlPoint* cp)
-{
- if (!drags) {
- 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);
- }
-
- alist->thaw ();
-
- update_pending = false;
- trackview.editor.current_session()->add_command (new MementoCommand<AutomationList>(*alist.get(), 0, &alist->get_state()));
- trackview.editor.current_session()->commit_reversible_command ();
- trackview.editor.current_session()->set_dirty ();
+ 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);
-
-
- /* 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);
- }
+ view_to_model_coord_y (view_y);
+ 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<ControlPoint*>::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;
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;
}
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<AutomationList> (memento_command_binder (), &before, &alist->get_state())
+ );
- trackview.editor.current_session()->add_command(new MementoCommand<AutomationList>(
- *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<Selectable*>& results)
+AutomationLine::get_selectables (framepos_t start, framepos_t end, double botfrac, double topfrac, list<Selectable*>& results)
{
-
- double top;
- double bot;
- nframes_t nstart;
- nframes_t nend;
- bool collecting = false;
-
- /* Curse X11 and its inverted coordinate system! */
-
- bot = _y_position + (1.0 - topfrac) * _height;
- top = _y_position + (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<ControlPoint*>::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<Selectable*>& results)
+AutomationLine::get_inverted_selectables (Selection&, list<Selectable*>& /*results*/)
{
// hmmm ....
}
void
-AutomationLine::set_selected_points (PointSelection& points)
+AutomationLine::set_selected_points (PointSelection const & points)
{
- double top;
- double bot;
-
for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
- (*i)->set_selected(false);
+ (*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 = _y_position + (1.0 - (*r).high_fract) * _height;
- top = _y_position + (1.0 - (*r).low_fract) * _height;
-
- for (vector<ControlPoint*>::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);
- }
- }
-
- }
- }
-
- out:
- for (vector<ControlPoint*>::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<ControlPoint*>::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<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
-
- (*i)->set_selected(false);
-
- for (list<AudioRange>::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 AutomationList& events)
+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<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
delete *i;
}
return;
}
- AutomationList::const_iterator ai;
+ /* hide all existing points, and the line */
+
+ for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
+ (*i)->hide();
+ }
+
+ line->hide ();
+ np = events.size();
+
+ Evoral::ControlList& e = const_cast<Evoral::ControlList&> (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 */
+
+ model_to_view_coord (tx, ty);
- for (ai = events.const_begin(); ai != events.const_end(); ++ai) {
+ if (std::isnan (tx) || std::isnan (ty)) {
+ warning << string_compose (_("Ignoring illegal points on AutomationLine \"%1\""),
+ _name) << endmsg;
+ continue;
+ }
+
+ if (tx >= max_framepos || tx < 0 || tx >= _maximum_time) {
+ continue;
+ }
- double translated_y = (*ai)->value;
- model_to_view_y (translated_y);
+ /* 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);
- tmp_points.push_back (ALPoint (trackview.editor.frame_to_unit ((*ai)->when),
- _y_position + _height - (translated_y * _height)));
+ 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 */
+
+ 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;
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<AutomationLine>(*this, &before, &get_state()));
- trackview.editor.current_session()->commit_reversible_command ();
- trackview.editor.current_session()->set_dirty ();
+
+ trackview.editor().session()->add_command (
+ new MementoCommand<AutomationList> (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::change_model_range (AutomationList::iterator start, AutomationList::iterator end, double xdelta, float ydelta)
+AutomationLine::set_list (boost::shared_ptr<ARDOUR::AutomationList> list)
{
- alist->move_range (start, end, xdelta, ydelta);
+ alist = list;
+ queue_reset ();
+ connect_to_list ();
}
void
-AutomationLine::show_all_control_points ()
+AutomationLine::add_visibility (VisibleAspects va)
{
- points_visible = true;
-
- for (vector<ControlPoint*>::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<ControlPoint*>::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);
}
}
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)
+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) {
- y = slider_position_to_gain (y);
+ if (alist->parameter().type() == GainAutomation ||
+ alist->parameter().type() == EnvelopeAutomation) {
+ 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)
+AutomationLine::model_to_view_coord (double& x, double& y) const
{
/* TODO: This should be more generic ... */
- if (alist->parameter().type() == GainAutomation) {
- y = gain_to_slider_position (y);
- } else if (alist->parameter().type() == PanAutomation) {
+ if (alist->parameter().type() == GainAutomation ||
+ alist->parameter().type() == EnvelopeAutomation) {
+ 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) {
} 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<AutomationList>*
+AutomationLine::memento_command_binder ()
+{
+ return new SimpleMementoCommandBinder<AutomationList> (*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<framepos_t, framepos_t>
+AutomationLine::get_point_x_range () const
+{
+ pair<framepos_t, framepos_t> 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 ();
+}