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_selectable.h"
45 #include "automation_time_axis.h"
46 #include "public_editor.h"
48 #include "ardour/event_type_map.h"
49 #include "ardour/session.h"
54 using namespace ARDOUR;
56 using namespace Editing;
57 using namespace Gnome; // for Canvas
59 static const Evoral::IdentityConverter<double, sframes_t> default_converter;
61 AutomationLine::AutomationLine (const string& name, TimeAxisView& tv, ArdourCanvas::Group& parent,
62 boost::shared_ptr<AutomationList> al,
63 const Evoral::TimeConverter<double, sframes_t>* converter)
67 , _parent_group (parent)
68 , _time_converter (converter ? (*converter) : default_converter)
70 points_visible = false;
71 update_pending = false;
72 _uses_gain_mapping = false;
75 terminal_points_can_slide = true;
78 group = new ArdourCanvas::Group (parent);
79 group->property_x() = 0.0;
80 group->property_y() = 0.0;
82 line = new ArdourCanvas::Line (*group);
83 line->property_width_pixels() = (guint)1;
84 line->set_data ("line", this);
86 line->signal_event().connect (sigc::mem_fun (*this, &AutomationLine::event_handler));
90 trackview.session()->register_with_memento_command_factory(alist->id(), this);
92 if (alist->parameter().type() == GainAutomation ||
93 alist->parameter().type() == EnvelopeAutomation) {
94 set_uses_gain_mapping (true);
97 interpolation_changed (alist->interpolation ());
102 AutomationLine::~AutomationLine ()
104 vector_delete (&control_points);
109 AutomationLine::event_handler (GdkEvent* event)
111 return PublicEditor::instance().canvas_line_event (event, line, this);
115 AutomationLine::queue_reset ()
117 if (!update_pending) {
118 update_pending = true;
119 Gtkmm2ext::UI::instance()->call_slot (invalidator (*this), boost::bind (&AutomationLine::reset, this));
124 AutomationLine::show ()
126 if (alist->interpolation() != AutomationList::Discrete) {
130 if (points_visible) {
131 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
140 AutomationLine::hide ()
143 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
150 AutomationLine::control_point_box_size ()
152 if (alist->interpolation() == AutomationList::Discrete) {
153 return max((_height*4.0) / (double)(alist->parameter().max() - alist->parameter().min()),
157 if (_height > TimeAxisView::preset_height (HeightLarger)) {
159 } else if (_height > (guint32) TimeAxisView::preset_height (HeightNormal)) {
166 AutomationLine::set_height (guint32 h)
171 double bsz = control_point_box_size();
173 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
174 (*i)->set_size (bsz);
182 AutomationLine::set_line_color (uint32_t color)
185 line->property_fill_color_rgba() = color;
189 AutomationLine::set_uses_gain_mapping (bool yn)
191 if (yn != _uses_gain_mapping) {
192 _uses_gain_mapping = yn;
198 AutomationLine::nth (uint32_t n)
200 if (n < control_points.size()) {
201 return control_points[n];
208 AutomationLine::modify_point_y (ControlPoint& cp, double y)
210 /* clamp y-coord appropriately. y is supposed to be a normalized fraction (0.0-1.0),
211 and needs to be converted to a canvas unit distance.
216 y = _height - (y * _height);
218 double const x = trackview.editor().frame_to_unit (_time_converter.to((*cp.model())->when));
220 trackview.editor().session()->begin_reversible_command (_("automation event move"));
221 trackview.editor().session()->add_command (
222 new MementoCommand<AutomationList> (memento_command_binder(), &get_state(), 0)
225 cp.move_to (x, y, ControlPoint::Full);
227 reset_line_coords (cp);
229 if (line_points.size() > 1) {
230 line->property_points() = line_points;
234 sync_model_with_view_point (cp, false, 0);
237 update_pending = false;
239 trackview.editor().session()->add_command (
240 new MementoCommand<AutomationList> (memento_command_binder(), 0, &alist->get_state())
243 trackview.editor().session()->commit_reversible_command ();
244 trackview.editor().session()->set_dirty ();
248 AutomationLine::reset_line_coords (ControlPoint& cp)
250 if (cp.view_index() < line_points.size()) {
251 line_points[cp.view_index()].set_x (cp.get_x());
252 line_points[cp.view_index()].set_y (cp.get_y());
257 AutomationLine::sync_model_with_view_points (list<ControlPoint*> cp, bool did_push, int64_t distance)
259 update_pending = true;
261 for (list<ControlPoint*>::iterator i = cp.begin(); i != cp.end(); ++i) {
262 sync_model_with_view_point (**i, did_push, distance);
267 AutomationLine::model_representation (ControlPoint& cp, ModelRepresentation& mr)
269 /* part one: find out where the visual control point is.
270 initial results are in canvas units. ask the
271 line to convert them to something relevant.
274 mr.xval = cp.get_x();
275 mr.yval = 1.0 - (cp.get_y() / _height);
277 /* if xval has not changed, set it directly from the model to avoid rounding errors */
279 if (mr.xval == trackview.editor().frame_to_unit(_time_converter.to((*cp.model())->when))) {
280 mr.xval = (*cp.model())->when;
282 mr.xval = trackview.editor().unit_to_frame (mr.xval);
283 mr.xval = _time_converter.from (mr.xval);
286 /* convert y to model units; the x was already done above
289 view_to_model_coord_y (mr.yval);
291 /* part 2: find out where the model point is now
294 mr.xpos = (*cp.model())->when;
295 mr.ypos = (*cp.model())->value;
297 /* part 3: get the position of the visual control
298 points before and after us.
301 ControlPoint* before;
304 if (cp.view_index()) {
305 before = nth (cp.view_index() - 1);
310 after = nth (cp.view_index() + 1);
313 mr.xmin = (*before->model())->when;
314 mr.ymin = (*before->model())->value;
315 mr.start = before->model();
320 mr.start = cp.model();
324 mr.end = after->model();
334 AutomationLine::determine_visible_control_points (ALPoints& points)
336 uint32_t view_index, pi, n;
337 AutomationList::iterator model;
339 uint32_t this_rx = 0;
340 uint32_t prev_rx = 0;
341 uint32_t this_ry = 0;
342 uint32_t prev_ry = 0;
346 /* hide all existing points, and the line */
348 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
354 if (points.empty()) {
358 npoints = points.size();
360 /* compute derivative/slope for the entire line */
362 slope = new double[npoints];
364 for (n = 0; n < npoints - 1; ++n) {
365 double xdelta = points[n+1].x - points[n].x;
366 double ydelta = points[n+1].y - points[n].y;
367 slope[n] = ydelta/xdelta;
370 box_size = (uint32_t) control_point_box_size ();
372 /* read all points and decide which ones to show as control points */
376 for (model = alist->begin(), pi = 0; pi < npoints; ++model, ++pi) {
378 double tx = points[pi].x;
379 double ty = points[pi].y;
381 if (find (_always_in_view.begin(), _always_in_view.end(), (*model)->when) != _always_in_view.end()) {
382 add_visible_control_point (view_index, pi, tx, ty, model, npoints);
389 if (isnan (tx) || isnan (ty)) {
390 warning << string_compose (_("Ignoring illegal points on AutomationLine \"%1\""),
395 /* now ensure that the control_points vector reflects the current curve
396 state, but don't plot control points too close together. also, don't
397 plot a series of points all with the same value.
399 always plot the first and last points, of course.
402 if (invalid_point (points, pi)) {
403 /* for some reason, we are supposed to ignore this point,
404 but still keep track of the model index.
409 if (pi > 0 && pi < npoints - 1) {
410 if (slope[pi] == slope[pi-1]) {
412 /* no reason to display this point */
418 /* need to round here. the ultimate coordinates are integer
419 pixels, so tiny deltas in the coords will be eliminated
420 and we end up with "colinear" line segments. since the
421 line rendering code in libart doesn't like this very
422 much, we eliminate them here. don't do this for the first and last
426 this_rx = (uint32_t) rint (tx);
427 this_ry = (uint32_t) rint (ty);
429 if (view_index && pi != npoints && /* not the first, not the last */
430 (((this_rx == prev_rx) && (this_ry == prev_ry)) || /* same point */
431 (((this_rx - prev_rx) < (box_size + 2)) && /* not identical, but still too close horizontally */
432 (abs ((int)(this_ry - prev_ry)) < (int) (box_size + 2))))) { /* too close vertically */
436 /* ok, we should display this point */
438 add_visible_control_point (view_index, pi, tx, ty, model, npoints);
446 /* discard extra CP's to avoid confusing ourselves */
448 while (control_points.size() > view_index) {
449 ControlPoint* cp = control_points.back();
450 control_points.pop_back ();
454 if (!terminal_points_can_slide) {
455 control_points.back()->set_can_slide(false);
460 if (view_index > 1) {
462 npoints = view_index;
464 /* reset the line coordinates */
466 while (line_points.size() < npoints) {
467 line_points.push_back (Art::Point (0,0));
470 while (line_points.size() > npoints) {
471 line_points.pop_back ();
474 for (view_index = 0; view_index < npoints; ++view_index) {
475 line_points[view_index].set_x (control_points[view_index]->get_x());
476 line_points[view_index].set_y (control_points[view_index]->get_y());
479 line->property_points() = line_points;
481 if (_visible && alist->interpolation() != AutomationList::Discrete) {
487 set_selected_points (trackview.editor().get_selection().points);
491 AutomationLine::get_verbose_cursor_string (double fraction) const
493 std::string s = fraction_to_string (fraction);
494 if (_uses_gain_mapping) {
502 * @param fraction y fraction
503 * @return string representation of this value, using dB if appropriate.
506 AutomationLine::fraction_to_string (double fraction) const
510 if (_uses_gain_mapping) {
511 if (fraction == 0.0) {
512 snprintf (buf, sizeof (buf), "-inf");
514 snprintf (buf, sizeof (buf), "%.1f", accurate_coefficient_to_dB (slider_position_to_gain (fraction)));
518 view_to_model_coord (dummy, fraction);
519 if (EventTypeMap::instance().is_integer (alist->parameter())) {
520 snprintf (buf, sizeof (buf), "%d", (int)fraction);
522 snprintf (buf, sizeof (buf), "%.2f", fraction);
531 * @param s Value string in the form as returned by fraction_to_string.
532 * @return Corresponding y fraction.
535 AutomationLine::string_to_fraction (string const & s) const
542 sscanf (s.c_str(), "%lf", &v);
544 if (_uses_gain_mapping) {
545 v = gain_to_slider_position (dB_to_coefficient (v));
548 model_to_view_coord (dummy, v);
555 AutomationLine::invalid_point (ALPoints& p, uint32_t index)
557 return p[index].x == max_frames && p[index].y == DBL_MAX;
561 AutomationLine::invalidate_point (ALPoints& p, uint32_t index)
563 p[index].x = max_frames;
564 p[index].y = DBL_MAX;
567 /** Start dragging a single point, possibly adding others if the supplied point is selected and there
568 * are other selected points.
570 * @param cp Point to drag.
571 * @param x Initial x position (units).
572 * @param fraction Initial y position (as a fraction of the track height, where 0 is the bottom and 1 the top)
575 AutomationLine::start_drag_single (ControlPoint* cp, double x, float fraction)
577 trackview.editor().session()->begin_reversible_command (_("automation event move"));
578 trackview.editor().session()->add_command (
579 new MementoCommand<AutomationList> (memento_command_binder(), &get_state(), 0)
582 _drag_points.clear ();
583 _drag_points.push_back (cp);
585 if (cp->selected ()) {
586 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
587 if (*i != cp && (*i)->selected()) {
588 _drag_points.push_back (*i);
593 start_drag_common (x, fraction);
596 /** Start dragging a line vertically (with no change in x)
597 * @param i1 Control point index of the `left' point on the line.
598 * @param i2 Control point index of the `right' point on the line.
599 * @param fraction Initial y position (as a fraction of the track height, where 0 is the bottom and 1 the top)
602 AutomationLine::start_drag_line (uint32_t i1, uint32_t i2, float fraction)
604 trackview.editor().session()->begin_reversible_command (_("automation range move"));
605 trackview.editor().session()->add_command (
606 new MementoCommand<AutomationList> (memento_command_binder (), &get_state(), 0)
609 _drag_points.clear ();
610 for (uint32_t i = i1; i <= i2; i++) {
611 _drag_points.push_back (nth (i));
614 start_drag_common (0, fraction);
617 /** Start dragging multiple points (with no change in x)
618 * @param cp Points to drag.
619 * @param fraction Initial y position (as a fraction of the track height, where 0 is the bottom and 1 the top)
622 AutomationLine::start_drag_multiple (list<ControlPoint*> cp, float fraction, XMLNode* state)
624 trackview.editor().session()->begin_reversible_command (_("automation range move"));
625 trackview.editor().session()->add_command (
626 new MementoCommand<AutomationList> (memento_command_binder(), state, 0)
630 start_drag_common (0, fraction);
634 struct ControlPointSorter
636 bool operator() (ControlPoint const * a, ControlPoint const * b) {
637 return a->get_x() < b->get_x();
641 /** Common parts of starting a drag.
642 * @param x Starting x position in units, or 0 if x is being ignored.
643 * @param fraction Starting y position (as a fraction of the track height, where 0 is the bottom and 1 the top)
646 AutomationLine::start_drag_common (double x, float fraction)
650 _last_drag_fraction = fraction;
651 _drag_had_movement = false;
654 _drag_points.sort (ControlPointSorter ());
656 /* find the additional points that will be dragged when the user is holding
660 uint32_t i = _drag_points.back()->view_index () + 1;
662 _push_points.clear ();
663 while ((p = nth (i)) != 0 && p->can_slide()) {
664 _push_points.push_back (p);
669 /** Should be called to indicate motion during a drag.
670 * @param x New x position of the drag in units, or undefined if ignore_x == true.
671 * @param fraction New y fraction.
672 * @return x position and y fraction that were actually used (once clamped).
675 AutomationLine::drag_motion (double x, float fraction, bool ignore_x, bool with_push)
677 /* setup the points that are to be moved this time round */
678 list<ControlPoint*> points = _drag_points;
680 copy (_push_points.begin(), _push_points.end(), back_inserter (points));
681 points.sort (ControlPointSorter ());
684 double dx = ignore_x ? 0 : (x - _drag_x);
685 double dy = fraction - _last_drag_fraction;
688 ControlPoint* before = 0;
689 ControlPoint* after = 0;
691 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
692 if ((*i)->get_x() < points.front()->get_x()) {
695 if ((*i)->get_x() > points.back()->get_x() && after == 0) {
700 double const before_x = before ? before->get_x() : 0;
701 double const after_x = after ? after->get_x() : DBL_MAX;
704 for (list<ControlPoint*>::iterator i = points.begin(); i != points.end(); ++i) {
706 if ((*i)->can_slide() && !ignore_x) {
709 double const a = (*i)->get_x() + dx;
710 double const b = before_x + 1;
718 if (after_x - before_x < 2) {
719 /* after and before are very close, so just leave this alone */
722 double const a = (*i)->get_x() + dx;
723 double const b = after_x - 1;
733 for (list<ControlPoint*>::iterator i = points.begin(); i != points.end(); ++i) {
734 double const y = ((_height - (*i)->get_y()) / _height) + dy;
743 pair<double, float> const clamped (_drag_x + dx, _last_drag_fraction + dy);
744 _drag_distance += dx;
746 _last_drag_fraction = fraction;
748 for (list<ControlPoint*>::iterator i = _drag_points.begin(); i != _drag_points.end(); ++i) {
749 (*i)->move_to ((*i)->get_x() + dx, (*i)->get_y() - _height * dy, ControlPoint::Full);
750 reset_line_coords (**i);
754 /* move push points, preserving their y */
755 for (list<ControlPoint*>::iterator i = _push_points.begin(); i != _push_points.end(); ++i) {
756 (*i)->move_to ((*i)->get_x() + dx, (*i)->get_y(), ControlPoint::Full);
757 reset_line_coords (**i);
761 if (line_points.size() > 1) {
762 line->property_points() = line_points;
765 _drag_had_movement = true;
766 did_push = with_push;
771 /** Should be called to indicate the end of a drag */
773 AutomationLine::end_drag ()
775 if (!_drag_had_movement) {
781 /* set up the points that were moved this time round */
782 list<ControlPoint*> points = _drag_points;
784 copy (_push_points.begin(), _push_points.end(), back_inserter (points));
785 points.sort (ControlPointSorter ());
788 sync_model_with_view_points (points, did_push, rint (_drag_distance * trackview.editor().get_current_zoom ()));
792 update_pending = false;
794 trackview.editor().session()->add_command (
795 new MementoCommand<AutomationList>(memento_command_binder (), 0, &alist->get_state())
798 trackview.editor().session()->commit_reversible_command ();
799 trackview.editor().session()->set_dirty ();
803 AutomationLine::sync_model_with_view_point (ControlPoint& cp, bool did_push, int64_t distance)
805 ModelRepresentation mr;
808 model_representation (cp, mr);
810 /* how much are we changing the central point by */
812 ydelta = mr.yval - mr.ypos;
815 apply the full change to the central point, and interpolate
816 on both axes to cover all model points represented
817 by the control point.
820 /* change all points before the primary point */
822 for (AutomationList::iterator i = mr.start; i != cp.model(); ++i) {
824 double fract = ((*i)->when - mr.xmin) / (mr.xpos - mr.xmin);
825 double y_delta = ydelta * fract;
826 double x_delta = distance * fract;
830 if (y_delta || x_delta) {
831 alist->modify (i, (*i)->when + x_delta, mr.ymin + y_delta);
835 /* change the primary point */
837 update_pending = true;
838 alist->modify (cp.model(), mr.xval, mr.yval);
840 /* change later points */
842 AutomationList::iterator i = cp.model();
846 while (i != mr.end) {
848 double delta = ydelta * (mr.xmax - (*i)->when) / (mr.xmax - mr.xpos);
850 /* all later points move by the same distance along the x-axis as the main point */
853 alist->modify (i, (*i)->when + distance, (*i)->value + delta);
861 /* move all points after the range represented by the view by the same distance
862 as the main point moved.
865 alist->slide (mr.end, distance);
870 AutomationLine::control_points_adjacent (double xval, uint32_t & before, uint32_t& after)
872 ControlPoint *bcp = 0;
873 ControlPoint *acp = 0;
876 unit_xval = trackview.editor().frame_to_unit (xval);
878 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
880 if ((*i)->get_x() <= unit_xval) {
882 if (!bcp || (*i)->get_x() > bcp->get_x()) {
884 before = bcp->view_index();
887 } else if ((*i)->get_x() > unit_xval) {
889 after = acp->view_index();
898 AutomationLine::is_last_point (ControlPoint& cp)
900 ModelRepresentation mr;
902 model_representation (cp, mr);
904 // If the list is not empty, and the point is the last point in the list
906 if (!alist->empty() && mr.end == alist->end()) {
914 AutomationLine::is_first_point (ControlPoint& cp)
916 ModelRepresentation mr;
918 model_representation (cp, mr);
920 // If the list is not empty, and the point is the first point in the list
922 if (!alist->empty() && mr.start == alist->begin()) {
929 // This is copied into AudioRegionGainLine
931 AutomationLine::remove_point (ControlPoint& cp)
933 ModelRepresentation mr;
935 model_representation (cp, mr);
937 trackview.editor().session()->begin_reversible_command (_("remove control point"));
938 XMLNode &before = alist->get_state();
940 alist->erase (mr.start, mr.end);
942 trackview.editor().session()->add_command(
943 new MementoCommand<AutomationList> (memento_command_binder (), &before, &alist->get_state())
946 trackview.editor().session()->commit_reversible_command ();
947 trackview.editor().session()->set_dirty ();
950 /** Get selectable points within an area.
951 * @param start Start position in session frames.
952 * @param end End position in session frames.
953 * @param bot Bottom y range, as a fraction of line height, where 0 is the bottom of the line.
954 * @param top Top y range, as a fraction of line height, where 0 is the bottom of the line.
955 * @param result Filled in with selectable things.
958 AutomationLine::get_selectables (
959 framepos_t start, framepos_t end, double botfrac, double topfrac, list<Selectable*>& results
962 /* these two are in AutomationList model coordinates */
966 bool collecting = false;
968 /* convert fractions to display coordinates with 0 at the top of the track */
969 double const bot_track = (1 - topfrac) * trackview.current_height ();
970 double const top_track = (1 - botfrac) * trackview.current_height ();
975 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
976 double const model_when = (*(*i)->model())->when;
977 framepos_t const session_frames_when = _time_converter.to (model_when) + _time_converter.origin_b ();
979 if (session_frames_when >= start && session_frames_when <= end) {
981 if ((*i)->get_y() >= bot_track && (*i)->get_y() <= top_track) {
984 (*i)->set_visible(true);
986 nstart = min (nstart, model_when);
987 nend = max (nend, model_when);
993 AutomationSelectable* s = new AutomationSelectable (nstart, nend, botfrac, topfrac, &trackview);
994 PointSelection& ps = trackview.editor().get_selection().points;
995 if (find (ps.begin(), ps.end(), *s) != ps.end()) {
996 s->set_selected (true);
999 results.push_back (s);
1009 AutomationSelectable* s = new AutomationSelectable (nstart, nend, botfrac, topfrac, &trackview);
1011 PointSelection& ps = trackview.editor().get_selection().points;
1012 if (find (ps.begin(), ps.end(), *s) != ps.end()) {
1013 s->set_selected (true);
1016 results.push_back (s);
1022 AutomationLine::get_inverted_selectables (Selection&, list<Selectable*>& /*results*/)
1027 /** Take a PointSelection and find ControlPoints that fall within it */
1029 AutomationLine::point_selection_to_control_points (PointSelection const & s)
1031 list<ControlPoint*> cp;
1033 for (PointSelection::const_iterator i = s.begin(); i != s.end(); ++i) {
1035 if (i->track != &trackview) {
1039 double const bot = (1 - i->high_fract) * trackview.current_height ();
1040 double const top = (1 - i->low_fract) * trackview.current_height ();
1042 for (vector<ControlPoint*>::iterator j = control_points.begin(); j != control_points.end(); ++j) {
1044 double const rstart = trackview.editor().frame_to_unit (_time_converter.to (i->start));
1045 double const rend = trackview.editor().frame_to_unit (_time_converter.to (i->end));
1047 if ((*j)->get_x() >= rstart && (*j)->get_x() <= rend) {
1048 if ((*j)->get_y() >= bot && (*j)->get_y() <= top) {
1060 AutomationLine::set_selected_points (PointSelection& points)
1062 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1063 (*i)->set_selected (false);
1066 if (!points.empty()) {
1067 list<ControlPoint*> cp = point_selection_to_control_points (points);
1068 for (list<ControlPoint*>::iterator i = cp.begin(); i != cp.end(); ++i) {
1069 (*i)->set_selected (true);
1076 void AutomationLine::set_colors ()
1078 set_line_color (ARDOUR_UI::config()->canvasvar_AutomationLine.get());
1079 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1085 AutomationLine::list_changed ()
1091 AutomationLine::reset_callback (const Evoral::ControlList& events)
1093 ALPoints tmp_points;
1094 uint32_t npoints = events.size();
1097 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1100 control_points.clear ();
1105 AutomationList::const_iterator ai;
1107 for (ai = events.begin(); ai != events.end(); ++ai) {
1109 double translated_x = (*ai)->when;
1110 double translated_y = (*ai)->value;
1111 model_to_view_coord (translated_x, translated_y);
1113 add_model_point (tmp_points, (*ai)->when, translated_y);
1116 determine_visible_control_points (tmp_points);
1121 AutomationLine::add_model_point (ALPoints& tmp_points, double frame, double yfract)
1123 tmp_points.push_back (ALPoint (trackview.editor().frame_to_unit (_time_converter.to(frame)),
1124 _height - (yfract * _height)));
1128 AutomationLine::reset ()
1130 update_pending = false;
1136 alist->apply_to_points (*this, &AutomationLine::reset_callback);
1140 AutomationLine::clear ()
1142 /* parent must create and commit command */
1143 XMLNode &before = alist->get_state();
1146 trackview.editor().session()->add_command (
1147 new MementoCommand<AutomationList> (memento_command_binder (), &before, &alist->get_state())
1152 AutomationLine::change_model (AutomationList::iterator /*i*/, double /*x*/, double /*y*/)
1157 AutomationLine::set_list (boost::shared_ptr<ARDOUR::AutomationList> list)
1165 AutomationLine::show_all_control_points ()
1167 points_visible = true;
1169 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1170 if (!(*i)->visible()) {
1172 (*i)->set_visible (true);
1178 AutomationLine::hide_all_but_selected_control_points ()
1180 if (alist->interpolation() == AutomationList::Discrete) {
1184 points_visible = false;
1186 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1187 if (!(*i)->selected()) {
1188 (*i)->set_visible (false);
1194 AutomationLine::track_entered()
1196 if (alist->interpolation() != AutomationList::Discrete) {
1197 show_all_control_points();
1202 AutomationLine::track_exited()
1204 if (alist->interpolation() != AutomationList::Discrete) {
1205 hide_all_but_selected_control_points();
1210 AutomationLine::get_state (void)
1212 /* function as a proxy for the model */
1213 return alist->get_state();
1217 AutomationLine::set_state (const XMLNode &node, int version)
1219 /* function as a proxy for the model */
1220 return alist->set_state (node, version);
1224 AutomationLine::view_to_model_coord (double& x, double& y) const
1226 x = _time_converter.from (x);
1227 view_to_model_coord_y (y);
1231 AutomationLine::view_to_model_coord_y (double& y) const
1233 /* TODO: This should be more generic ... */
1234 if (alist->parameter().type() == GainAutomation ||
1235 alist->parameter().type() == EnvelopeAutomation) {
1236 y = slider_position_to_gain (y);
1239 } else if (alist->parameter().type() == PanAutomation) {
1240 // vertical coordinate axis reversal
1242 } else if (alist->parameter().type() == PluginAutomation) {
1243 y = y * (double)(alist->get_max_y()- alist->get_min_y()) + alist->get_min_y();
1245 y = (int)(y * alist->parameter().max());
1250 AutomationLine::model_to_view_coord (double& x, double& y) const
1252 /* TODO: This should be more generic ... */
1253 if (alist->parameter().type() == GainAutomation ||
1254 alist->parameter().type() == EnvelopeAutomation) {
1255 y = gain_to_slider_position (y);
1256 } else if (alist->parameter().type() == PanAutomation) {
1257 // vertical coordinate axis reversal
1259 } else if (alist->parameter().type() == PluginAutomation) {
1260 y = (y - alist->get_min_y()) / (double)(alist->get_max_y()- alist->get_min_y());
1262 y = y / (double)alist->parameter().max(); /* ... like this */
1265 x = _time_converter.to(x);
1268 /** Called when our list has announced that its interpolation style has changed */
1270 AutomationLine::interpolation_changed (AutomationList::InterpolationStyle style)
1272 if (style == AutomationList::Discrete) {
1273 show_all_control_points();
1276 hide_all_but_selected_control_points();
1282 AutomationLine::add_visible_control_point (uint32_t view_index, uint32_t pi, double tx, double ty, AutomationList::iterator model, uint32_t npoints)
1284 if (view_index >= control_points.size()) {
1286 /* make sure we have enough control points */
1288 ControlPoint* ncp = new ControlPoint (*this);
1289 ncp->set_size (control_point_box_size ());
1291 control_points.push_back (ncp);
1294 ControlPoint::ShapeType shape;
1296 if (!terminal_points_can_slide) {
1298 control_points[view_index]->set_can_slide(false);
1300 shape = ControlPoint::Start;
1302 shape = ControlPoint::Full;
1304 } else if (pi == npoints - 1) {
1305 control_points[view_index]->set_can_slide(false);
1306 shape = ControlPoint::End;
1308 control_points[view_index]->set_can_slide(true);
1309 shape = ControlPoint::Full;
1312 control_points[view_index]->set_can_slide(true);
1313 shape = ControlPoint::Full;
1316 control_points[view_index]->reset (tx, ty, model, view_index, shape);
1318 /* finally, control visibility */
1320 if (_visible && points_visible) {
1321 control_points[view_index]->show ();
1322 control_points[view_index]->set_visible (true);
1324 if (!points_visible) {
1325 control_points[view_index]->set_visible (false);
1331 AutomationLine::add_always_in_view (double x)
1333 _always_in_view.push_back (x);
1334 alist->apply_to_points (*this, &AutomationLine::reset_callback);
1338 AutomationLine::clear_always_in_view ()
1340 _always_in_view.clear ();
1341 alist->apply_to_points (*this, &AutomationLine::reset_callback);
1345 AutomationLine::connect_to_list ()
1347 _list_connections.drop_connections ();
1349 alist->StateChanged.connect (_list_connections, invalidator (*this), boost::bind (&AutomationLine::list_changed, this), gui_context());
1351 alist->InterpolationChanged.connect (
1352 _list_connections, invalidator (*this), boost::bind (&AutomationLine::interpolation_changed, this, _1), gui_context()
1356 MementoCommandBinder<AutomationList>*
1357 AutomationLine::memento_command_binder ()
1359 return new SimpleMementoCommandBinder<AutomationList> (*alist.get());