2 Copyright (C) 2002-2003 Paul Davis
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
25 #include "pbd/stl_delete.h"
26 #include "pbd/memento_command.h"
27 #include "pbd/stacktrace.h"
29 #include "ardour/automation_list.h"
30 #include "ardour/dB.h"
31 #include "evoral/Curve.hpp"
33 #include "simplerect.h"
34 #include "automation_line.h"
35 #include "control_point.h"
36 #include "gui_thread.h"
37 #include "rgb_macros.h"
38 #include "ardour_ui.h"
39 #include "public_editor.h"
41 #include "selection.h"
42 #include "time_axis_view.h"
43 #include "point_selection.h"
44 #include "automation_selectable.h"
45 #include "automation_time_axis.h"
46 #include "public_editor.h"
48 #include "ardour/event_type_map.h"
49 #include "ardour/session.h"
54 using namespace ARDOUR;
56 using namespace Editing;
57 using namespace Gnome; // for Canvas
59 static const Evoral::IdentityConverter<double, sframes_t> default_converter;
61 AutomationLine::AutomationLine (const string& name, TimeAxisView& tv, ArdourCanvas::Group& parent,
62 boost::shared_ptr<AutomationList> al,
63 const Evoral::TimeConverter<double, sframes_t>* converter)
67 , _parent_group (parent)
68 , _time_converter (converter ? (*converter) : default_converter)
70 _interpolation = al->interpolation();
71 points_visible = false;
72 update_pending = false;
73 _uses_gain_mapping = false;
76 terminal_points_can_slide = true;
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 alist->StateChanged.connect (_state_connection, boost::bind (&AutomationLine::list_changed, this), gui_context());
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 set_interpolation(alist->interpolation());
101 AutomationLine::~AutomationLine ()
103 vector_delete (&control_points);
108 AutomationLine::event_handler (GdkEvent* event)
110 return PublicEditor::instance().canvas_line_event (event, line, this);
114 AutomationLine::queue_reset ()
116 if (!update_pending) {
117 update_pending = true;
118 Gtkmm2ext::UI::instance()->call_slot (boost::bind (&AutomationLine::reset, this));
123 AutomationLine::show ()
125 if (_interpolation != AutomationList::Discrete) {
129 if (points_visible) {
130 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
139 AutomationLine::hide ()
142 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
149 AutomationLine::control_point_box_size ()
151 if (_interpolation == AutomationList::Discrete) {
152 return max((_height*4.0) / (double)(alist->parameter().max() - alist->parameter().min()),
156 if (_height > TimeAxisView::hLarger) {
158 } else if (_height > (guint32) TimeAxisView::hNormal) {
165 AutomationLine::set_height (guint32 h)
170 double bsz = control_point_box_size();
172 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
173 (*i)->set_size (bsz);
181 AutomationLine::set_line_color (uint32_t color)
184 line->property_fill_color_rgba() = color;
188 AutomationLine::set_uses_gain_mapping (bool yn)
190 if (yn != _uses_gain_mapping) {
191 _uses_gain_mapping = yn;
197 AutomationLine::nth (uint32_t n)
199 if (n < control_points.size()) {
200 return control_points[n];
207 AutomationLine::modify_point_y (ControlPoint& cp, double y)
209 /* clamp y-coord appropriately. y is supposed to be a normalized fraction (0.0-1.0),
210 and needs to be converted to a canvas unit distance.
215 y = _height - (y * _height);
217 double const x = trackview.editor().frame_to_unit (_time_converter.to((*cp.model())->when));
219 trackview.editor().session()->begin_reversible_command (_("automation event move"));
220 trackview.editor().session()->add_command (new MementoCommand<AutomationList>(*alist.get(), &get_state(), 0));
222 cp.move_to (x, y, ControlPoint::Full);
223 reset_line_coords (cp);
225 if (line_points.size() > 1) {
226 line->property_points() = line_points;
230 sync_model_with_view_point (cp, false, 0);
233 update_pending = false;
235 trackview.editor().session()->add_command (new MementoCommand<AutomationList>(*alist.get(), 0, &alist->get_state()));
236 trackview.editor().session()->commit_reversible_command ();
237 trackview.editor().session()->set_dirty ();
242 AutomationLine::modify_view_point (ControlPoint& cp, double x, double y, bool keep_x, bool with_push)
245 uint32_t last_movable = UINT_MAX;
246 double x_limit = DBL_MAX;
248 /* this just changes the current view. it does not alter
249 the model in any way at all.
252 /* clamp y-coord appropriately. y is supposed to be a normalized fraction (0.0-1.0),
253 and needs to be converted to a canvas unit distance.
258 y = _height - (y * _height);
260 if (cp.can_slide() && !keep_x) {
262 /* x-coord cannot move beyond adjacent points or the start/end, and is
263 already in frames. it needs to be converted to canvas units.
266 x = trackview.editor().frame_to_unit (x);
268 /* clamp x position using view coordinates */
270 ControlPoint *before;
273 if (cp.view_index()) {
274 before = nth (cp.view_index() - 1);
275 x = max (x, before->get_x()+1.0);
282 if (cp.view_index() < control_points.size() - 1) {
284 after = nth (cp.view_index() + 1);
286 /*if it is a "spike" leave the x alone */
288 if (after->get_x() - before->get_x() < 2) {
292 x = min (x, after->get_x()-1.0);
302 /* find the first point that can't move */
304 for (uint32_t n = cp.view_index() + 1; (after = nth (n)) != 0; ++n) {
305 if (!after->can_slide()) {
306 x_limit = after->get_x() - 1.0;
307 last_movable = after->view_index();
312 delta = x - cp.get_x();
317 /* leave the x-coordinate alone */
319 x = trackview.editor().frame_to_unit (_time_converter.to((*cp.model())->when));
325 cp.move_to (x, y, ControlPoint::Full);
326 reset_line_coords (cp);
330 uint32_t limit = min (control_points.size(), (size_t)last_movable);
332 /* move the current point to wherever the user told it to go, subject
336 cp.move_to (min (x, x_limit), y, ControlPoint::Full);
337 reset_line_coords (cp);
339 /* now move all subsequent control points, to reflect the motion.
342 for (uint32_t i = cp.view_index() + 1; i < limit; ++i) {
343 ControlPoint *p = nth (i);
346 if (p->can_slide()) {
347 new_x = min (p->get_x() + delta, x_limit);
348 p->move_to (new_x, p->get_y(), ControlPoint::Full);
349 reset_line_coords (*p);
356 AutomationLine::reset_line_coords (ControlPoint& cp)
358 if (cp.view_index() < line_points.size()) {
359 line_points[cp.view_index()].set_x (cp.get_x());
360 line_points[cp.view_index()].set_y (cp.get_y());
365 AutomationLine::sync_model_with_view_points (list<ControlPoint*> cp, bool did_push, int64_t distance)
367 update_pending = true;
369 for (list<ControlPoint*>::iterator i = cp.begin(); i != cp.end(); ++i) {
370 sync_model_with_view_point (**i, did_push, distance);
375 AutomationLine::model_representation (ControlPoint& cp, ModelRepresentation& mr)
377 /* part one: find out where the visual control point is.
378 initial results are in canvas units. ask the
379 line to convert them to something relevant.
382 mr.xval = cp.get_x();
383 mr.yval = 1.0 - (cp.get_y() / _height);
385 /* if xval has not changed, set it directly from the model to avoid rounding errors */
387 if (mr.xval == trackview.editor().frame_to_unit(_time_converter.to((*cp.model())->when))) {
388 mr.xval = (*cp.model())->when;
390 mr.xval = trackview.editor().unit_to_frame (mr.xval);
393 /* convert to model units
396 view_to_model_coord (mr.xval, mr.yval);
398 /* part 2: find out where the model point is now
401 mr.xpos = (*cp.model())->when;
402 mr.ypos = (*cp.model())->value;
404 /* part 3: get the position of the visual control
405 points before and after us.
408 ControlPoint* before;
411 if (cp.view_index()) {
412 before = nth (cp.view_index() - 1);
417 after = nth (cp.view_index() + 1);
420 mr.xmin = (*before->model())->when;
421 mr.ymin = (*before->model())->value;
422 mr.start = before->model();
427 mr.start = cp.model();
431 mr.end = after->model();
441 AutomationLine::determine_visible_control_points (ALPoints& points)
443 uint32_t view_index, pi, n;
444 AutomationList::iterator model;
446 uint32_t this_rx = 0;
447 uint32_t prev_rx = 0;
448 uint32_t this_ry = 0;
449 uint32_t prev_ry = 0;
453 /* hide all existing points, and the line */
455 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
461 if (points.empty()) {
465 npoints = points.size();
467 /* compute derivative/slope for the entire line */
469 slope = new double[npoints];
471 for (n = 0; n < npoints - 1; ++n) {
472 double xdelta = points[n+1].x - points[n].x;
473 double ydelta = points[n+1].y - points[n].y;
474 slope[n] = ydelta/xdelta;
477 box_size = (uint32_t) control_point_box_size ();
479 /* read all points and decide which ones to show as control points */
483 for (model = alist->begin(), pi = 0; pi < npoints; ++model, ++pi) {
485 double tx = points[pi].x;
486 double ty = points[pi].y;
488 if (find (_always_in_view.begin(), _always_in_view.end(), (*model)->when) != _always_in_view.end()) {
489 add_visible_control_point (view_index, pi, tx, ty, model, npoints);
496 if (isnan (tx) || isnan (ty)) {
497 warning << string_compose (_("Ignoring illegal points on AutomationLine \"%1\""),
502 /* now ensure that the control_points vector reflects the current curve
503 state, but don't plot control points too close together. also, don't
504 plot a series of points all with the same value.
506 always plot the first and last points, of course.
509 if (invalid_point (points, pi)) {
510 /* for some reason, we are supposed to ignore this point,
511 but still keep track of the model index.
516 if (pi > 0 && pi < npoints - 1) {
517 if (slope[pi] == slope[pi-1]) {
519 /* no reason to display this point */
525 /* need to round here. the ultimate coordinates are integer
526 pixels, so tiny deltas in the coords will be eliminated
527 and we end up with "colinear" line segments. since the
528 line rendering code in libart doesn't like this very
529 much, we eliminate them here. don't do this for the first and last
533 this_rx = (uint32_t) rint (tx);
534 this_ry = (uint32_t) rint (ty);
536 if (view_index && pi != npoints && /* not the first, not the last */
537 (((this_rx == prev_rx) && (this_ry == prev_ry)) || /* same point */
538 (((this_rx - prev_rx) < (box_size + 2)) && /* not identical, but still too close horizontally */
539 (abs ((int)(this_ry - prev_ry)) < (int) (box_size + 2))))) { /* too close vertically */
543 /* ok, we should display this point */
545 add_visible_control_point (view_index, pi, tx, ty, model, npoints);
553 /* discard extra CP's to avoid confusing ourselves */
555 while (control_points.size() > view_index) {
556 ControlPoint* cp = control_points.back();
557 control_points.pop_back ();
561 if (!terminal_points_can_slide) {
562 control_points.back()->set_can_slide(false);
567 if (view_index > 1) {
569 npoints = view_index;
571 /* reset the line coordinates */
573 while (line_points.size() < npoints) {
574 line_points.push_back (Art::Point (0,0));
577 while (line_points.size() > npoints) {
578 line_points.pop_back ();
581 for (view_index = 0; view_index < npoints; ++view_index) {
582 line_points[view_index].set_x (control_points[view_index]->get_x());
583 line_points[view_index].set_y (control_points[view_index]->get_y());
586 line->property_points() = line_points;
588 if (_visible && _interpolation != AutomationList::Discrete) {
594 set_selected_points (trackview.editor().get_selection().points);
599 AutomationLine::get_verbose_cursor_string (double fraction) const
601 std::string s = fraction_to_string (fraction);
602 if (_uses_gain_mapping) {
610 * @param fraction y fraction
611 * @return string representation of this value, using dB if appropriate.
614 AutomationLine::fraction_to_string (double fraction) const
618 if (_uses_gain_mapping) {
619 if (fraction == 0.0) {
620 snprintf (buf, sizeof (buf), "-inf");
622 snprintf (buf, sizeof (buf), "%.1f", accurate_coefficient_to_dB (slider_position_to_gain (fraction)));
626 view_to_model_coord (dummy, fraction);
627 if (EventTypeMap::instance().is_integer (alist->parameter())) {
628 snprintf (buf, sizeof (buf), "%d", (int)fraction);
630 snprintf (buf, sizeof (buf), "%.2f", fraction);
639 * @param s Value string in the form as returned by fraction_to_string.
640 * @return Corresponding y fraction.
643 AutomationLine::string_to_fraction (string const & s) const
650 sscanf (s.c_str(), "%lf", &v);
652 if (_uses_gain_mapping) {
653 v = gain_to_slider_position (dB_to_coefficient (v));
656 model_to_view_coord (dummy, v);
663 AutomationLine::invalid_point (ALPoints& p, uint32_t index)
665 return p[index].x == max_frames && p[index].y == DBL_MAX;
669 AutomationLine::invalidate_point (ALPoints& p, uint32_t index)
671 p[index].x = max_frames;
672 p[index].y = DBL_MAX;
675 /** Start dragging a single point.
676 * @param cp Point to drag.
677 * @param x Initial x position (frames).
678 * @param fraction Initial y position (as a fraction of the track height, where 0 is the bottom and 1 the top)
681 AutomationLine::start_drag_single (ControlPoint* cp, nframes_t x, float fraction)
683 trackview.editor().session()->begin_reversible_command (_("automation event move"));
684 trackview.editor().session()->add_command (new MementoCommand<AutomationList>(*alist.get(), &get_state(), 0));
686 _drag_points.clear ();
687 _drag_points.push_back (cp);
688 start_drag_common (x, fraction);
691 /** Start dragging a line vertically (with no change in x)
692 * @param i1 Control point index of the `left' point on the line.
693 * @param i2 Control point index of the `right' point on the line.
694 * @param fraction Initial y position (as a fraction of the track height, where 0 is the bottom and 1 the top)
697 AutomationLine::start_drag_line (uint32_t i1, uint32_t i2, float fraction)
699 trackview.editor().session()->begin_reversible_command (_("automation range move"));
700 trackview.editor().session()->add_command (new MementoCommand<AutomationList>(*alist.get(), &get_state(), 0));
702 _drag_points.clear ();
704 // check if one of the control points on the line is in a selected range
705 bool range_found = false;
708 for (uint32_t i = i1 ; i <= i2; i++) {
710 if (cp->selected()) {
716 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
717 if ((*i)->selected()) {
718 _drag_points.push_back (*i);
722 for (uint32_t i = i1 ; i <= i2; i++) {
723 _drag_points.push_back (nth (i));
727 start_drag_common (0, fraction);
730 /** Start dragging multiple points (with no change in x)
731 * @param cp Points to drag.
732 * @param fraction Initial y position (as a fraction of the track height, where 0 is the bottom and 1 the top)
735 AutomationLine::start_drag_multiple (list<ControlPoint*> cp, float fraction, XMLNode* state)
737 trackview.editor().session()->begin_reversible_command (_("automation range move"));
738 trackview.editor().session()->add_command (new MementoCommand<AutomationList>(*alist.get(), state, 0));
741 start_drag_common (0, fraction);
744 /** Common parts of starting a drag.
745 * @param d Description of the drag.
746 * @param x Starting x position in frames, or 0 if x is being ignored.
747 * @param fraction Starting y position (as a fraction of the track height, where 0 is the bottom and 1 the top)
750 AutomationLine::start_drag_common (nframes_t x, float fraction)
754 _last_drag_fraction = fraction;
755 _drag_had_movement = false;
759 /** Should be called to indicate motion during a drag.
760 * @param x New x position of the drag in frames, or 0 if x is being ignored.
761 * @param fraction New y fraction.
764 AutomationLine::drag_motion (nframes_t x, float fraction, bool with_push)
766 int64_t const dx = x - drag_x;
770 double dy = fraction - _last_drag_fraction;
771 _last_drag_fraction = fraction;
773 /* clamp y so that the "lowest" point hits the bottom but goes no further */
774 for (list<ControlPoint*>::iterator i = _drag_points.begin(); i != _drag_points.end(); ++i) {
775 double const y = ((_height - (*i)->get_y()) / _height) + dy;
781 for (list<ControlPoint*>::iterator i = _drag_points.begin(); i != _drag_points.end(); ++i) {
785 trackview.editor().unit_to_frame ((*i)->get_x()) + dx,
786 ((_height - (*i)->get_y()) / _height) + dy,
792 if (line_points.size() > 1) {
793 line->property_points() = line_points;
796 _drag_had_movement = true;
797 did_push = with_push;
800 /** Should be called to indicate the end of a drag */
802 AutomationLine::end_drag ()
804 if (!_drag_had_movement) {
810 sync_model_with_view_points (_drag_points, did_push, drag_distance);
814 update_pending = false;
816 trackview.editor().session()->add_command (new MementoCommand<AutomationList>(*alist.get(), 0, &alist->get_state()));
817 trackview.editor().session()->commit_reversible_command ();
818 trackview.editor().session()->set_dirty ();
822 AutomationLine::sync_model_with_view_point (ControlPoint& cp, bool did_push, int64_t distance)
824 ModelRepresentation mr;
827 model_representation (cp, mr);
829 /* how much are we changing the central point by */
831 ydelta = mr.yval - mr.ypos;
834 apply the full change to the central point, and interpolate
835 on both axes to cover all model points represented
836 by the control point.
839 /* change all points before the primary point */
841 for (AutomationList::iterator i = mr.start; i != cp.model(); ++i) {
843 double fract = ((*i)->when - mr.xmin) / (mr.xpos - mr.xmin);
844 double y_delta = ydelta * fract;
845 double x_delta = distance * fract;
849 if (y_delta || x_delta) {
850 alist->modify (i, (*i)->when + x_delta, mr.ymin + y_delta);
854 /* change the primary point */
856 update_pending = true;
857 alist->modify (cp.model(), mr.xval, mr.yval);
860 /* change later points */
862 AutomationList::iterator i = cp.model();
866 while (i != mr.end) {
868 double delta = ydelta * (mr.xmax - (*i)->when) / (mr.xmax - mr.xpos);
870 /* all later points move by the same distance along the x-axis as the main point */
873 alist->modify (i, (*i)->when + distance, (*i)->value + delta);
881 /* move all points after the range represented by the view by the same distance
882 as the main point moved.
885 alist->slide (mr.end, drag_distance);
890 AutomationLine::control_points_adjacent (double xval, uint32_t & before, uint32_t& after)
892 ControlPoint *bcp = 0;
893 ControlPoint *acp = 0;
896 unit_xval = trackview.editor().frame_to_unit (xval);
898 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
900 if ((*i)->get_x() <= unit_xval) {
902 if (!bcp || (*i)->get_x() > bcp->get_x()) {
904 before = bcp->view_index();
907 } else if ((*i)->get_x() > unit_xval) {
909 after = acp->view_index();
918 AutomationLine::is_last_point (ControlPoint& cp)
920 ModelRepresentation mr;
922 model_representation (cp, mr);
924 // If the list is not empty, and the point is the last point in the list
926 if (!alist->empty() && mr.end == alist->end()) {
934 AutomationLine::is_first_point (ControlPoint& cp)
936 ModelRepresentation mr;
938 model_representation (cp, mr);
940 // If the list is not empty, and the point is the first point in the list
942 if (!alist->empty() && mr.start == alist->begin()) {
949 // This is copied into AudioRegionGainLine
951 AutomationLine::remove_point (ControlPoint& cp)
953 ModelRepresentation mr;
955 model_representation (cp, mr);
957 trackview.editor().session()->begin_reversible_command (_("remove control point"));
958 XMLNode &before = alist->get_state();
960 alist->erase (mr.start, mr.end);
962 trackview.editor().session()->add_command(new MementoCommand<AutomationList>(
963 *alist.get(), &before, &alist->get_state()));
964 trackview.editor().session()->commit_reversible_command ();
965 trackview.editor().session()->set_dirty ();
969 AutomationLine::get_selectables (nframes_t& start, nframes_t& end,
970 double botfrac, double topfrac, list<Selectable*>& results)
977 bool collecting = false;
979 /* Curse X11 and its inverted coordinate system! */
981 bot = (1.0 - topfrac) * _height;
982 top = (1.0 - botfrac) * _height;
987 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
988 double when = (*(*i)->model())->when;
990 if (when >= start && when <= end) {
992 if ((*i)->get_y() >= bot && (*i)->get_y() <= top) {
995 (*i)->set_visible(true);
997 nstart = min (nstart, when);
998 nend = max (nend, when);
1004 results.push_back (new AutomationSelectable (nstart, nend, botfrac, topfrac, &trackview));
1006 nstart = max_frames;
1014 results.push_back (new AutomationSelectable (nstart, nend, botfrac, topfrac, &trackview));
1020 AutomationLine::get_inverted_selectables (Selection&, list<Selectable*>& /*results*/)
1026 AutomationLine::set_selected_points (PointSelection& points)
1031 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1032 (*i)->set_selected(false);
1035 if (points.empty()) {
1039 for (PointSelection::iterator r = points.begin(); r != points.end(); ++r) {
1041 if ((*r).track != &trackview) {
1045 /* Curse X11 and its inverted coordinate system! */
1047 bot = (1.0 - (*r).high_fract) * _height;
1048 top = (1.0 - (*r).low_fract) * _height;
1050 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1052 double rstart, rend;
1054 rstart = trackview.editor().frame_to_unit ((*r).start);
1055 rend = trackview.editor().frame_to_unit ((*r).end);
1057 if ((*i)->get_x() >= rstart && (*i)->get_x() <= rend) {
1059 if ((*i)->get_y() >= bot && (*i)->get_y() <= top) {
1061 (*i)->set_selected(true);
1069 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1070 (*i)->show_color (false, !points_visible);
1075 void AutomationLine::set_colors() {
1076 set_line_color( ARDOUR_UI::config()->canvasvar_AutomationLine.get() );
1077 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1078 (*i)->show_color (false, !points_visible);
1083 AutomationLine::show_selection ()
1085 TimeSelection& time (trackview.editor().get_selection().time);
1087 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1089 (*i)->set_selected(false);
1091 for (list<AudioRange>::iterator r = time.begin(); r != time.end(); ++r) {
1092 double rstart, rend;
1094 rstart = trackview.editor().frame_to_unit ((*r).start);
1095 rend = trackview.editor().frame_to_unit ((*r).end);
1097 if ((*i)->get_x() >= rstart && (*i)->get_x() <= rend) {
1098 (*i)->set_selected(true);
1103 (*i)->show_color (false, !points_visible);
1108 AutomationLine::hide_selection ()
1110 // show_selection ();
1114 AutomationLine::list_changed ()
1120 AutomationLine::reset_callback (const Evoral::ControlList& events)
1122 ALPoints tmp_points;
1123 uint32_t npoints = events.size();
1126 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1129 control_points.clear ();
1134 AutomationList::const_iterator ai;
1136 for (ai = events.begin(); ai != events.end(); ++ai) {
1138 double translated_x = (*ai)->when;
1139 double translated_y = (*ai)->value;
1140 model_to_view_coord (translated_x, translated_y);
1142 add_model_point (tmp_points, (*ai)->when, translated_y);
1145 determine_visible_control_points (tmp_points);
1150 AutomationLine::add_model_point (ALPoints& tmp_points, double frame, double yfract)
1152 tmp_points.push_back (ALPoint (trackview.editor().frame_to_unit (_time_converter.to(frame)),
1153 _height - (yfract * _height)));
1157 AutomationLine::reset ()
1159 update_pending = false;
1165 alist->apply_to_points (*this, &AutomationLine::reset_callback);
1169 AutomationLine::clear ()
1171 /* parent must create command */
1172 XMLNode &before = get_state();
1174 trackview.editor().session()->add_command (
1175 new MementoCommand<AutomationLine>(*this, &before, &get_state()));
1176 trackview.editor().session()->commit_reversible_command ();
1177 trackview.editor().session()->set_dirty ();
1181 AutomationLine::change_model (AutomationList::iterator /*i*/, double /*x*/, double /*y*/)
1186 AutomationLine::set_list(boost::shared_ptr<ARDOUR::AutomationList> list)
1193 AutomationLine::show_all_control_points ()
1195 points_visible = true;
1197 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1198 (*i)->show_color((_interpolation != AutomationList::Discrete), false);
1200 (*i)->set_visible (true);
1205 AutomationLine::hide_all_but_selected_control_points ()
1207 if (alist->interpolation() == AutomationList::Discrete) {
1211 points_visible = false;
1213 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1214 if (!(*i)->selected()) {
1215 (*i)->set_visible (false);
1221 AutomationLine::track_entered()
1223 if (alist->interpolation() != AutomationList::Discrete) {
1224 show_all_control_points();
1229 AutomationLine::track_exited()
1231 if (alist->interpolation() != AutomationList::Discrete) {
1232 hide_all_but_selected_control_points();
1237 AutomationLine::get_state (void)
1239 /* function as a proxy for the model */
1240 return alist->get_state();
1244 AutomationLine::set_state (const XMLNode &node, int version)
1246 /* function as a proxy for the model */
1247 return alist->set_state (node, version);
1251 AutomationLine::view_to_model_coord (double& x, double& y) const
1253 /* TODO: This should be more generic ... */
1254 if (alist->parameter().type() == GainAutomation ||
1255 alist->parameter().type() == EnvelopeAutomation) {
1256 y = slider_position_to_gain (y);
1259 } else if (alist->parameter().type() == PanAutomation) {
1260 // vertical coordinate axis reversal
1262 } else if (alist->parameter().type() == PluginAutomation) {
1263 y = y * (double)(alist->get_max_y()- alist->get_min_y()) + alist->get_min_y();
1265 y = (int)(y * alist->parameter().max());
1268 x = _time_converter.from(x);
1272 AutomationLine::model_to_view_coord (double& x, double& y) const
1274 /* TODO: This should be more generic ... */
1275 if (alist->parameter().type() == GainAutomation ||
1276 alist->parameter().type() == EnvelopeAutomation) {
1277 y = gain_to_slider_position (y);
1278 } else if (alist->parameter().type() == PanAutomation) {
1279 // vertical coordinate axis reversal
1281 } else if (alist->parameter().type() == PluginAutomation) {
1282 y = (y - alist->get_min_y()) / (double)(alist->get_max_y()- alist->get_min_y());
1284 y = y / (double)alist->parameter().max(); /* ... like this */
1287 x = _time_converter.to(x);
1292 AutomationLine::set_interpolation(AutomationList::InterpolationStyle style)
1294 _interpolation = style;
1296 if (style == AutomationList::Discrete) {
1297 show_all_control_points();
1300 hide_all_but_selected_control_points();
1306 AutomationLine::add_visible_control_point (uint32_t view_index, uint32_t pi, double tx, double ty, AutomationList::iterator model, uint32_t npoints)
1308 if (view_index >= control_points.size()) {
1310 /* make sure we have enough control points */
1312 ControlPoint* ncp = new ControlPoint (*this);
1313 ncp->set_size (control_point_box_size ());
1315 control_points.push_back (ncp);
1318 ControlPoint::ShapeType shape;
1320 if (!terminal_points_can_slide) {
1322 control_points[view_index]->set_can_slide(false);
1324 shape = ControlPoint::Start;
1326 shape = ControlPoint::Full;
1328 } else if (pi == npoints - 1) {
1329 control_points[view_index]->set_can_slide(false);
1330 shape = ControlPoint::End;
1332 control_points[view_index]->set_can_slide(true);
1333 shape = ControlPoint::Full;
1336 control_points[view_index]->set_can_slide(true);
1337 shape = ControlPoint::Full;
1340 control_points[view_index]->reset (tx, ty, model, view_index, shape);
1342 /* finally, control visibility */
1344 if (_visible && points_visible) {
1345 control_points[view_index]->show ();
1346 control_points[view_index]->set_visible (true);
1348 if (!points_visible) {
1349 control_points[view_index]->set_visible (false);
1355 AutomationLine::add_always_in_view (double x)
1357 _always_in_view.push_back (x);
1358 alist->apply_to_points (*this, &AutomationLine::reset_callback);
1362 AutomationLine::clear_always_in_view ()
1364 _always_in_view.clear ();
1365 alist->apply_to_points (*this, &AutomationLine::reset_callback);