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 */
751 /* same point or too close to the last one horizontally */
753 (((this_rx == prev_rx) && (this_ry == prev_ry)) || ((this_rx - prev_rx) < (box_size + 2)))) {
757 /* ok, we should display this point */
759 if (view_index >= cpsize) {
761 /* make sure we have enough control points */
763 ControlPoint* ncp = new ControlPoint (*this);
765 ncp->set_size (box_size);
767 control_points.push_back (ncp);
771 ControlPoint::ShapeType shape;
773 if (!terminal_points_can_slide) {
775 control_points[view_index]->can_slide = false;
777 shape = ControlPoint::Start;
779 shape = ControlPoint::Full;
781 } else if (pi == npoints - 1) {
782 control_points[view_index]->can_slide = false;
783 shape = ControlPoint::End;
785 control_points[view_index]->can_slide = true;
786 shape = ControlPoint::Full;
789 control_points[view_index]->can_slide = true;
790 shape = ControlPoint::Full;
793 last_control_point_x = tx;
794 last_control_point_y = ty;
796 control_points[view_index]->reset (tx, ty, model, view_index, shape);
801 /* finally, control visibility */
803 if (_visible && points_visible) {
804 control_points[view_index]->show ();
805 control_points[view_index]->set_visible (true);
807 if (!points_visible) {
808 control_points[view_index]->set_visible (false);
815 /* discard extra CP's to avoid confusing ourselves */
817 while (control_points.size() > view_index) {
818 ControlPoint* cp = control_points.back();
819 control_points.pop_back ();
823 if (!terminal_points_can_slide) {
824 control_points.back()->can_slide = false;
829 if (view_index > 1) {
831 npoints = view_index;
833 /* reset the line coordinates */
835 while (line_points.size() < npoints) {
836 line_points.push_back (Art::Point (0,0));
839 while (line_points.size() > npoints) {
840 line_points.pop_back ();
843 for (view_index = 0; view_index < npoints; ++view_index) {
844 line_points[view_index].set_x (control_points[view_index]->get_x());
845 line_points[view_index].set_y (control_points[view_index]->get_y());
848 line->property_points() = line_points;
855 set_selected_points (trackview.editor.get_selection().points);
859 AutomationLine::get_verbose_cursor_string (float fraction)
863 if (_vc_uses_gain_mapping) {
864 if (fraction == 0.0) {
865 snprintf (buf, sizeof (buf), "-inf dB");
867 snprintf (buf, sizeof (buf), "%.1fdB", coefficient_to_dB (slider_position_to_gain (fraction)));
870 snprintf (buf, sizeof (buf), "%.2f", fraction);
877 AutomationLine::invalid_point (ALPoints& p, uint32_t index)
879 return p[index].x == max_frames && p[index].y == DBL_MAX;
883 AutomationLine::invalidate_point (ALPoints& p, uint32_t index)
885 p[index].x = max_frames;
886 p[index].y = DBL_MAX;
890 AutomationLine::start_drag (ControlPoint* cp, float fraction)
892 if (trackview.editor.current_session() == 0) { /* how? */
899 str = _("automation event move");
901 str = _("automation range drag");
904 trackview.editor.current_session()->begin_reversible_command (str);
905 trackview.editor.current_session()->add_command (new MementoCommand<AutomationLine>(*this, &get_state(), 0));
907 first_drag_fraction = fraction;
908 last_drag_fraction = fraction;
913 AutomationLine::point_drag (ControlPoint& cp, nframes_t x, float fraction, bool with_push)
915 modify_view (cp, x, fraction, with_push);
920 AutomationLine::line_drag (uint32_t i1, uint32_t i2, float fraction, bool with_push)
922 double ydelta = fraction - last_drag_fraction;
924 last_drag_fraction = fraction;
931 for (uint32_t i = i1 ; i <= i2; i++) {
933 modify_view_point (*cp, trackview.editor.unit_to_frame (cp->get_x()), ((_height - cp->get_y()) /_height) + ydelta, with_push);
942 AutomationLine::end_drag (ControlPoint* cp)
947 sync_model_from (*cp);
949 sync_model_with_view_line (line_drag_cp1, line_drag_cp2);
952 update_pending = false;
954 trackview.editor.current_session()->add_command (new MementoCommand<AutomationLine>(*this, 0, &get_state()));
955 trackview.editor.current_session()->commit_reversible_command ();
956 trackview.editor.current_session()->set_dirty ();
961 AutomationLine::control_points_adjacent (double xval, uint32_t & before, uint32_t& after)
963 ControlPoint *bcp = 0;
964 ControlPoint *acp = 0;
967 /* xval is in frames */
969 unit_xval = trackview.editor.frame_to_unit (xval);
971 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
973 if ((*i)->get_x() <= unit_xval) {
975 if (!bcp || (*i)->get_x() > bcp->get_x()) {
977 before = bcp->view_index;
980 } else if ((*i)->get_x() > unit_xval) {
982 after = acp->view_index;
991 AutomationLine::is_last_point (ControlPoint& cp)
993 ModelRepresentation mr;
995 model_representation (cp, mr);
997 // If the list is not empty, and the point is the last point in the list
999 if (!alist.empty() && mr.end == alist.end()) {
1007 AutomationLine::is_first_point (ControlPoint& cp)
1009 ModelRepresentation mr;
1011 model_representation (cp, mr);
1013 // If the list is not empty, and the point is the first point in the list
1015 if (!alist.empty() && mr.start == alist.begin()) {
1022 // This is copied into AudioRegionGainLine
1024 AutomationLine::remove_point (ControlPoint& cp)
1026 ModelRepresentation mr;
1028 model_representation (cp, mr);
1030 trackview.editor.current_session()->begin_reversible_command (_("remove control point"));
1031 XMLNode &before = get_state();
1033 alist.erase (mr.start, mr.end);
1035 trackview.editor.current_session()->add_command(new MementoCommand<AutomationLine>(*this, &before, &get_state()));
1036 trackview.editor.current_session()->commit_reversible_command ();
1037 trackview.editor.current_session()->set_dirty ();
1041 AutomationLine::get_selectables (nframes_t& start, nframes_t& end,
1042 double botfrac, double topfrac, list<Selectable*>& results)
1049 bool collecting = false;
1051 /* Curse X11 and its inverted coordinate system! */
1053 bot = (1.0 - topfrac) * _height;
1054 top = (1.0 - botfrac) * _height;
1056 nstart = max_frames;
1059 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1061 nframes_t when = (nframes_t) (*(*i)->model)->when;
1063 if (when >= start && when <= end) {
1065 if ((*i)->get_y() >= bot && (*i)->get_y() <= top) {
1068 (*i)->set_visible(true);
1070 nstart = min (nstart, when);
1071 nend = max (nend, when);
1077 results.push_back (new AutomationSelectable (nstart, nend, botfrac, topfrac, trackview));
1079 nstart = max_frames;
1087 results.push_back (new AutomationSelectable (nstart, nend, botfrac, topfrac, trackview));
1093 AutomationLine::get_inverted_selectables (Selection&, list<Selectable*>& results)
1099 AutomationLine::set_selected_points (PointSelection& points)
1104 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1105 (*i)->selected = false;
1108 if (points.empty()) {
1112 for (PointSelection::iterator r = points.begin(); r != points.end(); ++r) {
1114 if (&(*r).track != &trackview) {
1118 /* Curse X11 and its inverted coordinate system! */
1120 bot = (1.0 - (*r).high_fract) * _height;
1121 top = (1.0 - (*r).low_fract) * _height;
1123 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1125 double rstart, rend;
1127 rstart = trackview.editor.frame_to_unit ((*r).start);
1128 rend = trackview.editor.frame_to_unit ((*r).end);
1130 if ((*i)->get_x() >= rstart && (*i)->get_x() <= rend) {
1132 if ((*i)->get_y() >= bot && (*i)->get_y() <= top) {
1134 (*i)->selected = true;
1142 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1143 (*i)->show_color (false, !points_visible);
1149 AutomationLine::show_selection ()
1151 TimeSelection& time (trackview.editor.get_selection().time);
1153 // cerr << "show selection\n";
1155 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1157 (*i)->selected = false;
1159 for (list<AudioRange>::iterator r = time.begin(); r != time.end(); ++r) {
1160 double rstart, rend;
1162 rstart = trackview.editor.frame_to_unit ((*r).start);
1163 rend = trackview.editor.frame_to_unit ((*r).end);
1165 if ((*i)->get_x() >= rstart && (*i)->get_x() <= rend) {
1166 (*i)->selected = true;
1171 (*i)->show_color (false, !points_visible);
1176 AutomationLine::hide_selection ()
1178 // cerr << "hide selection\n";
1179 // show_selection ();
1183 AutomationLine::list_changed (Change ignored)
1189 AutomationLine::reset_callback (const AutomationList& events)
1191 ALPoints tmp_points;
1192 uint32_t npoints = events.size();
1195 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1198 control_points.clear ();
1203 AutomationList::const_iterator ai;
1205 for (ai = events.const_begin(); ai != events.const_end(); ++ai) {
1207 double translated_y = (*ai)->value;
1208 model_to_view_y (translated_y);
1210 tmp_points.push_back (ALPoint (trackview.editor.frame_to_unit ((*ai)->when),
1211 _height - (translated_y * _height)));
1214 determine_visible_control_points (tmp_points);
1218 AutomationLine::reset ()
1220 update_pending = false;
1226 alist.apply_to_points (*this, &AutomationLine::reset_callback);
1230 AutomationLine::clear ()
1232 /* parent must create command */
1233 XMLNode &before = get_state();
1235 trackview.editor.current_session()->add_command (new MementoCommand<AutomationLine>(*this, &before, &get_state()));
1236 trackview.editor.current_session()->commit_reversible_command ();
1237 trackview.editor.current_session()->set_dirty ();
1241 AutomationLine::change_model (AutomationList::iterator i, double x, double y)
1243 alist.modify (i, (nframes_t) x, y);
1247 AutomationLine::change_model_range (AutomationList::iterator start, AutomationList::iterator end, double xdelta, float ydelta)
1249 alist.move_range (start, end, xdelta, ydelta);
1253 AutomationLine::show_all_control_points ()
1255 points_visible = true;
1257 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1259 (*i)->set_visible (true);
1264 AutomationLine::hide_all_but_selected_control_points ()
1266 points_visible = false;
1268 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1269 if (!(*i)->selected) {
1270 (*i)->set_visible (false);
1276 AutomationLine::get_state (void)
1278 /* function as a proxy for the model */
1279 return alist.get_state();
1283 AutomationLine::set_state (const XMLNode &node)
1285 /* function as a proxy for the model */
1286 return alist.set_state (node);