changes associated with save/restore of AutomationControl id's
[ardour2.git] / gtk2_ardour / automation_line.cc
blobcd89abcc99688216307a2f95c7a23c49ab46bd11
1 /*
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.
20 #include <cmath>
21 #include <climits>
22 #include <vector>
23 #include <fstream>
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"
40 #include "utils.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"
50 #include "i18n.h"
52 using namespace std;
53 using namespace ARDOUR;
54 using namespace PBD;
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)
63 : trackview (tv)
64 , _name (name)
65 , alist (al)
66 , _parent_group (parent)
67 , _time_converter (converter ? (*converter) : default_converter)
68 , _maximum_time (max_framepos)
70 points_visible = false;
71 update_pending = false;
72 _uses_gain_mapping = false;
73 no_draw = false;
74 _visible = true;
75 _is_boolean = false;
76 terminal_points_can_slide = true;
77 _height = 0;
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));
89 connect_to_list ();
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 ());
100 connect_to_list ();
103 AutomationLine::~AutomationLine ()
105 vector_delete (&control_points);
106 delete group;
109 bool
110 AutomationLine::event_handler (GdkEvent* event)
112 return PublicEditor::instance().canvas_line_event (event, line, this);
115 void
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));
124 void
125 AutomationLine::show ()
127 if (alist->interpolation() != AutomationList::Discrete) {
128 line->show();
131 if (points_visible) {
132 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
133 (*i)->show ();
137 _visible = true;
140 void
141 AutomationLine::hide ()
143 line->hide();
144 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
145 (*i)->hide();
147 _visible = false;
150 double
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()),
155 4.0);
158 if (_height > TimeAxisView::preset_height (HeightLarger)) {
159 return 8.0;
160 } else if (_height > (guint32) TimeAxisView::preset_height (HeightNormal)) {
161 return 6.0;
163 return 4.0;
166 void
167 AutomationLine::set_height (guint32 h)
169 if (h != _height) {
170 _height = 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);
178 reset ();
182 void
183 AutomationLine::set_line_color (uint32_t color)
185 _line_color = color;
186 line->property_fill_color_rgba() = color;
189 void
190 AutomationLine::set_uses_gain_mapping (bool yn)
192 if (yn != _uses_gain_mapping) {
193 _uses_gain_mapping = yn;
194 reset ();
198 ControlPoint*
199 AutomationLine::nth (uint32_t n)
201 if (n < control_points.size()) {
202 return control_points[n];
203 } else {
204 return 0;
208 ControlPoint const *
209 AutomationLine::nth (uint32_t n) const
211 if (n < control_points.size()) {
212 return control_points[n];
213 } else {
214 return 0;
218 void
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.
225 y = max (0.0, y);
226 y = min (1.0, y);
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;
244 alist->freeze ();
245 sync_model_with_view_point (cp, false, 0);
246 alist->thaw ();
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 ();
258 void
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());
267 void
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);
277 void
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;
292 } else {
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;
313 ControlPoint* after;
315 if (cp.view_index()) {
316 before = nth (cp.view_index() - 1);
317 } else {
318 before = 0;
321 after = nth (cp.view_index() + 1);
323 if (before) {
324 mr.xmin = (*before->model())->when;
325 mr.ymin = (*before->model())->value;
326 mr.start = before->model();
327 ++mr.start;
328 } else {
329 mr.xmin = mr.xpos;
330 mr.ymin = mr.ypos;
331 mr.start = cp.model();
334 if (after) {
335 mr.end = after->model();
336 } else {
337 mr.xmax = mr.xpos;
338 mr.ymax = mr.ypos;
339 mr.end = cp.model();
340 ++mr.end;
344 void
345 AutomationLine::determine_visible_control_points (ALPoints& points)
347 uint32_t view_index, pi, n;
348 AutomationList::iterator model;
349 uint32_t npoints;
350 uint32_t this_rx = 0;
351 uint32_t prev_rx = 0;
352 uint32_t this_ry = 0;
353 uint32_t prev_ry = 0;
354 double* slope;
355 uint32_t box_size;
357 /* hide all existing points, and the line */
359 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
360 (*i)->hide();
363 line->hide ();
365 if (points.empty()) {
366 return;
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 */
385 view_index = 0;
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);
394 prev_rx = this_rx;
395 prev_ry = this_ry;
396 ++view_index;
397 continue;
400 if (isnan (tx) || isnan (ty)) {
401 warning << string_compose (_("Ignoring illegal points on AutomationLine \"%1\""),
402 _name) << endmsg;
403 continue;
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.
417 continue;
420 if (pi > 0 && pi < npoints - 1) {
421 if (slope[pi] == slope[pi-1]) {
423 /* no reason to display this point */
425 continue;
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
434 points.
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 */
444 continue;
447 /* ok, we should display this point */
449 add_visible_control_point (view_index, pi, tx, ty, model, npoints);
451 prev_rx = this_rx;
452 prev_ry = this_ry;
454 view_index++;
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 ();
462 delete cp;
465 if (!terminal_points_can_slide) {
466 control_points.back()->set_can_slide(false);
469 delete [] slope;
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) {
493 line->show();
498 set_selected_points (trackview.editor().get_selection().points);
501 string
502 AutomationLine::get_verbose_cursor_string (double fraction) const
504 std::string s = fraction_to_string (fraction);
505 if (_uses_gain_mapping) {
506 s += " dB";
509 return s;
513 * @param fraction y fraction
514 * @return string representation of this value, using dB if appropriate.
516 string
517 AutomationLine::fraction_to_string (double fraction) const
519 char buf[32];
521 if (_uses_gain_mapping) {
522 if (fraction == 0.0) {
523 snprintf (buf, sizeof (buf), "-inf");
524 } else {
525 snprintf (buf, sizeof (buf), "%.1f", accurate_coefficient_to_dB (slider_position_to_gain (fraction)));
527 } else {
528 double dummy = 0.0;
529 view_to_model_coord (dummy, fraction);
530 if (EventTypeMap::instance().is_integer (alist->parameter())) {
531 snprintf (buf, sizeof (buf), "%d", (int)fraction);
532 } else {
533 snprintf (buf, sizeof (buf), "%.2f", fraction);
537 return buf;
542 * @param s Value string in the form as returned by fraction_to_string.
543 * @return Corresponding y fraction.
545 double
546 AutomationLine::string_to_fraction (string const & s) const
548 if (s == "-inf") {
549 return 0;
552 double v;
553 sscanf (s.c_str(), "%lf", &v);
555 if (_uses_gain_mapping) {
556 v = gain_to_slider_position (dB_to_coefficient (v));
557 } else {
558 double dummy = 0.0;
559 model_to_view_coord (dummy, v);
562 return v;
565 bool
566 AutomationLine::invalid_point (ALPoints& p, uint32_t index)
568 return p[index].x == max_framepos && p[index].y == DBL_MAX;
571 void
572 AutomationLine::invalidate_point (ALPoints& p, uint32_t index)
574 p[index].x = max_framepos;
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)
585 void
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)
612 void
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)
632 void
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)
640 _drag_points = cp;
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)
656 void
657 AutomationLine::start_drag_common (double x, float fraction)
659 _drag_x = x;
660 _drag_distance = 0;
661 _last_drag_fraction = fraction;
662 _drag_had_movement = false;
663 did_push = false;
665 _drag_points.sort (ControlPointSorter ());
667 /* find the additional points that will be dragged when the user is holding
668 the "push" modifier
671 uint32_t i = _drag_points.back()->view_index () + 1;
672 ControlPoint* p = 0;
673 _push_points.clear ();
674 while ((p = nth (i)) != 0 && p->can_slide()) {
675 _push_points.push_back (p);
676 ++i;
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).
685 pair<double, float>
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;
690 if (with_push) {
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;
698 /* find x limits */
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()) {
704 before = *i;
706 if ((*i)->get_x() > points.back()->get_x() && after == 0) {
707 after = *i;
711 double const before_x = before ? before->get_x() : 0;
712 double const after_x = after ? after->get_x() : DBL_MAX;
714 /* clamp x */
715 for (list<ControlPoint*>::iterator i = points.begin(); i != points.end(); ++i) {
717 if ((*i)->can_slide() && !ignore_x) {
719 /* clamp min x */
720 double const a = (*i)->get_x() + dx;
721 double const b = before_x + 1;
722 if (a < b) {
723 dx += b - a;
726 /* clamp max x */
727 if (after) {
729 if (after_x - before_x < 2) {
730 /* after and before are very close, so just leave this alone */
731 dx = 0;
732 } else {
733 double const a = (*i)->get_x() + dx;
734 double const b = after_x - 1;
735 if (a > b) {
736 dx -= a - b;
743 /* clamp y */
744 for (list<ControlPoint*>::iterator i = points.begin(); i != points.end(); ++i) {
745 double const y = ((_height - (*i)->get_y()) / _height) + dy;
746 if (y < 0) {
747 dy -= y;
749 if (y > 1) {
750 dy -= (y - 1);
754 pair<double, float> const clamped (_drag_x + dx, _last_drag_fraction + dy);
755 _drag_distance += dx;
756 _drag_x = x;
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);
764 if (with_push) {
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;
779 return clamped;
782 /** Should be called to indicate the end of a drag */
783 void
784 AutomationLine::end_drag ()
786 if (!_drag_had_movement) {
787 return;
790 alist->freeze ();
792 /* set up the points that were moved this time round */
793 list<ControlPoint*> points = _drag_points;
794 if (did_push) {
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 ()));
801 alist->thaw ();
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()->set_dirty ();
812 void
813 AutomationLine::sync_model_with_view_point (ControlPoint& cp, bool did_push, int64_t distance)
815 ModelRepresentation mr;
816 double ydelta;
818 model_representation (cp, mr);
820 /* how much are we changing the central point by */
822 ydelta = mr.yval - mr.ypos;
825 apply the full change to the central point, and interpolate
826 on both axes to cover all model points represented
827 by the control point.
830 /* change all points before the primary point */
832 for (AutomationList::iterator i = mr.start; i != cp.model(); ++i) {
834 double fract = ((*i)->when - mr.xmin) / (mr.xpos - mr.xmin);
835 double y_delta = ydelta * fract;
836 double x_delta = distance * fract;
838 /* interpolate */
840 if (y_delta || x_delta) {
841 alist->modify (i, (*i)->when + x_delta, mr.ymin + y_delta);
845 /* change the primary point */
847 update_pending = true;
848 alist->modify (cp.model(), mr.xval, mr.yval);
850 /* change later points */
852 AutomationList::iterator i = cp.model();
854 ++i;
856 while (i != mr.end) {
858 double delta = ydelta * (mr.xmax - (*i)->when) / (mr.xmax - mr.xpos);
860 /* all later points move by the same distance along the x-axis as the main point */
862 if (delta) {
863 alist->modify (i, (*i)->when + distance, (*i)->value + delta);
866 ++i;
869 if (did_push) {
871 /* move all points after the range represented by the view by the same distance
872 as the main point moved.
875 alist->slide (mr.end, distance);
879 bool
880 AutomationLine::control_points_adjacent (double xval, uint32_t & before, uint32_t& after)
882 ControlPoint *bcp = 0;
883 ControlPoint *acp = 0;
884 double unit_xval;
886 unit_xval = trackview.editor().frame_to_unit (xval);
888 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
890 if ((*i)->get_x() <= unit_xval) {
892 if (!bcp || (*i)->get_x() > bcp->get_x()) {
893 bcp = *i;
894 before = bcp->view_index();
897 } else if ((*i)->get_x() > unit_xval) {
898 acp = *i;
899 after = acp->view_index();
900 break;
904 return bcp && acp;
907 bool
908 AutomationLine::is_last_point (ControlPoint& cp)
910 ModelRepresentation mr;
912 model_representation (cp, mr);
914 // If the list is not empty, and the point is the last point in the list
916 if (!alist->empty() && mr.end == alist->end()) {
917 return true;
920 return false;
923 bool
924 AutomationLine::is_first_point (ControlPoint& cp)
926 ModelRepresentation mr;
928 model_representation (cp, mr);
930 // If the list is not empty, and the point is the first point in the list
932 if (!alist->empty() && mr.start == alist->begin()) {
933 return true;
936 return false;
939 // This is copied into AudioRegionGainLine
940 void
941 AutomationLine::remove_point (ControlPoint& cp)
943 ModelRepresentation mr;
945 model_representation (cp, mr);
947 trackview.editor().session()->begin_reversible_command (_("remove control point"));
948 XMLNode &before = alist->get_state();
950 alist->erase (mr.start, mr.end);
952 trackview.editor().session()->add_command(
953 new MementoCommand<AutomationList> (memento_command_binder (), &before, &alist->get_state())
956 trackview.editor().session()->commit_reversible_command ();
957 trackview.editor().session()->set_dirty ();
960 /** Get selectable points within an area.
961 * @param start Start position in session frames.
962 * @param end End position in session frames.
963 * @param bot Bottom y range, as a fraction of line height, where 0 is the bottom of the line.
964 * @param top Top y range, as a fraction of line height, where 0 is the bottom of the line.
965 * @param result Filled in with selectable things; in this case, ControlPoints.
967 void
968 AutomationLine::get_selectables (
969 framepos_t start, framepos_t end, double botfrac, double topfrac, list<Selectable*>& results
972 /* convert fractions to display coordinates with 0 at the top of the track */
973 double const bot_track = (1 - topfrac) * trackview.current_height ();
974 double const top_track = (1 - botfrac) * trackview.current_height ();
976 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
977 double const model_when = (*(*i)->model())->when;
978 framepos_t const session_frames_when = _time_converter.to (model_when) + _time_converter.origin_b ();
980 if (session_frames_when >= start && session_frames_when <= end && (*i)->get_y() >= bot_track && (*i)->get_y() <= top_track) {
981 results.push_back (*i);
986 void
987 AutomationLine::get_inverted_selectables (Selection&, list<Selectable*>& /*results*/)
989 // hmmm ....
992 /** Take a PointSelection and find ControlPoints that fall within it */
993 list<ControlPoint*>
994 AutomationLine::point_selection_to_control_points (PointSelection const & s)
996 list<ControlPoint*> cp;
998 for (PointSelection::const_iterator i = s.begin(); i != s.end(); ++i) {
1000 if (i->track != &trackview) {
1001 continue;
1004 double const bot = (1 - i->high_fract) * trackview.current_height ();
1005 double const top = (1 - i->low_fract) * trackview.current_height ();
1007 for (vector<ControlPoint*>::iterator j = control_points.begin(); j != control_points.end(); ++j) {
1009 double const rstart = trackview.editor().frame_to_unit (_time_converter.to (i->start));
1010 double const rend = trackview.editor().frame_to_unit (_time_converter.to (i->end));
1012 if ((*j)->get_x() >= rstart && (*j)->get_x() <= rend) {
1013 if ((*j)->get_y() >= bot && (*j)->get_y() <= top) {
1014 cp.push_back (*j);
1021 return cp;
1024 void
1025 AutomationLine::set_selected_points (PointSelection& points)
1027 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1028 (*i)->set_selected (false);
1031 if (!points.empty()) {
1032 list<ControlPoint*> cp = point_selection_to_control_points (points);
1033 for (list<ControlPoint*>::iterator i = cp.begin(); i != cp.end(); ++i) {
1034 (*i)->set_selected (true);
1038 set_colors ();
1041 void AutomationLine::set_colors ()
1043 set_line_color (ARDOUR_UI::config()->canvasvar_AutomationLine.get());
1044 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1045 (*i)->set_color ();
1049 void
1050 AutomationLine::list_changed ()
1052 queue_reset ();
1055 void
1056 AutomationLine::reset_callback (const Evoral::ControlList& events)
1058 ALPoints tmp_points;
1059 uint32_t npoints = events.size();
1061 if (npoints == 0) {
1062 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1063 delete *i;
1065 control_points.clear ();
1066 line->hide();
1067 return;
1070 AutomationList::const_iterator ai;
1072 for (ai = events.begin(); ai != events.end(); ++ai) {
1074 double translated_x = (*ai)->when;
1075 double translated_y = (*ai)->value;
1076 model_to_view_coord (translated_x, translated_y);
1078 add_model_point (tmp_points, (*ai)->when, translated_y);
1081 determine_visible_control_points (tmp_points);
1085 void
1086 AutomationLine::add_model_point (ALPoints& tmp_points, double frame, double yfract)
1088 tmp_points.push_back (ALPoint (trackview.editor().frame_to_unit (_time_converter.to(frame)),
1089 _height - (yfract * _height)));
1092 void
1093 AutomationLine::reset ()
1095 update_pending = false;
1097 if (no_draw) {
1098 return;
1101 alist->apply_to_points (*this, &AutomationLine::reset_callback);
1104 void
1105 AutomationLine::clear ()
1107 /* parent must create and commit command */
1108 XMLNode &before = alist->get_state();
1109 alist->clear();
1111 trackview.editor().session()->add_command (
1112 new MementoCommand<AutomationList> (memento_command_binder (), &before, &alist->get_state())
1116 void
1117 AutomationLine::change_model (AutomationList::iterator /*i*/, double /*x*/, double /*y*/)
1121 void
1122 AutomationLine::set_list (boost::shared_ptr<ARDOUR::AutomationList> list)
1124 alist = list;
1125 queue_reset ();
1126 connect_to_list ();
1129 void
1130 AutomationLine::show_all_control_points ()
1132 if (_is_boolean) {
1133 // show the line but don't allow any control points
1134 return;
1137 points_visible = true;
1139 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1140 if (!(*i)->visible()) {
1141 (*i)->show ();
1142 (*i)->set_visible (true);
1147 void
1148 AutomationLine::hide_all_but_selected_control_points ()
1150 if (alist->interpolation() == AutomationList::Discrete) {
1151 return;
1154 points_visible = false;
1156 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1157 if (!(*i)->get_selected()) {
1158 (*i)->set_visible (false);
1163 void
1164 AutomationLine::track_entered()
1166 if (alist->interpolation() != AutomationList::Discrete) {
1167 show_all_control_points();
1171 void
1172 AutomationLine::track_exited()
1174 if (alist->interpolation() != AutomationList::Discrete) {
1175 hide_all_but_selected_control_points();
1179 XMLNode &
1180 AutomationLine::get_state (void)
1182 /* function as a proxy for the model */
1183 return alist->get_state();
1187 AutomationLine::set_state (const XMLNode &node, int version)
1189 /* function as a proxy for the model */
1190 return alist->set_state (node, version);
1193 void
1194 AutomationLine::view_to_model_coord (double& x, double& y) const
1196 x = _time_converter.from (x);
1197 view_to_model_coord_y (y);
1200 void
1201 AutomationLine::view_to_model_coord_y (double& y) const
1203 /* TODO: This should be more generic ... */
1204 if (alist->parameter().type() == GainAutomation ||
1205 alist->parameter().type() == EnvelopeAutomation) {
1206 y = slider_position_to_gain (y);
1207 y = max (0.0, y);
1208 y = min (2.0, y);
1209 } else if (alist->parameter().type() == PanAutomation) {
1210 // vertical coordinate axis reversal
1211 y = 1.0 - y;
1212 } else if (alist->parameter().type() == PluginAutomation) {
1213 y = y * (double)(alist->get_max_y()- alist->get_min_y()) + alist->get_min_y();
1214 } else {
1215 y = rint (y * alist->parameter().max());
1219 void
1220 AutomationLine::model_to_view_coord (double& x, double& y) const
1222 /* TODO: This should be more generic ... */
1223 if (alist->parameter().type() == GainAutomation ||
1224 alist->parameter().type() == EnvelopeAutomation) {
1225 y = gain_to_slider_position (y);
1226 } else if (alist->parameter().type() == PanAutomation) {
1227 // vertical coordinate axis reversal
1228 y = 1.0 - y;
1229 } else if (alist->parameter().type() == PluginAutomation) {
1230 y = (y - alist->get_min_y()) / (double)(alist->get_max_y()- alist->get_min_y());
1231 } else {
1232 y = y / (double)alist->parameter().max(); /* ... like this */
1235 x = _time_converter.to(x);
1238 /** Called when our list has announced that its interpolation style has changed */
1239 void
1240 AutomationLine::interpolation_changed (AutomationList::InterpolationStyle style)
1242 if (style == AutomationList::Discrete) {
1243 show_all_control_points();
1244 line->hide();
1245 } else {
1246 hide_all_but_selected_control_points();
1247 line->show();
1251 void
1252 AutomationLine::add_visible_control_point (uint32_t view_index, uint32_t pi, double tx, double ty, AutomationList::iterator model, uint32_t npoints)
1254 if (view_index >= control_points.size()) {
1256 /* make sure we have enough control points */
1258 ControlPoint* ncp = new ControlPoint (*this);
1259 ncp->set_size (control_point_box_size ());
1261 control_points.push_back (ncp);
1264 ControlPoint::ShapeType shape;
1266 if (!terminal_points_can_slide) {
1267 if (pi == 0) {
1268 control_points[view_index]->set_can_slide(false);
1269 if (tx == 0) {
1270 shape = ControlPoint::Start;
1271 } else {
1272 shape = ControlPoint::Full;
1274 } else if (pi == npoints - 1) {
1275 control_points[view_index]->set_can_slide(false);
1276 shape = ControlPoint::End;
1277 } else {
1278 control_points[view_index]->set_can_slide(true);
1279 shape = ControlPoint::Full;
1281 } else {
1282 control_points[view_index]->set_can_slide(true);
1283 shape = ControlPoint::Full;
1286 control_points[view_index]->reset (tx, ty, model, view_index, shape);
1288 /* finally, control visibility */
1290 if (_visible && points_visible) {
1291 control_points[view_index]->show ();
1292 control_points[view_index]->set_visible (true);
1293 } else {
1294 if (!points_visible) {
1295 control_points[view_index]->set_visible (false);
1300 void
1301 AutomationLine::add_always_in_view (double x)
1303 _always_in_view.push_back (x);
1304 alist->apply_to_points (*this, &AutomationLine::reset_callback);
1307 void
1308 AutomationLine::clear_always_in_view ()
1310 _always_in_view.clear ();
1311 alist->apply_to_points (*this, &AutomationLine::reset_callback);
1314 void
1315 AutomationLine::connect_to_list ()
1317 _list_connections.drop_connections ();
1319 alist->StateChanged.connect (_list_connections, invalidator (*this), boost::bind (&AutomationLine::list_changed, this), gui_context());
1321 alist->InterpolationChanged.connect (
1322 _list_connections, invalidator (*this), boost::bind (&AutomationLine::interpolation_changed, this, _1), gui_context()
1326 MementoCommandBinder<AutomationList>*
1327 AutomationLine::memento_command_binder ()
1329 return new SimpleMementoCommandBinder<AutomationList> (*alist.get());
1332 /** Set the maximum time that points on this line can be at, relative
1333 * to the start of the track or region that it is on.
1335 void
1336 AutomationLine::set_maximum_time (framepos_t t)
1338 _maximum_time = t;
1342 /** @return min and max x positions of points that are in the list, in session frames */
1343 pair<framepos_t, framepos_t>
1344 AutomationLine::get_point_x_range () const
1346 pair<framepos_t, framepos_t> r (max_framepos, 0);
1348 for (AutomationList::const_iterator i = the_list()->begin(); i != the_list()->end(); ++i) {
1349 r.first = min (r.first, _time_converter.to ((*i)->when) + _time_converter.origin_b ());
1350 r.second = max (r.second, _time_converter.to ((*i)->when) + _time_converter.origin_b ());
1353 return r;