Make the route group submenu of the RouteTimeAxisView menu apply to the selection.
[ardour2.git] / gtk2_ardour / automation_line.cc
blobbde9308ded05fdf107bf098072e958401d47b28b
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 , _offset (0)
68 , _time_converter (converter ? (*converter) : default_converter)
69 , _maximum_time (max_framepos)
71 points_visible = false;
72 update_pending = false;
73 _uses_gain_mapping = false;
74 no_draw = false;
75 _visible = true;
76 _is_boolean = false;
77 terminal_points_can_slide = true;
78 _height = 0;
80 group = new ArdourCanvas::Group (parent);
81 group->property_x() = 0.0;
82 group->property_y() = 0.0;
84 line = new ArdourCanvas::Line (*group);
85 line->property_width_pixels() = (guint)1;
86 line->set_data ("line", this);
88 line->signal_event().connect (sigc::mem_fun (*this, &AutomationLine::event_handler));
90 connect_to_list ();
92 trackview.session()->register_with_memento_command_factory(alist->id(), this);
94 if (alist->parameter().type() == GainAutomation ||
95 alist->parameter().type() == EnvelopeAutomation) {
96 set_uses_gain_mapping (true);
99 interpolation_changed (alist->interpolation ());
101 connect_to_list ();
104 AutomationLine::~AutomationLine ()
106 vector_delete (&control_points);
107 delete group;
110 bool
111 AutomationLine::event_handler (GdkEvent* event)
113 return PublicEditor::instance().canvas_line_event (event, line, this);
116 void
117 AutomationLine::queue_reset ()
119 if (!update_pending) {
120 update_pending = true;
121 Gtkmm2ext::UI::instance()->call_slot (invalidator (*this), boost::bind (&AutomationLine::reset, this));
125 void
126 AutomationLine::show ()
128 if (alist->interpolation() != AutomationList::Discrete) {
129 line->show();
132 if (points_visible) {
133 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
134 (*i)->show ();
138 _visible = true;
141 void
142 AutomationLine::hide ()
144 line->hide();
145 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
146 (*i)->hide();
148 _visible = false;
151 double
152 AutomationLine::control_point_box_size ()
154 if (alist->interpolation() == AutomationList::Discrete) {
155 return max((_height*4.0) / (double)(alist->parameter().max() - alist->parameter().min()),
156 4.0);
159 if (_height > TimeAxisView::preset_height (HeightLarger)) {
160 return 8.0;
161 } else if (_height > (guint32) TimeAxisView::preset_height (HeightNormal)) {
162 return 6.0;
164 return 4.0;
167 void
168 AutomationLine::set_height (guint32 h)
170 if (h != _height) {
171 _height = h;
173 double bsz = control_point_box_size();
175 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
176 (*i)->set_size (bsz);
179 reset ();
183 void
184 AutomationLine::set_line_color (uint32_t color)
186 _line_color = color;
187 line->property_fill_color_rgba() = color;
190 void
191 AutomationLine::set_uses_gain_mapping (bool yn)
193 if (yn != _uses_gain_mapping) {
194 _uses_gain_mapping = yn;
195 reset ();
199 ControlPoint*
200 AutomationLine::nth (uint32_t n)
202 if (n < control_points.size()) {
203 return control_points[n];
204 } else {
205 return 0;
209 ControlPoint const *
210 AutomationLine::nth (uint32_t n) const
212 if (n < control_points.size()) {
213 return control_points[n];
214 } else {
215 return 0;
219 void
220 AutomationLine::modify_point_y (ControlPoint& cp, double y)
222 /* clamp y-coord appropriately. y is supposed to be a normalized fraction (0.0-1.0),
223 and needs to be converted to a canvas unit distance.
226 y = max (0.0, y);
227 y = min (1.0, y);
228 y = _height - (y * _height);
230 double const x = trackview.editor().frame_to_unit (_time_converter.to((*cp.model())->when) - _offset);
232 trackview.editor().session()->begin_reversible_command (_("automation event move"));
233 trackview.editor().session()->add_command (
234 new MementoCommand<AutomationList> (memento_command_binder(), &get_state(), 0)
237 cp.move_to (x, y, ControlPoint::Full);
239 reset_line_coords (cp);
241 if (line_points.size() > 1) {
242 line->property_points() = line_points;
245 alist->freeze ();
246 sync_model_with_view_point (cp, false, 0);
247 alist->thaw ();
249 update_pending = false;
251 trackview.editor().session()->add_command (
252 new MementoCommand<AutomationList> (memento_command_binder(), 0, &alist->get_state())
255 trackview.editor().session()->commit_reversible_command ();
256 trackview.editor().session()->set_dirty ();
259 void
260 AutomationLine::reset_line_coords (ControlPoint& cp)
262 if (cp.view_index() < line_points.size()) {
263 line_points[cp.view_index()].set_x (cp.get_x());
264 line_points[cp.view_index()].set_y (cp.get_y());
268 void
269 AutomationLine::sync_model_with_view_points (list<ControlPoint*> cp, bool did_push, int64_t distance)
271 update_pending = true;
273 for (list<ControlPoint*>::iterator i = cp.begin(); i != cp.end(); ++i) {
274 sync_model_with_view_point (**i, did_push, distance);
278 void
279 AutomationLine::model_representation (ControlPoint& cp, ModelRepresentation& mr)
281 /* part one: find out where the visual control point is.
282 initial results are in canvas units. ask the
283 line to convert them to something relevant.
286 mr.xval = cp.get_x();
287 mr.yval = 1.0 - (cp.get_y() / _height);
289 /* if xval has not changed, set it directly from the model to avoid rounding errors */
291 if (mr.xval == trackview.editor().frame_to_unit(_time_converter.to((*cp.model())->when)) - _offset) {
292 mr.xval = (*cp.model())->when - _offset;
293 } else {
294 mr.xval = trackview.editor().unit_to_frame (mr.xval);
295 mr.xval = _time_converter.from (mr.xval + _offset);
298 /* convert y to model units; the x was already done above
301 view_to_model_coord_y (mr.yval);
303 /* part 2: find out where the model point is now
306 mr.xpos = (*cp.model())->when - _offset;
307 mr.ypos = (*cp.model())->value;
309 /* part 3: get the position of the visual control
310 points before and after us.
313 ControlPoint* before;
314 ControlPoint* after;
316 if (cp.view_index()) {
317 before = nth (cp.view_index() - 1);
318 } else {
319 before = 0;
322 after = nth (cp.view_index() + 1);
324 if (before) {
325 mr.xmin = (*before->model())->when;
326 mr.ymin = (*before->model())->value;
327 mr.start = before->model();
328 ++mr.start;
329 } else {
330 mr.xmin = mr.xpos;
331 mr.ymin = mr.ypos;
332 mr.start = cp.model();
335 if (after) {
336 mr.end = after->model();
337 } else {
338 mr.xmax = mr.xpos;
339 mr.ymax = mr.ypos;
340 mr.end = cp.model();
341 ++mr.end;
345 void
346 AutomationLine::determine_visible_control_points (ALPoints& points)
348 uint32_t view_index, pi, n;
349 AutomationList::iterator model;
350 uint32_t npoints;
351 uint32_t this_rx = 0;
352 uint32_t prev_rx = 0;
353 uint32_t this_ry = 0;
354 uint32_t prev_ry = 0;
355 double* slope;
356 uint32_t box_size;
358 /* hide all existing points, and the line */
360 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
361 (*i)->hide();
364 line->hide ();
366 if (points.empty()) {
367 return;
370 npoints = points.size();
372 /* compute derivative/slope for the entire line */
374 slope = new double[npoints];
376 for (n = 0; n < npoints - 1; ++n) {
377 double xdelta = points[n+1].x - points[n].x;
378 double ydelta = points[n+1].y - points[n].y;
379 slope[n] = ydelta/xdelta;
382 box_size = (uint32_t) control_point_box_size ();
384 /* read all points and decide which ones to show as control points */
386 view_index = 0;
388 for (model = alist->begin(), pi = 0; pi < npoints; ++model, ++pi) {
390 double tx = points[pi].x;
391 double ty = points[pi].y;
393 if (find (_always_in_view.begin(), _always_in_view.end(), (*model)->when) != _always_in_view.end()) {
394 add_visible_control_point (view_index, pi, tx, ty, model, npoints);
395 prev_rx = this_rx;
396 prev_ry = this_ry;
397 ++view_index;
398 continue;
401 if (isnan (tx) || isnan (ty)) {
402 warning << string_compose (_("Ignoring illegal points on AutomationLine \"%1\""),
403 _name) << endmsg;
404 continue;
407 /* now ensure that the control_points vector reflects the current curve
408 state, but don't plot control points too close together. also, don't
409 plot a series of points all with the same value.
411 always plot the first and last points, of course.
414 if (invalid_point (points, pi)) {
415 /* for some reason, we are supposed to ignore this point,
416 but still keep track of the model index.
418 continue;
421 if (pi > 0 && pi < npoints - 1) {
422 if (slope[pi] == slope[pi-1]) {
424 /* no reason to display this point */
426 continue;
430 /* need to round here. the ultimate coordinates are integer
431 pixels, so tiny deltas in the coords will be eliminated
432 and we end up with "colinear" line segments. since the
433 line rendering code in libart doesn't like this very
434 much, we eliminate them here. don't do this for the first and last
435 points.
438 this_rx = (uint32_t) rint (tx);
439 this_ry = (uint32_t) rint (ty);
441 if (view_index && pi != npoints && /* not the first, not the last */
442 (((this_rx == prev_rx) && (this_ry == prev_ry)) || /* same point */
443 (((this_rx - prev_rx) < (box_size + 2)) && /* not identical, but still too close horizontally */
444 (abs ((int)(this_ry - prev_ry)) < (int) (box_size + 2))))) { /* too close vertically */
445 continue;
448 /* ok, we should display this point */
450 add_visible_control_point (view_index, pi, tx, ty, model, npoints);
452 prev_rx = this_rx;
453 prev_ry = this_ry;
455 view_index++;
458 /* discard extra CP's to avoid confusing ourselves */
460 while (control_points.size() > view_index) {
461 ControlPoint* cp = control_points.back();
462 control_points.pop_back ();
463 delete cp;
466 if (!terminal_points_can_slide) {
467 control_points.back()->set_can_slide(false);
470 delete [] slope;
472 if (view_index > 1) {
474 npoints = view_index;
476 /* reset the line coordinates */
478 while (line_points.size() < npoints) {
479 line_points.push_back (Art::Point (0,0));
482 while (line_points.size() > npoints) {
483 line_points.pop_back ();
486 for (view_index = 0; view_index < npoints; ++view_index) {
487 line_points[view_index].set_x (control_points[view_index]->get_x());
488 line_points[view_index].set_y (control_points[view_index]->get_y());
491 line->property_points() = line_points;
493 if (_visible && alist->interpolation() != AutomationList::Discrete) {
494 line->show();
499 set_selected_points (trackview.editor().get_selection().points);
502 string
503 AutomationLine::get_verbose_cursor_string (double fraction) const
505 std::string s = fraction_to_string (fraction);
506 if (_uses_gain_mapping) {
507 s += " dB";
510 return s;
514 * @param fraction y fraction
515 * @return string representation of this value, using dB if appropriate.
517 string
518 AutomationLine::fraction_to_string (double fraction) const
520 char buf[32];
522 if (_uses_gain_mapping) {
523 if (fraction == 0.0) {
524 snprintf (buf, sizeof (buf), "-inf");
525 } else {
526 snprintf (buf, sizeof (buf), "%.1f", accurate_coefficient_to_dB (slider_position_to_gain (fraction)));
528 } else {
529 view_to_model_coord_y (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 - _offset) + _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 if (translated_x >= 0 && translated_x < _maximum_time) {
1079 tmp_points.push_back (ALPoint (
1080 trackview.editor().frame_to_unit (translated_x),
1081 _height - (translated_y * _height))
1086 determine_visible_control_points (tmp_points);
1089 void
1090 AutomationLine::reset ()
1092 update_pending = false;
1094 if (no_draw) {
1095 return;
1098 alist->apply_to_points (*this, &AutomationLine::reset_callback);
1101 void
1102 AutomationLine::clear ()
1104 /* parent must create and commit command */
1105 XMLNode &before = alist->get_state();
1106 alist->clear();
1108 trackview.editor().session()->add_command (
1109 new MementoCommand<AutomationList> (memento_command_binder (), &before, &alist->get_state())
1113 void
1114 AutomationLine::change_model (AutomationList::iterator /*i*/, double /*x*/, double /*y*/)
1118 void
1119 AutomationLine::set_list (boost::shared_ptr<ARDOUR::AutomationList> list)
1121 alist = list;
1122 queue_reset ();
1123 connect_to_list ();
1126 void
1127 AutomationLine::show_all_control_points ()
1129 if (_is_boolean) {
1130 // show the line but don't allow any control points
1131 return;
1134 points_visible = true;
1136 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1137 if (!(*i)->visible()) {
1138 (*i)->show ();
1139 (*i)->set_visible (true);
1144 void
1145 AutomationLine::hide_all_but_selected_control_points ()
1147 if (alist->interpolation() == AutomationList::Discrete) {
1148 return;
1151 points_visible = false;
1153 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1154 if (!(*i)->get_selected()) {
1155 (*i)->set_visible (false);
1160 void
1161 AutomationLine::track_entered()
1163 if (alist->interpolation() != AutomationList::Discrete) {
1164 show_all_control_points();
1168 void
1169 AutomationLine::track_exited()
1171 if (alist->interpolation() != AutomationList::Discrete) {
1172 hide_all_but_selected_control_points();
1176 XMLNode &
1177 AutomationLine::get_state (void)
1179 /* function as a proxy for the model */
1180 return alist->get_state();
1184 AutomationLine::set_state (const XMLNode &node, int version)
1186 /* function as a proxy for the model */
1187 return alist->set_state (node, version);
1190 void
1191 AutomationLine::view_to_model_coord (double& x, double& y) const
1193 x = _time_converter.from (x);
1194 view_to_model_coord_y (y);
1197 void
1198 AutomationLine::view_to_model_coord_y (double& y) const
1200 /* TODO: This should be more generic ... */
1201 if (alist->parameter().type() == GainAutomation ||
1202 alist->parameter().type() == EnvelopeAutomation) {
1203 y = slider_position_to_gain (y);
1204 y = max (0.0, y);
1205 y = min (2.0, y);
1206 } else if (alist->parameter().type() == PanAzimuthAutomation ||
1207 alist->parameter().type() == PanElevationAutomation ||
1208 alist->parameter().type() == PanWidthAutomation) {
1209 y = 1.0 - y;
1210 } else if (alist->parameter().type() == PluginAutomation) {
1211 y = y * (double)(alist->get_max_y()- alist->get_min_y()) + alist->get_min_y();
1212 } else {
1213 y = rint (y * alist->parameter().max());
1217 void
1218 AutomationLine::model_to_view_coord (double& x, double& y) const
1220 /* TODO: This should be more generic ... */
1221 if (alist->parameter().type() == GainAutomation ||
1222 alist->parameter().type() == EnvelopeAutomation) {
1223 y = gain_to_slider_position (y);
1224 } else if (alist->parameter().type() == PanAzimuthAutomation ||
1225 alist->parameter().type() == PanElevationAutomation ||
1226 alist->parameter().type() == PanWidthAutomation) {
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) - _offset;
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 (framecnt_t t)
1338 if (_maximum_time == t) {
1339 return;
1342 _maximum_time = t;
1343 reset ();
1347 /** @return min and max x positions of points that are in the list, in session frames */
1348 pair<framepos_t, framepos_t>
1349 AutomationLine::get_point_x_range () const
1351 pair<framepos_t, framepos_t> r (max_framepos, 0);
1353 for (AutomationList::const_iterator i = the_list()->begin(); i != the_list()->end(); ++i) {
1354 r.first = min (r.first, _time_converter.to ((*i)->when) + _offset + _time_converter.origin_b ());
1355 r.second = max (r.second, _time_converter.to ((*i)->when) + _offset + _time_converter.origin_b ());
1358 return r;
1361 void
1362 AutomationLine::set_offset (framepos_t off)
1364 if (_offset == off) {
1365 return;
1368 _offset = off;
1369 reset ();