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.
26 #include <pbd/stl_delete.h>
27 #include <pbd/memento_command.h>
29 #include <ardour/automation_event.h>
30 #include <ardour/curve.h>
31 #include <ardour/dB.h>
33 #include "simplerect.h"
34 #include "automation_line.h"
35 #include "rgb_macros.h"
36 #include "ardour_ui.h"
37 #include "public_editor.h"
39 #include "selection.h"
40 #include "time_axis_view.h"
41 #include "point_selection.h"
42 #include "automation_selectable.h"
43 #include "automation_time_axis.h"
44 #include "public_editor.h"
46 #include <ardour/session.h>
52 using namespace ARDOUR;
54 using namespace Editing;
55 using namespace Gnome; // for Canvas
57 ControlPoint::ControlPoint (AutomationLine& al)
60 model = al.the_list().end();
69 item = new Canvas::SimpleRect (line.canvas_group());
70 item->property_draw() = true;
71 item->property_fill() = false;
72 item->property_fill_color_rgba() = color_map[cControlPointFill];
73 item->property_outline_color_rgba() = color_map[cControlPointOutline];
74 item->property_outline_pixels() = 1;
75 item->set_data ("control_point", this);
76 item->signal_event().connect (mem_fun (this, &ControlPoint::event_handler));
82 ControlPoint::ControlPoint (const ControlPoint& other, bool dummy_arg_to_force_special_copy_constructor)
90 view_index = other.view_index;
91 can_slide = other.can_slide;
94 _shape = other._shape;
98 item = new Canvas::SimpleRect (line.canvas_group());
99 item->property_fill() = false;
100 item->property_outline_color_rgba() = color_map[cEnteredControlPointOutline];
101 item->property_outline_pixels() = 1;
103 /* NOTE: no event handling in copied ControlPoints */
109 ControlPoint::~ControlPoint ()
115 ControlPoint::event_handler (GdkEvent* event)
117 return PublicEditor::instance().canvas_control_point_event (event, item, this);
121 ControlPoint::hide ()
133 ControlPoint::set_visible (bool yn)
135 item->property_draw() = (gboolean) yn;
139 ControlPoint::reset (double x, double y, AutomationList::iterator mi, uint32_t vi, ShapeType shape)
143 move_to (x, y, shape);
147 ControlPoint::show_color (bool entered, bool hide_too)
151 item->property_outline_color_rgba() = color_map[cEnteredControlPointSelected];
154 item->property_outline_color_rgba() = color_map[cEnteredControlPoint];
162 item->property_outline_color_rgba() = color_map[cControlPointSelected];
165 item->property_outline_color_rgba() = color_map[cControlPoint];
174 ControlPoint::set_size (double sz)
180 item->property_fill() = (gboolean) TRUE;
182 item->property_fill() = (gboolean) FALSE;
186 move_to (_x, _y, _shape);
190 ControlPoint::move_to (double x, double y, ShapeType shape)
194 double half_size = rint(_size/2.0);
211 item->property_x1() = x1;
212 item->property_x2() = x2;
213 item->property_y1() = y - half_size;
214 item->property_y2() = y + half_size;
223 AutomationLine::AutomationLine (const string & name, TimeAxisView& tv, ArdourCanvas::Group& parent, AutomationList& al)
227 _parent_group (parent)
229 points_visible = false;
230 update_pending = false;
231 _vc_uses_gain_mapping = false;
234 terminal_points_can_slide = true;
237 group = new ArdourCanvas::Group (parent);
238 group->property_x() = 0.0;
239 group->property_y() = 0.0;
241 line = new ArdourCanvas::Line (*group);
242 line->property_width_pixels() = (guint)1;
243 line->set_data ("line", this);
245 line->signal_event().connect (mem_fun (*this, &AutomationLine::event_handler));
247 alist.StateChanged.connect (mem_fun(*this, &AutomationLine::list_changed));
249 trackview.session().register_with_memento_command_factory(alist.id(), this);
253 AutomationLine::~AutomationLine ()
255 vector_delete (&control_points);
260 AutomationLine::event_handler (GdkEvent* event)
262 return PublicEditor::instance().canvas_line_event (event, line, this);
266 AutomationLine::queue_reset ()
268 if (!update_pending) {
269 update_pending = true;
270 Gtkmm2ext::UI::instance()->call_slot (mem_fun(*this, &AutomationLine::reset));
275 AutomationLine::show ()
279 if (points_visible) {
280 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
289 AutomationLine::hide ()
292 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
299 AutomationLine::control_point_box_size ()
301 if (_height > TimeAxisView::hLarger) {
303 } else if (_height > (guint32) TimeAxisView::hNormal) {
310 AutomationLine::set_height (guint32 h)
315 double bsz = control_point_box_size();
317 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
318 (*i)->set_size (bsz);
326 AutomationLine::set_line_color (uint32_t color)
329 line->property_fill_color_rgba() = color;
333 AutomationLine::set_verbose_cursor_uses_gain_mapping (bool yn)
335 if (yn != _vc_uses_gain_mapping) {
336 _vc_uses_gain_mapping = yn;
342 AutomationLine::nth (uint32_t n)
344 if (n < control_points.size()) {
345 return control_points[n];
352 AutomationLine::modify_view_point (ControlPoint& cp, double x, double y, bool with_push)
355 uint32_t last_movable = UINT_MAX;
356 double x_limit = DBL_MAX;
358 /* this just changes the current view. it does not alter
359 the model in any way at all.
362 /* clamp y-coord appropriately. y is supposed to be a normalized fraction (0.0-1.0),
363 and needs to be converted to a canvas unit distance.
368 y = _height - (y * _height);
372 /* x-coord cannot move beyond adjacent points or the start/end, and is
373 already in frames. it needs to be converted to canvas units.
376 x = trackview.editor.frame_to_unit (x);
378 /* clamp x position using view coordinates */
380 ControlPoint *before;
384 before = nth (cp.view_index - 1);
385 x = max (x, before->get_x()+1.0);
392 if (cp.view_index < control_points.size() - 1) {
394 after = nth (cp.view_index + 1);
396 /*if it is a "spike" leave the x alone */
398 if (after->get_x() - before->get_x() < 2) {
402 x = min (x, after->get_x()-1.0);
412 /* find the first point that can't move */
414 for (uint32_t n = cp.view_index + 1; (after = nth (n)) != 0; ++n) {
415 if (!after->can_slide) {
416 x_limit = after->get_x() - 1.0;
417 last_movable = after->view_index;
422 delta = x - cp.get_x();
427 /* leave the x-coordinate alone */
429 x = trackview.editor.frame_to_unit ((*cp.model)->when);
435 cp.move_to (x, y, ControlPoint::Full);
436 reset_line_coords (cp);
440 uint32_t limit = min (control_points.size(), (size_t)last_movable);
442 /* move the current point to wherever the user told it to go, subject
446 cp.move_to (min (x, x_limit), y, ControlPoint::Full);
447 reset_line_coords (cp);
449 /* now move all subsequent control points, to reflect the motion.
452 for (uint32_t i = cp.view_index + 1; i < limit; ++i) {
453 ControlPoint *p = nth (i);
457 new_x = min (p->get_x() + delta, x_limit);
458 p->move_to (new_x, p->get_y(), ControlPoint::Full);
459 reset_line_coords (*p);
466 AutomationLine::reset_line_coords (ControlPoint& cp)
468 if (cp.view_index < line_points.size()) {
469 line_points[cp.view_index].set_x (cp.get_x());
470 line_points[cp.view_index].set_y (cp.get_y());
475 AutomationLine::sync_model_with_view_line (uint32_t start, uint32_t end)
480 update_pending = true;
482 for (uint32_t i = start; i <= end; ++i) {
484 sync_model_with_view_point (*p, false, 0);
489 AutomationLine::model_representation (ControlPoint& cp, ModelRepresentation& mr)
491 /* part one: find out where the visual control point is.
492 initial results are in canvas units. ask the
493 line to convert them to something relevant.
496 mr.xval = (nframes_t) floor (cp.get_x());
497 mr.yval = 1.0 - (cp.get_y() / _height);
499 /* if xval has not changed, set it directly from the model to avoid rounding errors */
501 if (mr.xval == trackview.editor.frame_to_unit((*cp.model)->when)) {
502 mr.xval = (nframes_t) (*cp.model)->when;
504 mr.xval = trackview.editor.unit_to_frame (mr.xval);
507 /* virtual call: this will do the right thing
508 for whatever particular type of line we are.
511 view_to_model_y (mr.yval);
513 /* part 2: find out where the model point is now
516 mr.xpos = (nframes_t) (*cp.model)->when;
517 mr.ypos = (*cp.model)->value;
519 /* part 3: get the position of the visual control
520 points before and after us.
523 ControlPoint* before;
527 before = nth (cp.view_index - 1);
532 after = nth (cp.view_index + 1);
535 mr.xmin = (nframes_t) (*before->model)->when;
536 mr.ymin = (*before->model)->value;
537 mr.start = before->model;
546 mr.end = after->model;
556 AutomationLine::determine_visible_control_points (ALPoints& points)
558 uint32_t view_index, pi, n;
559 AutomationList::iterator model;
561 double last_control_point_x = 0.0;
562 double last_control_point_y = 0.0;
563 uint32_t this_rx = 0;
564 uint32_t prev_rx = 0;
565 uint32_t this_ry = 0;
566 uint32_t prev_ry = 0;
571 /* hide all existing points, and the line */
575 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
582 if (points.empty()) {
586 npoints = points.size();
588 /* compute derivative/slope for the entire line */
590 slope = new double[npoints];
592 for (n = 0; n < npoints - 1; ++n) {
593 double xdelta = points[n+1].x - points[n].x;
594 double ydelta = points[n+1].y - points[n].y;
595 slope[n] = ydelta/xdelta;
598 box_size = (uint32_t) control_point_box_size ();
600 /* read all points and decide which ones to show as control points */
604 ofstream oout ("orig_coordinates");
606 for (model = alist.begin(), pi = 0; pi < npoints; ++model, ++pi) {
608 double tx = points[pi].x;
609 double ty = points[pi].y;
611 oout << tx << ' ' << ty << endl;
613 /* now ensure that the control_points vector reflects the current curve
614 state, but don't plot control points too close together. also, don't
615 plot a series of points all with the same value.
617 always plot the first and last points, of course.
620 if (invalid_point (points, pi)) {
621 /* for some reason, we are supposed to ignore this point,
622 but still keep track of the model index.
627 if (pi > 0 && pi < npoints - 1) {
628 if (slope[pi] == slope[pi-1]) {
630 /* no reason to display this point */
636 /* need to round here. the ultimate coordinates are integer
637 pixels, so tiny deltas in the coords will be eliminated
638 and we end up with "colinear" line segments. since the
639 line rendering code in libart doesn't like this very
640 much, we eliminate them here. don't do this for the first and last
644 this_rx = (uint32_t) rint (tx);
645 this_ry = (uint32_t) rint (ty);
647 if (view_index && pi != npoints && /* not the first, not the last */
648 (((this_rx == prev_rx) && (this_ry == prev_ry)) || /* same point */
649 (this_rx == prev_rx) || /* identical x coordinate */
650 (((this_rx - prev_rx) < (box_size + 2)) && /* not identical, but still too close horizontally */
651 ((abs ((int)(this_ry - prev_ry)) < (int) (box_size + 2)))))) { /* too close vertically */
655 /* ok, we should display this point */
657 if (view_index >= cpsize) {
659 /* make sure we have enough control points */
661 ControlPoint* ncp = new ControlPoint (*this);
663 ncp->set_size (box_size);
665 control_points.push_back (ncp);
669 ControlPoint::ShapeType shape;
671 if (!terminal_points_can_slide) {
673 control_points[view_index]->can_slide = false;
675 shape = ControlPoint::Start;
677 shape = ControlPoint::Full;
679 } else if (pi == npoints - 1) {
680 control_points[view_index]->can_slide = false;
681 shape = ControlPoint::End;
683 control_points[view_index]->can_slide = true;
684 shape = ControlPoint::Full;
687 control_points[view_index]->can_slide = true;
688 shape = ControlPoint::Full;
691 last_control_point_x = tx;
692 last_control_point_y = ty;
694 control_points[view_index]->reset (tx, ty, model, view_index, shape);
699 /* finally, control visibility */
701 if (_visible && points_visible) {
702 control_points[view_index]->show ();
703 control_points[view_index]->set_visible (true);
705 if (!points_visible) {
706 control_points[view_index]->set_visible (false);
715 /* discard extra CP's to avoid confusing ourselves */
717 while (control_points.size() > view_index) {
718 ControlPoint* cp = control_points.back();
719 control_points.pop_back ();
723 if (!terminal_points_can_slide) {
724 control_points.back()->can_slide = false;
729 if (view_index > 1) {
731 npoints = view_index;
733 /* reset the line coordinates */
735 while (line_points.size() < npoints) {
736 line_points.push_back (Art::Point (0,0));
739 while (line_points.size() > npoints) {
740 line_points.pop_back ();
743 for (view_index = 0; view_index < npoints; ++view_index) {
744 line_points[view_index].set_x (control_points[view_index]->get_x());
745 line_points[view_index].set_y (control_points[view_index]->get_y());
748 line->property_points() = line_points;
756 set_selected_points (trackview.editor.get_selection().points);
761 AutomationLine::get_verbose_cursor_string (float fraction)
765 if (_vc_uses_gain_mapping) {
766 if (fraction == 0.0) {
767 snprintf (buf, sizeof (buf), "-inf dB");
769 snprintf (buf, sizeof (buf), "%.1fdB", coefficient_to_dB (slider_position_to_gain (fraction)));
772 snprintf (buf, sizeof (buf), "%.2f", fraction);
779 AutomationLine::invalid_point (ALPoints& p, uint32_t index)
781 return p[index].x == max_frames && p[index].y == DBL_MAX;
785 AutomationLine::invalidate_point (ALPoints& p, uint32_t index)
787 p[index].x = max_frames;
788 p[index].y = DBL_MAX;
792 AutomationLine::start_drag (ControlPoint* cp, nframes_t x, float fraction)
794 if (trackview.editor.current_session() == 0) { /* how? */
801 str = _("automation event move");
803 str = _("automation range drag");
806 trackview.editor.current_session()->begin_reversible_command (str);
807 trackview.editor.current_session()->add_command (new MementoCommand<AutomationList>(alist, &get_state(), 0));
811 first_drag_fraction = fraction;
812 last_drag_fraction = fraction;
818 AutomationLine::point_drag (ControlPoint& cp, nframes_t x, float fraction, bool with_push)
821 drag_distance += (x - drag_x);
823 drag_distance -= (drag_x - x);
828 modify_view_point (cp, x, fraction, with_push);
830 if (line_points.size() > 1) {
831 line->property_points() = line_points;
835 did_push = with_push;
839 AutomationLine::line_drag (uint32_t i1, uint32_t i2, float fraction, bool with_push)
841 double ydelta = fraction - last_drag_fraction;
843 did_push = with_push;
845 last_drag_fraction = fraction;
852 for (uint32_t i = i1 ; i <= i2; i++) {
854 modify_view_point (*cp, trackview.editor.unit_to_frame (cp->get_x()), ((_height - cp->get_y()) /_height) + ydelta, with_push);
857 if (line_points.size() > 1) {
858 line->property_points() = line_points;
865 AutomationLine::end_drag (ControlPoint* cp)
874 sync_model_with_view_point (*cp, did_push, drag_distance);
876 sync_model_with_view_line (line_drag_cp1, line_drag_cp2);
881 update_pending = false;
883 trackview.editor.current_session()->add_command (new MementoCommand<AutomationList>(alist, 0, &alist.get_state()));
884 trackview.editor.current_session()->commit_reversible_command ();
885 trackview.editor.current_session()->set_dirty ();
890 AutomationLine::sync_model_with_view_point (ControlPoint& cp, bool did_push, int64_t distance)
892 ModelRepresentation mr;
895 model_representation (cp, mr);
897 /* how much are we changing the central point by */
899 ydelta = mr.yval - mr.ypos;
902 apply the full change to the central point, and interpolate
903 on both axes to cover all model points represented
904 by the control point.
907 /* change all points before the primary point */
909 for (AutomationList::iterator i = mr.start; i != cp.model; ++i) {
911 double fract = ((*i)->when - mr.xmin) / (mr.xpos - mr.xmin);
912 double y_delta = ydelta * fract;
913 double x_delta = distance * fract;
917 if (y_delta || x_delta) {
918 alist.modify (i, (*i)->when + x_delta, mr.ymin + y_delta);
922 /* change the primary point */
924 update_pending = true;
925 alist.modify (cp.model, mr.xval, mr.yval);
928 /* change later points */
930 AutomationList::iterator i = cp.model;
934 while (i != mr.end) {
936 double delta = ydelta * (mr.xmax - (*i)->when) / (mr.xmax - mr.xpos);
938 /* all later points move by the same distance along the x-axis as the main point */
941 alist.modify (i, (*i)->when + distance, (*i)->value + delta);
949 /* move all points after the range represented by the view by the same distance
950 as the main point moved.
953 alist.slide (mr.end, drag_distance);
959 AutomationLine::control_points_adjacent (double xval, uint32_t & before, uint32_t& after)
961 ControlPoint *bcp = 0;
962 ControlPoint *acp = 0;
965 /* xval is in frames */
967 unit_xval = trackview.editor.frame_to_unit (xval);
969 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
971 if ((*i)->get_x() <= unit_xval) {
973 if (!bcp || (*i)->get_x() > bcp->get_x()) {
975 before = bcp->view_index;
978 } else if ((*i)->get_x() > unit_xval) {
980 after = acp->view_index;
989 AutomationLine::is_last_point (ControlPoint& cp)
991 ModelRepresentation mr;
993 model_representation (cp, mr);
995 // If the list is not empty, and the point is the last point in the list
997 if (!alist.empty() && mr.end == alist.end()) {
1005 AutomationLine::is_first_point (ControlPoint& cp)
1007 ModelRepresentation mr;
1009 model_representation (cp, mr);
1011 // If the list is not empty, and the point is the first point in the list
1013 if (!alist.empty() && mr.start == alist.begin()) {
1020 // This is copied into AudioRegionGainLine
1022 AutomationLine::remove_point (ControlPoint& cp)
1024 ModelRepresentation mr;
1026 model_representation (cp, mr);
1028 trackview.editor.current_session()->begin_reversible_command (_("remove control point"));
1029 XMLNode &before = alist.get_state();
1031 alist.erase (mr.start, mr.end);
1033 trackview.editor.current_session()->add_command(new MementoCommand<AutomationList>(alist, &before, &alist.get_state()));
1034 trackview.editor.current_session()->commit_reversible_command ();
1035 trackview.editor.current_session()->set_dirty ();
1039 AutomationLine::get_selectables (nframes_t& start, nframes_t& end,
1040 double botfrac, double topfrac, list<Selectable*>& results)
1047 bool collecting = false;
1049 /* Curse X11 and its inverted coordinate system! */
1051 bot = (1.0 - topfrac) * _height;
1052 top = (1.0 - botfrac) * _height;
1054 nstart = max_frames;
1057 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1059 nframes_t when = (nframes_t) (*(*i)->model)->when;
1061 if (when >= start && when <= end) {
1063 if ((*i)->get_y() >= bot && (*i)->get_y() <= top) {
1066 (*i)->set_visible(true);
1068 nstart = min (nstart, when);
1069 nend = max (nend, when);
1075 results.push_back (new AutomationSelectable (nstart, nend, botfrac, topfrac, trackview));
1077 nstart = max_frames;
1085 results.push_back (new AutomationSelectable (nstart, nend, botfrac, topfrac, trackview));
1091 AutomationLine::get_inverted_selectables (Selection&, list<Selectable*>& results)
1097 AutomationLine::set_selected_points (PointSelection& points)
1102 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1103 (*i)->selected = false;
1106 if (points.empty()) {
1110 for (PointSelection::iterator r = points.begin(); r != points.end(); ++r) {
1112 if (&(*r).track != &trackview) {
1116 /* Curse X11 and its inverted coordinate system! */
1118 bot = (1.0 - (*r).high_fract) * _height;
1119 top = (1.0 - (*r).low_fract) * _height;
1121 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1123 double rstart, rend;
1125 rstart = trackview.editor.frame_to_unit ((*r).start);
1126 rend = trackview.editor.frame_to_unit ((*r).end);
1128 if ((*i)->get_x() >= rstart && (*i)->get_x() <= rend) {
1130 if ((*i)->get_y() >= bot && (*i)->get_y() <= top) {
1132 (*i)->selected = true;
1140 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1141 (*i)->show_color (false, !points_visible);
1147 AutomationLine::show_selection ()
1149 TimeSelection& time (trackview.editor.get_selection().time);
1151 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1153 (*i)->selected = false;
1155 for (list<AudioRange>::iterator r = time.begin(); r != time.end(); ++r) {
1156 double rstart, rend;
1158 rstart = trackview.editor.frame_to_unit ((*r).start);
1159 rend = trackview.editor.frame_to_unit ((*r).end);
1161 if ((*i)->get_x() >= rstart && (*i)->get_x() <= rend) {
1162 (*i)->selected = true;
1167 (*i)->show_color (false, !points_visible);
1172 AutomationLine::hide_selection ()
1174 // show_selection ();
1178 AutomationLine::list_changed ()
1184 AutomationLine::reset_callback (const AutomationList& events)
1186 ALPoints tmp_points;
1187 uint32_t npoints = events.size();
1190 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1193 control_points.clear ();
1198 AutomationList::const_iterator ai;
1200 for (ai = events.const_begin(); ai != events.const_end(); ++ai) {
1202 double translated_y = (*ai)->value;
1203 model_to_view_y (translated_y);
1205 tmp_points.push_back (ALPoint (trackview.editor.frame_to_unit ((*ai)->when),
1206 _height - (translated_y * _height)));
1209 determine_visible_control_points (tmp_points);
1213 AutomationLine::reset ()
1215 update_pending = false;
1221 alist.apply_to_points (*this, &AutomationLine::reset_callback);
1225 AutomationLine::clear ()
1227 /* parent must create command */
1228 XMLNode &before = get_state();
1230 trackview.editor.current_session()->add_command (new MementoCommand<AutomationLine>(*this, &before, &get_state()));
1231 trackview.editor.current_session()->commit_reversible_command ();
1232 trackview.editor.current_session()->set_dirty ();
1236 AutomationLine::change_model (AutomationList::iterator i, double x, double y)
1241 AutomationLine::change_model_range (AutomationList::iterator start, AutomationList::iterator end, double xdelta, float ydelta)
1243 alist.move_range (start, end, xdelta, ydelta);
1247 AutomationLine::show_all_control_points ()
1249 points_visible = true;
1251 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1253 (*i)->set_visible (true);
1258 AutomationLine::hide_all_but_selected_control_points ()
1260 points_visible = false;
1262 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1263 if (!(*i)->selected) {
1264 (*i)->set_visible (false);
1270 AutomationLine::get_state (void)
1272 /* function as a proxy for the model */
1273 return alist.get_state();
1277 AutomationLine::set_state (const XMLNode &node)
1279 /* function as a proxy for the model */
1280 return alist.set_state (node);