Add a couple of missing attach_buffers() calls after _ports has been changed. I...
[ardour2.git] / gtk2_ardour / automation_line.cc
blob979b9696215c2d6cfe609f9a41d1bd0b89641c42
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 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 ());
99 connect_to_list ();
102 AutomationLine::~AutomationLine ()
104 vector_delete (&control_points);
105 delete group;
108 bool
109 AutomationLine::event_handler (GdkEvent* event)
111 return PublicEditor::instance().canvas_line_event (event, line, this);
114 void
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));
123 void
124 AutomationLine::show ()
126 if (alist->interpolation() != AutomationList::Discrete) {
127 line->show();
130 if (points_visible) {
131 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
132 (*i)->show ();
136 _visible = true;
139 void
140 AutomationLine::hide ()
142 line->hide();
143 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
144 (*i)->hide();
146 _visible = false;
149 double
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()),
154 4.0);
157 if (_height > TimeAxisView::preset_height (HeightLarger)) {
158 return 8.0;
159 } else if (_height > (guint32) TimeAxisView::preset_height (HeightNormal)) {
160 return 6.0;
162 return 4.0;
165 void
166 AutomationLine::set_height (guint32 h)
168 if (h != _height) {
169 _height = 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);
177 reset ();
181 void
182 AutomationLine::set_line_color (uint32_t color)
184 _line_color = color;
185 line->property_fill_color_rgba() = color;
188 void
189 AutomationLine::set_uses_gain_mapping (bool yn)
191 if (yn != _uses_gain_mapping) {
192 _uses_gain_mapping = yn;
193 reset ();
197 ControlPoint*
198 AutomationLine::nth (uint32_t n)
200 if (n < control_points.size()) {
201 return control_points[n];
202 } else {
203 return 0;
207 ControlPoint const *
208 AutomationLine::nth (uint32_t n) const
210 if (n < control_points.size()) {
211 return control_points[n];
212 } else {
213 return 0;
217 void
218 AutomationLine::modify_point_y (ControlPoint& cp, double y)
220 /* clamp y-coord appropriately. y is supposed to be a normalized fraction (0.0-1.0),
221 and needs to be converted to a canvas unit distance.
224 y = max (0.0, y);
225 y = min (1.0, y);
226 y = _height - (y * _height);
228 double const x = trackview.editor().frame_to_unit (_time_converter.to((*cp.model())->when) - _offset);
230 trackview.editor().session()->begin_reversible_command (_("automation event move"));
231 trackview.editor().session()->add_command (
232 new MementoCommand<AutomationList> (memento_command_binder(), &get_state(), 0)
235 cp.move_to (x, y, ControlPoint::Full);
237 reset_line_coords (cp);
239 if (line_points.size() > 1) {
240 line->property_points() = line_points;
243 alist->freeze ();
244 sync_model_with_view_point (cp, false, 0);
245 alist->thaw ();
247 update_pending = false;
249 trackview.editor().session()->add_command (
250 new MementoCommand<AutomationList> (memento_command_binder(), 0, &alist->get_state())
253 trackview.editor().session()->commit_reversible_command ();
254 trackview.editor().session()->set_dirty ();
257 void
258 AutomationLine::reset_line_coords (ControlPoint& cp)
260 if (cp.view_index() < line_points.size()) {
261 line_points[cp.view_index()].set_x (cp.get_x());
262 line_points[cp.view_index()].set_y (cp.get_y());
266 void
267 AutomationLine::sync_model_with_view_points (list<ControlPoint*> cp, bool did_push, int64_t distance)
269 update_pending = true;
271 for (list<ControlPoint*>::iterator i = cp.begin(); i != cp.end(); ++i) {
272 sync_model_with_view_point (**i, did_push, distance);
276 void
277 AutomationLine::model_representation (ControlPoint& cp, ModelRepresentation& mr)
279 /* part one: find out where the visual control point is.
280 initial results are in canvas units. ask the
281 line to convert them to something relevant.
284 mr.xval = cp.get_x();
285 mr.yval = 1.0 - (cp.get_y() / _height);
287 /* if xval has not changed, set it directly from the model to avoid rounding errors */
289 if (mr.xval == trackview.editor().frame_to_unit(_time_converter.to((*cp.model())->when)) - _offset) {
290 mr.xval = (*cp.model())->when - _offset;
291 } else {
292 mr.xval = trackview.editor().unit_to_frame (mr.xval);
293 mr.xval = _time_converter.from (mr.xval + _offset);
296 /* convert y to model units; the x was already done above
299 view_to_model_coord_y (mr.yval);
301 /* part 2: find out where the model point is now
304 mr.xpos = (*cp.model())->when - _offset;
305 mr.ypos = (*cp.model())->value;
307 /* part 3: get the position of the visual control
308 points before and after us.
311 ControlPoint* before;
312 ControlPoint* after;
314 if (cp.view_index()) {
315 before = nth (cp.view_index() - 1);
316 } else {
317 before = 0;
320 after = nth (cp.view_index() + 1);
322 if (before) {
323 mr.xmin = (*before->model())->when;
324 mr.ymin = (*before->model())->value;
325 mr.start = before->model();
326 ++mr.start;
327 } else {
328 mr.xmin = mr.xpos;
329 mr.ymin = mr.ypos;
330 mr.start = cp.model();
333 if (after) {
334 mr.end = after->model();
335 } else {
336 mr.xmax = mr.xpos;
337 mr.ymax = mr.ypos;
338 mr.end = cp.model();
339 ++mr.end;
343 void
344 AutomationLine::determine_visible_control_points (ALPoints& points)
346 uint32_t view_index, pi, n;
347 AutomationList::iterator model;
348 uint32_t npoints;
349 uint32_t this_rx = 0;
350 uint32_t prev_rx = 0;
351 uint32_t this_ry = 0;
352 uint32_t prev_ry = 0;
353 double* slope;
354 uint32_t box_size;
356 /* hide all existing points, and the line */
358 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
359 (*i)->hide();
362 line->hide ();
364 if (points.empty()) {
365 return;
368 npoints = points.size();
370 /* compute derivative/slope for the entire line */
372 slope = new double[npoints];
374 for (n = 0; n < npoints - 1; ++n) {
375 double xdelta = points[n+1].x - points[n].x;
376 double ydelta = points[n+1].y - points[n].y;
377 slope[n] = ydelta/xdelta;
380 box_size = (uint32_t) control_point_box_size ();
382 /* read all points and decide which ones to show as control points */
384 view_index = 0;
386 for (model = alist->begin(), pi = 0; pi < npoints; ++model, ++pi) {
388 double tx = points[pi].x;
389 double ty = points[pi].y;
391 if (find (_always_in_view.begin(), _always_in_view.end(), (*model)->when) != _always_in_view.end()) {
392 add_visible_control_point (view_index, pi, tx, ty, model, npoints);
393 prev_rx = this_rx;
394 prev_ry = this_ry;
395 ++view_index;
396 continue;
399 if (isnan (tx) || isnan (ty)) {
400 warning << string_compose (_("Ignoring illegal points on AutomationLine \"%1\""),
401 _name) << endmsg;
402 continue;
405 /* now ensure that the control_points vector reflects the current curve
406 state, but don't plot control points too close together. also, don't
407 plot a series of points all with the same value.
409 always plot the first and last points, of course.
412 if (invalid_point (points, pi)) {
413 /* for some reason, we are supposed to ignore this point,
414 but still keep track of the model index.
416 continue;
419 if (pi > 0 && pi < npoints - 1) {
420 if (slope[pi] == slope[pi-1]) {
422 /* no reason to display this point */
424 continue;
428 /* need to round here. the ultimate coordinates are integer
429 pixels, so tiny deltas in the coords will be eliminated
430 and we end up with "colinear" line segments. since the
431 line rendering code in libart doesn't like this very
432 much, we eliminate them here. don't do this for the first and last
433 points.
436 this_rx = (uint32_t) rint (tx);
437 this_ry = (uint32_t) rint (ty);
439 if (view_index && pi != npoints && /* not the first, not the last */
440 (((this_rx == prev_rx) && (this_ry == prev_ry)) || /* same point */
441 (((this_rx - prev_rx) < (box_size + 2)) && /* not identical, but still too close horizontally */
442 (abs ((int)(this_ry - prev_ry)) < (int) (box_size + 2))))) { /* too close vertically */
443 continue;
446 /* ok, we should display this point */
448 add_visible_control_point (view_index, pi, tx, ty, model, npoints);
450 prev_rx = this_rx;
451 prev_ry = this_ry;
453 view_index++;
456 /* discard extra CP's to avoid confusing ourselves */
458 while (control_points.size() > view_index) {
459 ControlPoint* cp = control_points.back();
460 control_points.pop_back ();
461 delete cp;
464 if (!terminal_points_can_slide) {
465 control_points.back()->set_can_slide(false);
468 delete [] slope;
470 if (view_index > 1) {
472 npoints = view_index;
474 /* reset the line coordinates */
476 while (line_points.size() < npoints) {
477 line_points.push_back (Art::Point (0,0));
480 while (line_points.size() > npoints) {
481 line_points.pop_back ();
484 for (view_index = 0; view_index < npoints; ++view_index) {
485 line_points[view_index].set_x (control_points[view_index]->get_x());
486 line_points[view_index].set_y (control_points[view_index]->get_y());
489 line->property_points() = line_points;
491 if (_visible && alist->interpolation() != AutomationList::Discrete) {
492 line->show();
497 set_selected_points (trackview.editor().get_selection().points);
500 string
501 AutomationLine::get_verbose_cursor_string (double fraction) const
503 std::string s = fraction_to_string (fraction);
504 if (_uses_gain_mapping) {
505 s += " dB";
508 return s;
512 * @param fraction y fraction
513 * @return string representation of this value, using dB if appropriate.
515 string
516 AutomationLine::fraction_to_string (double fraction) const
518 char buf[32];
520 if (_uses_gain_mapping) {
521 if (fraction == 0.0) {
522 snprintf (buf, sizeof (buf), "-inf");
523 } else {
524 snprintf (buf, sizeof (buf), "%.1f", accurate_coefficient_to_dB (slider_position_to_gain_with_max (fraction, Config->get_max_gain())));
526 } else {
527 view_to_model_coord_y (fraction);
528 if (EventTypeMap::instance().is_integer (alist->parameter())) {
529 snprintf (buf, sizeof (buf), "%d", (int)fraction);
530 } else {
531 snprintf (buf, sizeof (buf), "%.2f", fraction);
535 return buf;
540 * @param s Value string in the form as returned by fraction_to_string.
541 * @return Corresponding y fraction.
543 double
544 AutomationLine::string_to_fraction (string const & s) const
546 if (s == "-inf") {
547 return 0;
550 double v;
551 sscanf (s.c_str(), "%lf", &v);
553 if (_uses_gain_mapping) {
554 v = gain_to_slider_position_with_max (dB_to_coefficient (v), Config->get_max_gain());
555 } else {
556 double dummy = 0.0;
557 model_to_view_coord (dummy, v);
560 return v;
563 bool
564 AutomationLine::invalid_point (ALPoints& p, uint32_t index)
566 return p[index].x == max_framepos && p[index].y == DBL_MAX;
569 void
570 AutomationLine::invalidate_point (ALPoints& p, uint32_t index)
572 p[index].x = max_framepos;
573 p[index].y = DBL_MAX;
576 /** Start dragging a single point, possibly adding others if the supplied point is selected and there
577 * are other selected points.
579 * @param cp Point to drag.
580 * @param x Initial x position (units).
581 * @param fraction Initial y position (as a fraction of the track height, where 0 is the bottom and 1 the top)
583 void
584 AutomationLine::start_drag_single (ControlPoint* cp, double x, float fraction)
586 trackview.editor().session()->begin_reversible_command (_("automation event move"));
587 trackview.editor().session()->add_command (
588 new MementoCommand<AutomationList> (memento_command_binder(), &get_state(), 0)
591 _drag_points.clear ();
592 _drag_points.push_back (cp);
594 if (cp->get_selected ()) {
595 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
596 if (*i != cp && (*i)->get_selected()) {
597 _drag_points.push_back (*i);
602 start_drag_common (x, fraction);
605 /** Start dragging a line vertically (with no change in x)
606 * @param i1 Control point index of the `left' point on the line.
607 * @param i2 Control point index of the `right' point on the line.
608 * @param fraction Initial y position (as a fraction of the track height, where 0 is the bottom and 1 the top)
610 void
611 AutomationLine::start_drag_line (uint32_t i1, uint32_t i2, float fraction)
613 trackview.editor().session()->begin_reversible_command (_("automation range move"));
614 trackview.editor().session()->add_command (
615 new MementoCommand<AutomationList> (memento_command_binder (), &get_state(), 0)
618 _drag_points.clear ();
619 for (uint32_t i = i1; i <= i2; i++) {
620 _drag_points.push_back (nth (i));
623 start_drag_common (0, fraction);
626 /** Start dragging multiple points (with no change in x)
627 * @param cp Points to drag.
628 * @param fraction Initial y position (as a fraction of the track height, where 0 is the bottom and 1 the top)
630 void
631 AutomationLine::start_drag_multiple (list<ControlPoint*> cp, float fraction, XMLNode* state)
633 trackview.editor().session()->begin_reversible_command (_("automation range move"));
634 trackview.editor().session()->add_command (
635 new MementoCommand<AutomationList> (memento_command_binder(), state, 0)
638 _drag_points = cp;
639 start_drag_common (0, fraction);
643 struct ControlPointSorter
645 bool operator() (ControlPoint const * a, ControlPoint const * b) {
646 return a->get_x() < b->get_x();
650 /** Common parts of starting a drag.
651 * @param x Starting x position in units, or 0 if x is being ignored.
652 * @param fraction Starting y position (as a fraction of the track height, where 0 is the bottom and 1 the top)
654 void
655 AutomationLine::start_drag_common (double x, float fraction)
657 _drag_x = x;
658 _drag_distance = 0;
659 _last_drag_fraction = fraction;
660 _drag_had_movement = false;
661 did_push = false;
663 _drag_points.sort (ControlPointSorter ());
665 /* find the additional points that will be dragged when the user is holding
666 the "push" modifier
669 uint32_t i = _drag_points.back()->view_index () + 1;
670 ControlPoint* p = 0;
671 _push_points.clear ();
672 while ((p = nth (i)) != 0 && p->can_slide()) {
673 _push_points.push_back (p);
674 ++i;
678 /** Should be called to indicate motion during a drag.
679 * @param x New x position of the drag in units, or undefined if ignore_x == true.
680 * @param fraction New y fraction.
681 * @return x position and y fraction that were actually used (once clamped).
683 pair<double, float>
684 AutomationLine::drag_motion (double x, float fraction, bool ignore_x, bool with_push)
686 /* setup the points that are to be moved this time round */
687 list<ControlPoint*> points = _drag_points;
688 if (with_push) {
689 copy (_push_points.begin(), _push_points.end(), back_inserter (points));
690 points.sort (ControlPointSorter ());
693 double dx = ignore_x ? 0 : (x - _drag_x);
694 double dy = fraction - _last_drag_fraction;
696 /* find x limits */
697 ControlPoint* before = 0;
698 ControlPoint* after = 0;
700 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
701 if ((*i)->get_x() < points.front()->get_x()) {
702 before = *i;
704 if ((*i)->get_x() > points.back()->get_x() && after == 0) {
705 after = *i;
709 double const before_x = before ? before->get_x() : 0;
710 double const after_x = after ? after->get_x() : DBL_MAX;
712 /* clamp x */
713 for (list<ControlPoint*>::iterator i = points.begin(); i != points.end(); ++i) {
715 if ((*i)->can_slide() && !ignore_x) {
717 /* clamp min x */
718 double const a = (*i)->get_x() + dx;
719 double const b = before_x + 1;
720 if (a < b) {
721 dx += b - a;
724 /* clamp max x */
725 if (after) {
727 if (after_x - before_x < 2) {
728 /* after and before are very close, so just leave this alone */
729 dx = 0;
730 } else {
731 double const a = (*i)->get_x() + dx;
732 double const b = after_x - 1;
733 if (a > b) {
734 dx -= a - b;
741 /* clamp y */
742 for (list<ControlPoint*>::iterator i = points.begin(); i != points.end(); ++i) {
743 double const y = ((_height - (*i)->get_y()) / _height) + dy;
744 if (y < 0) {
745 dy -= y;
747 if (y > 1) {
748 dy -= (y - 1);
752 pair<double, float> const clamped (_drag_x + dx, _last_drag_fraction + dy);
753 _drag_distance += dx;
754 _drag_x = x;
755 _last_drag_fraction = fraction;
757 for (list<ControlPoint*>::iterator i = _drag_points.begin(); i != _drag_points.end(); ++i) {
758 (*i)->move_to ((*i)->get_x() + dx, (*i)->get_y() - _height * dy, ControlPoint::Full);
759 reset_line_coords (**i);
762 if (with_push) {
763 /* move push points, preserving their y */
764 for (list<ControlPoint*>::iterator i = _push_points.begin(); i != _push_points.end(); ++i) {
765 (*i)->move_to ((*i)->get_x() + dx, (*i)->get_y(), ControlPoint::Full);
766 reset_line_coords (**i);
770 if (line_points.size() > 1) {
771 line->property_points() = line_points;
774 _drag_had_movement = true;
775 did_push = with_push;
777 return clamped;
780 /** Should be called to indicate the end of a drag */
781 void
782 AutomationLine::end_drag ()
784 if (!_drag_had_movement) {
785 return;
788 alist->freeze ();
790 /* set up the points that were moved this time round */
791 list<ControlPoint*> points = _drag_points;
792 if (did_push) {
793 copy (_push_points.begin(), _push_points.end(), back_inserter (points));
794 points.sort (ControlPointSorter ());
797 sync_model_with_view_points (points, did_push, rint (_drag_distance * trackview.editor().get_current_zoom ()));
799 alist->thaw ();
801 update_pending = false;
803 trackview.editor().session()->add_command (
804 new MementoCommand<AutomationList>(memento_command_binder (), 0, &alist->get_state())
807 trackview.editor().session()->set_dirty ();
810 void
811 AutomationLine::sync_model_with_view_point (ControlPoint& cp, bool did_push, int64_t distance)
813 ModelRepresentation mr;
814 double ydelta;
816 model_representation (cp, mr);
818 /* how much are we changing the central point by */
820 ydelta = mr.yval - mr.ypos;
823 apply the full change to the central point, and interpolate
824 on both axes to cover all model points represented
825 by the control point.
828 /* change all points before the primary point */
830 for (AutomationList::iterator i = mr.start; i != cp.model(); ++i) {
832 double fract = ((*i)->when - mr.xmin) / (mr.xpos - mr.xmin);
833 double y_delta = ydelta * fract;
834 double x_delta = distance * fract;
836 /* interpolate */
838 if (y_delta || x_delta) {
839 alist->modify (i, (*i)->when + x_delta, mr.ymin + y_delta);
843 /* change the primary point */
845 update_pending = true;
846 alist->modify (cp.model(), mr.xval, mr.yval);
848 /* change later points */
850 AutomationList::iterator i = cp.model();
852 ++i;
854 while (i != mr.end) {
856 double delta = ydelta * (mr.xmax - (*i)->when) / (mr.xmax - mr.xpos);
858 /* all later points move by the same distance along the x-axis as the main point */
860 if (delta) {
861 alist->modify (i, (*i)->when + distance, (*i)->value + delta);
864 ++i;
867 if (did_push) {
869 /* move all points after the range represented by the view by the same distance
870 as the main point moved.
873 alist->slide (mr.end, distance);
877 bool
878 AutomationLine::control_points_adjacent (double xval, uint32_t & before, uint32_t& after)
880 ControlPoint *bcp = 0;
881 ControlPoint *acp = 0;
882 double unit_xval;
884 unit_xval = trackview.editor().frame_to_unit (xval);
886 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
888 if ((*i)->get_x() <= unit_xval) {
890 if (!bcp || (*i)->get_x() > bcp->get_x()) {
891 bcp = *i;
892 before = bcp->view_index();
895 } else if ((*i)->get_x() > unit_xval) {
896 acp = *i;
897 after = acp->view_index();
898 break;
902 return bcp && acp;
905 bool
906 AutomationLine::is_last_point (ControlPoint& cp)
908 ModelRepresentation mr;
910 model_representation (cp, mr);
912 // If the list is not empty, and the point is the last point in the list
914 if (!alist->empty() && mr.end == alist->end()) {
915 return true;
918 return false;
921 bool
922 AutomationLine::is_first_point (ControlPoint& cp)
924 ModelRepresentation mr;
926 model_representation (cp, mr);
928 // If the list is not empty, and the point is the first point in the list
930 if (!alist->empty() && mr.start == alist->begin()) {
931 return true;
934 return false;
937 // This is copied into AudioRegionGainLine
938 void
939 AutomationLine::remove_point (ControlPoint& cp)
941 ModelRepresentation mr;
943 model_representation (cp, mr);
945 trackview.editor().session()->begin_reversible_command (_("remove control point"));
946 XMLNode &before = alist->get_state();
948 alist->erase (mr.start, mr.end);
950 trackview.editor().session()->add_command(
951 new MementoCommand<AutomationList> (memento_command_binder (), &before, &alist->get_state())
954 trackview.editor().session()->commit_reversible_command ();
955 trackview.editor().session()->set_dirty ();
958 /** Get selectable points within an area.
959 * @param start Start position in session frames.
960 * @param end End position in session frames.
961 * @param bot Bottom y range, as a fraction of line height, where 0 is the bottom of the line.
962 * @param top Top y range, as a fraction of line height, where 0 is the bottom of the line.
963 * @param result Filled in with selectable things; in this case, ControlPoints.
965 void
966 AutomationLine::get_selectables (
967 framepos_t start, framepos_t end, double botfrac, double topfrac, list<Selectable*>& results
970 /* convert fractions to display coordinates with 0 at the top of the track */
971 double const bot_track = (1 - topfrac) * trackview.current_height ();
972 double const top_track = (1 - botfrac) * trackview.current_height ();
974 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
975 double const model_when = (*(*i)->model())->when;
976 framepos_t const session_frames_when = _time_converter.to (model_when - _offset) + _time_converter.origin_b ();
978 if (session_frames_when >= start && session_frames_when <= end && (*i)->get_y() >= bot_track && (*i)->get_y() <= top_track) {
979 results.push_back (*i);
984 void
985 AutomationLine::get_inverted_selectables (Selection&, list<Selectable*>& /*results*/)
987 // hmmm ....
990 /** Take a PointSelection and find ControlPoints that fall within it */
991 list<ControlPoint*>
992 AutomationLine::point_selection_to_control_points (PointSelection const & s)
994 list<ControlPoint*> cp;
996 for (PointSelection::const_iterator i = s.begin(); i != s.end(); ++i) {
998 if (i->track != &trackview) {
999 continue;
1002 double const bot = (1 - i->high_fract) * trackview.current_height ();
1003 double const top = (1 - i->low_fract) * trackview.current_height ();
1005 for (vector<ControlPoint*>::iterator j = control_points.begin(); j != control_points.end(); ++j) {
1007 double const rstart = trackview.editor().frame_to_unit (_time_converter.to (i->start));
1008 double const rend = trackview.editor().frame_to_unit (_time_converter.to (i->end));
1010 if ((*j)->get_x() >= rstart && (*j)->get_x() <= rend) {
1011 if ((*j)->get_y() >= bot && (*j)->get_y() <= top) {
1012 cp.push_back (*j);
1019 return cp;
1022 void
1023 AutomationLine::set_selected_points (PointSelection& points)
1025 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1026 (*i)->set_selected (false);
1029 if (!points.empty()) {
1030 list<ControlPoint*> cp = point_selection_to_control_points (points);
1031 for (list<ControlPoint*>::iterator i = cp.begin(); i != cp.end(); ++i) {
1032 (*i)->set_selected (true);
1036 set_colors ();
1039 void AutomationLine::set_colors ()
1041 set_line_color (ARDOUR_UI::config()->canvasvar_AutomationLine.get());
1042 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1043 (*i)->set_color ();
1047 void
1048 AutomationLine::list_changed ()
1050 queue_reset ();
1053 void
1054 AutomationLine::reset_callback (const Evoral::ControlList& events)
1056 ALPoints tmp_points;
1057 uint32_t npoints = events.size();
1059 if (npoints == 0) {
1060 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1061 delete *i;
1063 control_points.clear ();
1064 line->hide();
1065 return;
1068 AutomationList::const_iterator ai;
1070 for (ai = events.begin(); ai != events.end(); ++ai) {
1072 double translated_x = (*ai)->when;
1073 double translated_y = (*ai)->value;
1074 model_to_view_coord (translated_x, translated_y);
1076 if (translated_x >= 0 && translated_x < _maximum_time) {
1077 tmp_points.push_back (ALPoint (
1078 trackview.editor().frame_to_unit (translated_x),
1079 _height - (translated_y * _height))
1084 determine_visible_control_points (tmp_points);
1087 void
1088 AutomationLine::reset ()
1090 update_pending = false;
1092 if (no_draw) {
1093 return;
1096 alist->apply_to_points (*this, &AutomationLine::reset_callback);
1099 void
1100 AutomationLine::clear ()
1102 /* parent must create and commit command */
1103 XMLNode &before = alist->get_state();
1104 alist->clear();
1106 trackview.editor().session()->add_command (
1107 new MementoCommand<AutomationList> (memento_command_binder (), &before, &alist->get_state())
1111 void
1112 AutomationLine::change_model (AutomationList::iterator /*i*/, double /*x*/, double /*y*/)
1116 void
1117 AutomationLine::set_list (boost::shared_ptr<ARDOUR::AutomationList> list)
1119 alist = list;
1120 queue_reset ();
1121 connect_to_list ();
1124 void
1125 AutomationLine::show_all_control_points ()
1127 if (_is_boolean) {
1128 // show the line but don't allow any control points
1129 return;
1132 points_visible = true;
1134 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1135 if (!(*i)->visible()) {
1136 (*i)->show ();
1137 (*i)->set_visible (true);
1142 void
1143 AutomationLine::hide_all_but_selected_control_points ()
1145 if (alist->interpolation() == AutomationList::Discrete) {
1146 return;
1149 points_visible = false;
1151 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1152 if (!(*i)->get_selected()) {
1153 (*i)->set_visible (false);
1158 void
1159 AutomationLine::track_entered()
1161 if (alist->interpolation() != AutomationList::Discrete) {
1162 show_all_control_points();
1166 void
1167 AutomationLine::track_exited()
1169 if (alist->interpolation() != AutomationList::Discrete) {
1170 hide_all_but_selected_control_points();
1174 XMLNode &
1175 AutomationLine::get_state (void)
1177 /* function as a proxy for the model */
1178 return alist->get_state();
1182 AutomationLine::set_state (const XMLNode &node, int version)
1184 /* function as a proxy for the model */
1185 return alist->set_state (node, version);
1188 void
1189 AutomationLine::view_to_model_coord (double& x, double& y) const
1191 x = _time_converter.from (x);
1192 view_to_model_coord_y (y);
1195 void
1196 AutomationLine::view_to_model_coord_y (double& y) const
1198 /* TODO: This should be more generic ... */
1199 if (alist->parameter().type() == GainAutomation ||
1200 alist->parameter().type() == EnvelopeAutomation) {
1201 y = slider_position_to_gain_with_max (y, Config->get_max_gain());
1202 y = max (0.0, y);
1203 y = min (2.0, y);
1204 } else if (alist->parameter().type() == PanAzimuthAutomation ||
1205 alist->parameter().type() == PanElevationAutomation ||
1206 alist->parameter().type() == PanWidthAutomation) {
1207 y = 1.0 - y;
1208 } else if (alist->parameter().type() == PluginAutomation) {
1209 y = y * (double)(alist->get_max_y()- alist->get_min_y()) + alist->get_min_y();
1210 } else {
1211 y = rint (y * alist->parameter().max());
1215 void
1216 AutomationLine::model_to_view_coord (double& x, double& y) const
1218 /* TODO: This should be more generic ... */
1219 if (alist->parameter().type() == GainAutomation ||
1220 alist->parameter().type() == EnvelopeAutomation) {
1221 y = gain_to_slider_position_with_max (y, Config->get_max_gain());
1222 } else if (alist->parameter().type() == PanAzimuthAutomation ||
1223 alist->parameter().type() == PanElevationAutomation ||
1224 alist->parameter().type() == PanWidthAutomation) {
1225 // vertical coordinate axis reversal
1226 y = 1.0 - y;
1227 } else if (alist->parameter().type() == PluginAutomation) {
1228 y = (y - alist->get_min_y()) / (double)(alist->get_max_y()- alist->get_min_y());
1229 } else {
1230 y = y / (double)alist->parameter().max(); /* ... like this */
1233 x = _time_converter.to (x) - _offset;
1236 /** Called when our list has announced that its interpolation style has changed */
1237 void
1238 AutomationLine::interpolation_changed (AutomationList::InterpolationStyle style)
1240 if (style == AutomationList::Discrete) {
1241 show_all_control_points();
1242 line->hide();
1243 } else {
1244 hide_all_but_selected_control_points();
1245 line->show();
1249 void
1250 AutomationLine::add_visible_control_point (uint32_t view_index, uint32_t pi, double tx, double ty, AutomationList::iterator model, uint32_t npoints)
1252 if (view_index >= control_points.size()) {
1254 /* make sure we have enough control points */
1256 ControlPoint* ncp = new ControlPoint (*this);
1257 ncp->set_size (control_point_box_size ());
1259 control_points.push_back (ncp);
1262 ControlPoint::ShapeType shape;
1264 if (!terminal_points_can_slide) {
1265 if (pi == 0) {
1266 control_points[view_index]->set_can_slide(false);
1267 if (tx == 0) {
1268 shape = ControlPoint::Start;
1269 } else {
1270 shape = ControlPoint::Full;
1272 } else if (pi == npoints - 1) {
1273 control_points[view_index]->set_can_slide(false);
1274 shape = ControlPoint::End;
1275 } else {
1276 control_points[view_index]->set_can_slide(true);
1277 shape = ControlPoint::Full;
1279 } else {
1280 control_points[view_index]->set_can_slide(true);
1281 shape = ControlPoint::Full;
1284 control_points[view_index]->reset (tx, ty, model, view_index, shape);
1286 /* finally, control visibility */
1288 if (_visible && points_visible) {
1289 control_points[view_index]->show ();
1290 control_points[view_index]->set_visible (true);
1291 } else {
1292 if (!points_visible) {
1293 control_points[view_index]->set_visible (false);
1298 void
1299 AutomationLine::add_always_in_view (double x)
1301 _always_in_view.push_back (x);
1302 alist->apply_to_points (*this, &AutomationLine::reset_callback);
1305 void
1306 AutomationLine::clear_always_in_view ()
1308 _always_in_view.clear ();
1309 alist->apply_to_points (*this, &AutomationLine::reset_callback);
1312 void
1313 AutomationLine::connect_to_list ()
1315 _list_connections.drop_connections ();
1317 alist->StateChanged.connect (_list_connections, invalidator (*this), boost::bind (&AutomationLine::list_changed, this), gui_context());
1319 alist->InterpolationChanged.connect (
1320 _list_connections, invalidator (*this), boost::bind (&AutomationLine::interpolation_changed, this, _1), gui_context()
1324 MementoCommandBinder<AutomationList>*
1325 AutomationLine::memento_command_binder ()
1327 return new SimpleMementoCommandBinder<AutomationList> (*alist.get());
1330 /** Set the maximum time that points on this line can be at, relative
1331 * to the start of the track or region that it is on.
1333 void
1334 AutomationLine::set_maximum_time (framecnt_t t)
1336 if (_maximum_time == t) {
1337 return;
1340 _maximum_time = t;
1341 reset ();
1345 /** @return min and max x positions of points that are in the list, in session frames */
1346 pair<framepos_t, framepos_t>
1347 AutomationLine::get_point_x_range () const
1349 pair<framepos_t, framepos_t> r (max_framepos, 0);
1351 for (AutomationList::const_iterator i = the_list()->begin(); i != the_list()->end(); ++i) {
1352 r.first = min (r.first, _time_converter.to ((*i)->when) + _offset + _time_converter.origin_b ());
1353 r.second = max (r.second, _time_converter.to ((*i)->when) + _offset + _time_converter.origin_b ());
1356 return r;
1359 void
1360 AutomationLine::set_offset (framepos_t off)
1362 if (_offset == off) {
1363 return;
1366 _offset = off;
1367 reset ();