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);
226 reset_line_coords (cp);
228 if (line_points.size() > 1) {
229 line->property_points() = line_points;
233 sync_model_with_view_point (cp, false, 0);
236 update_pending = false;
238 trackview.editor().session()->add_command (
239 new MementoCommand<AutomationList> (memento_command_binder(), 0, &alist->get_state())
242 trackview.editor().session()->commit_reversible_command ();
243 trackview.editor().session()->set_dirty ();
247 AutomationLine::reset_line_coords (ControlPoint& cp)
249 if (cp.view_index() < line_points.size()) {
250 line_points[cp.view_index()].set_x (cp.get_x());
251 line_points[cp.view_index()].set_y (cp.get_y());
256 AutomationLine::sync_model_with_view_points (list<ControlPoint*> cp, bool did_push, int64_t distance)
258 update_pending = true;
260 for (list<ControlPoint*>::iterator i = cp.begin(); i != cp.end(); ++i) {
261 sync_model_with_view_point (**i, did_push, distance);
266 AutomationLine::model_representation (ControlPoint& cp, ModelRepresentation& mr)
268 /* part one: find out where the visual control point is.
269 initial results are in canvas units. ask the
270 line to convert them to something relevant.
273 mr.xval = cp.get_x();
274 mr.yval = 1.0 - (cp.get_y() / _height);
276 /* if xval has not changed, set it directly from the model to avoid rounding errors */
278 if (mr.xval == trackview.editor().frame_to_unit(_time_converter.to((*cp.model())->when))) {
279 mr.xval = (*cp.model())->when;
281 mr.xval = trackview.editor().unit_to_frame (mr.xval);
284 /* convert to model units
287 view_to_model_coord (mr.xval, mr.yval);
289 /* part 2: find out where the model point is now
292 mr.xpos = (*cp.model())->when;
293 mr.ypos = (*cp.model())->value;
295 /* part 3: get the position of the visual control
296 points before and after us.
299 ControlPoint* before;
302 if (cp.view_index()) {
303 before = nth (cp.view_index() - 1);
308 after = nth (cp.view_index() + 1);
311 mr.xmin = (*before->model())->when;
312 mr.ymin = (*before->model())->value;
313 mr.start = before->model();
318 mr.start = cp.model();
322 mr.end = after->model();
332 AutomationLine::determine_visible_control_points (ALPoints& points)
334 uint32_t view_index, pi, n;
335 AutomationList::iterator model;
337 uint32_t this_rx = 0;
338 uint32_t prev_rx = 0;
339 uint32_t this_ry = 0;
340 uint32_t prev_ry = 0;
344 /* hide all existing points, and the line */
346 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
352 if (points.empty()) {
356 npoints = points.size();
358 /* compute derivative/slope for the entire line */
360 slope = new double[npoints];
362 for (n = 0; n < npoints - 1; ++n) {
363 double xdelta = points[n+1].x - points[n].x;
364 double ydelta = points[n+1].y - points[n].y;
365 slope[n] = ydelta/xdelta;
368 box_size = (uint32_t) control_point_box_size ();
370 /* read all points and decide which ones to show as control points */
374 for (model = alist->begin(), pi = 0; pi < npoints; ++model, ++pi) {
376 double tx = points[pi].x;
377 double ty = points[pi].y;
379 if (find (_always_in_view.begin(), _always_in_view.end(), (*model)->when) != _always_in_view.end()) {
380 add_visible_control_point (view_index, pi, tx, ty, model, npoints);
387 if (isnan (tx) || isnan (ty)) {
388 warning << string_compose (_("Ignoring illegal points on AutomationLine \"%1\""),
393 /* now ensure that the control_points vector reflects the current curve
394 state, but don't plot control points too close together. also, don't
395 plot a series of points all with the same value.
397 always plot the first and last points, of course.
400 if (invalid_point (points, pi)) {
401 /* for some reason, we are supposed to ignore this point,
402 but still keep track of the model index.
407 if (pi > 0 && pi < npoints - 1) {
408 if (slope[pi] == slope[pi-1]) {
410 /* no reason to display this point */
416 /* need to round here. the ultimate coordinates are integer
417 pixels, so tiny deltas in the coords will be eliminated
418 and we end up with "colinear" line segments. since the
419 line rendering code in libart doesn't like this very
420 much, we eliminate them here. don't do this for the first and last
424 this_rx = (uint32_t) rint (tx);
425 this_ry = (uint32_t) rint (ty);
427 if (view_index && pi != npoints && /* not the first, not the last */
428 (((this_rx == prev_rx) && (this_ry == prev_ry)) || /* same point */
429 (((this_rx - prev_rx) < (box_size + 2)) && /* not identical, but still too close horizontally */
430 (abs ((int)(this_ry - prev_ry)) < (int) (box_size + 2))))) { /* too close vertically */
434 /* ok, we should display this point */
436 add_visible_control_point (view_index, pi, tx, ty, model, npoints);
444 /* discard extra CP's to avoid confusing ourselves */
446 while (control_points.size() > view_index) {
447 ControlPoint* cp = control_points.back();
448 control_points.pop_back ();
452 if (!terminal_points_can_slide) {
453 control_points.back()->set_can_slide(false);
458 if (view_index > 1) {
460 npoints = view_index;
462 /* reset the line coordinates */
464 while (line_points.size() < npoints) {
465 line_points.push_back (Art::Point (0,0));
468 while (line_points.size() > npoints) {
469 line_points.pop_back ();
472 for (view_index = 0; view_index < npoints; ++view_index) {
473 line_points[view_index].set_x (control_points[view_index]->get_x());
474 line_points[view_index].set_y (control_points[view_index]->get_y());
477 line->property_points() = line_points;
479 if (_visible && alist->interpolation() != AutomationList::Discrete) {
485 set_selected_points (trackview.editor().get_selection().points);
489 AutomationLine::get_verbose_cursor_string (double fraction) const
491 std::string s = fraction_to_string (fraction);
492 if (_uses_gain_mapping) {
500 * @param fraction y fraction
501 * @return string representation of this value, using dB if appropriate.
504 AutomationLine::fraction_to_string (double fraction) const
508 if (_uses_gain_mapping) {
509 if (fraction == 0.0) {
510 snprintf (buf, sizeof (buf), "-inf");
512 snprintf (buf, sizeof (buf), "%.1f", accurate_coefficient_to_dB (slider_position_to_gain (fraction)));
516 view_to_model_coord (dummy, fraction);
517 if (EventTypeMap::instance().is_integer (alist->parameter())) {
518 snprintf (buf, sizeof (buf), "%d", (int)fraction);
520 snprintf (buf, sizeof (buf), "%.2f", fraction);
529 * @param s Value string in the form as returned by fraction_to_string.
530 * @return Corresponding y fraction.
533 AutomationLine::string_to_fraction (string const & s) const
540 sscanf (s.c_str(), "%lf", &v);
542 if (_uses_gain_mapping) {
543 v = gain_to_slider_position (dB_to_coefficient (v));
546 model_to_view_coord (dummy, v);
553 AutomationLine::invalid_point (ALPoints& p, uint32_t index)
555 return p[index].x == max_frames && p[index].y == DBL_MAX;
559 AutomationLine::invalidate_point (ALPoints& p, uint32_t index)
561 p[index].x = max_frames;
562 p[index].y = DBL_MAX;
565 /** Start dragging a single point, possibly adding others if the supplied point is selected and there
566 * are other selected points.
568 * @param cp Point to drag.
569 * @param x Initial x position (units).
570 * @param fraction Initial y position (as a fraction of the track height, where 0 is the bottom and 1 the top)
573 AutomationLine::start_drag_single (ControlPoint* cp, double x, float fraction)
575 trackview.editor().session()->begin_reversible_command (_("automation event move"));
576 trackview.editor().session()->add_command (
577 new MementoCommand<AutomationList> (memento_command_binder(), &get_state(), 0)
580 _drag_points.clear ();
581 _drag_points.push_back (cp);
583 if (cp->selected ()) {
584 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
585 if (*i != cp && (*i)->selected()) {
586 _drag_points.push_back (*i);
591 start_drag_common (x, fraction);
594 /** Start dragging a line vertically (with no change in x)
595 * @param i1 Control point index of the `left' point on the line.
596 * @param i2 Control point index of the `right' point on the line.
597 * @param fraction Initial y position (as a fraction of the track height, where 0 is the bottom and 1 the top)
600 AutomationLine::start_drag_line (uint32_t i1, uint32_t i2, float fraction)
602 trackview.editor().session()->begin_reversible_command (_("automation range move"));
603 trackview.editor().session()->add_command (
604 new MementoCommand<AutomationList> (memento_command_binder (), &get_state(), 0)
607 _drag_points.clear ();
608 for (uint32_t i = i1; i <= i2; i++) {
609 _drag_points.push_back (nth (i));
612 start_drag_common (0, fraction);
615 /** Start dragging multiple points (with no change in x)
616 * @param cp Points to drag.
617 * @param fraction Initial y position (as a fraction of the track height, where 0 is the bottom and 1 the top)
620 AutomationLine::start_drag_multiple (list<ControlPoint*> cp, float fraction, XMLNode* state)
622 trackview.editor().session()->begin_reversible_command (_("automation range move"));
623 trackview.editor().session()->add_command (
624 new MementoCommand<AutomationList> (memento_command_binder(), state, 0)
628 start_drag_common (0, fraction);
632 struct ControlPointSorter
634 bool operator() (ControlPoint const * a, ControlPoint const * b) {
635 return a->get_x() < b->get_x();
639 /** Common parts of starting a drag.
640 * @param x Starting x position in units, or 0 if x is being ignored.
641 * @param fraction Starting y position (as a fraction of the track height, where 0 is the bottom and 1 the top)
644 AutomationLine::start_drag_common (double x, float fraction)
648 _last_drag_fraction = fraction;
649 _drag_had_movement = false;
652 _drag_points.sort (ControlPointSorter ());
654 /* find the additional points that will be dragged when the user is holding
658 uint32_t i = _drag_points.back()->view_index () + 1;
660 _push_points.clear ();
661 while ((p = nth (i)) != 0 && p->can_slide()) {
662 _push_points.push_back (p);
667 /** Should be called to indicate motion during a drag.
668 * @param x New x position of the drag in units, or undefined if ignore_x == true.
669 * @param fraction New y fraction.
670 * @return x position and y fraction that were actually used (once clamped).
673 AutomationLine::drag_motion (double x, float fraction, bool ignore_x, bool with_push)
675 /* setup the points that are to be moved this time round */
676 list<ControlPoint*> points = _drag_points;
678 copy (_push_points.begin(), _push_points.end(), back_inserter (points));
679 points.sort (ControlPointSorter ());
682 double dx = ignore_x ? 0 : (x - _drag_x);
683 double dy = fraction - _last_drag_fraction;
686 ControlPoint* before = 0;
687 ControlPoint* after = 0;
689 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
690 if ((*i)->get_x() < points.front()->get_x()) {
693 if ((*i)->get_x() > points.back()->get_x() && after == 0) {
698 double const before_x = before ? before->get_x() : 0;
699 double const after_x = after ? after->get_x() : DBL_MAX;
702 for (list<ControlPoint*>::iterator i = points.begin(); i != points.end(); ++i) {
704 if ((*i)->can_slide() && !ignore_x) {
707 double const a = (*i)->get_x() + dx;
708 double const b = before_x + 1;
716 if (after_x - before_x < 2) {
717 /* after and before are very close, so just leave this alone */
720 double const a = (*i)->get_x() + dx;
721 double const b = after_x - 1;
731 for (list<ControlPoint*>::iterator i = points.begin(); i != points.end(); ++i) {
732 double const y = ((_height - (*i)->get_y()) / _height) + dy;
741 pair<double, float> const clamped (_drag_x + dx, _last_drag_fraction + dy);
742 _drag_distance += dx;
744 _last_drag_fraction = fraction;
746 for (list<ControlPoint*>::iterator i = _drag_points.begin(); i != _drag_points.end(); ++i) {
747 (*i)->move_to ((*i)->get_x() + dx, (*i)->get_y() - _height * dy, ControlPoint::Full);
748 reset_line_coords (**i);
752 /* move push points, preserving their y */
753 for (list<ControlPoint*>::iterator i = _push_points.begin(); i != _push_points.end(); ++i) {
754 (*i)->move_to ((*i)->get_x() + dx, (*i)->get_y(), ControlPoint::Full);
755 reset_line_coords (**i);
759 if (line_points.size() > 1) {
760 line->property_points() = line_points;
763 _drag_had_movement = true;
764 did_push = with_push;
769 /** Should be called to indicate the end of a drag */
771 AutomationLine::end_drag ()
773 if (!_drag_had_movement) {
779 /* set up the points that were moved this time round */
780 list<ControlPoint*> points = _drag_points;
782 copy (_push_points.begin(), _push_points.end(), back_inserter (points));
783 points.sort (ControlPointSorter ());
786 sync_model_with_view_points (points, did_push, rint (_drag_distance * trackview.editor().get_current_zoom ()));
790 update_pending = false;
792 trackview.editor().session()->add_command (
793 new MementoCommand<AutomationList>(memento_command_binder (), 0, &alist->get_state())
796 trackview.editor().session()->commit_reversible_command ();
797 trackview.editor().session()->set_dirty ();
801 AutomationLine::sync_model_with_view_point (ControlPoint& cp, bool did_push, int64_t distance)
803 ModelRepresentation mr;
806 model_representation (cp, mr);
808 /* how much are we changing the central point by */
810 ydelta = mr.yval - mr.ypos;
813 apply the full change to the central point, and interpolate
814 on both axes to cover all model points represented
815 by the control point.
818 /* change all points before the primary point */
820 for (AutomationList::iterator i = mr.start; i != cp.model(); ++i) {
822 double fract = ((*i)->when - mr.xmin) / (mr.xpos - mr.xmin);
823 double y_delta = ydelta * fract;
824 double x_delta = distance * fract;
828 if (y_delta || x_delta) {
829 alist->modify (i, (*i)->when + x_delta, mr.ymin + y_delta);
833 /* change the primary point */
835 update_pending = true;
836 alist->modify (cp.model(), mr.xval, mr.yval);
839 /* change later points */
841 AutomationList::iterator i = cp.model();
845 while (i != mr.end) {
847 double delta = ydelta * (mr.xmax - (*i)->when) / (mr.xmax - mr.xpos);
849 /* all later points move by the same distance along the x-axis as the main point */
852 alist->modify (i, (*i)->when + distance, (*i)->value + delta);
860 /* move all points after the range represented by the view by the same distance
861 as the main point moved.
864 alist->slide (mr.end, distance);
869 AutomationLine::control_points_adjacent (double xval, uint32_t & before, uint32_t& after)
871 ControlPoint *bcp = 0;
872 ControlPoint *acp = 0;
875 unit_xval = trackview.editor().frame_to_unit (xval);
877 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
879 if ((*i)->get_x() <= unit_xval) {
881 if (!bcp || (*i)->get_x() > bcp->get_x()) {
883 before = bcp->view_index();
886 } else if ((*i)->get_x() > unit_xval) {
888 after = acp->view_index();
897 AutomationLine::is_last_point (ControlPoint& cp)
899 ModelRepresentation mr;
901 model_representation (cp, mr);
903 // If the list is not empty, and the point is the last point in the list
905 if (!alist->empty() && mr.end == alist->end()) {
913 AutomationLine::is_first_point (ControlPoint& cp)
915 ModelRepresentation mr;
917 model_representation (cp, mr);
919 // If the list is not empty, and the point is the first point in the list
921 if (!alist->empty() && mr.start == alist->begin()) {
928 // This is copied into AudioRegionGainLine
930 AutomationLine::remove_point (ControlPoint& cp)
932 ModelRepresentation mr;
934 model_representation (cp, mr);
936 trackview.editor().session()->begin_reversible_command (_("remove control point"));
937 XMLNode &before = alist->get_state();
939 alist->erase (mr.start, mr.end);
941 trackview.editor().session()->add_command(
942 new MementoCommand<AutomationList> (memento_command_binder (), &before, &alist->get_state())
945 trackview.editor().session()->commit_reversible_command ();
946 trackview.editor().session()->set_dirty ();
950 AutomationLine::get_selectables (nframes_t& start, nframes_t& end,
951 double botfrac, double topfrac, list<Selectable*>& results)
958 bool collecting = false;
960 /* Curse X11 and its inverted coordinate system! */
962 bot = (1.0 - topfrac) * _height;
963 top = (1.0 - botfrac) * _height;
968 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
969 double when = (*(*i)->model())->when;
971 if (when >= start && when <= end) {
973 if ((*i)->get_y() >= bot && (*i)->get_y() <= top) {
976 (*i)->set_visible(true);
978 nstart = min (nstart, when);
979 nend = max (nend, when);
985 results.push_back (new AutomationSelectable (nstart, nend, botfrac, topfrac, &trackview));
995 results.push_back (new AutomationSelectable (nstart, nend, botfrac, topfrac, &trackview));
1001 AutomationLine::get_inverted_selectables (Selection&, list<Selectable*>& /*results*/)
1006 /** Take a PointSelection and find ControlPoints that fall within it */
1008 AutomationLine::point_selection_to_control_points (PointSelection const & s)
1010 list<ControlPoint*> cp;
1012 for (PointSelection::const_iterator i = s.begin(); i != s.end(); ++i) {
1014 if (i->track != &trackview) {
1018 /* Curse X11 and its inverted coordinate system! */
1020 double const bot = (1.0 - i->high_fract) * _height;
1021 double const top = (1.0 - i->low_fract) * _height;
1023 for (vector<ControlPoint*>::iterator j = control_points.begin(); j != control_points.end(); ++j) {
1025 double const rstart = trackview.editor().frame_to_unit (i->start);
1026 double const rend = trackview.editor().frame_to_unit (i->end);
1028 if ((*j)->get_x() >= rstart && (*j)->get_x() <= rend) {
1029 if ((*j)->get_y() >= bot && (*j)->get_y() <= top) {
1041 AutomationLine::set_selected_points (PointSelection& points)
1043 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1044 (*i)->set_selected (false);
1047 if (!points.empty()) {
1048 list<ControlPoint*> cp = point_selection_to_control_points (points);
1049 for (list<ControlPoint*>::iterator i = cp.begin(); i != cp.end(); ++i) {
1050 (*i)->set_selected (true);
1057 void AutomationLine::set_colors ()
1059 set_line_color (ARDOUR_UI::config()->canvasvar_AutomationLine.get());
1060 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1066 AutomationLine::list_changed ()
1072 AutomationLine::reset_callback (const Evoral::ControlList& events)
1074 ALPoints tmp_points;
1075 uint32_t npoints = events.size();
1078 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1081 control_points.clear ();
1086 AutomationList::const_iterator ai;
1088 for (ai = events.begin(); ai != events.end(); ++ai) {
1090 double translated_x = (*ai)->when;
1091 double translated_y = (*ai)->value;
1092 model_to_view_coord (translated_x, translated_y);
1094 add_model_point (tmp_points, (*ai)->when, translated_y);
1097 determine_visible_control_points (tmp_points);
1102 AutomationLine::add_model_point (ALPoints& tmp_points, double frame, double yfract)
1104 tmp_points.push_back (ALPoint (trackview.editor().frame_to_unit (_time_converter.to(frame)),
1105 _height - (yfract * _height)));
1109 AutomationLine::reset ()
1111 update_pending = false;
1117 alist->apply_to_points (*this, &AutomationLine::reset_callback);
1121 AutomationLine::clear ()
1123 /* parent must create command */
1124 XMLNode &before = alist->get_state();
1127 trackview.editor().session()->add_command (
1128 new MementoCommand<AutomationList> (memento_command_binder (), &before, &alist->get_state())
1131 trackview.editor().session()->commit_reversible_command ();
1132 trackview.editor().session()->set_dirty ();
1136 AutomationLine::change_model (AutomationList::iterator /*i*/, double /*x*/, double /*y*/)
1141 AutomationLine::set_list (boost::shared_ptr<ARDOUR::AutomationList> list)
1149 AutomationLine::show_all_control_points ()
1151 points_visible = true;
1153 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1154 if (!(*i)->visible()) {
1156 (*i)->set_visible (true);
1162 AutomationLine::hide_all_but_selected_control_points ()
1164 if (alist->interpolation() == AutomationList::Discrete) {
1168 points_visible = false;
1170 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1171 if (!(*i)->selected()) {
1172 (*i)->set_visible (false);
1178 AutomationLine::track_entered()
1180 if (alist->interpolation() != AutomationList::Discrete) {
1181 show_all_control_points();
1186 AutomationLine::track_exited()
1188 if (alist->interpolation() != AutomationList::Discrete) {
1189 hide_all_but_selected_control_points();
1194 AutomationLine::get_state (void)
1196 /* function as a proxy for the model */
1197 return alist->get_state();
1201 AutomationLine::set_state (const XMLNode &node, int version)
1203 /* function as a proxy for the model */
1204 return alist->set_state (node, version);
1208 AutomationLine::view_to_model_coord (double& x, double& y) const
1210 /* TODO: This should be more generic ... */
1211 if (alist->parameter().type() == GainAutomation ||
1212 alist->parameter().type() == EnvelopeAutomation) {
1213 y = slider_position_to_gain (y);
1216 } else if (alist->parameter().type() == PanAutomation) {
1217 // vertical coordinate axis reversal
1219 } else if (alist->parameter().type() == PluginAutomation) {
1220 y = y * (double)(alist->get_max_y()- alist->get_min_y()) + alist->get_min_y();
1222 y = (int)(y * alist->parameter().max());
1225 x = _time_converter.from(x);
1229 AutomationLine::model_to_view_coord (double& x, double& y) const
1231 /* TODO: This should be more generic ... */
1232 if (alist->parameter().type() == GainAutomation ||
1233 alist->parameter().type() == EnvelopeAutomation) {
1234 y = gain_to_slider_position (y);
1235 } else if (alist->parameter().type() == PanAutomation) {
1236 // vertical coordinate axis reversal
1238 } else if (alist->parameter().type() == PluginAutomation) {
1239 y = (y - alist->get_min_y()) / (double)(alist->get_max_y()- alist->get_min_y());
1241 y = y / (double)alist->parameter().max(); /* ... like this */
1244 x = _time_converter.to(x);
1247 /** Called when our list has announced that its interpolation style has changed */
1249 AutomationLine::interpolation_changed (AutomationList::InterpolationStyle style)
1251 if (style == AutomationList::Discrete) {
1252 show_all_control_points();
1255 hide_all_but_selected_control_points();
1261 AutomationLine::add_visible_control_point (uint32_t view_index, uint32_t pi, double tx, double ty, AutomationList::iterator model, uint32_t npoints)
1263 if (view_index >= control_points.size()) {
1265 /* make sure we have enough control points */
1267 ControlPoint* ncp = new ControlPoint (*this);
1268 ncp->set_size (control_point_box_size ());
1270 control_points.push_back (ncp);
1273 ControlPoint::ShapeType shape;
1275 if (!terminal_points_can_slide) {
1277 control_points[view_index]->set_can_slide(false);
1279 shape = ControlPoint::Start;
1281 shape = ControlPoint::Full;
1283 } else if (pi == npoints - 1) {
1284 control_points[view_index]->set_can_slide(false);
1285 shape = ControlPoint::End;
1287 control_points[view_index]->set_can_slide(true);
1288 shape = ControlPoint::Full;
1291 control_points[view_index]->set_can_slide(true);
1292 shape = ControlPoint::Full;
1295 control_points[view_index]->reset (tx, ty, model, view_index, shape);
1297 /* finally, control visibility */
1299 if (_visible && points_visible) {
1300 control_points[view_index]->show ();
1301 control_points[view_index]->set_visible (true);
1303 if (!points_visible) {
1304 control_points[view_index]->set_visible (false);
1310 AutomationLine::add_always_in_view (double x)
1312 _always_in_view.push_back (x);
1313 alist->apply_to_points (*this, &AutomationLine::reset_callback);
1317 AutomationLine::clear_always_in_view ()
1319 _always_in_view.clear ();
1320 alist->apply_to_points (*this, &AutomationLine::reset_callback);
1324 AutomationLine::connect_to_list ()
1326 _list_connections.drop_connections ();
1328 alist->StateChanged.connect (_list_connections, invalidator (*this), boost::bind (&AutomationLine::list_changed, this), gui_context());
1330 alist->InterpolationChanged.connect (
1331 _list_connections, invalidator (*this), boost::bind (&AutomationLine::interpolation_changed, this, _1), gui_context()
1335 MementoCommandBinder<AutomationList>*
1336 AutomationLine::memento_command_binder ()
1338 return new SimpleMementoCommandBinder<AutomationList> (*alist.get());