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 uint32_t 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 = 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 = (unsigned long) rint (ty);
749 if (view_index && pi != npoints && (this_rx == prev_rx) && (this_ry == prev_ry) ||
750 ((this_rx - prev_rx) < (box_size + 2))) {
754 /* ok, we should display this point */
756 if (view_index >= cpsize) {
758 /* make sure we have enough control points */
760 ControlPoint* ncp = new ControlPoint (*this);
762 ncp->set_size (box_size);
764 control_points.push_back (ncp);
768 ControlPoint::ShapeType shape;
770 if (!terminal_points_can_slide) {
772 control_points[view_index]->can_slide = false;
774 shape = ControlPoint::Start;
776 shape = ControlPoint::Full;
778 } else if (pi == npoints - 1) {
779 control_points[view_index]->can_slide = false;
780 shape = ControlPoint::End;
782 control_points[view_index]->can_slide = true;
783 shape = ControlPoint::Full;
786 control_points[view_index]->can_slide = true;
787 shape = ControlPoint::Full;
790 last_control_point_x = tx;
791 last_control_point_y = ty;
793 control_points[view_index]->reset (tx, ty, model, view_index, shape);
798 /* finally, control visibility */
800 if (_visible && points_visible) {
801 control_points[view_index]->show ();
802 control_points[view_index]->set_visible (true);
804 if (!points_visible) {
805 control_points[view_index]->set_visible (false);
812 /* discard extra CP's to avoid confusing ourselves */
814 while (control_points.size() > view_index) {
815 ControlPoint* cp = control_points.back();
816 control_points.pop_back ();
820 if (!terminal_points_can_slide) {
821 control_points.back()->can_slide = false;
826 if (view_index > 1) {
828 npoints = view_index;
830 /* reset the line coordinates */
832 while (line_points.size() < npoints) {
833 line_points.push_back (Art::Point (0,0));
836 while (line_points.size() > npoints) {
837 line_points.pop_back ();
840 for (view_index = 0; view_index < npoints; ++view_index) {
841 line_points[view_index].set_x (control_points[view_index]->get_x());
842 line_points[view_index].set_y (control_points[view_index]->get_y());
845 line->property_points() = line_points;
852 set_selected_points (trackview.editor.get_selection().points);
856 AutomationLine::get_verbose_cursor_string (float fraction)
860 if (_vc_uses_gain_mapping) {
861 if (fraction == 0.0) {
862 snprintf (buf, sizeof (buf), "-inf dB");
864 snprintf (buf, sizeof (buf), "%.1fdB", coefficient_to_dB (slider_position_to_gain (fraction)));
867 snprintf (buf, sizeof (buf), "%.2f", fraction);
874 AutomationLine::invalid_point (ALPoints& p, uint32_t index)
876 return p[index].x == max_frames && p[index].y == DBL_MAX;
880 AutomationLine::invalidate_point (ALPoints& p, uint32_t index)
882 p[index].x = max_frames;
883 p[index].y = DBL_MAX;
887 AutomationLine::start_drag (ControlPoint* cp, float fraction)
889 if (trackview.editor.current_session() == 0) { /* how? */
896 str = _("automation event move");
898 str = _("automation range drag");
901 trackview.editor.current_session()->begin_reversible_command (str);
902 trackview.editor.current_session()->add_command (new MementoCommand<AutomationLine>(*this, &get_state(), 0));
904 first_drag_fraction = fraction;
905 last_drag_fraction = fraction;
910 AutomationLine::point_drag (ControlPoint& cp, nframes_t x, float fraction, bool with_push)
912 modify_view (cp, x, fraction, with_push);
917 AutomationLine::line_drag (uint32_t i1, uint32_t i2, float fraction, bool with_push)
919 double ydelta = fraction - last_drag_fraction;
921 last_drag_fraction = fraction;
928 for (uint32_t i = i1 ; i <= i2; i++) {
930 modify_view_point (*cp, trackview.editor.unit_to_frame (cp->get_x()), ((_height - cp->get_y()) /_height) + ydelta, with_push);
939 AutomationLine::end_drag (ControlPoint* cp)
944 sync_model_from (*cp);
946 sync_model_with_view_line (line_drag_cp1, line_drag_cp2);
949 update_pending = false;
951 trackview.editor.current_session()->add_command (new MementoCommand<AutomationLine>(*this, 0, &get_state()));
952 trackview.editor.current_session()->commit_reversible_command ();
953 trackview.editor.current_session()->set_dirty ();
958 AutomationLine::control_points_adjacent (double xval, uint32_t & before, uint32_t& after)
960 ControlPoint *bcp = 0;
961 ControlPoint *acp = 0;
964 /* xval is in frames */
966 unit_xval = trackview.editor.frame_to_unit (xval);
968 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
970 if ((*i)->get_x() <= unit_xval) {
972 if (!bcp || (*i)->get_x() > bcp->get_x()) {
974 before = bcp->view_index;
977 } else if ((*i)->get_x() > unit_xval) {
979 after = acp->view_index;
988 AutomationLine::is_last_point (ControlPoint& cp)
990 ModelRepresentation mr;
992 model_representation (cp, mr);
994 // If the list is not empty, and the point is the last point in the list
996 if (!alist.empty() && mr.end == alist.end()) {
1004 AutomationLine::is_first_point (ControlPoint& cp)
1006 ModelRepresentation mr;
1008 model_representation (cp, mr);
1010 // If the list is not empty, and the point is the first point in the list
1012 if (!alist.empty() && mr.start == alist.begin()) {
1019 // This is copied into AudioRegionGainLine
1021 AutomationLine::remove_point (ControlPoint& cp)
1023 ModelRepresentation mr;
1025 model_representation (cp, mr);
1027 trackview.editor.current_session()->begin_reversible_command (_("remove control point"));
1028 XMLNode &before = get_state();
1030 alist.erase (mr.start, mr.end);
1032 trackview.editor.current_session()->add_command(new MementoCommand<AutomationLine>(*this, &before, &get_state()));
1033 trackview.editor.current_session()->commit_reversible_command ();
1034 trackview.editor.current_session()->set_dirty ();
1038 AutomationLine::get_selectables (nframes_t& start, nframes_t& end,
1039 double botfrac, double topfrac, list<Selectable*>& results)
1046 bool collecting = false;
1048 /* Curse X11 and its inverted coordinate system! */
1050 bot = (1.0 - topfrac) * _height;
1051 top = (1.0 - botfrac) * _height;
1053 nstart = max_frames;
1056 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1058 nframes_t when = (nframes_t) (*(*i)->model)->when;
1060 if (when >= start && when <= end) {
1062 if ((*i)->get_y() >= bot && (*i)->get_y() <= top) {
1065 (*i)->set_visible(true);
1067 nstart = min (nstart, when);
1068 nend = max (nend, when);
1074 results.push_back (new AutomationSelectable (nstart, nend, botfrac, topfrac, trackview));
1076 nstart = max_frames;
1084 results.push_back (new AutomationSelectable (nstart, nend, botfrac, topfrac, trackview));
1090 AutomationLine::get_inverted_selectables (Selection&, list<Selectable*>& results)
1096 AutomationLine::set_selected_points (PointSelection& points)
1101 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1102 (*i)->selected = false;
1105 if (points.empty()) {
1109 for (PointSelection::iterator r = points.begin(); r != points.end(); ++r) {
1111 if (&(*r).track != &trackview) {
1115 /* Curse X11 and its inverted coordinate system! */
1117 bot = (1.0 - (*r).high_fract) * _height;
1118 top = (1.0 - (*r).low_fract) * _height;
1120 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1122 double rstart, rend;
1124 rstart = trackview.editor.frame_to_unit ((*r).start);
1125 rend = trackview.editor.frame_to_unit ((*r).end);
1127 if ((*i)->get_x() >= rstart && (*i)->get_x() <= rend) {
1129 if ((*i)->get_y() >= bot && (*i)->get_y() <= top) {
1131 (*i)->selected = true;
1139 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1140 (*i)->show_color (false, !points_visible);
1146 AutomationLine::show_selection ()
1148 TimeSelection& time (trackview.editor.get_selection().time);
1150 // cerr << "show selection\n";
1152 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1154 (*i)->selected = false;
1156 for (list<AudioRange>::iterator r = time.begin(); r != time.end(); ++r) {
1157 double rstart, rend;
1159 rstart = trackview.editor.frame_to_unit ((*r).start);
1160 rend = trackview.editor.frame_to_unit ((*r).end);
1162 if ((*i)->get_x() >= rstart && (*i)->get_x() <= rend) {
1163 (*i)->selected = true;
1168 (*i)->show_color (false, !points_visible);
1173 AutomationLine::hide_selection ()
1175 // cerr << "hide selection\n";
1176 // show_selection ();
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 = (*ai)->value;
1205 model_to_view_y (translated_y);
1207 tmp_points.push_back (ALPoint (trackview.editor.frame_to_unit ((*ai)->when),
1208 _height - (translated_y * _height)));
1211 determine_visible_control_points (tmp_points);
1215 AutomationLine::reset ()
1217 update_pending = false;
1223 alist.apply_to_points (*this, &AutomationLine::reset_callback);
1227 AutomationLine::clear ()
1229 /* parent must create command */
1230 XMLNode &before = get_state();
1232 trackview.editor.current_session()->add_command (new MementoCommand<AutomationLine>(*this, &before, &get_state()));
1233 trackview.editor.current_session()->commit_reversible_command ();
1234 trackview.editor.current_session()->set_dirty ();
1238 AutomationLine::change_model (AutomationList::iterator i, double x, double y)
1240 alist.modify (i, (nframes_t) x, y);
1244 AutomationLine::change_model_range (AutomationList::iterator start, AutomationList::iterator end, double xdelta, float ydelta)
1246 alist.move_range (start, end, xdelta, ydelta);
1250 AutomationLine::show_all_control_points ()
1252 points_visible = true;
1254 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1256 (*i)->set_visible (true);
1261 AutomationLine::hide_all_but_selected_control_points ()
1263 points_visible = false;
1265 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1266 if (!(*i)->selected) {
1267 (*i)->set_visible (false);
1273 AutomationLine::get_state (void)
1275 /* function as a proxy for the model */
1276 return alist.get_state();
1280 AutomationLine::set_state (const XMLNode &node)
1282 /* function as a proxy for the model */
1283 return alist.set_state (node);