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;
52 using namespace Editing;
53 using namespace Gnome; // for Canvas
55 ControlPoint::ControlPoint (AutomationLine& al)
58 model = al.the_list().end();
67 item = new Canvas::SimpleRect (line.canvas_group());
68 item->property_draw() = true;
69 item->property_fill() = false;
70 item->property_fill_color_rgba() = color_map[cControlPointFill];
71 item->property_outline_color_rgba() = color_map[cControlPointOutline];
72 item->property_outline_pixels() = 1;
73 item->set_data ("control_point", this);
74 item->signal_event().connect (mem_fun (this, &ControlPoint::event_handler));
80 ControlPoint::ControlPoint (const ControlPoint& other, bool dummy_arg_to_force_special_copy_constructor)
88 view_index = other.view_index;
89 can_slide = other.can_slide;
92 _shape = other._shape;
96 item = new Canvas::SimpleRect (line.canvas_group());
97 item->property_fill() = false;
98 item->property_outline_color_rgba() = color_map[cEnteredControlPointOutline];
99 item->property_outline_pixels() = 1;
101 /* NOTE: no event handling in copied ControlPoints */
107 ControlPoint::~ControlPoint ()
113 ControlPoint::event_handler (GdkEvent* event)
115 return PublicEditor::instance().canvas_control_point_event (event, item, this);
119 ControlPoint::hide ()
131 ControlPoint::set_visible (bool yn)
133 item->property_draw() = (gboolean) yn;
137 ControlPoint::reset (double x, double y, AutomationList::iterator mi, uint32_t vi, ShapeType shape)
141 move_to (x, y, shape);
145 ControlPoint::show_color (bool entered, bool hide_too)
149 item->property_outline_color_rgba() = color_map[cEnteredControlPointSelected];
152 item->property_outline_color_rgba() = color_map[cEnteredControlPoint];
160 item->property_outline_color_rgba() = color_map[cControlPointSelected];
163 item->property_outline_color_rgba() = color_map[cControlPoint];
172 ControlPoint::set_size (double sz)
178 item->property_fill() = (gboolean) TRUE;
180 item->property_fill() = (gboolean) FALSE;
184 move_to (_x, _y, _shape);
188 ControlPoint::move_to (double x, double y, ShapeType shape)
192 double half_size = rint(_size/2.0);
209 item->property_x1() = x1;
210 item->property_x2() = x2;
211 item->property_y1() = y - half_size;
212 item->property_y2() = y + half_size;
221 AutomationLine::AutomationLine (const string & name, TimeAxisView& tv, ArdourCanvas::Group& parent, AutomationList& al)
225 _parent_group (parent)
227 points_visible = false;
228 update_pending = false;
229 _vc_uses_gain_mapping = false;
232 terminal_points_can_slide = true;
235 group = new ArdourCanvas::Group (parent);
236 group->property_x() = 0.0;
237 group->property_y() = 0.0;
239 line = new ArdourCanvas::Line (*group);
240 line->property_width_pixels() = (guint)1;
241 line->set_data ("line", this);
243 line->signal_event().connect (mem_fun (*this, &AutomationLine::event_handler));
245 alist.StateChanged.connect (mem_fun(*this, &AutomationLine::list_changed));
248 AutomationLine::~AutomationLine ()
250 vector_delete (&control_points);
255 AutomationLine::event_handler (GdkEvent* event)
257 return PublicEditor::instance().canvas_line_event (event, line, this);
261 AutomationLine::queue_reset ()
263 if (!update_pending) {
264 update_pending = true;
265 Gtkmm2ext::UI::instance()->call_slot (mem_fun(*this, &AutomationLine::reset));
270 AutomationLine::set_point_size (double sz)
272 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
278 AutomationLine::show ()
282 if (points_visible) {
283 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
292 AutomationLine::hide ()
295 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
302 AutomationLine::set_height (guint32 h)
307 if (_height > (guint32) TimeAxisView::Larger) {
308 set_point_size (8.0);
309 } else if (_height > (guint32) TimeAxisView::Normal) {
310 set_point_size (6.0);
312 set_point_size (4.0);
320 AutomationLine::set_line_color (uint32_t color)
323 line->property_fill_color_rgba() = color;
327 AutomationLine::set_verbose_cursor_uses_gain_mapping (bool yn)
329 if (yn != _vc_uses_gain_mapping) {
330 _vc_uses_gain_mapping = yn;
336 AutomationLine::nth (uint32_t n)
338 if (n < control_points.size()) {
339 return control_points[n];
346 AutomationLine::modify_view (ControlPoint& cp, double x, double y, bool with_push)
348 modify_view_point (cp, x, y, with_push);
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 line_points[cp.view_index].set_x (cp.get_x());
470 line_points[cp.view_index].set_y (cp.get_y());
474 AutomationLine::update_line ()
476 line->property_points() = line_points;
480 AutomationLine::sync_model_with_view_line (uint32_t start, uint32_t end)
485 update_pending = true;
487 for (uint32_t i = start; i <= end; ++i) {
489 sync_model_with_view_point(*p);
496 AutomationLine::model_representation (ControlPoint& cp, ModelRepresentation& mr)
498 /* part one: find out where the visual control point is.
499 initial results are in canvas units. ask the
500 line to convert them to something relevant.
503 mr.xval = (jack_nframes_t) floor (cp.get_x());
504 mr.yval = 1.0 - (cp.get_y() / _height);
507 /* if xval has not changed, set it directly from the model to avoid rounding errors */
509 if (mr.xval == trackview.editor.frame_to_unit((*cp.model)->when)) {
510 mr.xval = (jack_nframes_t) (*cp.model)->when;
512 mr.xval = trackview.editor.unit_to_frame (mr.xval);
516 /* virtual call: this will do the right thing
517 for whatever particular type of line we are.
520 view_to_model_y (mr.yval);
522 /* part 2: find out where the model point is now
525 mr.xpos = (jack_nframes_t) (*cp.model)->when;
526 mr.ypos = (*cp.model)->value;
528 /* part 3: get the position of the visual control
529 points before and after us.
532 ControlPoint* before;
536 before = nth (cp.view_index - 1);
541 after = nth (cp.view_index + 1);
544 mr.xmin = (jack_nframes_t) (*before->model)->when;
545 mr.ymin = (*before->model)->value;
546 mr.start = before->model;
555 mr.end = after->model;
565 AutomationLine::sync_model_from (ControlPoint& cp)
570 sync_model_with_view_point (cp);
572 /* we might have moved all points after `cp' by some amount
573 if we pressed the with_push modifyer some of the time during the drag
574 so all subsequent points have to be resynced
577 lasti = control_points.size() - 1;
580 update_pending = true;
582 while (p != &cp && lasti) {
583 sync_model_with_view_point (*p);
590 AutomationLine::sync_model_with_view_point (ControlPoint& cp)
592 ModelRepresentation mr;
595 model_representation (cp, mr);
597 /* part 4: how much are we changing the central point by */
599 ydelta = mr.yval - mr.ypos;
601 /* IMPORTANT: changing the model when the x-coordinate changes
602 may invalidate the iterators that we are using. this means that we have
603 to change the points before+after the one corresponding to the visual CP
604 first (their x-coordinate doesn't change). then we change the
607 apply the full change to the central point, and interpolate
608 in each direction to cover all model points represented
609 by the control point.
612 /* part 5: change all points before the primary point */
614 for (AutomationList::iterator i = mr.start; i != cp.model; ++i) {
618 delta = ydelta * ((*i)->when - mr.xmin) / (mr.xpos - mr.xmin);
620 /* x-coordinate (generally time) stays where it is,
621 y-coordinate moves by a certain amount.
624 update_pending = true;
625 change_model (i, (*i)->when, mr.yval + delta);
628 /* part 6: change later points */
630 AutomationList::iterator i = cp.model;
634 while (i != mr.end) {
638 delta = ydelta * (mr.xmax - (*i)->when) / (mr.xmax - mr.xpos);
640 /* x-coordinate (generally time) stays where it is,
641 y-coordinate moves by a certain amount.
644 update_pending = true;
645 change_model (i, (*i)->when, (*i)->value + delta);
650 /* part 7: change the primary point */
652 update_pending = true;
653 change_model (cp.model, mr.xval, mr.yval);
657 AutomationLine::determine_visible_control_points (ALPoints& points)
659 uint32_t view_index, pi, n;
660 AutomationList::iterator model;
662 double last_control_point_x = 0.0;
663 double last_control_point_y = 0.0;
664 uint32_t this_rx = 0;
665 uint32_t prev_rx = 0;
666 uint32_t this_ry = 0;
667 uint32_t prev_ry = 0;
670 /* hide all existing points, and the line */
672 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
678 if (points.empty()) {
682 npoints = points.size();
684 /* compute derivative/slope for the entire line */
686 slope = new double[npoints];
688 for (n = 0; n < npoints - 1; ++n) {
689 double xdelta = points[n+1].x - points[n].x;
690 double ydelta = points[n+1].y - points[n].y;
691 slope[n] = ydelta/xdelta;
694 /* read all points and decide which ones to show as control points */
698 for (model = alist.begin(), pi = 0; pi < npoints; ++model, ++pi) {
700 double tx = points[pi].x;
701 double ty = points[pi].y;
703 /* now ensure that the control_points vector reflects the current curve
704 state, but don't plot control points too close together. also, don't
705 plot a series of points all with the same value.
707 always plot the first and last points, of course.
710 if (invalid_point (points, pi)) {
711 /* for some reason, we are supposed to ignore this point,
712 but still keep track of the model index.
717 if (pi > 0 && pi < npoints - 1) {
718 if (slope[pi] == slope[pi-1]) {
720 /* no reason to display this point */
726 /* need to round here. the ultimate coordinates are integer
727 pixels, so tiny deltas in the coords will be eliminated
728 and we end up with "colinear" line segments. since the
729 line rendering code in libart doesn't like this very
730 much, we eliminate them here. don't do this for the first and last
734 this_rx = (uint32_t) rint (tx);
735 this_ry = (unsigned long) rint (ty);
737 if (view_index && pi != npoints && (this_rx == prev_rx) && (this_ry == prev_ry)) {
742 /* ok, we should display this point */
744 if (view_index >= control_points.size()) {
745 /* make sure we have enough control points */
747 ControlPoint* ncp = new ControlPoint (*this);
749 if (_height > (guint32) TimeAxisView::Larger) {
751 } else if (_height > (guint32) TimeAxisView::Normal) {
757 control_points.push_back (ncp);
760 ControlPoint::ShapeType shape;
762 if (!terminal_points_can_slide) {
764 control_points[view_index]->can_slide = false;
766 shape = ControlPoint::Start;
768 shape = ControlPoint::Full;
770 } else if (pi == npoints - 1) {
771 control_points[view_index]->can_slide = false;
772 shape = ControlPoint::End;
774 control_points[view_index]->can_slide = true;
775 shape = ControlPoint::Full;
778 control_points[view_index]->can_slide = true;
779 shape = ControlPoint::Full;
782 last_control_point_x = tx;
783 last_control_point_y = ty;
785 control_points[view_index]->reset (tx, ty, model, view_index, shape);
790 /* finally, control visibility */
792 if (_visible && points_visible) {
793 control_points[view_index]->show ();
794 control_points[view_index]->set_visible (true);
796 if (!points_visible) {
797 control_points[view_index]->set_visible (false);
804 /* discard extra CP's to avoid confusing ourselves */
806 while (control_points.size() > view_index) {
807 ControlPoint* cp = control_points.back();
808 control_points.pop_back ();
812 if (!terminal_points_can_slide) {
813 control_points.back()->can_slide = false;
818 if (view_index > 1) {
820 npoints = view_index;
822 /* reset the line coordinates */
824 while (line_points.size() < npoints) {
825 line_points.push_back (Art::Point (0,0));
828 for (view_index = 0; view_index < npoints; ++view_index) {
829 line_points[view_index].set_x (control_points[view_index]->get_x());
830 line_points[view_index].set_y (control_points[view_index]->get_y());
833 line->property_points() = line_points;
840 set_selected_points (trackview.editor.get_selection().points);
844 AutomationLine::get_verbose_cursor_string (float fraction)
848 if (_vc_uses_gain_mapping) {
849 if (fraction == 0.0) {
850 snprintf (buf, sizeof (buf), "-inf dB");
852 snprintf (buf, sizeof (buf), "%.1fdB", coefficient_to_dB (slider_position_to_gain (fraction)));
855 snprintf (buf, sizeof (buf), "%.2f", fraction);
862 AutomationLine::invalid_point (ALPoints& p, uint32_t index)
864 return p[index].x == max_frames && p[index].y == DBL_MAX;
868 AutomationLine::invalidate_point (ALPoints& p, uint32_t index)
870 p[index].x = max_frames;
871 p[index].y = DBL_MAX;
875 AutomationLine::start_drag (ControlPoint* cp, float fraction)
877 if (trackview.editor.current_session() == 0) { /* how? */
884 str = _("automation event move");
886 str = _("automation range drag");
889 trackview.editor.current_session()->begin_reversible_command (str);
890 trackview.editor.current_session()->add_command (MementoUndoCommand<AutomationLine>(*this, get_state()));
892 first_drag_fraction = fraction;
893 last_drag_fraction = fraction;
898 AutomationLine::point_drag (ControlPoint& cp, jack_nframes_t x, float fraction, bool with_push)
900 modify_view (cp, x, fraction, with_push);
905 AutomationLine::line_drag (uint32_t i1, uint32_t i2, float fraction, bool with_push)
907 double ydelta = fraction - last_drag_fraction;
909 last_drag_fraction = fraction;
916 for (uint32_t i = i1 ; i <= i2; i++) {
918 modify_view_point (*cp, trackview.editor.unit_to_frame (cp->get_x()), ((_height - cp->get_y()) /_height) + ydelta, with_push);
927 AutomationLine::end_drag (ControlPoint* cp)
932 sync_model_from (*cp);
934 sync_model_with_view_line (line_drag_cp1, line_drag_cp2);
937 update_pending = false;
939 trackview.editor.current_session()->add_command (MementoRedoCommand<AutomationLine>(*this, get_state()));
940 trackview.editor.current_session()->commit_reversible_command ();
941 trackview.editor.current_session()->set_dirty ();
946 AutomationLine::control_points_adjacent (double xval, uint32_t & before, uint32_t& after)
948 ControlPoint *bcp = 0;
949 ControlPoint *acp = 0;
952 /* xval is in frames */
954 unit_xval = trackview.editor.frame_to_unit (xval);
956 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
958 if ((*i)->get_x() <= unit_xval) {
960 if (!bcp || (*i)->get_x() > bcp->get_x()) {
962 before = bcp->view_index;
965 } else if ((*i)->get_x() > unit_xval) {
967 after = acp->view_index;
976 AutomationLine::is_last_point (ControlPoint& cp)
978 ModelRepresentation mr;
980 model_representation (cp, mr);
982 // If the list is not empty, and the point is the last point in the list
984 if (!alist.empty() && mr.end == alist.end()) {
992 AutomationLine::is_first_point (ControlPoint& cp)
994 ModelRepresentation mr;
996 model_representation (cp, mr);
998 // If the list is not empty, and the point is the first point in the list
1000 if (!alist.empty() && mr.start == alist.begin()) {
1007 // This is copied into AudioRegionGainLine
1009 AutomationLine::remove_point (ControlPoint& cp)
1011 ModelRepresentation mr;
1013 model_representation (cp, mr);
1015 trackview.editor.current_session()->begin_reversible_command (_("remove control point"));
1016 XMLNode &before = get_state();
1018 alist.erase (mr.start, mr.end);
1020 trackview.editor.current_session()->add_command(MementoCommand<AutomationLine>(*this, before, get_state()));
1021 trackview.editor.current_session()->commit_reversible_command ();
1022 trackview.editor.current_session()->set_dirty ();
1026 AutomationLine::get_selectables (jack_nframes_t& start, jack_nframes_t& end,
1027 double botfrac, double topfrac, list<Selectable*>& results)
1032 jack_nframes_t nstart;
1033 jack_nframes_t nend;
1034 bool collecting = false;
1036 /* Curse X11 and its inverted coordinate system! */
1038 bot = (1.0 - topfrac) * _height;
1039 top = (1.0 - botfrac) * _height;
1041 nstart = max_frames;
1044 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1046 jack_nframes_t when = (jack_nframes_t) (*(*i)->model)->when;
1048 if (when >= start && when <= end) {
1050 if ((*i)->get_y() >= bot && (*i)->get_y() <= top) {
1053 (*i)->set_visible(true);
1055 nstart = min (nstart, when);
1056 nend = max (nend, when);
1062 results.push_back (new AutomationSelectable (nstart, nend, botfrac, topfrac, trackview));
1064 nstart = max_frames;
1072 results.push_back (new AutomationSelectable (nstart, nend, botfrac, topfrac, trackview));
1078 AutomationLine::get_inverted_selectables (Selection&, list<Selectable*>& results)
1084 AutomationLine::set_selected_points (PointSelection& points)
1089 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1090 (*i)->selected = false;
1093 if (points.empty()) {
1097 for (PointSelection::iterator r = points.begin(); r != points.end(); ++r) {
1099 if (&(*r).track != &trackview) {
1103 /* Curse X11 and its inverted coordinate system! */
1105 bot = (1.0 - (*r).high_fract) * _height;
1106 top = (1.0 - (*r).low_fract) * _height;
1108 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1110 double rstart, rend;
1112 rstart = trackview.editor.frame_to_unit ((*r).start);
1113 rend = trackview.editor.frame_to_unit ((*r).end);
1115 if ((*i)->get_x() >= rstart && (*i)->get_x() <= rend) {
1117 if ((*i)->get_y() >= bot && (*i)->get_y() <= top) {
1119 (*i)->selected = true;
1127 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1128 (*i)->show_color (false, !points_visible);
1134 AutomationLine::show_selection ()
1136 TimeSelection& time (trackview.editor.get_selection().time);
1138 // cerr << "show selection\n";
1140 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1142 (*i)->selected = false;
1144 for (list<AudioRange>::iterator r = time.begin(); r != time.end(); ++r) {
1145 double rstart, rend;
1147 rstart = trackview.editor.frame_to_unit ((*r).start);
1148 rend = trackview.editor.frame_to_unit ((*r).end);
1150 if ((*i)->get_x() >= rstart && (*i)->get_x() <= rend) {
1151 (*i)->selected = true;
1156 (*i)->show_color (false, !points_visible);
1161 AutomationLine::hide_selection ()
1163 // cerr << "hide selection\n";
1164 // show_selection ();
1168 // This is copied into AudioRegionGainLine
1170 AutomationLine::get_memento ()
1172 return alist.get_memento();
1176 AutomationLine::list_changed (Change ignored)
1182 AutomationLine::reset_callback (const AutomationList& events)
1184 ALPoints tmp_points;
1185 uint32_t npoints = events.size();
1188 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1191 control_points.clear ();
1196 AutomationList::const_iterator ai;
1198 for (ai = events.const_begin(); ai != events.const_end(); ++ai) {
1200 double translated_y;
1202 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 (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)
1238 alist.modify (i, (jack_nframes_t) x, y);
1242 AutomationLine::change_model_range (AutomationList::iterator start, AutomationList::iterator end, double xdelta, float ydelta)
1244 alist.move_range (start, end, xdelta, ydelta);
1248 AutomationLine::show_all_control_points ()
1250 points_visible = true;
1252 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1254 (*i)->set_visible (true);
1259 AutomationLine::hide_all_but_selected_control_points ()
1261 points_visible = false;
1263 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1264 if (!(*i)->selected) {
1265 (*i)->set_visible (false);