2 Copyright (C) 2002-2003 Paul Davis
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
25 #include "pbd/stl_delete.h"
26 #include "pbd/memento_command.h"
27 #include "pbd/stacktrace.h"
29 #include "ardour/automation_list.h"
30 #include "ardour/dB.h"
31 #include "evoral/Curve.hpp"
33 #include "simplerect.h"
34 #include "automation_line.h"
35 #include "control_point.h"
36 #include "gui_thread.h"
37 #include "rgb_macros.h"
38 #include "ardour_ui.h"
39 #include "public_editor.h"
41 #include "selection.h"
42 #include "time_axis_view.h"
43 #include "point_selection.h"
44 #include "automation_time_axis.h"
45 #include "public_editor.h"
47 #include "ardour/event_type_map.h"
48 #include "ardour/session.h"
53 using namespace ARDOUR;
55 using namespace Editing;
56 using namespace Gnome; // for Canvas
58 static const Evoral::IdentityConverter<double, framepos_t> default_converter;
60 AutomationLine::AutomationLine (const string& name, TimeAxisView& tv, ArdourCanvas::Group& parent,
61 boost::shared_ptr<AutomationList> al,
62 const Evoral::TimeConverter<double, framepos_t>* converter)
66 , _parent_group (parent)
68 , _time_converter (converter ? (*converter) : default_converter)
69 , _maximum_time (max_framepos)
71 points_visible = false;
72 update_pending = false;
73 _uses_gain_mapping = false;
77 terminal_points_can_slide = true;
80 group = new ArdourCanvas::Group (parent);
81 group->property_x() = 0.0;
82 group->property_y() = 0.0;
84 line = new ArdourCanvas::Line (*group);
85 line->property_width_pixels() = (guint)1;
86 line->set_data ("line", this);
88 line->signal_event().connect (sigc::mem_fun (*this, &AutomationLine::event_handler));
92 trackview.session()->register_with_memento_command_factory(alist->id(), this);
94 if (alist->parameter().type() == GainAutomation ||
95 alist->parameter().type() == EnvelopeAutomation) {
96 set_uses_gain_mapping (true);
99 interpolation_changed (alist->interpolation ());
104 AutomationLine::~AutomationLine ()
106 vector_delete (&control_points);
111 AutomationLine::event_handler (GdkEvent* event)
113 return PublicEditor::instance().canvas_line_event (event, line, this);
117 AutomationLine::queue_reset ()
119 if (!update_pending) {
120 update_pending = true;
121 Gtkmm2ext::UI::instance()->call_slot (invalidator (*this), boost::bind (&AutomationLine::reset, this));
126 AutomationLine::show ()
128 if (alist->interpolation() != AutomationList::Discrete) {
132 if (points_visible) {
133 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
142 AutomationLine::hide ()
145 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
152 AutomationLine::control_point_box_size ()
154 if (alist->interpolation() == AutomationList::Discrete) {
155 return max((_height*4.0) / (double)(alist->parameter().max() - alist->parameter().min()),
159 if (_height > TimeAxisView::preset_height (HeightLarger)) {
161 } else if (_height > (guint32) TimeAxisView::preset_height (HeightNormal)) {
168 AutomationLine::set_height (guint32 h)
173 double bsz = control_point_box_size();
175 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
176 (*i)->set_size (bsz);
184 AutomationLine::set_line_color (uint32_t color)
187 line->property_fill_color_rgba() = color;
191 AutomationLine::set_uses_gain_mapping (bool yn)
193 if (yn != _uses_gain_mapping) {
194 _uses_gain_mapping = yn;
200 AutomationLine::nth (uint32_t n)
202 if (n < control_points.size()) {
203 return control_points[n];
210 AutomationLine::nth (uint32_t n) const
212 if (n < control_points.size()) {
213 return control_points[n];
220 AutomationLine::modify_point_y (ControlPoint& cp, double y)
222 /* clamp y-coord appropriately. y is supposed to be a normalized fraction (0.0-1.0),
223 and needs to be converted to a canvas unit distance.
228 y = _height - (y * _height);
230 double const x = trackview.editor().frame_to_unit (_time_converter.to((*cp.model())->when) - _offset);
232 trackview.editor().session()->begin_reversible_command (_("automation event move"));
233 trackview.editor().session()->add_command (
234 new MementoCommand<AutomationList> (memento_command_binder(), &get_state(), 0)
237 cp.move_to (x, y, ControlPoint::Full);
239 reset_line_coords (cp);
241 if (line_points.size() > 1) {
242 line->property_points() = line_points;
246 sync_model_with_view_point (cp, false, 0);
249 update_pending = false;
251 trackview.editor().session()->add_command (
252 new MementoCommand<AutomationList> (memento_command_binder(), 0, &alist->get_state())
255 trackview.editor().session()->commit_reversible_command ();
256 trackview.editor().session()->set_dirty ();
260 AutomationLine::reset_line_coords (ControlPoint& cp)
262 if (cp.view_index() < line_points.size()) {
263 line_points[cp.view_index()].set_x (cp.get_x());
264 line_points[cp.view_index()].set_y (cp.get_y());
269 AutomationLine::sync_model_with_view_points (list<ControlPoint*> cp, bool did_push, int64_t distance)
271 update_pending = true;
273 for (list<ControlPoint*>::iterator i = cp.begin(); i != cp.end(); ++i) {
274 sync_model_with_view_point (**i, did_push, distance);
279 AutomationLine::model_representation (ControlPoint& cp, ModelRepresentation& mr)
281 /* part one: find out where the visual control point is.
282 initial results are in canvas units. ask the
283 line to convert them to something relevant.
286 mr.xval = cp.get_x();
287 mr.yval = 1.0 - (cp.get_y() / _height);
289 /* if xval has not changed, set it directly from the model to avoid rounding errors */
291 if (mr.xval == trackview.editor().frame_to_unit(_time_converter.to((*cp.model())->when)) - _offset) {
292 mr.xval = (*cp.model())->when - _offset;
294 mr.xval = trackview.editor().unit_to_frame (mr.xval);
295 mr.xval = _time_converter.from (mr.xval + _offset);
298 /* convert y to model units; the x was already done above
301 view_to_model_coord_y (mr.yval);
303 /* part 2: find out where the model point is now
306 mr.xpos = (*cp.model())->when - _offset;
307 mr.ypos = (*cp.model())->value;
309 /* part 3: get the position of the visual control
310 points before and after us.
313 ControlPoint* before;
316 if (cp.view_index()) {
317 before = nth (cp.view_index() - 1);
322 after = nth (cp.view_index() + 1);
325 mr.xmin = (*before->model())->when;
326 mr.ymin = (*before->model())->value;
327 mr.start = before->model();
332 mr.start = cp.model();
336 mr.end = after->model();
346 AutomationLine::determine_visible_control_points (ALPoints& points)
348 uint32_t view_index, pi, n;
349 AutomationList::iterator model;
351 uint32_t this_rx = 0;
352 uint32_t prev_rx = 0;
353 uint32_t this_ry = 0;
354 uint32_t prev_ry = 0;
358 /* hide all existing points, and the line */
360 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
366 if (points.empty()) {
370 npoints = points.size();
372 /* compute derivative/slope for the entire line */
374 slope = new double[npoints];
376 for (n = 0; n < npoints - 1; ++n) {
377 double xdelta = points[n+1].x - points[n].x;
378 double ydelta = points[n+1].y - points[n].y;
379 slope[n] = ydelta/xdelta;
382 box_size = (uint32_t) control_point_box_size ();
384 /* read all points and decide which ones to show as control points */
388 for (model = alist->begin(), pi = 0; pi < npoints; ++model, ++pi) {
390 double tx = points[pi].x;
391 double ty = points[pi].y;
393 if (find (_always_in_view.begin(), _always_in_view.end(), (*model)->when) != _always_in_view.end()) {
394 add_visible_control_point (view_index, pi, tx, ty, model, npoints);
401 if (isnan (tx) || isnan (ty)) {
402 warning << string_compose (_("Ignoring illegal points on AutomationLine \"%1\""),
407 /* now ensure that the control_points vector reflects the current curve
408 state, but don't plot control points too close together. also, don't
409 plot a series of points all with the same value.
411 always plot the first and last points, of course.
414 if (invalid_point (points, pi)) {
415 /* for some reason, we are supposed to ignore this point,
416 but still keep track of the model index.
421 if (pi > 0 && pi < npoints - 1) {
422 if (slope[pi] == slope[pi-1]) {
424 /* no reason to display this point */
430 /* need to round here. the ultimate coordinates are integer
431 pixels, so tiny deltas in the coords will be eliminated
432 and we end up with "colinear" line segments. since the
433 line rendering code in libart doesn't like this very
434 much, we eliminate them here. don't do this for the first and last
438 this_rx = (uint32_t) rint (tx);
439 this_ry = (uint32_t) rint (ty);
441 if (view_index && pi != npoints && /* not the first, not the last */
442 (((this_rx == prev_rx) && (this_ry == prev_ry)) || /* same point */
443 (((this_rx - prev_rx) < (box_size + 2)) && /* not identical, but still too close horizontally */
444 (abs ((int)(this_ry - prev_ry)) < (int) (box_size + 2))))) { /* too close vertically */
448 /* ok, we should display this point */
450 add_visible_control_point (view_index, pi, tx, ty, model, npoints);
458 /* discard extra CP's to avoid confusing ourselves */
460 while (control_points.size() > view_index) {
461 ControlPoint* cp = control_points.back();
462 control_points.pop_back ();
466 if (!terminal_points_can_slide) {
467 control_points.back()->set_can_slide(false);
472 if (view_index > 1) {
474 npoints = view_index;
476 /* reset the line coordinates */
478 while (line_points.size() < npoints) {
479 line_points.push_back (Art::Point (0,0));
482 while (line_points.size() > npoints) {
483 line_points.pop_back ();
486 for (view_index = 0; view_index < npoints; ++view_index) {
487 line_points[view_index].set_x (control_points[view_index]->get_x());
488 line_points[view_index].set_y (control_points[view_index]->get_y());
491 line->property_points() = line_points;
493 if (_visible && alist->interpolation() != AutomationList::Discrete) {
499 set_selected_points (trackview.editor().get_selection().points);
503 AutomationLine::get_verbose_cursor_string (double fraction) const
505 std::string s = fraction_to_string (fraction);
506 if (_uses_gain_mapping) {
514 * @param fraction y fraction
515 * @return string representation of this value, using dB if appropriate.
518 AutomationLine::fraction_to_string (double fraction) const
522 if (_uses_gain_mapping) {
523 if (fraction == 0.0) {
524 snprintf (buf, sizeof (buf), "-inf");
526 snprintf (buf, sizeof (buf), "%.1f", accurate_coefficient_to_dB (slider_position_to_gain (fraction)));
530 view_to_model_coord (dummy, fraction);
531 if (EventTypeMap::instance().is_integer (alist->parameter())) {
532 snprintf (buf, sizeof (buf), "%d", (int)fraction);
534 snprintf (buf, sizeof (buf), "%.2f", fraction);
543 * @param s Value string in the form as returned by fraction_to_string.
544 * @return Corresponding y fraction.
547 AutomationLine::string_to_fraction (string const & s) const
554 sscanf (s.c_str(), "%lf", &v);
556 if (_uses_gain_mapping) {
557 v = gain_to_slider_position (dB_to_coefficient (v));
560 model_to_view_coord (dummy, v);
567 AutomationLine::invalid_point (ALPoints& p, uint32_t index)
569 return p[index].x == max_framepos && p[index].y == DBL_MAX;
573 AutomationLine::invalidate_point (ALPoints& p, uint32_t index)
575 p[index].x = max_framepos;
576 p[index].y = DBL_MAX;
579 /** Start dragging a single point, possibly adding others if the supplied point is selected and there
580 * are other selected points.
582 * @param cp Point to drag.
583 * @param x Initial x position (units).
584 * @param fraction Initial y position (as a fraction of the track height, where 0 is the bottom and 1 the top)
587 AutomationLine::start_drag_single (ControlPoint* cp, double x, float fraction)
589 trackview.editor().session()->begin_reversible_command (_("automation event move"));
590 trackview.editor().session()->add_command (
591 new MementoCommand<AutomationList> (memento_command_binder(), &get_state(), 0)
594 _drag_points.clear ();
595 _drag_points.push_back (cp);
597 if (cp->get_selected ()) {
598 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
599 if (*i != cp && (*i)->get_selected()) {
600 _drag_points.push_back (*i);
605 start_drag_common (x, fraction);
608 /** Start dragging a line vertically (with no change in x)
609 * @param i1 Control point index of the `left' point on the line.
610 * @param i2 Control point index of the `right' point on the line.
611 * @param fraction Initial y position (as a fraction of the track height, where 0 is the bottom and 1 the top)
614 AutomationLine::start_drag_line (uint32_t i1, uint32_t i2, float fraction)
616 trackview.editor().session()->begin_reversible_command (_("automation range move"));
617 trackview.editor().session()->add_command (
618 new MementoCommand<AutomationList> (memento_command_binder (), &get_state(), 0)
621 _drag_points.clear ();
622 for (uint32_t i = i1; i <= i2; i++) {
623 _drag_points.push_back (nth (i));
626 start_drag_common (0, fraction);
629 /** Start dragging multiple points (with no change in x)
630 * @param cp Points to drag.
631 * @param fraction Initial y position (as a fraction of the track height, where 0 is the bottom and 1 the top)
634 AutomationLine::start_drag_multiple (list<ControlPoint*> cp, float fraction, XMLNode* state)
636 trackview.editor().session()->begin_reversible_command (_("automation range move"));
637 trackview.editor().session()->add_command (
638 new MementoCommand<AutomationList> (memento_command_binder(), state, 0)
642 start_drag_common (0, fraction);
646 struct ControlPointSorter
648 bool operator() (ControlPoint const * a, ControlPoint const * b) {
649 return a->get_x() < b->get_x();
653 /** Common parts of starting a drag.
654 * @param x Starting x position in units, or 0 if x is being ignored.
655 * @param fraction Starting y position (as a fraction of the track height, where 0 is the bottom and 1 the top)
658 AutomationLine::start_drag_common (double x, float fraction)
662 _last_drag_fraction = fraction;
663 _drag_had_movement = false;
666 _drag_points.sort (ControlPointSorter ());
668 /* find the additional points that will be dragged when the user is holding
672 uint32_t i = _drag_points.back()->view_index () + 1;
674 _push_points.clear ();
675 while ((p = nth (i)) != 0 && p->can_slide()) {
676 _push_points.push_back (p);
681 /** Should be called to indicate motion during a drag.
682 * @param x New x position of the drag in units, or undefined if ignore_x == true.
683 * @param fraction New y fraction.
684 * @return x position and y fraction that were actually used (once clamped).
687 AutomationLine::drag_motion (double x, float fraction, bool ignore_x, bool with_push)
689 /* setup the points that are to be moved this time round */
690 list<ControlPoint*> points = _drag_points;
692 copy (_push_points.begin(), _push_points.end(), back_inserter (points));
693 points.sort (ControlPointSorter ());
696 double dx = ignore_x ? 0 : (x - _drag_x);
697 double dy = fraction - _last_drag_fraction;
700 ControlPoint* before = 0;
701 ControlPoint* after = 0;
703 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
704 if ((*i)->get_x() < points.front()->get_x()) {
707 if ((*i)->get_x() > points.back()->get_x() && after == 0) {
712 double const before_x = before ? before->get_x() : 0;
713 double const after_x = after ? after->get_x() : DBL_MAX;
716 for (list<ControlPoint*>::iterator i = points.begin(); i != points.end(); ++i) {
718 if ((*i)->can_slide() && !ignore_x) {
721 double const a = (*i)->get_x() + dx;
722 double const b = before_x + 1;
730 if (after_x - before_x < 2) {
731 /* after and before are very close, so just leave this alone */
734 double const a = (*i)->get_x() + dx;
735 double const b = after_x - 1;
745 for (list<ControlPoint*>::iterator i = points.begin(); i != points.end(); ++i) {
746 double const y = ((_height - (*i)->get_y()) / _height) + dy;
755 pair<double, float> const clamped (_drag_x + dx, _last_drag_fraction + dy);
756 _drag_distance += dx;
758 _last_drag_fraction = fraction;
760 for (list<ControlPoint*>::iterator i = _drag_points.begin(); i != _drag_points.end(); ++i) {
761 (*i)->move_to ((*i)->get_x() + dx, (*i)->get_y() - _height * dy, ControlPoint::Full);
762 reset_line_coords (**i);
766 /* move push points, preserving their y */
767 for (list<ControlPoint*>::iterator i = _push_points.begin(); i != _push_points.end(); ++i) {
768 (*i)->move_to ((*i)->get_x() + dx, (*i)->get_y(), ControlPoint::Full);
769 reset_line_coords (**i);
773 if (line_points.size() > 1) {
774 line->property_points() = line_points;
777 _drag_had_movement = true;
778 did_push = with_push;
783 /** Should be called to indicate the end of a drag */
785 AutomationLine::end_drag ()
787 if (!_drag_had_movement) {
793 /* set up the points that were moved this time round */
794 list<ControlPoint*> points = _drag_points;
796 copy (_push_points.begin(), _push_points.end(), back_inserter (points));
797 points.sort (ControlPointSorter ());
800 sync_model_with_view_points (points, did_push, rint (_drag_distance * trackview.editor().get_current_zoom ()));
804 update_pending = false;
806 trackview.editor().session()->add_command (
807 new MementoCommand<AutomationList>(memento_command_binder (), 0, &alist->get_state())
810 trackview.editor().session()->set_dirty ();
814 AutomationLine::sync_model_with_view_point (ControlPoint& cp, bool did_push, int64_t distance)
816 ModelRepresentation mr;
819 model_representation (cp, mr);
821 /* how much are we changing the central point by */
823 ydelta = mr.yval - mr.ypos;
826 apply the full change to the central point, and interpolate
827 on both axes to cover all model points represented
828 by the control point.
831 /* change all points before the primary point */
833 for (AutomationList::iterator i = mr.start; i != cp.model(); ++i) {
835 double fract = ((*i)->when - mr.xmin) / (mr.xpos - mr.xmin);
836 double y_delta = ydelta * fract;
837 double x_delta = distance * fract;
841 if (y_delta || x_delta) {
842 alist->modify (i, (*i)->when + x_delta, mr.ymin + y_delta);
846 /* change the primary point */
848 update_pending = true;
849 alist->modify (cp.model(), mr.xval, mr.yval);
851 /* change later points */
853 AutomationList::iterator i = cp.model();
857 while (i != mr.end) {
859 double delta = ydelta * (mr.xmax - (*i)->when) / (mr.xmax - mr.xpos);
861 /* all later points move by the same distance along the x-axis as the main point */
864 alist->modify (i, (*i)->when + distance, (*i)->value + delta);
872 /* move all points after the range represented by the view by the same distance
873 as the main point moved.
876 alist->slide (mr.end, distance);
881 AutomationLine::control_points_adjacent (double xval, uint32_t & before, uint32_t& after)
883 ControlPoint *bcp = 0;
884 ControlPoint *acp = 0;
887 unit_xval = trackview.editor().frame_to_unit (xval);
889 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
891 if ((*i)->get_x() <= unit_xval) {
893 if (!bcp || (*i)->get_x() > bcp->get_x()) {
895 before = bcp->view_index();
898 } else if ((*i)->get_x() > unit_xval) {
900 after = acp->view_index();
909 AutomationLine::is_last_point (ControlPoint& cp)
911 ModelRepresentation mr;
913 model_representation (cp, mr);
915 // If the list is not empty, and the point is the last point in the list
917 if (!alist->empty() && mr.end == alist->end()) {
925 AutomationLine::is_first_point (ControlPoint& cp)
927 ModelRepresentation mr;
929 model_representation (cp, mr);
931 // If the list is not empty, and the point is the first point in the list
933 if (!alist->empty() && mr.start == alist->begin()) {
940 // This is copied into AudioRegionGainLine
942 AutomationLine::remove_point (ControlPoint& cp)
944 ModelRepresentation mr;
946 model_representation (cp, mr);
948 trackview.editor().session()->begin_reversible_command (_("remove control point"));
949 XMLNode &before = alist->get_state();
951 alist->erase (mr.start, mr.end);
953 trackview.editor().session()->add_command(
954 new MementoCommand<AutomationList> (memento_command_binder (), &before, &alist->get_state())
957 trackview.editor().session()->commit_reversible_command ();
958 trackview.editor().session()->set_dirty ();
961 /** Get selectable points within an area.
962 * @param start Start position in session frames.
963 * @param end End position in session frames.
964 * @param bot Bottom y range, as a fraction of line height, where 0 is the bottom of the line.
965 * @param top Top y range, as a fraction of line height, where 0 is the bottom of the line.
966 * @param result Filled in with selectable things; in this case, ControlPoints.
969 AutomationLine::get_selectables (
970 framepos_t start, framepos_t end, double botfrac, double topfrac, list<Selectable*>& results
973 /* convert fractions to display coordinates with 0 at the top of the track */
974 double const bot_track = (1 - topfrac) * trackview.current_height ();
975 double const top_track = (1 - botfrac) * trackview.current_height ();
977 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
978 double const model_when = (*(*i)->model())->when;
979 framepos_t const session_frames_when = _time_converter.to (model_when - _offset) + _time_converter.origin_b ();
981 if (session_frames_when >= start && session_frames_when <= end && (*i)->get_y() >= bot_track && (*i)->get_y() <= top_track) {
982 results.push_back (*i);
988 AutomationLine::get_inverted_selectables (Selection&, list<Selectable*>& /*results*/)
993 /** Take a PointSelection and find ControlPoints that fall within it */
995 AutomationLine::point_selection_to_control_points (PointSelection const & s)
997 list<ControlPoint*> cp;
999 for (PointSelection::const_iterator i = s.begin(); i != s.end(); ++i) {
1001 if (i->track != &trackview) {
1005 double const bot = (1 - i->high_fract) * trackview.current_height ();
1006 double const top = (1 - i->low_fract) * trackview.current_height ();
1008 for (vector<ControlPoint*>::iterator j = control_points.begin(); j != control_points.end(); ++j) {
1010 double const rstart = trackview.editor().frame_to_unit (_time_converter.to (i->start));
1011 double const rend = trackview.editor().frame_to_unit (_time_converter.to (i->end));
1013 if ((*j)->get_x() >= rstart && (*j)->get_x() <= rend) {
1014 if ((*j)->get_y() >= bot && (*j)->get_y() <= top) {
1026 AutomationLine::set_selected_points (PointSelection& points)
1028 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1029 (*i)->set_selected (false);
1032 if (!points.empty()) {
1033 list<ControlPoint*> cp = point_selection_to_control_points (points);
1034 for (list<ControlPoint*>::iterator i = cp.begin(); i != cp.end(); ++i) {
1035 (*i)->set_selected (true);
1042 void AutomationLine::set_colors ()
1044 set_line_color (ARDOUR_UI::config()->canvasvar_AutomationLine.get());
1045 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1051 AutomationLine::list_changed ()
1057 AutomationLine::reset_callback (const Evoral::ControlList& events)
1059 ALPoints tmp_points;
1060 uint32_t npoints = events.size();
1063 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1066 control_points.clear ();
1071 AutomationList::const_iterator ai;
1073 for (ai = events.begin(); ai != events.end(); ++ai) {
1075 double translated_x = (*ai)->when;
1076 double translated_y = (*ai)->value;
1077 model_to_view_coord (translated_x, translated_y);
1079 if (translated_x >= 0 && translated_x < _maximum_time) {
1080 tmp_points.push_back (ALPoint (
1081 trackview.editor().frame_to_unit (translated_x),
1082 _height - (translated_y * _height))
1087 determine_visible_control_points (tmp_points);
1091 AutomationLine::reset ()
1093 update_pending = false;
1099 alist->apply_to_points (*this, &AutomationLine::reset_callback);
1103 AutomationLine::clear ()
1105 /* parent must create and commit command */
1106 XMLNode &before = alist->get_state();
1109 trackview.editor().session()->add_command (
1110 new MementoCommand<AutomationList> (memento_command_binder (), &before, &alist->get_state())
1115 AutomationLine::change_model (AutomationList::iterator /*i*/, double /*x*/, double /*y*/)
1120 AutomationLine::set_list (boost::shared_ptr<ARDOUR::AutomationList> list)
1128 AutomationLine::show_all_control_points ()
1131 // show the line but don't allow any control points
1135 points_visible = true;
1137 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1138 if (!(*i)->visible()) {
1140 (*i)->set_visible (true);
1146 AutomationLine::hide_all_but_selected_control_points ()
1148 if (alist->interpolation() == AutomationList::Discrete) {
1152 points_visible = false;
1154 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1155 if (!(*i)->get_selected()) {
1156 (*i)->set_visible (false);
1162 AutomationLine::track_entered()
1164 if (alist->interpolation() != AutomationList::Discrete) {
1165 show_all_control_points();
1170 AutomationLine::track_exited()
1172 if (alist->interpolation() != AutomationList::Discrete) {
1173 hide_all_but_selected_control_points();
1178 AutomationLine::get_state (void)
1180 /* function as a proxy for the model */
1181 return alist->get_state();
1185 AutomationLine::set_state (const XMLNode &node, int version)
1187 /* function as a proxy for the model */
1188 return alist->set_state (node, version);
1192 AutomationLine::view_to_model_coord (double& x, double& y) const
1194 x = _time_converter.from (x);
1195 view_to_model_coord_y (y);
1199 AutomationLine::view_to_model_coord_y (double& y) const
1201 /* TODO: This should be more generic ... */
1202 if (alist->parameter().type() == GainAutomation ||
1203 alist->parameter().type() == EnvelopeAutomation) {
1204 y = slider_position_to_gain (y);
1207 } else if (alist->parameter().type() == PanAzimuthAutomation ||
1208 alist->parameter().type() == PanElevationAutomation ||
1209 alist->parameter().type() == PanWidthAutomation) {
1211 } else if (alist->parameter().type() == PluginAutomation) {
1212 y = y * (double)(alist->get_max_y()- alist->get_min_y()) + alist->get_min_y();
1214 y = rint (y * alist->parameter().max());
1219 AutomationLine::model_to_view_coord (double& x, double& y) const
1221 /* TODO: This should be more generic ... */
1222 if (alist->parameter().type() == GainAutomation ||
1223 alist->parameter().type() == EnvelopeAutomation) {
1224 y = gain_to_slider_position (y);
1225 } else if (alist->parameter().type() == PanAzimuthAutomation ||
1226 alist->parameter().type() == PanElevationAutomation ||
1227 alist->parameter().type() == PanWidthAutomation) {
1228 // vertical coordinate axis reversal
1230 } else if (alist->parameter().type() == PluginAutomation) {
1231 y = (y - alist->get_min_y()) / (double)(alist->get_max_y()- alist->get_min_y());
1233 y = y / (double)alist->parameter().max(); /* ... like this */
1236 x = _time_converter.to (x) - _offset;
1239 /** Called when our list has announced that its interpolation style has changed */
1241 AutomationLine::interpolation_changed (AutomationList::InterpolationStyle style)
1243 if (style == AutomationList::Discrete) {
1244 show_all_control_points();
1247 hide_all_but_selected_control_points();
1253 AutomationLine::add_visible_control_point (uint32_t view_index, uint32_t pi, double tx, double ty, AutomationList::iterator model, uint32_t npoints)
1255 if (view_index >= control_points.size()) {
1257 /* make sure we have enough control points */
1259 ControlPoint* ncp = new ControlPoint (*this);
1260 ncp->set_size (control_point_box_size ());
1262 control_points.push_back (ncp);
1265 ControlPoint::ShapeType shape;
1267 if (!terminal_points_can_slide) {
1269 control_points[view_index]->set_can_slide(false);
1271 shape = ControlPoint::Start;
1273 shape = ControlPoint::Full;
1275 } else if (pi == npoints - 1) {
1276 control_points[view_index]->set_can_slide(false);
1277 shape = ControlPoint::End;
1279 control_points[view_index]->set_can_slide(true);
1280 shape = ControlPoint::Full;
1283 control_points[view_index]->set_can_slide(true);
1284 shape = ControlPoint::Full;
1287 control_points[view_index]->reset (tx, ty, model, view_index, shape);
1289 /* finally, control visibility */
1291 if (_visible && points_visible) {
1292 control_points[view_index]->show ();
1293 control_points[view_index]->set_visible (true);
1295 if (!points_visible) {
1296 control_points[view_index]->set_visible (false);
1302 AutomationLine::add_always_in_view (double x)
1304 _always_in_view.push_back (x);
1305 alist->apply_to_points (*this, &AutomationLine::reset_callback);
1309 AutomationLine::clear_always_in_view ()
1311 _always_in_view.clear ();
1312 alist->apply_to_points (*this, &AutomationLine::reset_callback);
1316 AutomationLine::connect_to_list ()
1318 _list_connections.drop_connections ();
1320 alist->StateChanged.connect (_list_connections, invalidator (*this), boost::bind (&AutomationLine::list_changed, this), gui_context());
1322 alist->InterpolationChanged.connect (
1323 _list_connections, invalidator (*this), boost::bind (&AutomationLine::interpolation_changed, this, _1), gui_context()
1327 MementoCommandBinder<AutomationList>*
1328 AutomationLine::memento_command_binder ()
1330 return new SimpleMementoCommandBinder<AutomationList> (*alist.get());
1333 /** Set the maximum time that points on this line can be at, relative
1334 * to the start of the track or region that it is on.
1337 AutomationLine::set_maximum_time (framecnt_t t)
1339 if (_maximum_time == t) {
1348 /** @return min and max x positions of points that are in the list, in session frames */
1349 pair<framepos_t, framepos_t>
1350 AutomationLine::get_point_x_range () const
1352 pair<framepos_t, framepos_t> r (max_framepos, 0);
1354 for (AutomationList::const_iterator i = the_list()->begin(); i != the_list()->end(); ++i) {
1355 r.first = min (r.first, _time_converter.to ((*i)->when) + _offset + _time_converter.origin_b ());
1356 r.second = max (r.second, _time_converter.to ((*i)->when) + _offset + _time_converter.origin_b ());
1363 AutomationLine::set_offset (framepos_t off)
1365 if (_offset == off) {