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>
28 #include <ardour/automation_event.h>
29 #include <ardour/curve.h>
30 #include <ardour/dB.h>
32 #include "simplerect.h"
33 #include "automation_line.h"
34 #include "rgb_macros.h"
35 #include "ardour_ui.h"
36 #include "public_editor.h"
38 #include "selection.h"
39 #include "time_axis_view.h"
40 #include "point_selection.h"
41 #include "automation_selectable.h"
42 #include "automation_time_axis.h"
43 #include "public_editor.h"
45 #include <ardour/session.h>
51 using namespace ARDOUR;
53 using namespace Editing;
54 using namespace Gnome; // for Canvas
56 ControlPoint::ControlPoint (AutomationLine& al)
59 model = al.the_list().end();
68 item = new Canvas::SimpleRect (line.canvas_group());
69 item->property_draw() = true;
70 item->property_fill() = false;
71 item->property_fill_color_rgba() = color_map[cControlPointFill];
72 item->property_outline_color_rgba() = color_map[cControlPointOutline];
73 item->property_outline_pixels() = 1;
74 item->set_data ("control_point", this);
75 item->signal_event().connect (mem_fun (this, &ControlPoint::event_handler));
81 ControlPoint::ControlPoint (const ControlPoint& other, bool dummy_arg_to_force_special_copy_constructor)
89 view_index = other.view_index;
90 can_slide = other.can_slide;
93 _shape = other._shape;
97 item = new Canvas::SimpleRect (line.canvas_group());
98 item->property_fill() = false;
99 item->property_outline_color_rgba() = color_map[cEnteredControlPointOutline];
100 item->property_outline_pixels() = 1;
102 /* NOTE: no event handling in copied ControlPoints */
108 ControlPoint::~ControlPoint ()
114 ControlPoint::event_handler (GdkEvent* event)
116 return PublicEditor::instance().canvas_control_point_event (event, item, this);
120 ControlPoint::hide ()
132 ControlPoint::set_visible (bool yn)
134 item->property_draw() = (gboolean) yn;
138 ControlPoint::reset (double x, double y, AutomationList::iterator mi, uint32_t vi, ShapeType shape)
142 move_to (x, y, shape);
146 ControlPoint::show_color (bool entered, bool hide_too)
150 item->property_outline_color_rgba() = color_map[cEnteredControlPointSelected];
153 item->property_outline_color_rgba() = color_map[cEnteredControlPoint];
161 item->property_outline_color_rgba() = color_map[cControlPointSelected];
164 item->property_outline_color_rgba() = color_map[cControlPoint];
173 ControlPoint::set_size (double sz)
179 item->property_fill() = (gboolean) TRUE;
181 item->property_fill() = (gboolean) FALSE;
185 move_to (_x, _y, _shape);
189 ControlPoint::move_to (double x, double y, ShapeType shape)
193 double half_size = rint(_size/2.0);
210 item->property_x1() = x1;
211 item->property_x2() = x2;
212 item->property_y1() = y - half_size;
213 item->property_y2() = y + half_size;
222 AutomationLine::AutomationLine (const string & name, TimeAxisView& tv, ArdourCanvas::Group& parent, AutomationList& al)
226 _parent_group (parent)
228 points_visible = false;
229 update_pending = false;
230 _vc_uses_gain_mapping = false;
233 terminal_points_can_slide = true;
236 group = new ArdourCanvas::Group (parent);
237 group->property_x() = 0.0;
238 group->property_y() = 0.0;
240 line = new ArdourCanvas::Line (*group);
241 line->property_width_pixels() = (guint)1;
242 line->set_data ("line", this);
244 line->signal_event().connect (mem_fun (*this, &AutomationLine::event_handler));
246 alist.StateChanged.connect (mem_fun(*this, &AutomationLine::list_changed));
248 trackview.session().register_with_memento_command_factory(alist.id(), this);
252 AutomationLine::~AutomationLine ()
254 vector_delete (&control_points);
259 AutomationLine::event_handler (GdkEvent* event)
261 return PublicEditor::instance().canvas_line_event (event, line, this);
265 AutomationLine::queue_reset ()
267 if (!update_pending) {
268 update_pending = true;
269 Gtkmm2ext::UI::instance()->call_slot (mem_fun(*this, &AutomationLine::reset));
274 AutomationLine::show ()
278 if (points_visible) {
279 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
288 AutomationLine::hide ()
291 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
298 AutomationLine::control_point_box_size ()
300 if (_height > TimeAxisView::hLarger) {
302 } else if (_height > (guint32) TimeAxisView::hNormal) {
309 AutomationLine::set_height (guint32 h)
314 double bsz = control_point_box_size();
316 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
317 (*i)->set_size (bsz);
325 AutomationLine::set_line_color (uint32_t color)
328 line->property_fill_color_rgba() = color;
332 AutomationLine::set_verbose_cursor_uses_gain_mapping (bool yn)
334 if (yn != _vc_uses_gain_mapping) {
335 _vc_uses_gain_mapping = yn;
341 AutomationLine::nth (uint32_t n)
343 if (n < control_points.size()) {
344 return control_points[n];
351 AutomationLine::modify_view (ControlPoint& cp, double x, double y, bool with_push)
353 modify_view_point (cp, x, y, with_push);
358 AutomationLine::modify_view_point (ControlPoint& cp, double x, double y, bool with_push)
361 uint32_t last_movable = UINT_MAX;
362 double x_limit = DBL_MAX;
364 /* this just changes the current view. it does not alter
365 the model in any way at all.
368 /* clamp y-coord appropriately. y is supposed to be a normalized fraction (0.0-1.0),
369 and needs to be converted to a canvas unit distance.
374 y = _height - (y * _height);
378 /* x-coord cannot move beyond adjacent points or the start/end, and is
379 already in frames. it needs to be converted to canvas units.
382 x = trackview.editor.frame_to_unit (x);
384 /* clamp x position using view coordinates */
386 ControlPoint *before;
390 before = nth (cp.view_index - 1);
391 x = max (x, before->get_x()+1.0);
398 if (cp.view_index < control_points.size() - 1) {
400 after = nth (cp.view_index + 1);
402 /*if it is a "spike" leave the x alone */
404 if (after->get_x() - before->get_x() < 2) {
408 x = min (x, after->get_x()-1.0);
418 /* find the first point that can't move */
420 for (uint32_t n = cp.view_index + 1; (after = nth (n)) != 0; ++n) {
421 if (!after->can_slide) {
422 x_limit = after->get_x() - 1.0;
423 last_movable = after->view_index;
428 delta = x - cp.get_x();
433 /* leave the x-coordinate alone */
435 x = trackview.editor.frame_to_unit ((*cp.model)->when);
441 cp.move_to (x, y, ControlPoint::Full);
442 reset_line_coords (cp);
446 uint32_t limit = min (control_points.size(), (size_t)last_movable);
448 /* move the current point to wherever the user told it to go, subject
452 cp.move_to (min (x, x_limit), y, ControlPoint::Full);
453 reset_line_coords (cp);
455 /* now move all subsequent control points, to reflect the motion.
458 for (uint32_t i = cp.view_index + 1; i < limit; ++i) {
459 ControlPoint *p = nth (i);
463 new_x = min (p->get_x() + delta, x_limit);
464 p->move_to (new_x, p->get_y(), ControlPoint::Full);
465 reset_line_coords (*p);
472 AutomationLine::reset_line_coords (ControlPoint& cp)
474 line_points[cp.view_index].set_x (cp.get_x());
475 line_points[cp.view_index].set_y (cp.get_y());
479 AutomationLine::update_line ()
481 line->property_points() = line_points;
485 AutomationLine::sync_model_with_view_line (uint32_t start, uint32_t end)
490 update_pending = true;
492 for (uint32_t i = start; i <= end; ++i) {
494 sync_model_with_view_point(*p);
501 AutomationLine::model_representation (ControlPoint& cp, ModelRepresentation& mr)
503 /* part one: find out where the visual control point is.
504 initial results are in canvas units. ask the
505 line to convert them to something relevant.
508 mr.xval = (nframes_t) floor (cp.get_x());
509 mr.yval = 1.0 - (cp.get_y() / _height);
512 /* if xval has not changed, set it directly from the model to avoid rounding errors */
514 if (mr.xval == trackview.editor.frame_to_unit((*cp.model)->when)) {
515 mr.xval = (nframes_t) (*cp.model)->when;
517 mr.xval = trackview.editor.unit_to_frame (mr.xval);
521 /* virtual call: this will do the right thing
522 for whatever particular type of line we are.
525 view_to_model_y (mr.yval);
527 /* part 2: find out where the model point is now
530 mr.xpos = (nframes_t) (*cp.model)->when;
531 mr.ypos = (*cp.model)->value;
533 /* part 3: get the position of the visual control
534 points before and after us.
537 ControlPoint* before;
541 before = nth (cp.view_index - 1);
546 after = nth (cp.view_index + 1);
549 mr.xmin = (nframes_t) (*before->model)->when;
550 mr.ymin = (*before->model)->value;
551 mr.start = before->model;
560 mr.end = after->model;
570 AutomationLine::sync_model_from (ControlPoint& cp)
575 sync_model_with_view_point (cp);
577 /* we might have moved all points after `cp' by some amount
578 if we pressed the with_push modifyer some of the time during the drag
579 so all subsequent points have to be resynced
582 lasti = control_points.size() - 1;
585 update_pending = true;
587 while (p != &cp && lasti) {
588 sync_model_with_view_point (*p);
595 AutomationLine::sync_model_with_view_point (ControlPoint& cp)
597 ModelRepresentation mr;
600 model_representation (cp, mr);
602 /* part 4: how much are we changing the central point by */
604 ydelta = mr.yval - mr.ypos;
606 /* IMPORTANT: changing the model when the x-coordinate changes
607 may invalidate the iterators that we are using. this means that we have
608 to change the points before+after the one corresponding to the visual CP
609 first (their x-coordinate doesn't change). then we change the
612 apply the full change to the central point, and interpolate
613 in each direction to cover all model points represented
614 by the control point.
617 /* part 5: change all points before the primary point */
619 for (AutomationList::iterator i = mr.start; i != cp.model; ++i) {
623 delta = ydelta * ((*i)->when - mr.xmin) / (mr.xpos - mr.xmin);
625 /* x-coordinate (generally time) stays where it is,
626 y-coordinate moves by a certain amount.
629 update_pending = true;
630 change_model (i, (*i)->when, mr.yval + delta);
633 /* part 6: change later points */
635 AutomationList::iterator i = cp.model;
639 while (i != mr.end) {
643 delta = ydelta * (mr.xmax - (*i)->when) / (mr.xmax - mr.xpos);
645 /* x-coordinate (generally time) stays where it is,
646 y-coordinate moves by a certain amount.
649 update_pending = true;
650 change_model (i, (*i)->when, (*i)->value + delta);
655 /* part 7: change the primary point */
657 update_pending = true;
658 change_model (cp.model, mr.xval, mr.yval);
662 AutomationLine::determine_visible_control_points (ALPoints& points)
664 uint32_t view_index, pi, n;
665 AutomationList::iterator model;
667 double last_control_point_x = 0.0;
668 double last_control_point_y = 0.0;
669 uint32_t this_rx = 0;
670 uint32_t prev_rx = 0;
671 uint32_t this_ry = 0;
672 uint32_t prev_ry = 0;
677 /* hide all existing points, and the line */
681 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
688 if (points.empty()) {
692 npoints = points.size();
694 /* compute derivative/slope for the entire line */
696 slope = new double[npoints];
698 for (n = 0; n < npoints - 1; ++n) {
699 double xdelta = points[n+1].x - points[n].x;
700 double ydelta = points[n+1].y - points[n].y;
701 slope[n] = ydelta/xdelta;
704 box_size = (uint32_t) control_point_box_size ();
706 /* read all points and decide which ones to show as control points */
710 for (model = alist.begin(), pi = 0; pi < npoints; ++model, ++pi) {
712 double tx = points[pi].x;
713 double ty = points[pi].y;
715 /* now ensure that the control_points vector reflects the current curve
716 state, but don't plot control points too close together. also, don't
717 plot a series of points all with the same value.
719 always plot the first and last points, of course.
722 if (invalid_point (points, pi)) {
723 /* for some reason, we are supposed to ignore this point,
724 but still keep track of the model index.
729 if (pi > 0 && pi < npoints - 1) {
730 if (slope[pi] == slope[pi-1]) {
732 /* no reason to display this point */
738 /* need to round here. the ultimate coordinates are integer
739 pixels, so tiny deltas in the coords will be eliminated
740 and we end up with "colinear" line segments. since the
741 line rendering code in libart doesn't like this very
742 much, we eliminate them here. don't do this for the first and last
746 this_rx = (uint32_t) rint (tx);
747 this_ry = (uint32_t) rint (ty);
749 if (view_index && pi != npoints && /* not the first, not the last */
750 (((this_rx == prev_rx) && (this_ry == prev_ry)) || /* same point */
751 (((this_rx - prev_rx) < (box_size + 2)) && /* too close horizontally */
752 ((abs ((int)(this_ry - prev_ry)) < (int) (box_size + 2)))))) { /* too close vertically */
756 /* ok, we should display this point */
758 if (view_index >= cpsize) {
760 /* make sure we have enough control points */
762 ControlPoint* ncp = new ControlPoint (*this);
764 ncp->set_size (box_size);
766 control_points.push_back (ncp);
770 ControlPoint::ShapeType shape;
772 if (!terminal_points_can_slide) {
774 control_points[view_index]->can_slide = false;
776 shape = ControlPoint::Start;
778 shape = ControlPoint::Full;
780 } else if (pi == npoints - 1) {
781 control_points[view_index]->can_slide = false;
782 shape = ControlPoint::End;
784 control_points[view_index]->can_slide = true;
785 shape = ControlPoint::Full;
788 control_points[view_index]->can_slide = true;
789 shape = ControlPoint::Full;
792 last_control_point_x = tx;
793 last_control_point_y = ty;
795 control_points[view_index]->reset (tx, ty, model, view_index, shape);
800 /* finally, control visibility */
802 if (_visible && points_visible) {
803 control_points[view_index]->show ();
804 control_points[view_index]->set_visible (true);
806 if (!points_visible) {
807 control_points[view_index]->set_visible (false);
814 /* discard extra CP's to avoid confusing ourselves */
816 while (control_points.size() > view_index) {
817 ControlPoint* cp = control_points.back();
818 control_points.pop_back ();
822 if (!terminal_points_can_slide) {
823 control_points.back()->can_slide = false;
828 if (view_index > 1) {
830 npoints = view_index;
832 /* reset the line coordinates */
834 while (line_points.size() < npoints) {
835 line_points.push_back (Art::Point (0,0));
838 while (line_points.size() > npoints) {
839 line_points.pop_back ();
842 for (view_index = 0; view_index < npoints; ++view_index) {
843 line_points[view_index].set_x (control_points[view_index]->get_x());
844 line_points[view_index].set_y (control_points[view_index]->get_y());
847 line->property_points() = line_points;
854 set_selected_points (trackview.editor.get_selection().points);
858 AutomationLine::get_verbose_cursor_string (float fraction)
862 if (_vc_uses_gain_mapping) {
863 if (fraction == 0.0) {
864 snprintf (buf, sizeof (buf), "-inf dB");
866 snprintf (buf, sizeof (buf), "%.1fdB", coefficient_to_dB (slider_position_to_gain (fraction)));
869 snprintf (buf, sizeof (buf), "%.2f", fraction);
876 AutomationLine::invalid_point (ALPoints& p, uint32_t index)
878 return p[index].x == max_frames && p[index].y == DBL_MAX;
882 AutomationLine::invalidate_point (ALPoints& p, uint32_t index)
884 p[index].x = max_frames;
885 p[index].y = DBL_MAX;
889 AutomationLine::start_drag (ControlPoint* cp, float fraction)
891 if (trackview.editor.current_session() == 0) { /* how? */
898 str = _("automation event move");
900 str = _("automation range drag");
903 trackview.editor.current_session()->begin_reversible_command (str);
904 trackview.editor.current_session()->add_command (new MementoCommand<AutomationLine>(*this, &get_state(), 0));
906 first_drag_fraction = fraction;
907 last_drag_fraction = fraction;
912 AutomationLine::point_drag (ControlPoint& cp, nframes_t x, float fraction, bool with_push)
914 modify_view (cp, x, fraction, with_push);
919 AutomationLine::line_drag (uint32_t i1, uint32_t i2, float fraction, bool with_push)
921 double ydelta = fraction - last_drag_fraction;
923 last_drag_fraction = fraction;
930 for (uint32_t i = i1 ; i <= i2; i++) {
932 modify_view_point (*cp, trackview.editor.unit_to_frame (cp->get_x()), ((_height - cp->get_y()) /_height) + ydelta, with_push);
941 AutomationLine::end_drag (ControlPoint* cp)
946 sync_model_from (*cp);
948 sync_model_with_view_line (line_drag_cp1, line_drag_cp2);
951 update_pending = false;
953 trackview.editor.current_session()->add_command (new MementoCommand<AutomationLine>(*this, 0, &get_state()));
954 trackview.editor.current_session()->commit_reversible_command ();
955 trackview.editor.current_session()->set_dirty ();
960 AutomationLine::control_points_adjacent (double xval, uint32_t & before, uint32_t& after)
962 ControlPoint *bcp = 0;
963 ControlPoint *acp = 0;
966 /* xval is in frames */
968 unit_xval = trackview.editor.frame_to_unit (xval);
970 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
972 if ((*i)->get_x() <= unit_xval) {
974 if (!bcp || (*i)->get_x() > bcp->get_x()) {
976 before = bcp->view_index;
979 } else if ((*i)->get_x() > unit_xval) {
981 after = acp->view_index;
990 AutomationLine::is_last_point (ControlPoint& cp)
992 ModelRepresentation mr;
994 model_representation (cp, mr);
996 // If the list is not empty, and the point is the last point in the list
998 if (!alist.empty() && mr.end == alist.end()) {
1006 AutomationLine::is_first_point (ControlPoint& cp)
1008 ModelRepresentation mr;
1010 model_representation (cp, mr);
1012 // If the list is not empty, and the point is the first point in the list
1014 if (!alist.empty() && mr.start == alist.begin()) {
1021 // This is copied into AudioRegionGainLine
1023 AutomationLine::remove_point (ControlPoint& cp)
1025 ModelRepresentation mr;
1027 model_representation (cp, mr);
1029 trackview.editor.current_session()->begin_reversible_command (_("remove control point"));
1030 XMLNode &before = get_state();
1032 alist.erase (mr.start, mr.end);
1034 trackview.editor.current_session()->add_command(new MementoCommand<AutomationLine>(*this, &before, &get_state()));
1035 trackview.editor.current_session()->commit_reversible_command ();
1036 trackview.editor.current_session()->set_dirty ();
1040 AutomationLine::get_selectables (nframes_t& start, nframes_t& end,
1041 double botfrac, double topfrac, list<Selectable*>& results)
1048 bool collecting = false;
1050 /* Curse X11 and its inverted coordinate system! */
1052 bot = (1.0 - topfrac) * _height;
1053 top = (1.0 - botfrac) * _height;
1055 nstart = max_frames;
1058 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1060 nframes_t when = (nframes_t) (*(*i)->model)->when;
1062 if (when >= start && when <= end) {
1064 if ((*i)->get_y() >= bot && (*i)->get_y() <= top) {
1067 (*i)->set_visible(true);
1069 nstart = min (nstart, when);
1070 nend = max (nend, when);
1076 results.push_back (new AutomationSelectable (nstart, nend, botfrac, topfrac, trackview));
1078 nstart = max_frames;
1086 results.push_back (new AutomationSelectable (nstart, nend, botfrac, topfrac, trackview));
1092 AutomationLine::get_inverted_selectables (Selection&, list<Selectable*>& results)
1098 AutomationLine::set_selected_points (PointSelection& points)
1103 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1104 (*i)->selected = false;
1107 if (points.empty()) {
1111 for (PointSelection::iterator r = points.begin(); r != points.end(); ++r) {
1113 if (&(*r).track != &trackview) {
1117 /* Curse X11 and its inverted coordinate system! */
1119 bot = (1.0 - (*r).high_fract) * _height;
1120 top = (1.0 - (*r).low_fract) * _height;
1122 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1124 double rstart, rend;
1126 rstart = trackview.editor.frame_to_unit ((*r).start);
1127 rend = trackview.editor.frame_to_unit ((*r).end);
1129 if ((*i)->get_x() >= rstart && (*i)->get_x() <= rend) {
1131 if ((*i)->get_y() >= bot && (*i)->get_y() <= top) {
1133 (*i)->selected = true;
1141 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1142 (*i)->show_color (false, !points_visible);
1148 AutomationLine::show_selection ()
1150 TimeSelection& time (trackview.editor.get_selection().time);
1152 // cerr << "show selection\n";
1154 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1156 (*i)->selected = false;
1158 for (list<AudioRange>::iterator r = time.begin(); r != time.end(); ++r) {
1159 double rstart, rend;
1161 rstart = trackview.editor.frame_to_unit ((*r).start);
1162 rend = trackview.editor.frame_to_unit ((*r).end);
1164 if ((*i)->get_x() >= rstart && (*i)->get_x() <= rend) {
1165 (*i)->selected = true;
1170 (*i)->show_color (false, !points_visible);
1175 AutomationLine::hide_selection ()
1177 // cerr << "hide selection\n";
1178 // show_selection ();
1182 AutomationLine::list_changed ()
1188 AutomationLine::reset_callback (const AutomationList& events)
1190 ALPoints tmp_points;
1191 uint32_t npoints = events.size();
1194 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1197 control_points.clear ();
1202 AutomationList::const_iterator ai;
1204 for (ai = events.const_begin(); ai != events.const_end(); ++ai) {
1206 double translated_y = (*ai)->value;
1207 model_to_view_y (translated_y);
1209 tmp_points.push_back (ALPoint (trackview.editor.frame_to_unit ((*ai)->when),
1210 _height - (translated_y * _height)));
1213 determine_visible_control_points (tmp_points);
1217 AutomationLine::reset ()
1219 update_pending = false;
1225 alist.apply_to_points (*this, &AutomationLine::reset_callback);
1229 AutomationLine::clear ()
1231 /* parent must create command */
1232 XMLNode &before = get_state();
1234 trackview.editor.current_session()->add_command (new MementoCommand<AutomationLine>(*this, &before, &get_state()));
1235 trackview.editor.current_session()->commit_reversible_command ();
1236 trackview.editor.current_session()->set_dirty ();
1240 AutomationLine::change_model (AutomationList::iterator i, double x, double y)
1242 alist.modify (i, (nframes_t) x, y);
1246 AutomationLine::change_model_range (AutomationList::iterator start, AutomationList::iterator end, double xdelta, float ydelta)
1248 alist.move_range (start, end, xdelta, ydelta);
1252 AutomationLine::show_all_control_points ()
1254 points_visible = true;
1256 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1258 (*i)->set_visible (true);
1263 AutomationLine::hide_all_but_selected_control_points ()
1265 points_visible = false;
1267 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1268 if (!(*i)->selected) {
1269 (*i)->set_visible (false);
1275 AutomationLine::get_state (void)
1277 /* function as a proxy for the model */
1278 return alist.get_state();
1282 AutomationLine::set_state (const XMLNode &node)
1284 /* function as a proxy for the model */
1285 return alist.set_state (node);