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>
27 #include <ardour/automation_event.h>
28 #include <ardour/curve.h>
29 #include <ardour/dB.h>
31 #include "simplerect.h"
32 #include "automation_line.h"
33 #include "rgb_macros.h"
34 #include "ardour_ui.h"
35 #include "public_editor.h"
37 #include "selection.h"
38 #include "time_axis_view.h"
39 #include "point_selection.h"
40 #include "automation_selectable.h"
41 #include "automation_time_axis.h"
42 #include "public_editor.h"
44 #include <ardour/session.h>
50 using namespace ARDOUR;
51 using namespace Editing;
52 using namespace Gnome; // for Canvas
54 ControlPoint::ControlPoint (AutomationLine& al, sigc::slot<bool,GdkEvent*,ControlPoint*> handler)
57 model = al.the_list().end();
66 item = new Canvas::SimpleRect (line.canvas_group());
67 item->property_draw() = true;
68 item->property_fill() = false;
69 item->property_fill_color_rgba() = color_map[cControlPointFill];
70 item->property_outline_color_rgba() = color_map[cControlPointOutline];
71 item->property_outline_pixels() = 1;
72 item->set_data ("control_point", this);
73 item->signal_event().connect (bind (handler, this));
79 ControlPoint::ControlPoint (const ControlPoint& other, bool dummy_arg_to_force_special_copy_constructor)
87 view_index = other.view_index;
88 can_slide = other.can_slide;
91 _shape = other._shape;
95 item = new Canvas::SimpleRect (line.canvas_group());
96 item->property_fill() = false;
97 item->property_outline_color_rgba() = color_map[cEnteredControlPointOutline];
98 item->property_outline_pixels() = 1;
100 /* NOTE: no event handling in copied ControlPoints */
106 ControlPoint::~ControlPoint ()
108 gtk_object_destroy (GTK_OBJECT(item));
112 ControlPoint::hide ()
124 ControlPoint::set_visible (bool yn)
126 item->set_property ("draw", (gboolean) yn);
130 ControlPoint::reset (double x, double y, AutomationList::iterator mi, uint32_t vi, ShapeType shape)
134 move_to (x, y, shape);
138 ControlPoint::show_color (bool entered, bool hide_too)
142 item->set_property ("outline_color_rgba", color_map[cEnteredControlPointSelected]);
145 item->set_property ("outline_color_rgba", color_map[cEnteredControlPoint]);
153 item->set_property ("outline_color_rgba", color_map[cControlPointSelected]);
156 item->set_property ("outline_color_rgba", color_map[cControlPoint]);
165 ControlPoint::set_size (double sz)
171 item->set_property ("fill", (gboolean) TRUE);
173 item->set_property ("fill", (gboolean) FALSE);
177 move_to (_x, _y, _shape);
181 ControlPoint::move_to (double x, double y, ShapeType shape)
185 double half_size = rint(_size/2.0);
202 item->set_property ("x1", x1);
203 item->set_property ("x2", x2);
204 item->set_property ("y1", y - half_size);
205 item->set_property ("y2", y + half_size);
214 AutomationLine::AutomationLine (string name, TimeAxisView& tv, Gnome::Canvas::Group& parent, AutomationList& al,
215 slot<bool,GdkEvent*,ControlPoint*> point_handler,
216 slot<bool,GdkEvent*,AutomationLine*> line_handler)
221 _parent_group (parent)
223 points_visible = false;
224 update_pending = false;
225 _vc_uses_gain_mapping = false;
228 point_slot = point_handler;
229 terminal_points_can_slide = true;
232 group = new Gnome::Canvas::Group (parent);
233 group->set_property ("x", 0.0);
234 group->set_property ("y", 0.0);
236 line = new Gnome::Canvas::Line (*group);
237 line->set_property ("width_pixels", (guint)1);
239 line->set_data ("line", this);
240 line->signal_event().connect (bind (line_handler, this));
242 alist.StateChanged.connect (mem_fun(*this, &AutomationLine::list_changed));
245 AutomationLine::~AutomationLine ()
247 vector_delete (&control_points);
249 gtk_object_destroy (GTK_OBJECT(group));
253 AutomationLine::queue_reset ()
255 if (!update_pending) {
256 update_pending = true;
257 Gtkmm2ext::UI::instance()->call_slot (mem_fun(*this, &AutomationLine::reset));
262 AutomationLine::set_point_size (double sz)
264 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
270 AutomationLine::show ()
274 if (points_visible) {
275 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
284 AutomationLine::hide ()
287 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
294 AutomationLine::set_height (guint32 h)
299 if (_height > (guint32) TimeAxisView::Larger) {
300 set_point_size (8.0);
301 } else if (_height > (guint32) TimeAxisView::Normal) {
302 set_point_size (6.0);
304 set_point_size (4.0);
312 AutomationLine::set_line_color (uint32_t color)
315 line->set_property ("fill_color_rgba", color);
319 AutomationLine::set_verbose_cursor_uses_gain_mapping (bool yn)
321 if (yn != _vc_uses_gain_mapping) {
322 _vc_uses_gain_mapping = yn;
328 AutomationLine::nth (uint32_t n)
330 if (n < control_points.size()) {
331 return control_points[n];
338 AutomationLine::modify_view (ControlPoint& cp, double x, double y, bool with_push)
340 modify_view_point (cp, x, y, with_push);
345 AutomationLine::modify_view_point (ControlPoint& cp, double x, double y, bool with_push)
348 uint32_t last_movable = UINT_MAX;
349 double x_limit = DBL_MAX;
351 /* this just changes the current view. it does not alter
352 the model in any way at all.
355 /* clamp y-coord appropriately. y is supposed to be a normalized fraction (0.0-1.0),
356 and needs to be converted to a canvas unit distance.
361 y = _height - (y * _height);
365 /* x-coord cannot move beyond adjacent points or the start/end, and is
366 already in frames. it needs to be converted to canvas units.
369 x = trackview.editor.frame_to_unit (x);
371 /* clamp x position using view coordinates */
373 ControlPoint *before;
377 before = nth (cp.view_index - 1);
378 x = max (x, before->get_x()+1.0);
385 if (cp.view_index < control_points.size() - 1) {
387 after = nth (cp.view_index + 1);
389 /*if it is a "spike" leave the x alone */
391 if (after->get_x() - before->get_x() < 2) {
395 x = min (x, after->get_x()-1.0);
405 /* find the first point that can't move */
407 for (uint32_t n = cp.view_index + 1; (after = nth (n)) != 0; ++n) {
408 if (!after->can_slide) {
409 x_limit = after->get_x() - 1.0;
410 last_movable = after->view_index;
415 delta = x - cp.get_x();
420 /* leave the x-coordinate alone */
422 x = trackview.editor.frame_to_unit ((*cp.model)->when);
428 cp.move_to (x, y, ControlPoint::Full);
429 reset_line_coords (cp);
433 uint32_t limit = min (control_points.size(), (size_t)last_movable);
435 /* move the current point to wherever the user told it to go, subject
439 cp.move_to (min (x, x_limit), y, ControlPoint::Full);
440 reset_line_coords (cp);
442 /* now move all subsequent control points, to reflect the motion.
445 for (uint32_t i = cp.view_index + 1; i < limit; ++i) {
446 ControlPoint *p = nth (i);
450 new_x = min (p->get_x() + delta, x_limit);
451 p->move_to (new_x, p->get_y(), ControlPoint::Full);
452 reset_line_coords (*p);
459 AutomationLine::reset_line_coords (ControlPoint& cp)
461 line_points[cp.view_index].set_x (cp.get_x());
462 line_points[cp.view_index].set_y (cp.get_y());
466 AutomationLine::update_line ()
468 line->property_points() = line_points;
472 AutomationLine::sync_model_with_view_line (uint32_t start, uint32_t end)
477 update_pending = true;
479 for (uint32_t i = start; i <= end; ++i) {
481 sync_model_with_view_point(*p);
488 AutomationLine::model_representation (ControlPoint& cp, ModelRepresentation& mr)
490 /* part one: find out where the visual control point is.
491 initial results are in canvas units. ask the
492 line to convert them to something relevant.
495 mr.xval = (jack_nframes_t) floor (cp.get_x());
496 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 = (jack_nframes_t) (*cp.model)->when;
504 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 = (jack_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 = (jack_nframes_t) (*before->model)->when;
537 mr.ymin = (*before->model)->value;
538 mr.start = before->model;
547 mr.end = after->model;
557 AutomationLine::sync_model_from (ControlPoint& cp)
562 sync_model_with_view_point (cp);
564 /* we might have moved all points after `cp' by some amount
565 if we pressed the with_push modifyer some of the time during the drag
566 so all subsequent points have to be resynced
569 lasti = control_points.size() - 1;
572 update_pending = true;
574 while (p != &cp && lasti) {
575 sync_model_with_view_point (*p);
582 AutomationLine::sync_model_with_view_point (ControlPoint& cp)
584 ModelRepresentation mr;
587 model_representation (cp, mr);
589 /* part 4: how much are we changing the central point by */
591 ydelta = mr.yval - mr.ypos;
593 /* IMPORTANT: changing the model when the x-coordinate changes
594 may invalidate the iterators that we are using. this means that we have
595 to change the points before+after the one corresponding to the visual CP
596 first (their x-coordinate doesn't change). then we change the
599 apply the full change to the central point, and interpolate
600 in each direction to cover all model points represented
601 by the control point.
604 /* part 5: change all points before the primary point */
606 for (AutomationList::iterator i = mr.start; i != cp.model; ++i) {
610 delta = ydelta * ((*i)->when - mr.xmin) / (mr.xpos - mr.xmin);
612 /* x-coordinate (generally time) stays where it is,
613 y-coordinate moves by a certain amount.
616 update_pending = true;
617 change_model (i, (*i)->when, mr.yval + delta);
620 /* part 6: change later points */
622 AutomationList::iterator i = cp.model;
626 while (i != mr.end) {
630 delta = ydelta * (mr.xmax - (*i)->when) / (mr.xmax - mr.xpos);
632 /* x-coordinate (generally time) stays where it is,
633 y-coordinate moves by a certain amount.
636 update_pending = true;
637 change_model (i, (*i)->when, (*i)->value + delta);
642 /* part 7: change the primary point */
644 update_pending = true;
645 change_model (cp.model, mr.xval, mr.yval);
649 AutomationLine::determine_visible_control_points (ALPoints& points)
651 uint32_t view_index, pi, n;
652 AutomationList::iterator model;
654 double last_control_point_x = 0.0;
655 double last_control_point_y = 0.0;
656 uint32_t this_rx = 0;
657 uint32_t prev_rx = 0;
658 uint32_t this_ry = 0;
659 uint32_t prev_ry = 0;
662 /* hide all existing points, and the line */
664 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
670 if (points.empty()) {
674 npoints = points.size();
676 /* compute derivative/slope for the entire line */
678 slope = new double[npoints];
680 for (n = 0; n < npoints - 1; ++n) {
681 double xdelta = points[n+1].x - points[n].x;
682 double ydelta = points[n+1].y - points[n].y;
683 slope[n] = ydelta/xdelta;
686 /* read all points and decide which ones to show as control points */
690 for (model = alist.begin(), pi = 0; pi < npoints; ++model, ++pi) {
692 double tx = points[pi].x;
693 double ty = points[pi].y;
695 /* now ensure that the control_points vector reflects the current curve
696 state, but don't plot control points too close together. also, don't
697 plot a series of points all with the same value.
699 always plot the first and last points, of course.
702 if (invalid_point (points, pi)) {
703 /* for some reason, we are supposed to ignore this point,
704 but still keep track of the model index.
709 if (pi > 0 && pi < npoints - 1) {
710 if (slope[pi] == slope[pi-1]) {
712 /* no reason to display this point */
718 /* need to round here. the ultimate coordinates are integer
719 pixels, so tiny deltas in the coords will be eliminated
720 and we end up with "colinear" line segments. since the
721 line rendering code in libart doesn't like this very
722 much, we eliminate them here. don't do this for the first and last
726 this_rx = (uint32_t) rint (tx);
727 this_ry = (unsigned long) rint (ty);
729 if (view_index && pi != npoints && (this_rx == prev_rx) && (this_ry == prev_ry)) {
734 /* ok, we should display this point */
736 if (view_index >= control_points.size()) {
737 /* make sure we have enough control points */
739 ControlPoint* ncp = new ControlPoint (*this, point_slot);
741 if (_height > (guint32) TimeAxisView::Larger) {
743 } else if (_height > (guint32) TimeAxisView::Normal) {
749 control_points.push_back (ncp);
752 ControlPoint::ShapeType shape;
754 if (!terminal_points_can_slide) {
756 control_points[view_index]->can_slide = false;
758 shape = ControlPoint::Start;
760 shape = ControlPoint::Full;
762 } else if (pi == npoints - 1) {
763 control_points[view_index]->can_slide = false;
764 shape = ControlPoint::End;
766 control_points[view_index]->can_slide = true;
767 shape = ControlPoint::Full;
770 control_points[view_index]->can_slide = true;
771 shape = ControlPoint::Full;
774 last_control_point_x = tx;
775 last_control_point_y = ty;
777 control_points[view_index]->reset (tx, ty, model, view_index, shape);
782 /* finally, control visibility */
784 if (_visible && points_visible) {
785 control_points[view_index]->show ();
786 control_points[view_index]->set_visible (true);
788 if (!points_visible) {
789 control_points[view_index]->set_visible (false);
796 /* discard extra CP's to avoid confusing ourselves */
798 while (control_points.size() > view_index) {
799 ControlPoint* cp = control_points.back();
800 control_points.pop_back ();
804 if (!terminal_points_can_slide) {
805 control_points.back()->can_slide = false;
810 if (view_index > 1) {
812 npoints = view_index;
814 /* reset the line coordinates */
816 while (line_points.size() < npoints) {
817 line_points.push_back (Art::Point (0,0));
820 for (view_index = 0; view_index < npoints; ++view_index) {
821 line_points[view_index].set_x (control_points[view_index]->get_x());
822 line_points[view_index].set_y (control_points[view_index]->get_y());
825 line->property_points() = line_points;
832 set_selected_points (trackview.editor.get_selection().points);
836 AutomationLine::get_verbose_cursor_string (float fraction)
840 if (_vc_uses_gain_mapping) {
841 if (fraction == 0.0) {
842 snprintf (buf, sizeof (buf), "-inf dB");
844 snprintf (buf, sizeof (buf), "%.1fdB", coefficient_to_dB (slider_position_to_gain (fraction)));
847 snprintf (buf, sizeof (buf), "%.2f", fraction);
854 AutomationLine::invalid_point (ALPoints& p, uint32_t index)
856 return p[index].x == max_frames && p[index].y == DBL_MAX;
860 AutomationLine::invalidate_point (ALPoints& p, uint32_t index)
862 p[index].x = max_frames;
863 p[index].y = DBL_MAX;
867 AutomationLine::start_drag (ControlPoint* cp, float fraction)
869 if (trackview.editor.current_session() == 0) { /* how? */
876 str = _("automation event move");
878 str = _("automation range drag");
881 trackview.editor.current_session()->begin_reversible_command (str);
882 trackview.editor.current_session()->add_undo (get_memento());
884 first_drag_fraction = fraction;
885 last_drag_fraction = fraction;
890 AutomationLine::point_drag (ControlPoint& cp, jack_nframes_t x, float fraction, bool with_push)
892 modify_view (cp, x, fraction, with_push);
897 AutomationLine::line_drag (uint32_t i1, uint32_t i2, float fraction, bool with_push)
899 double ydelta = fraction - last_drag_fraction;
901 last_drag_fraction = fraction;
908 for (uint32_t i = i1 ; i <= i2; i++) {
910 modify_view_point (*cp, trackview.editor.unit_to_frame (cp->get_x()), ((_height - cp->get_y()) /_height) + ydelta, with_push);
919 AutomationLine::end_drag (ControlPoint* cp)
924 sync_model_from (*cp);
926 sync_model_with_view_line (line_drag_cp1, line_drag_cp2);
929 update_pending = false;
931 trackview.editor.current_session()->add_redo_no_execute (get_memento());
932 trackview.editor.current_session()->commit_reversible_command ();
933 trackview.editor.current_session()->set_dirty ();
938 AutomationLine::control_points_adjacent (double xval, uint32_t & before, uint32_t& after)
940 ControlPoint *bcp = 0;
941 ControlPoint *acp = 0;
944 /* xval is in frames */
946 unit_xval = trackview.editor.frame_to_unit (xval);
948 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
950 if ((*i)->get_x() <= unit_xval) {
952 if (!bcp || (*i)->get_x() > bcp->get_x()) {
954 before = bcp->view_index;
957 } else if ((*i)->get_x() > unit_xval) {
959 after = acp->view_index;
968 AutomationLine::is_last_point (ControlPoint& cp)
970 ModelRepresentation mr;
972 model_representation (cp, mr);
974 // If the list is not empty, and the point is the last point in the list
976 if (!alist.empty() && mr.end == alist.end()) {
984 AutomationLine::is_first_point (ControlPoint& cp)
986 ModelRepresentation mr;
988 model_representation (cp, mr);
990 // If the list is not empty, and the point is the first point in the list
992 if (!alist.empty() && mr.start == alist.begin()) {
999 // This is copied into AudioRegionGainLine
1001 AutomationLine::remove_point (ControlPoint& cp)
1003 ModelRepresentation mr;
1005 model_representation (cp, mr);
1007 trackview.editor.current_session()->begin_reversible_command (_("remove control point"));
1008 trackview.editor.current_session()->add_undo (get_memento());
1010 alist.erase (mr.start, mr.end);
1012 trackview.editor.current_session()->add_redo_no_execute (get_memento());
1013 trackview.editor.current_session()->commit_reversible_command ();
1014 trackview.editor.current_session()->set_dirty ();
1018 AutomationLine::get_selectables (jack_nframes_t& start, jack_nframes_t& end,
1019 double botfrac, double topfrac, list<Selectable*>& results)
1024 jack_nframes_t nstart;
1025 jack_nframes_t nend;
1026 bool collecting = false;
1028 /* Curse X11 and its inverted coordinate system! */
1030 bot = (1.0 - topfrac) * _height;
1031 top = (1.0 - botfrac) * _height;
1033 nstart = max_frames;
1036 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1038 jack_nframes_t when = (jack_nframes_t) (*(*i)->model)->when;
1040 if (when >= start && when <= end) {
1042 if ((*i)->get_y() >= bot && (*i)->get_y() <= top) {
1045 (*i)->set_visible(true);
1047 nstart = min (nstart, when);
1048 nend = max (nend, when);
1054 results.push_back (new AutomationSelectable (nstart, nend, botfrac, topfrac, trackview));
1056 nstart = max_frames;
1064 results.push_back (new AutomationSelectable (nstart, nend, botfrac, topfrac, trackview));
1070 AutomationLine::get_inverted_selectables (Selection&, list<Selectable*>& results)
1076 AutomationLine::set_selected_points (PointSelection& points)
1081 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1082 (*i)->selected = false;
1085 if (points.empty()) {
1089 for (PointSelection::iterator r = points.begin(); r != points.end(); ++r) {
1091 if (&(*r).track != &trackview) {
1095 /* Curse X11 and its inverted coordinate system! */
1097 bot = (1.0 - (*r).high_fract) * _height;
1098 top = (1.0 - (*r).low_fract) * _height;
1100 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1102 double rstart, rend;
1104 rstart = trackview.editor.frame_to_unit ((*r).start);
1105 rend = trackview.editor.frame_to_unit ((*r).end);
1107 if ((*i)->get_x() >= rstart && (*i)->get_x() <= rend) {
1109 if ((*i)->get_y() >= bot && (*i)->get_y() <= top) {
1111 (*i)->selected = true;
1119 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1120 (*i)->show_color (false, !points_visible);
1126 AutomationLine::show_selection ()
1128 TimeSelection& time (trackview.editor.get_selection().time);
1130 // cerr << "show selection\n";
1132 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1134 (*i)->selected = false;
1136 for (list<AudioRange>::iterator r = time.begin(); r != time.end(); ++r) {
1137 double rstart, rend;
1139 rstart = trackview.editor.frame_to_unit ((*r).start);
1140 rend = trackview.editor.frame_to_unit ((*r).end);
1142 if ((*i)->get_x() >= rstart && (*i)->get_x() <= rend) {
1143 (*i)->selected = true;
1148 (*i)->show_color (false, !points_visible);
1153 AutomationLine::hide_selection ()
1155 // cerr << "hide selection\n";
1156 // show_selection ();
1160 // This is copied into AudioRegionGainLine
1162 AutomationLine::get_memento ()
1164 return alist.get_memento();
1168 AutomationLine::list_changed (Change ignored)
1174 AutomationLine::reset_callback (const AutomationList& events)
1176 ALPoints tmp_points;
1177 uint32_t npoints = events.size();
1180 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1183 control_points.clear ();
1188 AutomationList::const_iterator ai;
1190 for (ai = events.const_begin(); ai != events.const_end(); ++ai) {
1192 double translated_y;
1194 translated_y = (*ai)->value;
1195 model_to_view_y (translated_y);
1197 tmp_points.push_back (ALPoint (trackview.editor.frame_to_unit ((*ai)->when),
1198 _height - (translated_y * _height)));
1201 determine_visible_control_points (tmp_points);
1205 AutomationLine::reset ()
1207 update_pending = false;
1213 alist.apply_to_points (*this, &AutomationLine::reset_callback);
1217 AutomationLine::clear ()
1219 /* parent must create command */
1220 trackview.editor.current_session()->add_undo (get_memento());
1222 trackview.editor.current_session()->add_redo_no_execute (get_memento());
1223 trackview.editor.current_session()->commit_reversible_command ();
1224 trackview.editor.current_session()->set_dirty ();
1228 AutomationLine::change_model (AutomationList::iterator i, double x, double y)
1230 alist.modify (i, (jack_nframes_t) x, y);
1234 AutomationLine::change_model_range (AutomationList::iterator start, AutomationList::iterator end, double xdelta, float ydelta)
1236 alist.move_range (start, end, xdelta, ydelta);
1240 AutomationLine::show_all_control_points ()
1242 points_visible = true;
1244 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1246 (*i)->set_visible (true);
1251 AutomationLine::hide_all_but_selected_control_points ()
1253 points_visible = false;
1255 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1256 if (!(*i)->selected) {
1257 (*i)->set_visible (false);