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(_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::set_point_size (double sz)
276 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
282 AutomationLine::show ()
286 if (points_visible) {
287 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
296 AutomationLine::hide ()
299 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
306 AutomationLine::set_height (guint32 h)
311 if (_height > (guint32) TimeAxisView::Larger) {
312 set_point_size (8.0);
313 } else if (_height > (guint32) TimeAxisView::Normal) {
314 set_point_size (6.0);
316 set_point_size (4.0);
324 AutomationLine::set_line_color (uint32_t color)
327 line->property_fill_color_rgba() = color;
331 AutomationLine::set_verbose_cursor_uses_gain_mapping (bool yn)
333 if (yn != _vc_uses_gain_mapping) {
334 _vc_uses_gain_mapping = yn;
340 AutomationLine::nth (uint32_t n)
342 if (n < control_points.size()) {
343 return control_points[n];
350 AutomationLine::modify_view (ControlPoint& cp, double x, double y, bool with_push)
352 modify_view_point (cp, x, y, with_push);
357 AutomationLine::modify_view_point (ControlPoint& cp, double x, double y, bool with_push)
360 uint32_t last_movable = UINT_MAX;
361 double x_limit = DBL_MAX;
363 /* this just changes the current view. it does not alter
364 the model in any way at all.
367 /* clamp y-coord appropriately. y is supposed to be a normalized fraction (0.0-1.0),
368 and needs to be converted to a canvas unit distance.
373 y = _height - (y * _height);
377 /* x-coord cannot move beyond adjacent points or the start/end, and is
378 already in frames. it needs to be converted to canvas units.
381 x = trackview.editor.frame_to_unit (x);
383 /* clamp x position using view coordinates */
385 ControlPoint *before;
389 before = nth (cp.view_index - 1);
390 x = max (x, before->get_x()+1.0);
397 if (cp.view_index < control_points.size() - 1) {
399 after = nth (cp.view_index + 1);
401 /*if it is a "spike" leave the x alone */
403 if (after->get_x() - before->get_x() < 2) {
407 x = min (x, after->get_x()-1.0);
417 /* find the first point that can't move */
419 for (uint32_t n = cp.view_index + 1; (after = nth (n)) != 0; ++n) {
420 if (!after->can_slide) {
421 x_limit = after->get_x() - 1.0;
422 last_movable = after->view_index;
427 delta = x - cp.get_x();
432 /* leave the x-coordinate alone */
434 x = trackview.editor.frame_to_unit ((*cp.model)->when);
440 cp.move_to (x, y, ControlPoint::Full);
441 reset_line_coords (cp);
445 uint32_t limit = min (control_points.size(), (size_t)last_movable);
447 /* move the current point to wherever the user told it to go, subject
451 cp.move_to (min (x, x_limit), y, ControlPoint::Full);
452 reset_line_coords (cp);
454 /* now move all subsequent control points, to reflect the motion.
457 for (uint32_t i = cp.view_index + 1; i < limit; ++i) {
458 ControlPoint *p = nth (i);
462 new_x = min (p->get_x() + delta, x_limit);
463 p->move_to (new_x, p->get_y(), ControlPoint::Full);
464 reset_line_coords (*p);
471 AutomationLine::reset_line_coords (ControlPoint& cp)
473 line_points[cp.view_index].set_x (cp.get_x());
474 line_points[cp.view_index].set_y (cp.get_y());
478 AutomationLine::update_line ()
480 line->property_points() = line_points;
484 AutomationLine::sync_model_with_view_line (uint32_t start, uint32_t end)
489 update_pending = true;
491 for (uint32_t i = start; i <= end; ++i) {
493 sync_model_with_view_point(*p);
500 AutomationLine::model_representation (ControlPoint& cp, ModelRepresentation& mr)
502 /* part one: find out where the visual control point is.
503 initial results are in canvas units. ask the
504 line to convert them to something relevant.
507 mr.xval = (nframes_t) floor (cp.get_x());
508 mr.yval = 1.0 - (cp.get_y() / _height);
511 /* if xval has not changed, set it directly from the model to avoid rounding errors */
513 if (mr.xval == trackview.editor.frame_to_unit((*cp.model)->when)) {
514 mr.xval = (nframes_t) (*cp.model)->when;
516 mr.xval = trackview.editor.unit_to_frame (mr.xval);
520 /* virtual call: this will do the right thing
521 for whatever particular type of line we are.
524 view_to_model_y (mr.yval);
526 /* part 2: find out where the model point is now
529 mr.xpos = (nframes_t) (*cp.model)->when;
530 mr.ypos = (*cp.model)->value;
532 /* part 3: get the position of the visual control
533 points before and after us.
536 ControlPoint* before;
540 before = nth (cp.view_index - 1);
545 after = nth (cp.view_index + 1);
548 mr.xmin = (nframes_t) (*before->model)->when;
549 mr.ymin = (*before->model)->value;
550 mr.start = before->model;
559 mr.end = after->model;
569 AutomationLine::sync_model_from (ControlPoint& cp)
574 sync_model_with_view_point (cp);
576 /* we might have moved all points after `cp' by some amount
577 if we pressed the with_push modifyer some of the time during the drag
578 so all subsequent points have to be resynced
581 lasti = control_points.size() - 1;
584 update_pending = true;
586 while (p != &cp && lasti) {
587 sync_model_with_view_point (*p);
594 AutomationLine::sync_model_with_view_point (ControlPoint& cp)
596 ModelRepresentation mr;
599 model_representation (cp, mr);
601 /* part 4: how much are we changing the central point by */
603 ydelta = mr.yval - mr.ypos;
605 /* IMPORTANT: changing the model when the x-coordinate changes
606 may invalidate the iterators that we are using. this means that we have
607 to change the points before+after the one corresponding to the visual CP
608 first (their x-coordinate doesn't change). then we change the
611 apply the full change to the central point, and interpolate
612 in each direction to cover all model points represented
613 by the control point.
616 /* part 5: change all points before the primary point */
618 for (AutomationList::iterator i = mr.start; i != cp.model; ++i) {
622 delta = ydelta * ((*i)->when - mr.xmin) / (mr.xpos - mr.xmin);
624 /* x-coordinate (generally time) stays where it is,
625 y-coordinate moves by a certain amount.
628 update_pending = true;
629 change_model (i, (*i)->when, mr.yval + delta);
632 /* part 6: change later points */
634 AutomationList::iterator i = cp.model;
638 while (i != mr.end) {
642 delta = ydelta * (mr.xmax - (*i)->when) / (mr.xmax - mr.xpos);
644 /* x-coordinate (generally time) stays where it is,
645 y-coordinate moves by a certain amount.
648 update_pending = true;
649 change_model (i, (*i)->when, (*i)->value + delta);
654 /* part 7: change the primary point */
656 update_pending = true;
657 change_model (cp.model, mr.xval, mr.yval);
661 AutomationLine::determine_visible_control_points (ALPoints& points)
663 uint32_t view_index, pi, n;
664 AutomationList::iterator model;
666 double last_control_point_x = 0.0;
667 double last_control_point_y = 0.0;
668 uint32_t this_rx = 0;
669 uint32_t prev_rx = 0;
670 uint32_t this_ry = 0;
671 uint32_t prev_ry = 0;
674 /* hide all existing points, and the line */
676 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
682 if (points.empty()) {
686 npoints = points.size();
688 /* compute derivative/slope for the entire line */
690 slope = new double[npoints];
692 for (n = 0; n < npoints - 1; ++n) {
693 double xdelta = points[n+1].x - points[n].x;
694 double ydelta = points[n+1].y - points[n].y;
695 slope[n] = ydelta/xdelta;
698 /* read all points and decide which ones to show as control points */
702 for (model = alist.begin(), pi = 0; pi < npoints; ++model, ++pi) {
704 double tx = points[pi].x;
705 double ty = points[pi].y;
707 /* now ensure that the control_points vector reflects the current curve
708 state, but don't plot control points too close together. also, don't
709 plot a series of points all with the same value.
711 always plot the first and last points, of course.
714 if (invalid_point (points, pi)) {
715 /* for some reason, we are supposed to ignore this point,
716 but still keep track of the model index.
721 if (pi > 0 && pi < npoints - 1) {
722 if (slope[pi] == slope[pi-1]) {
724 /* no reason to display this point */
730 /* need to round here. the ultimate coordinates are integer
731 pixels, so tiny deltas in the coords will be eliminated
732 and we end up with "colinear" line segments. since the
733 line rendering code in libart doesn't like this very
734 much, we eliminate them here. don't do this for the first and last
738 this_rx = (uint32_t) rint (tx);
739 this_ry = (unsigned long) rint (ty);
741 if (view_index && pi != npoints && (this_rx == prev_rx) && (this_ry == prev_ry)) {
746 /* ok, we should display this point */
748 if (view_index >= control_points.size()) {
749 /* make sure we have enough control points */
751 ControlPoint* ncp = new ControlPoint (*this);
753 if (_height > (guint32) TimeAxisView::Larger) {
755 } else if (_height > (guint32) TimeAxisView::Normal) {
761 control_points.push_back (ncp);
764 ControlPoint::ShapeType shape;
766 if (!terminal_points_can_slide) {
768 control_points[view_index]->can_slide = false;
770 shape = ControlPoint::Start;
772 shape = ControlPoint::Full;
774 } else if (pi == npoints - 1) {
775 control_points[view_index]->can_slide = false;
776 shape = ControlPoint::End;
778 control_points[view_index]->can_slide = true;
779 shape = ControlPoint::Full;
782 control_points[view_index]->can_slide = true;
783 shape = ControlPoint::Full;
786 last_control_point_x = tx;
787 last_control_point_y = ty;
789 control_points[view_index]->reset (tx, ty, model, view_index, shape);
794 /* finally, control visibility */
796 if (_visible && points_visible) {
797 control_points[view_index]->show ();
798 control_points[view_index]->set_visible (true);
800 if (!points_visible) {
801 control_points[view_index]->set_visible (false);
808 /* discard extra CP's to avoid confusing ourselves */
810 while (control_points.size() > view_index) {
811 ControlPoint* cp = control_points.back();
812 control_points.pop_back ();
816 if (!terminal_points_can_slide) {
817 control_points.back()->can_slide = false;
822 if (view_index > 1) {
824 npoints = view_index;
826 /* reset the line coordinates */
828 while (line_points.size() < npoints) {
829 line_points.push_back (Art::Point (0,0));
832 for (view_index = 0; view_index < npoints; ++view_index) {
833 line_points[view_index].set_x (control_points[view_index]->get_x());
834 line_points[view_index].set_y (control_points[view_index]->get_y());
837 line->property_points() = line_points;
844 set_selected_points (trackview.editor.get_selection().points);
848 AutomationLine::get_verbose_cursor_string (float fraction)
852 if (_vc_uses_gain_mapping) {
853 if (fraction == 0.0) {
854 snprintf (buf, sizeof (buf), "-inf dB");
856 snprintf (buf, sizeof (buf), "%.1fdB", coefficient_to_dB (slider_position_to_gain (fraction)));
859 snprintf (buf, sizeof (buf), "%.2f", fraction);
866 AutomationLine::invalid_point (ALPoints& p, uint32_t index)
868 return p[index].x == max_frames && p[index].y == DBL_MAX;
872 AutomationLine::invalidate_point (ALPoints& p, uint32_t index)
874 p[index].x = max_frames;
875 p[index].y = DBL_MAX;
879 AutomationLine::start_drag (ControlPoint* cp, float fraction)
881 if (trackview.editor.current_session() == 0) { /* how? */
888 str = _("automation event move");
890 str = _("automation range drag");
893 trackview.editor.current_session()->begin_reversible_command (str);
894 trackview.editor.current_session()->add_command (new MementoCommand<AutomationLine>(*this, &get_state(), 0));
896 first_drag_fraction = fraction;
897 last_drag_fraction = fraction;
902 AutomationLine::point_drag (ControlPoint& cp, nframes_t x, float fraction, bool with_push)
904 modify_view (cp, x, fraction, with_push);
909 AutomationLine::line_drag (uint32_t i1, uint32_t i2, float fraction, bool with_push)
911 double ydelta = fraction - last_drag_fraction;
913 last_drag_fraction = fraction;
920 for (uint32_t i = i1 ; i <= i2; i++) {
922 modify_view_point (*cp, trackview.editor.unit_to_frame (cp->get_x()), ((_height - cp->get_y()) /_height) + ydelta, with_push);
931 AutomationLine::end_drag (ControlPoint* cp)
936 sync_model_from (*cp);
938 sync_model_with_view_line (line_drag_cp1, line_drag_cp2);
941 update_pending = false;
943 trackview.editor.current_session()->add_command (new MementoCommand<AutomationLine>(*this, 0, &get_state()));
944 trackview.editor.current_session()->commit_reversible_command ();
945 trackview.editor.current_session()->set_dirty ();
950 AutomationLine::control_points_adjacent (double xval, uint32_t & before, uint32_t& after)
952 ControlPoint *bcp = 0;
953 ControlPoint *acp = 0;
956 /* xval is in frames */
958 unit_xval = trackview.editor.frame_to_unit (xval);
960 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
962 if ((*i)->get_x() <= unit_xval) {
964 if (!bcp || (*i)->get_x() > bcp->get_x()) {
966 before = bcp->view_index;
969 } else if ((*i)->get_x() > unit_xval) {
971 after = acp->view_index;
980 AutomationLine::is_last_point (ControlPoint& cp)
982 ModelRepresentation mr;
984 model_representation (cp, mr);
986 // If the list is not empty, and the point is the last point in the list
988 if (!alist.empty() && mr.end == alist.end()) {
996 AutomationLine::is_first_point (ControlPoint& cp)
998 ModelRepresentation mr;
1000 model_representation (cp, mr);
1002 // If the list is not empty, and the point is the first point in the list
1004 if (!alist.empty() && mr.start == alist.begin()) {
1011 // This is copied into AudioRegionGainLine
1013 AutomationLine::remove_point (ControlPoint& cp)
1015 ModelRepresentation mr;
1017 model_representation (cp, mr);
1019 trackview.editor.current_session()->begin_reversible_command (_("remove control point"));
1020 XMLNode &before = get_state();
1022 alist.erase (mr.start, mr.end);
1024 trackview.editor.current_session()->add_command(new MementoCommand<AutomationLine>(*this, &before, &get_state()));
1025 trackview.editor.current_session()->commit_reversible_command ();
1026 trackview.editor.current_session()->set_dirty ();
1030 AutomationLine::get_selectables (nframes_t& start, nframes_t& end,
1031 double botfrac, double topfrac, list<Selectable*>& results)
1038 bool collecting = false;
1040 /* Curse X11 and its inverted coordinate system! */
1042 bot = (1.0 - topfrac) * _height;
1043 top = (1.0 - botfrac) * _height;
1045 nstart = max_frames;
1048 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1050 nframes_t when = (nframes_t) (*(*i)->model)->when;
1052 if (when >= start && when <= end) {
1054 if ((*i)->get_y() >= bot && (*i)->get_y() <= top) {
1057 (*i)->set_visible(true);
1059 nstart = min (nstart, when);
1060 nend = max (nend, when);
1066 results.push_back (new AutomationSelectable (nstart, nend, botfrac, topfrac, trackview));
1068 nstart = max_frames;
1076 results.push_back (new AutomationSelectable (nstart, nend, botfrac, topfrac, trackview));
1082 AutomationLine::get_inverted_selectables (Selection&, list<Selectable*>& results)
1088 AutomationLine::set_selected_points (PointSelection& points)
1093 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1094 (*i)->selected = false;
1097 if (points.empty()) {
1101 for (PointSelection::iterator r = points.begin(); r != points.end(); ++r) {
1103 if (&(*r).track != &trackview) {
1107 /* Curse X11 and its inverted coordinate system! */
1109 bot = (1.0 - (*r).high_fract) * _height;
1110 top = (1.0 - (*r).low_fract) * _height;
1112 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1114 double rstart, rend;
1116 rstart = trackview.editor.frame_to_unit ((*r).start);
1117 rend = trackview.editor.frame_to_unit ((*r).end);
1119 if ((*i)->get_x() >= rstart && (*i)->get_x() <= rend) {
1121 if ((*i)->get_y() >= bot && (*i)->get_y() <= top) {
1123 (*i)->selected = true;
1131 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1132 (*i)->show_color (false, !points_visible);
1138 AutomationLine::show_selection ()
1140 TimeSelection& time (trackview.editor.get_selection().time);
1142 // cerr << "show selection\n";
1144 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1146 (*i)->selected = false;
1148 for (list<AudioRange>::iterator r = time.begin(); r != time.end(); ++r) {
1149 double rstart, rend;
1151 rstart = trackview.editor.frame_to_unit ((*r).start);
1152 rend = trackview.editor.frame_to_unit ((*r).end);
1154 if ((*i)->get_x() >= rstart && (*i)->get_x() <= rend) {
1155 (*i)->selected = true;
1160 (*i)->show_color (false, !points_visible);
1165 AutomationLine::hide_selection ()
1167 // cerr << "hide selection\n";
1168 // show_selection ();
1172 // This is copied into AudioRegionGainLine
1174 AutomationLine::get_memento ()
1176 return alist.get_memento();
1180 AutomationLine::list_changed (Change ignored)
1186 AutomationLine::reset_callback (const AutomationList& events)
1188 ALPoints tmp_points;
1189 uint32_t npoints = events.size();
1192 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1195 control_points.clear ();
1200 AutomationList::const_iterator ai;
1202 for (ai = events.const_begin(); ai != events.const_end(); ++ai) {
1204 double translated_y;
1206 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);
1274 XMLNode &AutomationLine::get_state(void)
1276 XMLNode *node = new XMLNode("AutomationLine");
1277 node->add_child_nocopy(alist.get_state());
1281 int AutomationLine::set_state(const XMLNode &node)
1284 //alist.set_state(node);