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, sframes_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, sframes_t>* converter)
66 , _parent_group (parent)
67 , _time_converter (converter ? (*converter) : default_converter)
68 , _maximum_time (max_frames)
70 points_visible = false;
71 update_pending = false;
72 _uses_gain_mapping = false;
76 terminal_points_can_slide = true;
79 group = new ArdourCanvas::Group (parent);
80 group->property_x() = 0.0;
81 group->property_y() = 0.0;
83 line = new ArdourCanvas::Line (*group);
84 line->property_width_pixels() = (guint)1;
85 line->set_data ("line", this);
87 line->signal_event().connect (sigc::mem_fun (*this, &AutomationLine::event_handler));
91 trackview.session()->register_with_memento_command_factory(alist->id(), this);
93 if (alist->parameter().type() == GainAutomation ||
94 alist->parameter().type() == EnvelopeAutomation) {
95 set_uses_gain_mapping (true);
98 interpolation_changed (alist->interpolation ());
103 AutomationLine::~AutomationLine ()
105 vector_delete (&control_points);
110 AutomationLine::event_handler (GdkEvent* event)
112 return PublicEditor::instance().canvas_line_event (event, line, this);
116 AutomationLine::queue_reset ()
118 if (!update_pending) {
119 update_pending = true;
120 Gtkmm2ext::UI::instance()->call_slot (invalidator (*this), boost::bind (&AutomationLine::reset, this));
125 AutomationLine::show ()
127 if (alist->interpolation() != AutomationList::Discrete) {
131 if (points_visible) {
132 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
141 AutomationLine::hide ()
144 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
151 AutomationLine::control_point_box_size ()
153 if (alist->interpolation() == AutomationList::Discrete) {
154 return max((_height*4.0) / (double)(alist->parameter().max() - alist->parameter().min()),
158 if (_height > TimeAxisView::preset_height (HeightLarger)) {
160 } else if (_height > (guint32) TimeAxisView::preset_height (HeightNormal)) {
167 AutomationLine::set_height (guint32 h)
172 double bsz = control_point_box_size();
174 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
175 (*i)->set_size (bsz);
183 AutomationLine::set_line_color (uint32_t color)
186 line->property_fill_color_rgba() = color;
190 AutomationLine::set_uses_gain_mapping (bool yn)
192 if (yn != _uses_gain_mapping) {
193 _uses_gain_mapping = yn;
199 AutomationLine::nth (uint32_t n)
201 if (n < control_points.size()) {
202 return control_points[n];
209 AutomationLine::nth (uint32_t n) const
211 if (n < control_points.size()) {
212 return control_points[n];
219 AutomationLine::modify_point_y (ControlPoint& cp, double y)
221 /* clamp y-coord appropriately. y is supposed to be a normalized fraction (0.0-1.0),
222 and needs to be converted to a canvas unit distance.
227 y = _height - (y * _height);
229 double const x = trackview.editor().frame_to_unit (_time_converter.to((*cp.model())->when));
231 trackview.editor().session()->begin_reversible_command (_("automation event move"));
232 trackview.editor().session()->add_command (
233 new MementoCommand<AutomationList> (memento_command_binder(), &get_state(), 0)
236 cp.move_to (x, y, ControlPoint::Full);
238 reset_line_coords (cp);
240 if (line_points.size() > 1) {
241 line->property_points() = line_points;
245 sync_model_with_view_point (cp, false, 0);
248 update_pending = false;
250 trackview.editor().session()->add_command (
251 new MementoCommand<AutomationList> (memento_command_binder(), 0, &alist->get_state())
254 trackview.editor().session()->commit_reversible_command ();
255 trackview.editor().session()->set_dirty ();
259 AutomationLine::reset_line_coords (ControlPoint& cp)
261 if (cp.view_index() < line_points.size()) {
262 line_points[cp.view_index()].set_x (cp.get_x());
263 line_points[cp.view_index()].set_y (cp.get_y());
268 AutomationLine::sync_model_with_view_points (list<ControlPoint*> cp, bool did_push, int64_t distance)
270 update_pending = true;
272 for (list<ControlPoint*>::iterator i = cp.begin(); i != cp.end(); ++i) {
273 sync_model_with_view_point (**i, did_push, distance);
278 AutomationLine::model_representation (ControlPoint& cp, ModelRepresentation& mr)
280 /* part one: find out where the visual control point is.
281 initial results are in canvas units. ask the
282 line to convert them to something relevant.
285 mr.xval = cp.get_x();
286 mr.yval = 1.0 - (cp.get_y() / _height);
288 /* if xval has not changed, set it directly from the model to avoid rounding errors */
290 if (mr.xval == trackview.editor().frame_to_unit(_time_converter.to((*cp.model())->when))) {
291 mr.xval = (*cp.model())->when;
293 mr.xval = trackview.editor().unit_to_frame (mr.xval);
294 mr.xval = _time_converter.from (mr.xval);
297 /* convert y to model units; the x was already done above
300 view_to_model_coord_y (mr.yval);
302 /* part 2: find out where the model point is now
305 mr.xpos = (*cp.model())->when;
306 mr.ypos = (*cp.model())->value;
308 /* part 3: get the position of the visual control
309 points before and after us.
312 ControlPoint* before;
315 if (cp.view_index()) {
316 before = nth (cp.view_index() - 1);
321 after = nth (cp.view_index() + 1);
324 mr.xmin = (*before->model())->when;
325 mr.ymin = (*before->model())->value;
326 mr.start = before->model();
331 mr.start = cp.model();
335 mr.end = after->model();
345 AutomationLine::determine_visible_control_points (ALPoints& points)
347 uint32_t view_index, pi, n;
348 AutomationList::iterator model;
350 uint32_t this_rx = 0;
351 uint32_t prev_rx = 0;
352 uint32_t this_ry = 0;
353 uint32_t prev_ry = 0;
357 /* hide all existing points, and the line */
359 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
365 if (points.empty()) {
369 npoints = points.size();
371 /* compute derivative/slope for the entire line */
373 slope = new double[npoints];
375 for (n = 0; n < npoints - 1; ++n) {
376 double xdelta = points[n+1].x - points[n].x;
377 double ydelta = points[n+1].y - points[n].y;
378 slope[n] = ydelta/xdelta;
381 box_size = (uint32_t) control_point_box_size ();
383 /* read all points and decide which ones to show as control points */
387 for (model = alist->begin(), pi = 0; pi < npoints; ++model, ++pi) {
389 double tx = points[pi].x;
390 double ty = points[pi].y;
392 if (find (_always_in_view.begin(), _always_in_view.end(), (*model)->when) != _always_in_view.end()) {
393 add_visible_control_point (view_index, pi, tx, ty, model, npoints);
400 if (isnan (tx) || isnan (ty)) {
401 warning << string_compose (_("Ignoring illegal points on AutomationLine \"%1\""),
406 /* now ensure that the control_points vector reflects the current curve
407 state, but don't plot control points too close together. also, don't
408 plot a series of points all with the same value.
410 always plot the first and last points, of course.
413 if (invalid_point (points, pi)) {
414 /* for some reason, we are supposed to ignore this point,
415 but still keep track of the model index.
420 if (pi > 0 && pi < npoints - 1) {
421 if (slope[pi] == slope[pi-1]) {
423 /* no reason to display this point */
429 /* need to round here. the ultimate coordinates are integer
430 pixels, so tiny deltas in the coords will be eliminated
431 and we end up with "colinear" line segments. since the
432 line rendering code in libart doesn't like this very
433 much, we eliminate them here. don't do this for the first and last
437 this_rx = (uint32_t) rint (tx);
438 this_ry = (uint32_t) rint (ty);
440 if (view_index && pi != npoints && /* not the first, not the last */
441 (((this_rx == prev_rx) && (this_ry == prev_ry)) || /* same point */
442 (((this_rx - prev_rx) < (box_size + 2)) && /* not identical, but still too close horizontally */
443 (abs ((int)(this_ry - prev_ry)) < (int) (box_size + 2))))) { /* too close vertically */
447 /* ok, we should display this point */
449 add_visible_control_point (view_index, pi, tx, ty, model, npoints);
457 /* discard extra CP's to avoid confusing ourselves */
459 while (control_points.size() > view_index) {
460 ControlPoint* cp = control_points.back();
461 control_points.pop_back ();
465 if (!terminal_points_can_slide) {
466 control_points.back()->set_can_slide(false);
471 if (view_index > 1) {
473 npoints = view_index;
475 /* reset the line coordinates */
477 while (line_points.size() < npoints) {
478 line_points.push_back (Art::Point (0,0));
481 while (line_points.size() > npoints) {
482 line_points.pop_back ();
485 for (view_index = 0; view_index < npoints; ++view_index) {
486 line_points[view_index].set_x (control_points[view_index]->get_x());
487 line_points[view_index].set_y (control_points[view_index]->get_y());
490 line->property_points() = line_points;
492 if (_visible && alist->interpolation() != AutomationList::Discrete) {
498 set_selected_points (trackview.editor().get_selection().points);
502 AutomationLine::get_verbose_cursor_string (double fraction) const
504 std::string s = fraction_to_string (fraction);
505 if (_uses_gain_mapping) {
513 * @param fraction y fraction
514 * @return string representation of this value, using dB if appropriate.
517 AutomationLine::fraction_to_string (double fraction) const
521 if (_uses_gain_mapping) {
522 if (fraction == 0.0) {
523 snprintf (buf, sizeof (buf), "-inf");
525 snprintf (buf, sizeof (buf), "%.1f", accurate_coefficient_to_dB (slider_position_to_gain (fraction)));
529 view_to_model_coord (dummy, fraction);
530 if (EventTypeMap::instance().is_integer (alist->parameter())) {
531 snprintf (buf, sizeof (buf), "%d", (int)fraction);
533 snprintf (buf, sizeof (buf), "%.2f", fraction);
542 * @param s Value string in the form as returned by fraction_to_string.
543 * @return Corresponding y fraction.
546 AutomationLine::string_to_fraction (string const & s) const
553 sscanf (s.c_str(), "%lf", &v);
555 if (_uses_gain_mapping) {
556 v = gain_to_slider_position (dB_to_coefficient (v));
559 model_to_view_coord (dummy, v);
566 AutomationLine::invalid_point (ALPoints& p, uint32_t index)
568 return p[index].x == max_frames && p[index].y == DBL_MAX;
572 AutomationLine::invalidate_point (ALPoints& p, uint32_t index)
574 p[index].x = max_frames;
575 p[index].y = DBL_MAX;
578 /** Start dragging a single point, possibly adding others if the supplied point is selected and there
579 * are other selected points.
581 * @param cp Point to drag.
582 * @param x Initial x position (units).
583 * @param fraction Initial y position (as a fraction of the track height, where 0 is the bottom and 1 the top)
586 AutomationLine::start_drag_single (ControlPoint* cp, double x, float fraction)
588 trackview.editor().session()->begin_reversible_command (_("automation event move"));
589 trackview.editor().session()->add_command (
590 new MementoCommand<AutomationList> (memento_command_binder(), &get_state(), 0)
593 _drag_points.clear ();
594 _drag_points.push_back (cp);
596 if (cp->get_selected ()) {
597 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
598 if (*i != cp && (*i)->get_selected()) {
599 _drag_points.push_back (*i);
604 start_drag_common (x, fraction);
607 /** Start dragging a line vertically (with no change in x)
608 * @param i1 Control point index of the `left' point on the line.
609 * @param i2 Control point index of the `right' point on the line.
610 * @param fraction Initial y position (as a fraction of the track height, where 0 is the bottom and 1 the top)
613 AutomationLine::start_drag_line (uint32_t i1, uint32_t i2, float fraction)
615 trackview.editor().session()->begin_reversible_command (_("automation range move"));
616 trackview.editor().session()->add_command (
617 new MementoCommand<AutomationList> (memento_command_binder (), &get_state(), 0)
620 _drag_points.clear ();
621 for (uint32_t i = i1; i <= i2; i++) {
622 _drag_points.push_back (nth (i));
625 start_drag_common (0, fraction);
628 /** Start dragging multiple points (with no change in x)
629 * @param cp Points to drag.
630 * @param fraction Initial y position (as a fraction of the track height, where 0 is the bottom and 1 the top)
633 AutomationLine::start_drag_multiple (list<ControlPoint*> cp, float fraction, XMLNode* state)
635 trackview.editor().session()->begin_reversible_command (_("automation range move"));
636 trackview.editor().session()->add_command (
637 new MementoCommand<AutomationList> (memento_command_binder(), state, 0)
641 start_drag_common (0, fraction);
645 struct ControlPointSorter
647 bool operator() (ControlPoint const * a, ControlPoint const * b) {
648 return a->get_x() < b->get_x();
652 /** Common parts of starting a drag.
653 * @param x Starting x position in units, or 0 if x is being ignored.
654 * @param fraction Starting y position (as a fraction of the track height, where 0 is the bottom and 1 the top)
657 AutomationLine::start_drag_common (double x, float fraction)
661 _last_drag_fraction = fraction;
662 _drag_had_movement = false;
665 _drag_points.sort (ControlPointSorter ());
667 /* find the additional points that will be dragged when the user is holding
671 uint32_t i = _drag_points.back()->view_index () + 1;
673 _push_points.clear ();
674 while ((p = nth (i)) != 0 && p->can_slide()) {
675 _push_points.push_back (p);
680 /** Should be called to indicate motion during a drag.
681 * @param x New x position of the drag in units, or undefined if ignore_x == true.
682 * @param fraction New y fraction.
683 * @return x position and y fraction that were actually used (once clamped).
686 AutomationLine::drag_motion (double x, float fraction, bool ignore_x, bool with_push)
688 /* setup the points that are to be moved this time round */
689 list<ControlPoint*> points = _drag_points;
691 copy (_push_points.begin(), _push_points.end(), back_inserter (points));
692 points.sort (ControlPointSorter ());
695 double dx = ignore_x ? 0 : (x - _drag_x);
696 double dy = fraction - _last_drag_fraction;
699 ControlPoint* before = 0;
700 ControlPoint* after = 0;
702 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
703 if ((*i)->get_x() < points.front()->get_x()) {
706 if ((*i)->get_x() > points.back()->get_x() && after == 0) {
711 double const before_x = before ? before->get_x() : 0;
712 double const after_x = after ? after->get_x() : DBL_MAX;
715 for (list<ControlPoint*>::iterator i = points.begin(); i != points.end(); ++i) {
717 if ((*i)->can_slide() && !ignore_x) {
720 double const a = (*i)->get_x() + dx;
721 double const b = before_x + 1;
729 if (after_x - before_x < 2) {
730 /* after and before are very close, so just leave this alone */
733 double const a = (*i)->get_x() + dx;
734 double const b = after_x - 1;
744 for (list<ControlPoint*>::iterator i = points.begin(); i != points.end(); ++i) {
745 double const y = ((_height - (*i)->get_y()) / _height) + dy;
754 pair<double, float> const clamped (_drag_x + dx, _last_drag_fraction + dy);
755 _drag_distance += dx;
757 _last_drag_fraction = fraction;
759 for (list<ControlPoint*>::iterator i = _drag_points.begin(); i != _drag_points.end(); ++i) {
760 (*i)->move_to ((*i)->get_x() + dx, (*i)->get_y() - _height * dy, ControlPoint::Full);
761 reset_line_coords (**i);
765 /* move push points, preserving their y */
766 for (list<ControlPoint*>::iterator i = _push_points.begin(); i != _push_points.end(); ++i) {
767 (*i)->move_to ((*i)->get_x() + dx, (*i)->get_y(), ControlPoint::Full);
768 reset_line_coords (**i);
772 if (line_points.size() > 1) {
773 line->property_points() = line_points;
776 _drag_had_movement = true;
777 did_push = with_push;
782 /** Should be called to indicate the end of a drag */
784 AutomationLine::end_drag ()
786 if (!_drag_had_movement) {
792 /* set up the points that were moved this time round */
793 list<ControlPoint*> points = _drag_points;
795 copy (_push_points.begin(), _push_points.end(), back_inserter (points));
796 points.sort (ControlPointSorter ());
799 sync_model_with_view_points (points, did_push, rint (_drag_distance * trackview.editor().get_current_zoom ()));
803 update_pending = false;
805 trackview.editor().session()->add_command (
806 new MementoCommand<AutomationList>(memento_command_binder (), 0, &alist->get_state())
809 trackview.editor().session()->commit_reversible_command ();
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) + _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 add_model_point (tmp_points, (*ai)->when, translated_y);
1082 determine_visible_control_points (tmp_points);
1087 AutomationLine::add_model_point (ALPoints& tmp_points, double frame, double yfract)
1089 tmp_points.push_back (ALPoint (trackview.editor().frame_to_unit (_time_converter.to(frame)),
1090 _height - (yfract * _height)));
1094 AutomationLine::reset ()
1096 update_pending = false;
1102 alist->apply_to_points (*this, &AutomationLine::reset_callback);
1106 AutomationLine::clear ()
1108 /* parent must create and commit command */
1109 XMLNode &before = alist->get_state();
1112 trackview.editor().session()->add_command (
1113 new MementoCommand<AutomationList> (memento_command_binder (), &before, &alist->get_state())
1118 AutomationLine::change_model (AutomationList::iterator /*i*/, double /*x*/, double /*y*/)
1123 AutomationLine::set_list (boost::shared_ptr<ARDOUR::AutomationList> list)
1131 AutomationLine::show_all_control_points ()
1134 // show the line but don't allow any control points
1138 points_visible = true;
1140 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1141 if (!(*i)->visible()) {
1143 (*i)->set_visible (true);
1149 AutomationLine::hide_all_but_selected_control_points ()
1151 if (alist->interpolation() == AutomationList::Discrete) {
1155 points_visible = false;
1157 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1158 if (!(*i)->get_selected()) {
1159 (*i)->set_visible (false);
1165 AutomationLine::track_entered()
1167 if (alist->interpolation() != AutomationList::Discrete) {
1168 show_all_control_points();
1173 AutomationLine::track_exited()
1175 if (alist->interpolation() != AutomationList::Discrete) {
1176 hide_all_but_selected_control_points();
1181 AutomationLine::get_state (void)
1183 /* function as a proxy for the model */
1184 return alist->get_state();
1188 AutomationLine::set_state (const XMLNode &node, int version)
1190 /* function as a proxy for the model */
1191 return alist->set_state (node, version);
1195 AutomationLine::view_to_model_coord (double& x, double& y) const
1197 x = _time_converter.from (x);
1198 view_to_model_coord_y (y);
1202 AutomationLine::view_to_model_coord_y (double& y) const
1204 /* TODO: This should be more generic ... */
1205 if (alist->parameter().type() == GainAutomation ||
1206 alist->parameter().type() == EnvelopeAutomation) {
1207 y = slider_position_to_gain (y);
1210 } else if (alist->parameter().type() == PanAutomation) {
1211 // vertical coordinate axis reversal
1213 } else if (alist->parameter().type() == PluginAutomation) {
1214 y = y * (double)(alist->get_max_y()- alist->get_min_y()) + alist->get_min_y();
1216 y = (int)(y * alist->parameter().max());
1221 AutomationLine::model_to_view_coord (double& x, double& y) const
1223 /* TODO: This should be more generic ... */
1224 if (alist->parameter().type() == GainAutomation ||
1225 alist->parameter().type() == EnvelopeAutomation) {
1226 y = gain_to_slider_position (y);
1227 } else if (alist->parameter().type() == PanAutomation) {
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);
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 (framepos_t t)