the missing flush call for plugins
[ardour2.git] / gtk2_ardour / automation_line.cc
blob94273626c35534fc9ccf1477797828b01427cdd9
1 /*
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.
20 #include <cmath>
21 #include <climits>
22 #include <vector>
23 #include <fstream>
25 #include <pbd/stl_delete.h>
26 #include <pbd/memento_command.h>
27 #include <pbd/stacktrace.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"
38 #include "utils.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>
48 #include "i18n.h"
50 using namespace std;
51 using namespace sigc;
52 using namespace ARDOUR;
53 using namespace PBD;
54 using namespace Editing;
55 using namespace Gnome; // for Canvas
57 ControlPoint::ControlPoint (AutomationLine& al)
58 : line (al)
60 model = al.the_list().end();
61 view_index = 0;
62 can_slide = true;
63 _x = 0;
64 _y = 0;
65 _shape = Full;
66 _size = 4.0;
67 selected = false;
69 item = new Canvas::SimpleRect (line.canvas_group());
70 item->property_draw() = true;
71 item->property_fill() = false;
72 item->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_ControlPointFill.get();
73 item->property_outline_color_rgba() = ARDOUR_UI::config()->canvasvar_ControlPointOutline.get();
74 item->property_outline_pixels() = 1;
75 item->set_data ("control_point", this);
76 item->signal_event().connect (mem_fun (this, &ControlPoint::event_handler));
78 hide ();
79 set_visible (false);
82 ControlPoint::ControlPoint (const ControlPoint& other, bool dummy_arg_to_force_special_copy_constructor)
83 : line (other.line)
85 if (&other == this) {
86 return;
89 model = other.model;
90 view_index = other.view_index;
91 can_slide = other.can_slide;
92 _x = other._x;
93 _y = other._y;
94 _shape = other._shape;
95 _size = other._size;
96 selected = false;
98 item = new Canvas::SimpleRect (line.canvas_group());
99 item->property_fill() = false;
100 item->property_outline_color_rgba() = ARDOUR_UI::config()->canvasvar_EnteredControlPointOutline.get();
101 item->property_outline_pixels() = 1;
103 /* NOTE: no event handling in copied ControlPoints */
105 hide ();
106 set_visible (false);
109 ControlPoint::~ControlPoint ()
111 delete item;
114 bool
115 ControlPoint::event_handler (GdkEvent* event)
117 return PublicEditor::instance().canvas_control_point_event (event, item, this);
120 void
121 ControlPoint::hide ()
123 item->hide();
126 void
127 ControlPoint::show()
129 item->show();
132 void
133 ControlPoint::set_visible (bool yn)
135 item->property_draw() = (gboolean) yn;
138 void
139 ControlPoint::reset (double x, double y, AutomationList::iterator mi, uint32_t vi, ShapeType shape)
141 model = mi;
142 view_index = vi;
143 move_to (x, y, shape);
146 void
147 ControlPoint::show_color (bool entered, bool hide_too)
149 if (entered) {
150 if (selected) {
151 item->property_outline_color_rgba() = ARDOUR_UI::config()->canvasvar_EnteredControlPointSelected.get();
152 set_visible(true);
153 } else {
154 item->property_outline_color_rgba() = ARDOUR_UI::config()->canvasvar_EnteredControlPoint.get();
155 if (hide_too) {
156 set_visible(false);
160 } else {
161 if (selected) {
162 item->property_outline_color_rgba() = ARDOUR_UI::config()->canvasvar_ControlPointSelected.get();
163 set_visible(true);
164 } else {
165 item->property_outline_color_rgba() = ARDOUR_UI::config()->canvasvar_ControlPoint.get();
166 if (hide_too) {
167 set_visible(false);
173 void
174 ControlPoint::set_size (double sz)
176 _size = sz;
178 #if 0
179 if (_size > 6.0) {
180 item->property_fill() = (gboolean) TRUE;
181 } else {
182 item->property_fill() = (gboolean) FALSE;
184 #endif
186 move_to (_x, _y, _shape);
189 void
190 ControlPoint::move_to (double x, double y, ShapeType shape)
192 double x1 = 0;
193 double x2 = 0;
194 double half_size = rint(_size/2.0);
196 switch (shape) {
197 case Full:
198 x1 = x - half_size;
199 x2 = x + half_size;
200 break;
201 case Start:
202 x1 = x;
203 x2 = x + half_size;
204 break;
205 case End:
206 x1 = x - half_size;
207 x2 = x;
208 break;
211 item->property_x1() = x1;
212 item->property_x2() = x2;
213 item->property_y1() = y - half_size;
214 item->property_y2() = y + half_size;
216 _x = x;
217 _y = y;
218 _shape = shape;
221 /*****/
223 AutomationLine::AutomationLine (const string & name, TimeAxisView& tv, ArdourCanvas::Group& parent, AutomationList& al)
224 : trackview (tv),
225 _name (name),
226 alist (al),
227 _parent_group (parent)
229 points_visible = false;
230 update_pending = false;
231 _vc_uses_gain_mapping = false;
232 auto_is_boolean = false;
233 _visible = true;
234 terminal_points_can_slide = true;
235 _height = 0;
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);
256 delete group;
259 bool
260 AutomationLine::event_handler (GdkEvent* event)
262 return PublicEditor::instance().canvas_line_event (event, line, this);
265 void
266 AutomationLine::queue_reset ()
268 if (!update_pending) {
269 update_pending = true;
270 Gtkmm2ext::UI::instance()->call_slot (mem_fun(*this, &AutomationLine::reset));
274 void
275 AutomationLine::show ()
277 line->show();
279 if (points_visible) {
280 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
281 (*i)->show ();
285 _visible = true;
288 void
289 AutomationLine::hide ()
291 line->hide();
292 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
293 (*i)->hide();
295 _visible = false;
298 double
299 AutomationLine::control_point_box_size ()
301 if (_height > TimeAxisView::hLarger) {
302 return 8.0;
303 } else if (_height > (guint32) TimeAxisView::hNormal) {
304 return 6.0;
306 return 4.0;
309 void
310 AutomationLine::set_height (guint32 h)
312 if (h != _height) {
313 _height = 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);
321 reset ();
325 void
326 AutomationLine::set_line_color (uint32_t color)
328 _line_color = color;
329 line->property_fill_color_rgba() = color;
332 void
333 AutomationLine::set_verbose_cursor_uses_gain_mapping (bool yn)
335 if (yn != _vc_uses_gain_mapping) {
336 _vc_uses_gain_mapping = yn;
337 reset ();
341 ControlPoint*
342 AutomationLine::nth (uint32_t n)
344 if (n < control_points.size()) {
345 return control_points[n];
346 } else {
347 return 0;
351 void
352 AutomationLine::modify_view_point (ControlPoint& cp, double x, double y, bool with_push)
354 double delta = 0.0;
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.
366 y = max (0.0, y);
367 y = min (1.0, y);
368 y = _height - (y * _height);
370 if (cp.can_slide) {
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;
381 ControlPoint *after;
383 if (cp.view_index) {
384 before = nth (cp.view_index - 1);
385 x = max (x, before->get_x()+1.0);
386 } else {
387 before = &cp;
391 if (!with_push) {
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) {
399 x = cp.get_x();
401 } else {
402 x = min (x, after->get_x()-1.0);
404 } else {
405 after = &cp;
408 } else {
410 ControlPoint* after;
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;
418 break;
422 delta = x - cp.get_x();
425 } else {
427 /* leave the x-coordinate alone */
429 x = trackview.editor.frame_to_unit ((*cp.model)->when);
433 if (!with_push) {
435 cp.move_to (x, y, ControlPoint::Full);
436 reset_line_coords (cp);
438 } else {
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
443 to x_limit.
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);
454 double new_x;
456 if (p->can_slide) {
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);
465 void
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());
474 void
475 AutomationLine::sync_model_with_view_line (uint32_t start, uint32_t end)
478 ControlPoint *p;
480 update_pending = true;
482 for (uint32_t i = start; i <= end; ++i) {
483 p = nth(i);
484 sync_model_with_view_point (*p, false, 0);
488 void
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;
503 } else {
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;
524 ControlPoint* after;
526 if (cp.view_index) {
527 before = nth (cp.view_index - 1);
528 } else {
529 before = 0;
532 after = nth (cp.view_index + 1);
534 if (before) {
535 mr.xmin = (nframes_t) (*before->model)->when;
536 mr.ymin = (*before->model)->value;
537 mr.start = before->model;
538 ++mr.start;
539 } else {
540 mr.xmin = mr.xpos;
541 mr.ymin = mr.ypos;
542 mr.start = cp.model;
545 if (after) {
546 mr.end = after->model;
547 } else {
548 mr.xmax = mr.xpos;
549 mr.ymax = mr.ypos;
550 mr.end = cp.model;
551 ++mr.end;
555 void
556 AutomationLine::determine_visible_control_points (ALPoints& points)
558 uint32_t view_index, pi, n;
559 AutomationList::iterator model;
560 uint32_t npoints;
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;
567 double* slope;
568 uint32_t box_size;
569 uint32_t cpsize;
571 /* hide all existing points, and the line */
573 cpsize = 0;
575 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
576 (*i)->hide();
577 ++cpsize;
580 line->hide ();
582 if (points.empty()) {
583 return;
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 */
602 view_index = 0;
604 for (model = alist.begin(), pi = 0; pi < npoints; ++model, ++pi) {
606 double tx = points[pi].x;
607 double ty = points[pi].y;
609 if (isnan (tx) || isnan (ty)) {
610 warning << string_compose (_("Ignoring illegal points on AutomationLine \"%1\""),
611 _name) << endmsg;
612 continue;
615 /* now ensure that the control_points vector reflects the current curve
616 state, but don't plot control points too close together. also, don't
617 plot a series of points all with the same value.
619 always plot the first and last points, of course.
622 if (invalid_point (points, pi)) {
623 /* for some reason, we are supposed to ignore this point,
624 but still keep track of the model index.
626 continue;
629 if (pi > 0 && pi < npoints - 1) {
630 if (slope[pi] == slope[pi-1]) {
632 /* no reason to display this point */
634 continue;
638 /* need to round here. the ultimate coordinates are integer
639 pixels, so tiny deltas in the coords will be eliminated
640 and we end up with "colinear" line segments. since the
641 line rendering code in libart doesn't like this very
642 much, we eliminate them here. don't do this for the first and last
643 points.
646 this_rx = (uint32_t) rint (tx);
647 this_ry = (uint32_t) rint (ty);
649 if (view_index && pi != npoints && /* not the first, not the last */
650 (((this_rx == prev_rx) && (this_ry == prev_ry)) || /* same point */
651 (((this_rx - prev_rx) < (box_size + 2)) && /* not identical, but still too close horizontally */
652 (abs ((int)(this_ry - prev_ry)) < (int) (box_size + 2))))) { /* too close vertically */
653 continue;
656 /* ok, we should display this point */
658 if (view_index >= cpsize) {
660 /* make sure we have enough control points */
662 ControlPoint* ncp = new ControlPoint (*this);
664 ncp->set_size (box_size);
666 control_points.push_back (ncp);
667 ++cpsize;
670 ControlPoint::ShapeType shape;
672 if (!terminal_points_can_slide) {
673 if (pi == 0) {
674 control_points[view_index]->can_slide = false;
675 if (tx == 0) {
676 shape = ControlPoint::Start;
677 } else {
678 shape = ControlPoint::Full;
680 } else if (pi == npoints - 1) {
681 control_points[view_index]->can_slide = false;
682 shape = ControlPoint::End;
683 } else {
684 control_points[view_index]->can_slide = true;
685 shape = ControlPoint::Full;
687 } else {
688 control_points[view_index]->can_slide = true;
689 shape = ControlPoint::Full;
692 last_control_point_x = tx;
693 last_control_point_y = ty;
695 control_points[view_index]->reset (tx, ty, model, view_index, shape);
697 prev_rx = this_rx;
698 prev_ry = this_ry;
700 /* finally, control visibility */
702 if (_visible && points_visible) {
703 control_points[view_index]->show ();
704 control_points[view_index]->set_visible (true);
705 } else {
706 if (!points_visible) {
707 control_points[view_index]->set_visible (false);
711 view_index++;
714 /* discard extra CP's to avoid confusing ourselves */
716 while (control_points.size() > view_index) {
717 ControlPoint* cp = control_points.back();
718 control_points.pop_back ();
719 delete cp;
722 if (!terminal_points_can_slide) {
723 control_points.back()->can_slide = false;
726 delete [] slope;
728 if (view_index > 1) {
730 npoints = view_index;
732 /* reset the line coordinates */
734 while (line_points.size() < npoints) {
735 line_points.push_back (Art::Point (0,0));
738 while (line_points.size() > npoints) {
739 line_points.pop_back ();
742 for (view_index = 0; view_index < npoints; ++view_index) {
743 line_points[view_index].set_x (control_points[view_index]->get_x());
744 line_points[view_index].set_y (control_points[view_index]->get_y());
747 line->property_points() = line_points;
749 if (_visible) {
750 line->show ();
755 set_selected_points (trackview.editor.get_selection().points);
759 string
760 AutomationLine::get_verbose_cursor_string (float fraction)
762 char buf[32];
764 if (_vc_uses_gain_mapping) {
765 if (fraction == 0.0) {
766 snprintf (buf, sizeof (buf), "-inf dB");
767 } else {
768 snprintf (buf, sizeof (buf), "%.1fdB", accurate_coefficient_to_dB (slider_position_to_gain (fraction)));
770 } else {
771 snprintf (buf, sizeof (buf), "%.2f", fraction);
774 return buf;
777 bool
778 AutomationLine::invalid_point (ALPoints& p, uint32_t index)
780 return p[index].x == max_frames && p[index].y == DBL_MAX;
783 void
784 AutomationLine::invalidate_point (ALPoints& p, uint32_t index)
786 p[index].x = max_frames;
787 p[index].y = DBL_MAX;
790 void
791 AutomationLine::start_drag (ControlPoint* cp, nframes_t x, float fraction)
793 if (trackview.editor.current_session() == 0) { /* how? */
794 return;
797 string str;
799 if (cp) {
800 str = _("automation event move");
801 } else {
802 str = _("automation range drag");
805 trackview.editor.current_session()->begin_reversible_command (str);
806 trackview.editor.current_session()->add_command (new MementoCommand<AutomationList>(alist, &get_state(), 0));
808 drag_x = x;
809 drag_distance = 0;
810 first_drag_fraction = fraction;
811 last_drag_fraction = fraction;
812 drags = 0;
813 did_push = false;
816 void
817 AutomationLine::point_drag (ControlPoint& cp, nframes_t x, float fraction, bool with_push)
819 if (x > drag_x) {
820 drag_distance += (x - drag_x);
821 } else {
822 drag_distance -= (drag_x - x);
825 drag_x = x;
827 modify_view_point (cp, x, fraction, with_push);
829 if (line_points.size() > 1) {
830 line->property_points() = line_points;
833 drags++;
834 did_push = with_push;
837 void
838 AutomationLine::line_drag (uint32_t i1, uint32_t i2, float fraction, bool with_push)
840 double ydelta = fraction - last_drag_fraction;
842 did_push = with_push;
844 last_drag_fraction = fraction;
846 line_drag_cp1 = i1;
847 line_drag_cp2 = i2;
849 //check if one of the control points on the line is in a selected range
850 bool range_found = false;
851 ControlPoint *cp;
852 for (uint32_t i = i1 ; i <= i2; i++) {
853 cp = nth (i);
854 if ( cp->selected )
855 range_found = true;
858 if (range_found) {
859 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
860 if ( (*i)->selected ) {
861 modify_view_point (*(*i), trackview.editor.unit_to_frame ((*i)->get_x()), ((_height - (*i)->get_y()) /_height) + ydelta, with_push);
864 } else {
865 ControlPoint *cp;
866 for (uint32_t i = i1 ; i <= i2; i++) {
867 cp = nth (i);
868 modify_view_point (*cp, trackview.editor.unit_to_frame (cp->get_x()), ((_height - cp->get_y()) /_height) + ydelta, with_push);
872 if (line_points.size() > 1) {
873 line->property_points() = line_points;
876 drags++;
879 void
880 AutomationLine::end_drag (ControlPoint* cp)
882 if (!drags) {
883 return;
886 alist.freeze ();
888 if (cp) {
889 sync_model_with_view_point (*cp, did_push, drag_distance);
890 } else {
891 sync_model_with_view_line (line_drag_cp1, line_drag_cp2);
894 alist.thaw ();
896 update_pending = false;
898 trackview.editor.current_session()->add_command (new MementoCommand<AutomationList>(alist, 0, &alist.get_state()));
899 trackview.editor.current_session()->commit_reversible_command ();
900 trackview.editor.current_session()->set_dirty ();
904 void
905 AutomationLine::sync_model_with_view_point (ControlPoint& cp, bool did_push, int64_t distance)
907 ModelRepresentation mr;
908 double ydelta;
910 model_representation (cp, mr);
912 /* how much are we changing the central point by */
914 ydelta = mr.yval - mr.ypos;
917 apply the full change to the central point, and interpolate
918 on both axes to cover all model points represented
919 by the control point.
922 /* change all points before the primary point */
924 for (AutomationList::iterator i = mr.start; i != cp.model; ++i) {
926 double fract = ((*i)->when - mr.xmin) / (mr.xpos - mr.xmin);
927 double y_delta = ydelta * fract;
928 double x_delta = distance * fract;
930 /* interpolate */
932 if (y_delta || x_delta) {
933 alist.modify (i, (*i)->when + x_delta, mr.ymin + y_delta);
937 /* change the primary point */
939 update_pending = true;
940 alist.modify (cp.model, mr.xval, mr.yval);
943 /* change later points */
945 AutomationList::iterator i = cp.model;
947 ++i;
949 while (i != mr.end) {
951 double delta = ydelta * (mr.xmax - (*i)->when) / (mr.xmax - mr.xpos);
953 /* all later points move by the same distance along the x-axis as the main point */
955 if (delta) {
956 alist.modify (i, (*i)->when + distance, (*i)->value + delta);
959 ++i;
962 if (did_push) {
964 /* move all points after the range represented by the view by the same distance
965 as the main point moved.
968 alist.slide (mr.end, drag_distance);
973 bool
974 AutomationLine::control_points_adjacent (double xval, uint32_t & before, uint32_t& after)
976 ControlPoint *bcp = 0;
977 ControlPoint *acp = 0;
978 double unit_xval;
980 /* xval is in frames */
982 unit_xval = trackview.editor.frame_to_unit (xval);
984 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
986 if ((*i)->get_x() <= unit_xval) {
988 if (!bcp || (*i)->get_x() > bcp->get_x()) {
989 bcp = *i;
990 before = bcp->view_index;
993 } else if ((*i)->get_x() > unit_xval) {
994 acp = *i;
995 after = acp->view_index;
996 break;
1000 return bcp && acp;
1003 bool
1004 AutomationLine::is_last_point (ControlPoint& cp)
1006 ModelRepresentation mr;
1008 model_representation (cp, mr);
1010 // If the list is not empty, and the point is the last point in the list
1012 if (!alist.empty() && mr.end == alist.end()) {
1013 return true;
1016 return false;
1019 bool
1020 AutomationLine::is_first_point (ControlPoint& cp)
1022 ModelRepresentation mr;
1024 model_representation (cp, mr);
1026 // If the list is not empty, and the point is the first point in the list
1028 if (!alist.empty() && mr.start == alist.begin()) {
1029 return true;
1032 return false;
1035 // This is copied into AudioRegionGainLine
1036 void
1037 AutomationLine::remove_point (ControlPoint& cp)
1039 ModelRepresentation mr;
1041 model_representation (cp, mr);
1043 trackview.editor.current_session()->begin_reversible_command (_("remove control point"));
1044 XMLNode &before = alist.get_state();
1046 alist.erase (mr.start, mr.end);
1048 trackview.editor.current_session()->add_command(new MementoCommand<AutomationList>(alist, &before, &alist.get_state()));
1049 trackview.editor.current_session()->commit_reversible_command ();
1050 trackview.editor.current_session()->set_dirty ();
1053 void
1054 AutomationLine::get_selectables (nframes_t& start, nframes_t& end,
1055 double botfrac, double topfrac, list<Selectable*>& results)
1058 double top;
1059 double bot;
1060 nframes_t nstart;
1061 nframes_t nend;
1062 bool collecting = false;
1064 /* Curse X11 and its inverted coordinate system! */
1066 bot = (1.0 - topfrac) * _height;
1067 top = (1.0 - botfrac) * _height;
1069 nstart = max_frames;
1070 nend = 0;
1072 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1074 nframes_t when = (nframes_t) (*(*i)->model)->when;
1076 if (when >= start && when <= end) {
1078 if ((*i)->get_y() >= bot && (*i)->get_y() <= top) {
1080 (*i)->show();
1081 (*i)->set_visible(true);
1082 collecting = true;
1083 nstart = min (nstart, when);
1084 nend = max (nend, when);
1086 } else {
1088 if (collecting) {
1090 results.push_back (new AutomationSelectable (nstart, nend, botfrac, topfrac, trackview));
1091 collecting = false;
1092 nstart = max_frames;
1093 nend = 0;
1099 if (collecting) {
1100 results.push_back (new AutomationSelectable (nstart, nend, botfrac, topfrac, trackview));
1105 void
1106 AutomationLine::get_inverted_selectables (Selection&, list<Selectable*>& results)
1108 // hmmm ....
1111 void
1112 AutomationLine::set_selected_points (PointSelection& points)
1114 double top;
1115 double bot;
1117 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1118 (*i)->selected = false;
1121 if (points.empty()) {
1122 goto out;
1125 for (PointSelection::iterator r = points.begin(); r != points.end(); ++r) {
1127 if (&(*r).track != &trackview) {
1128 continue;
1131 /* Curse X11 and its inverted coordinate system! */
1133 bot = (1.0 - (*r).high_fract) * _height;
1134 top = (1.0 - (*r).low_fract) * _height;
1136 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1138 double rstart, rend;
1140 rstart = trackview.editor.frame_to_unit ((*r).start);
1141 rend = trackview.editor.frame_to_unit ((*r).end);
1143 if ((*i)->get_x() >= rstart && (*i)->get_x() <= rend) {
1145 if ((*i)->get_y() >= bot && (*i)->get_y() <= top) {
1147 (*i)->selected = true;
1154 out:
1155 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1156 (*i)->show_color (false, !points_visible);
1161 void AutomationLine::set_colors() {
1162 set_line_color( ARDOUR_UI::config()->canvasvar_AutomationLine.get() );
1163 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1164 (*i)->show_color (false, !points_visible);
1168 void
1169 AutomationLine::show_selection ()
1171 TimeSelection& time (trackview.editor.get_selection().time);
1173 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1175 (*i)->selected = false;
1177 for (list<AudioRange>::iterator r = time.begin(); r != time.end(); ++r) {
1178 double rstart, rend;
1180 rstart = trackview.editor.frame_to_unit ((*r).start);
1181 rend = trackview.editor.frame_to_unit ((*r).end);
1183 if ((*i)->get_x() >= rstart && (*i)->get_x() <= rend) {
1184 (*i)->selected = true;
1185 break;
1189 (*i)->show_color (false, !points_visible);
1193 void
1194 AutomationLine::hide_selection ()
1196 // show_selection ();
1199 void
1200 AutomationLine::list_changed ()
1202 queue_reset ();
1205 void
1206 AutomationLine::reset_callback (const AutomationList& events)
1208 ALPoints tmp_points;
1209 uint32_t npoints = events.size();
1211 if (npoints == 0) {
1212 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1213 delete *i;
1215 control_points.clear ();
1216 line->hide();
1217 return;
1220 AutomationList::const_iterator ai;
1222 for (ai = events.const_begin(); ai != events.const_end(); ++ai) {
1224 double translated_y = (*ai)->value;
1225 model_to_view_y (translated_y);
1227 add_model_point (tmp_points, (*ai)->when, translated_y);
1230 determine_visible_control_points (tmp_points);
1233 void
1234 AutomationLine::add_model_point (ALPoints& tmp_points, double frame, double yfract)
1236 tmp_points.push_back (ALPoint (trackview.editor.frame_to_unit (frame),
1237 _height - (yfract * _height)));
1240 void
1241 AutomationLine::reset ()
1243 update_pending = false;
1245 alist.apply_to_points (*this, &AutomationLine::reset_callback);
1248 void
1249 AutomationLine::clear ()
1251 /* parent must create command */
1252 XMLNode &before = get_state();
1253 alist.clear();
1254 trackview.editor.current_session()->add_command (new MementoCommand<AutomationLine>(*this, &before, &get_state()));
1255 trackview.editor.current_session()->commit_reversible_command ();
1256 trackview.editor.current_session()->set_dirty ();
1259 void
1260 AutomationLine::change_model (AutomationList::iterator i, double x, double y)
1264 void
1265 AutomationLine::change_model_range (AutomationList::iterator start, AutomationList::iterator end, double xdelta, float ydelta)
1267 alist.move_range (start, end, xdelta, ydelta);
1270 void
1271 AutomationLine::show_all_control_points ()
1273 if (auto_is_boolean) { //show the automation line but don't allow any control points
1274 return;
1277 points_visible = true;
1279 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1280 (*i)->show ();
1281 (*i)->set_visible (true);
1285 void
1286 AutomationLine::hide_all_but_selected_control_points ()
1288 points_visible = false;
1290 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1291 if (!(*i)->selected) {
1292 (*i)->set_visible (false);
1297 XMLNode &
1298 AutomationLine::get_state (void)
1300 /* function as a proxy for the model */
1301 return alist.get_state();
1304 int
1305 AutomationLine::set_state (const XMLNode &node)
1307 /* function as a proxy for the model */
1308 return alist.set_state (node);