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>
28 #include <pbd/stacktrace.h>
30 #include <ardour/automation_event.h>
31 #include <ardour/curve.h>
32 #include <ardour/dB.h>
34 #include "simplerect.h"
35 #include "automation_line.h"
36 #include "rgb_macros.h"
37 #include "ardour_ui.h"
38 #include "public_editor.h"
40 #include "selection.h"
41 #include "time_axis_view.h"
42 #include "point_selection.h"
43 #include "automation_selectable.h"
44 #include "automation_time_axis.h"
45 #include "public_editor.h"
47 #include <ardour/session.h>
53 using namespace ARDOUR;
55 using namespace Editing;
56 using namespace Gnome; // for Canvas
58 ControlPoint::ControlPoint (AutomationLine& al)
61 model = al.the_list().end();
70 item = new Canvas::SimpleRect (line.canvas_group());
71 item->property_draw() = true;
72 item->property_fill() = false;
73 item->property_fill_color_rgba() = color_map[cControlPointFill];
74 item->property_outline_color_rgba() = color_map[cControlPointOutline];
75 item->property_outline_pixels() = 1;
76 item->set_data ("control_point", this);
77 item->signal_event().connect (mem_fun (this, &ControlPoint::event_handler));
83 ControlPoint::ControlPoint (const ControlPoint& other, bool dummy_arg_to_force_special_copy_constructor)
91 view_index = other.view_index;
92 can_slide = other.can_slide;
95 _shape = other._shape;
99 item = new Canvas::SimpleRect (line.canvas_group());
100 item->property_fill() = false;
101 item->property_outline_color_rgba() = color_map[cEnteredControlPointOutline];
102 item->property_outline_pixels() = 1;
104 /* NOTE: no event handling in copied ControlPoints */
110 ControlPoint::~ControlPoint ()
116 ControlPoint::event_handler (GdkEvent* event)
118 return PublicEditor::instance().canvas_control_point_event (event, item, this);
122 ControlPoint::hide ()
134 ControlPoint::set_visible (bool yn)
136 item->property_draw() = (gboolean) yn;
140 ControlPoint::reset (double x, double y, AutomationList::iterator mi, uint32_t vi, ShapeType shape)
144 move_to (x, y, shape);
148 ControlPoint::show_color (bool entered, bool hide_too)
152 item->property_outline_color_rgba() = color_map[cEnteredControlPointSelected];
155 item->property_outline_color_rgba() = color_map[cEnteredControlPoint];
163 item->property_outline_color_rgba() = color_map[cControlPointSelected];
166 item->property_outline_color_rgba() = color_map[cControlPoint];
175 ControlPoint::set_size (double sz)
181 item->property_fill() = (gboolean) TRUE;
183 item->property_fill() = (gboolean) FALSE;
187 move_to (_x, _y, _shape);
191 ControlPoint::move_to (double x, double y, ShapeType shape)
195 double half_size = rint(_size/2.0);
212 item->property_x1() = x1;
213 item->property_x2() = x2;
214 item->property_y1() = y - half_size;
215 item->property_y2() = y + half_size;
224 AutomationLine::AutomationLine (const string & name, TimeAxisView& tv, ArdourCanvas::Group& parent, AutomationList& al)
228 _parent_group (parent)
230 points_visible = false;
231 update_pending = false;
232 _vc_uses_gain_mapping = false;
235 terminal_points_can_slide = true;
238 group = new ArdourCanvas::Group (parent);
239 group->property_x() = 0.0;
240 group->property_y() = 0.0;
242 line = new ArdourCanvas::Line (*group);
243 line->property_width_pixels() = (guint)1;
244 line->set_data ("line", this);
246 line->signal_event().connect (mem_fun (*this, &AutomationLine::event_handler));
248 alist.StateChanged.connect (mem_fun(*this, &AutomationLine::list_changed));
250 trackview.session().register_with_memento_command_factory(alist.id(), this);
254 AutomationLine::~AutomationLine ()
256 vector_delete (&control_points);
261 AutomationLine::event_handler (GdkEvent* event)
263 return PublicEditor::instance().canvas_line_event (event, line, this);
267 AutomationLine::queue_reset ()
269 if (!update_pending) {
270 update_pending = true;
271 Gtkmm2ext::UI::instance()->call_slot (mem_fun(*this, &AutomationLine::reset));
276 AutomationLine::show ()
280 if (points_visible) {
281 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
290 AutomationLine::hide ()
293 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
300 AutomationLine::control_point_box_size ()
302 if (_height > TimeAxisView::hLarger) {
304 } else if (_height > (guint32) TimeAxisView::hNormal) {
311 AutomationLine::set_height (guint32 h)
316 double bsz = control_point_box_size();
318 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
319 (*i)->set_size (bsz);
327 AutomationLine::set_line_color (uint32_t color)
330 line->property_fill_color_rgba() = color;
334 AutomationLine::set_verbose_cursor_uses_gain_mapping (bool yn)
336 if (yn != _vc_uses_gain_mapping) {
337 _vc_uses_gain_mapping = yn;
343 AutomationLine::nth (uint32_t n)
345 if (n < control_points.size()) {
346 return control_points[n];
353 AutomationLine::modify_view_point (ControlPoint& cp, double x, double y, bool with_push)
356 uint32_t last_movable = UINT_MAX;
357 double x_limit = DBL_MAX;
359 /* this just changes the current view. it does not alter
360 the model in any way at all.
363 /* clamp y-coord appropriately. y is supposed to be a normalized fraction (0.0-1.0),
364 and needs to be converted to a canvas unit distance.
369 y = _height - (y * _height);
373 /* x-coord cannot move beyond adjacent points or the start/end, and is
374 already in frames. it needs to be converted to canvas units.
377 x = trackview.editor.frame_to_unit (x);
379 /* clamp x position using view coordinates */
381 ControlPoint *before;
385 before = nth (cp.view_index - 1);
386 x = max (x, before->get_x()+1.0);
393 if (cp.view_index < control_points.size() - 1) {
395 after = nth (cp.view_index + 1);
397 /*if it is a "spike" leave the x alone */
399 if (after->get_x() - before->get_x() < 2) {
403 x = min (x, after->get_x()-1.0);
413 /* find the first point that can't move */
415 for (uint32_t n = cp.view_index + 1; (after = nth (n)) != 0; ++n) {
416 if (!after->can_slide) {
417 x_limit = after->get_x() - 1.0;
418 last_movable = after->view_index;
423 delta = x - cp.get_x();
428 /* leave the x-coordinate alone */
430 x = trackview.editor.frame_to_unit ((*cp.model)->when);
436 cp.move_to (x, y, ControlPoint::Full);
437 reset_line_coords (cp);
441 uint32_t limit = min (control_points.size(), (size_t)last_movable);
443 /* move the current point to wherever the user told it to go, subject
447 cp.move_to (min (x, x_limit), y, ControlPoint::Full);
448 reset_line_coords (cp);
450 /* now move all subsequent control points, to reflect the motion.
453 for (uint32_t i = cp.view_index + 1; i < limit; ++i) {
454 ControlPoint *p = nth (i);
458 new_x = min (p->get_x() + delta, x_limit);
459 p->move_to (new_x, p->get_y(), ControlPoint::Full);
460 reset_line_coords (*p);
467 AutomationLine::reset_line_coords (ControlPoint& cp)
469 if (cp.view_index < line_points.size()) {
470 line_points[cp.view_index].set_x (cp.get_x());
471 line_points[cp.view_index].set_y (cp.get_y());
476 AutomationLine::sync_model_with_view_line (uint32_t start, uint32_t end)
481 update_pending = true;
483 for (uint32_t i = start; i <= end; ++i) {
485 sync_model_with_view_point (*p, false, 0);
490 AutomationLine::model_representation (ControlPoint& cp, ModelRepresentation& mr)
492 /* part one: find out where the visual control point is.
493 initial results are in canvas units. ask the
494 line to convert them to something relevant.
497 mr.xval = (nframes_t) floor (cp.get_x());
498 mr.yval = 1.0 - (cp.get_y() / _height);
500 /* if xval has not changed, set it directly from the model to avoid rounding errors */
502 if (mr.xval == trackview.editor.frame_to_unit((*cp.model)->when)) {
503 mr.xval = (nframes_t) (*cp.model)->when;
505 mr.xval = trackview.editor.unit_to_frame (mr.xval);
508 /* virtual call: this will do the right thing
509 for whatever particular type of line we are.
512 view_to_model_y (mr.yval);
514 /* part 2: find out where the model point is now
517 mr.xpos = (nframes_t) (*cp.model)->when;
518 mr.ypos = (*cp.model)->value;
520 /* part 3: get the position of the visual control
521 points before and after us.
524 ControlPoint* before;
528 before = nth (cp.view_index - 1);
533 after = nth (cp.view_index + 1);
536 mr.xmin = (nframes_t) (*before->model)->when;
537 mr.ymin = (*before->model)->value;
538 mr.start = before->model;
547 mr.end = after->model;
557 AutomationLine::determine_visible_control_points (ALPoints& points)
559 uint32_t view_index, pi, n;
560 AutomationList::iterator model;
562 double last_control_point_x = 0.0;
563 double last_control_point_y = 0.0;
564 uint32_t this_rx = 0;
565 uint32_t prev_rx = 0;
566 uint32_t this_ry = 0;
567 uint32_t prev_ry = 0;
572 /* hide all existing points, and the line */
576 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
583 if (points.empty()) {
587 npoints = points.size();
589 /* compute derivative/slope for the entire line */
591 slope = new double[npoints];
593 for (n = 0; n < npoints - 1; ++n) {
594 double xdelta = points[n+1].x - points[n].x;
595 double ydelta = points[n+1].y - points[n].y;
596 slope[n] = ydelta/xdelta;
599 box_size = (uint32_t) control_point_box_size ();
601 /* read all points and decide which ones to show as control points */
605 for (model = alist.begin(), pi = 0; pi < npoints; ++model, ++pi) {
607 double tx = points[pi].x;
608 double ty = points[pi].y;
610 /* now ensure that the control_points vector reflects the current curve
611 state, but don't plot control points too close together. also, don't
612 plot a series of points all with the same value.
614 always plot the first and last points, of course.
617 if (invalid_point (points, pi)) {
618 /* for some reason, we are supposed to ignore this point,
619 but still keep track of the model index.
624 if (pi > 0 && pi < npoints - 1) {
625 if (slope[pi] == slope[pi-1]) {
627 /* no reason to display this point */
633 /* need to round here. the ultimate coordinates are integer
634 pixels, so tiny deltas in the coords will be eliminated
635 and we end up with "colinear" line segments. since the
636 line rendering code in libart doesn't like this very
637 much, we eliminate them here. don't do this for the first and last
641 this_rx = (uint32_t) rint (tx);
642 this_ry = (uint32_t) rint (ty);
644 if (view_index && pi != npoints && /* not the first, not the last */
645 (((this_rx == prev_rx) && (this_ry == prev_ry)) || /* same point */
646 (((this_rx - prev_rx) < (box_size + 2)) && /* not identical, but still too close horizontally */
647 (abs ((int)(this_ry - prev_ry)) < (int) (box_size + 2))))) { /* too close vertically */
651 /* ok, we should display this point */
653 if (view_index >= cpsize) {
655 /* make sure we have enough control points */
657 ControlPoint* ncp = new ControlPoint (*this);
659 ncp->set_size (box_size);
661 control_points.push_back (ncp);
665 ControlPoint::ShapeType shape;
667 if (!terminal_points_can_slide) {
669 control_points[view_index]->can_slide = false;
671 shape = ControlPoint::Start;
673 shape = ControlPoint::Full;
675 } else if (pi == npoints - 1) {
676 control_points[view_index]->can_slide = false;
677 shape = ControlPoint::End;
679 control_points[view_index]->can_slide = true;
680 shape = ControlPoint::Full;
683 control_points[view_index]->can_slide = true;
684 shape = ControlPoint::Full;
687 last_control_point_x = tx;
688 last_control_point_y = ty;
690 control_points[view_index]->reset (tx, ty, model, view_index, shape);
695 /* finally, control visibility */
697 if (_visible && points_visible) {
698 control_points[view_index]->show ();
699 control_points[view_index]->set_visible (true);
701 if (!points_visible) {
702 control_points[view_index]->set_visible (false);
709 /* discard extra CP's to avoid confusing ourselves */
711 while (control_points.size() > view_index) {
712 ControlPoint* cp = control_points.back();
713 control_points.pop_back ();
717 if (!terminal_points_can_slide) {
718 control_points.back()->can_slide = false;
723 if (view_index > 1) {
725 npoints = view_index;
727 /* reset the line coordinates */
729 while (line_points.size() < npoints) {
730 line_points.push_back (Art::Point (0,0));
733 while (line_points.size() > npoints) {
734 line_points.pop_back ();
737 for (view_index = 0; view_index < npoints; ++view_index) {
738 line_points[view_index].set_x (control_points[view_index]->get_x());
739 line_points[view_index].set_y (control_points[view_index]->get_y());
742 line->property_points() = line_points;
750 set_selected_points (trackview.editor.get_selection().points);
755 AutomationLine::get_verbose_cursor_string (float fraction)
759 if (_vc_uses_gain_mapping) {
760 if (fraction == 0.0) {
761 snprintf (buf, sizeof (buf), "-inf dB");
763 snprintf (buf, sizeof (buf), "%.1fdB", coefficient_to_dB (slider_position_to_gain (fraction)));
766 snprintf (buf, sizeof (buf), "%.2f", fraction);
773 AutomationLine::invalid_point (ALPoints& p, uint32_t index)
775 return p[index].x == max_frames && p[index].y == DBL_MAX;
779 AutomationLine::invalidate_point (ALPoints& p, uint32_t index)
781 p[index].x = max_frames;
782 p[index].y = DBL_MAX;
786 AutomationLine::start_drag (ControlPoint* cp, nframes_t x, float fraction)
788 if (trackview.editor.current_session() == 0) { /* how? */
795 str = _("automation event move");
797 str = _("automation range drag");
800 trackview.editor.current_session()->begin_reversible_command (str);
801 trackview.editor.current_session()->add_command (new MementoCommand<AutomationList>(alist, &get_state(), 0));
805 first_drag_fraction = fraction;
806 last_drag_fraction = fraction;
812 AutomationLine::point_drag (ControlPoint& cp, nframes_t x, float fraction, bool with_push)
815 drag_distance += (x - drag_x);
817 drag_distance -= (drag_x - x);
822 modify_view_point (cp, x, fraction, with_push);
824 if (line_points.size() > 1) {
825 line->property_points() = line_points;
829 did_push = with_push;
833 AutomationLine::line_drag (uint32_t i1, uint32_t i2, float fraction, bool with_push)
835 double ydelta = fraction - last_drag_fraction;
837 did_push = with_push;
839 last_drag_fraction = fraction;
846 for (uint32_t i = i1 ; i <= i2; i++) {
848 modify_view_point (*cp, trackview.editor.unit_to_frame (cp->get_x()), ((_height - cp->get_y()) /_height) + ydelta, with_push);
851 if (line_points.size() > 1) {
852 line->property_points() = line_points;
859 AutomationLine::end_drag (ControlPoint* cp)
868 sync_model_with_view_point (*cp, did_push, drag_distance);
870 sync_model_with_view_line (line_drag_cp1, line_drag_cp2);
875 update_pending = false;
877 trackview.editor.current_session()->add_command (new MementoCommand<AutomationList>(alist, 0, &alist.get_state()));
878 trackview.editor.current_session()->commit_reversible_command ();
879 trackview.editor.current_session()->set_dirty ();
884 AutomationLine::sync_model_with_view_point (ControlPoint& cp, bool did_push, int64_t distance)
886 ModelRepresentation mr;
889 model_representation (cp, mr);
891 /* how much are we changing the central point by */
893 ydelta = mr.yval - mr.ypos;
896 apply the full change to the central point, and interpolate
897 on both axes to cover all model points represented
898 by the control point.
901 /* change all points before the primary point */
903 for (AutomationList::iterator i = mr.start; i != cp.model; ++i) {
905 double fract = ((*i)->when - mr.xmin) / (mr.xpos - mr.xmin);
906 double y_delta = ydelta * fract;
907 double x_delta = distance * fract;
911 if (y_delta || x_delta) {
912 alist.modify (i, (*i)->when + x_delta, mr.ymin + y_delta);
916 /* change the primary point */
918 update_pending = true;
919 alist.modify (cp.model, mr.xval, mr.yval);
922 /* change later points */
924 AutomationList::iterator i = cp.model;
928 while (i != mr.end) {
930 double delta = ydelta * (mr.xmax - (*i)->when) / (mr.xmax - mr.xpos);
932 /* all later points move by the same distance along the x-axis as the main point */
935 alist.modify (i, (*i)->when + distance, (*i)->value + delta);
943 /* move all points after the range represented by the view by the same distance
944 as the main point moved.
947 alist.slide (mr.end, drag_distance);
953 AutomationLine::control_points_adjacent (double xval, uint32_t & before, uint32_t& after)
955 ControlPoint *bcp = 0;
956 ControlPoint *acp = 0;
959 /* xval is in frames */
961 unit_xval = trackview.editor.frame_to_unit (xval);
963 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
965 if ((*i)->get_x() <= unit_xval) {
967 if (!bcp || (*i)->get_x() > bcp->get_x()) {
969 before = bcp->view_index;
972 } else if ((*i)->get_x() > unit_xval) {
974 after = acp->view_index;
983 AutomationLine::is_last_point (ControlPoint& cp)
985 ModelRepresentation mr;
987 model_representation (cp, mr);
989 // If the list is not empty, and the point is the last point in the list
991 if (!alist.empty() && mr.end == alist.end()) {
999 AutomationLine::is_first_point (ControlPoint& cp)
1001 ModelRepresentation mr;
1003 model_representation (cp, mr);
1005 // If the list is not empty, and the point is the first point in the list
1007 if (!alist.empty() && mr.start == alist.begin()) {
1014 // This is copied into AudioRegionGainLine
1016 AutomationLine::remove_point (ControlPoint& cp)
1018 ModelRepresentation mr;
1020 model_representation (cp, mr);
1022 trackview.editor.current_session()->begin_reversible_command (_("remove control point"));
1023 XMLNode &before = alist.get_state();
1025 alist.erase (mr.start, mr.end);
1027 trackview.editor.current_session()->add_command(new MementoCommand<AutomationList>(alist, &before, &alist.get_state()));
1028 trackview.editor.current_session()->commit_reversible_command ();
1029 trackview.editor.current_session()->set_dirty ();
1033 AutomationLine::get_selectables (nframes_t& start, nframes_t& end,
1034 double botfrac, double topfrac, list<Selectable*>& results)
1041 bool collecting = false;
1043 /* Curse X11 and its inverted coordinate system! */
1045 bot = (1.0 - topfrac) * _height;
1046 top = (1.0 - botfrac) * _height;
1048 nstart = max_frames;
1051 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1053 nframes_t when = (nframes_t) (*(*i)->model)->when;
1055 if (when >= start && when <= end) {
1057 if ((*i)->get_y() >= bot && (*i)->get_y() <= top) {
1060 (*i)->set_visible(true);
1062 nstart = min (nstart, when);
1063 nend = max (nend, when);
1069 results.push_back (new AutomationSelectable (nstart, nend, botfrac, topfrac, trackview));
1071 nstart = max_frames;
1079 results.push_back (new AutomationSelectable (nstart, nend, botfrac, topfrac, trackview));
1085 AutomationLine::get_inverted_selectables (Selection&, list<Selectable*>& results)
1091 AutomationLine::set_selected_points (PointSelection& points)
1096 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1097 (*i)->selected = false;
1100 if (points.empty()) {
1104 for (PointSelection::iterator r = points.begin(); r != points.end(); ++r) {
1106 if (&(*r).track != &trackview) {
1110 /* Curse X11 and its inverted coordinate system! */
1112 bot = (1.0 - (*r).high_fract) * _height;
1113 top = (1.0 - (*r).low_fract) * _height;
1115 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1117 double rstart, rend;
1119 rstart = trackview.editor.frame_to_unit ((*r).start);
1120 rend = trackview.editor.frame_to_unit ((*r).end);
1122 if ((*i)->get_x() >= rstart && (*i)->get_x() <= rend) {
1124 if ((*i)->get_y() >= bot && (*i)->get_y() <= top) {
1126 (*i)->selected = true;
1134 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1135 (*i)->show_color (false, !points_visible);
1141 AutomationLine::show_selection ()
1143 TimeSelection& time (trackview.editor.get_selection().time);
1145 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1147 (*i)->selected = false;
1149 for (list<AudioRange>::iterator r = time.begin(); r != time.end(); ++r) {
1150 double rstart, rend;
1152 rstart = trackview.editor.frame_to_unit ((*r).start);
1153 rend = trackview.editor.frame_to_unit ((*r).end);
1155 if ((*i)->get_x() >= rstart && (*i)->get_x() <= rend) {
1156 (*i)->selected = true;
1161 (*i)->show_color (false, !points_visible);
1166 AutomationLine::hide_selection ()
1168 // show_selection ();
1172 AutomationLine::list_changed ()
1178 AutomationLine::reset_callback (const AutomationList& events)
1180 ALPoints tmp_points;
1181 uint32_t npoints = events.size();
1184 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1187 control_points.clear ();
1192 AutomationList::const_iterator ai;
1194 for (ai = events.const_begin(); ai != events.const_end(); ++ai) {
1196 double translated_y = (*ai)->value;
1197 model_to_view_y (translated_y);
1199 tmp_points.push_back (ALPoint (trackview.editor.frame_to_unit ((*ai)->when),
1200 _height - (translated_y * _height)));
1203 determine_visible_control_points (tmp_points);
1207 AutomationLine::reset ()
1209 update_pending = false;
1215 alist.apply_to_points (*this, &AutomationLine::reset_callback);
1219 AutomationLine::clear ()
1221 /* parent must create command */
1222 XMLNode &before = get_state();
1224 trackview.editor.current_session()->add_command (new MementoCommand<AutomationLine>(*this, &before, &get_state()));
1225 trackview.editor.current_session()->commit_reversible_command ();
1226 trackview.editor.current_session()->set_dirty ();
1230 AutomationLine::change_model (AutomationList::iterator i, double x, double y)
1235 AutomationLine::change_model_range (AutomationList::iterator start, AutomationList::iterator end, double xdelta, float ydelta)
1237 alist.move_range (start, end, xdelta, ydelta);
1241 AutomationLine::show_all_control_points ()
1243 points_visible = true;
1245 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1247 (*i)->set_visible (true);
1252 AutomationLine::hide_all_but_selected_control_points ()
1254 points_visible = false;
1256 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1257 if (!(*i)->selected) {
1258 (*i)->set_visible (false);
1264 AutomationLine::get_state (void)
1266 /* function as a proxy for the model */
1267 return alist.get_state();
1271 AutomationLine::set_state (const XMLNode &node)
1273 /* function as a proxy for the model */
1274 return alist.set_state (node);