Add stacked lanes mode which displays regions on different layers at different positi...
[ardour.git] / gtk2_ardour / automation_line.cc
1 /*
2     Copyright (C) 2002-2003 Paul Davis 
3
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.
8
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.
13
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.
17
18 */
19
20 #include <cmath>
21 #include <climits>
22 #include <vector>
23 #include <fstream>
24
25 #include <pbd/stl_delete.h>
26 #include <pbd/memento_command.h>
27 #include <pbd/stacktrace.h>
28
29 #include <ardour/automation_event.h>
30 #include <ardour/curve.h>
31 #include <ardour/dB.h>
32
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"
45
46 #include <ardour/session.h>
47
48 #include "i18n.h"
49
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
56
57 ControlPoint::ControlPoint (AutomationLine& al)
58         : line (al)
59 {
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;
68
69         item = new Canvas::SimpleRect (line.canvas_group());
70         item->property_draw() = true;
71         item->property_fill() = false;
72         item->property_fill_color_rgba() =  color_map[cControlPointFill];
73         item->property_outline_color_rgba() = color_map[cControlPointOutline];
74         item->property_outline_pixels() = 1;
75         item->set_data ("control_point", this);
76         item->signal_event().connect (mem_fun (this, &ControlPoint::event_handler));
77
78         hide ();
79         set_visible (false);
80 }
81
82 ControlPoint::ControlPoint (const ControlPoint& other, bool dummy_arg_to_force_special_copy_constructor)
83         : line (other.line)
84 {
85         if (&other == this) {
86                 return;
87         }
88
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;
97
98         item = new Canvas::SimpleRect (line.canvas_group());
99         item->property_fill() = false;
100         item->property_outline_color_rgba() = color_map[cEnteredControlPointOutline];
101         item->property_outline_pixels() = 1;
102         
103         /* NOTE: no event handling in copied ControlPoints */
104
105         hide ();
106         set_visible (false);
107 }
108
109 ControlPoint::~ControlPoint ()
110 {
111         delete item;
112 }
113
114 bool
115 ControlPoint::event_handler (GdkEvent* event)
116 {
117         return PublicEditor::instance().canvas_control_point_event (event, item, this);
118 }
119
120 void
121 ControlPoint::hide ()
122 {
123         item->hide();
124 }
125
126 void
127 ControlPoint::show()
128 {
129         item->show();
130 }
131
132 void
133 ControlPoint::set_visible (bool yn)
134 {
135         item->property_draw() = (gboolean) yn;
136 }
137
138 void
139 ControlPoint::reset (double x, double y, AutomationList::iterator mi, uint32_t vi, ShapeType shape)
140 {
141         model = mi;
142         view_index = vi;
143         move_to (x, y, shape);
144 }
145
146 void
147 ControlPoint::show_color (bool entered, bool hide_too)
148 {
149         if (entered) {
150                 if (selected) {
151                         item->property_outline_color_rgba() = color_map[cEnteredControlPointSelected];
152                         set_visible(true);
153                 } else {
154                         item->property_outline_color_rgba() = color_map[cEnteredControlPoint];
155                         if (hide_too) {
156                                 set_visible(false);
157                         }
158                 }
159
160         } else {
161                 if (selected) {
162                         item->property_outline_color_rgba() = color_map[cControlPointSelected];
163                         set_visible(true);
164                 } else {
165                         item->property_outline_color_rgba() = color_map[cControlPoint];
166                         if (hide_too) {
167                                 set_visible(false);
168                         }
169                 }
170         }
171 }
172
173 void
174 ControlPoint::set_size (double sz)
175 {
176         _size = sz;
177
178 #if 0   
179         if (_size > 6.0) {
180                 item->property_fill() = (gboolean) TRUE;
181         } else {
182                 item->property_fill() = (gboolean) FALSE;
183         }
184 #endif
185
186         move_to (_x, _y, _shape);
187 }
188
189 void
190 ControlPoint::move_to (double x, double y, ShapeType shape)
191 {
192         double x1 = 0;
193         double x2 = 0;
194         double half_size = rint(_size/2.0);
195
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;
209         }
210
211         item->property_x1() = x1;
212         item->property_x2() = x2;
213         item->property_y1() = y - half_size;
214         item->property_y2() = y + half_size;
215
216         _x = x;
217         _y = y;
218         _shape = shape;
219 }
220
221 /*****/
222
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)
228 {
229         points_visible = false;
230         update_pending = false;
231         _vc_uses_gain_mapping = false;
232         no_draw = false;
233         _visible = true;
234         terminal_points_can_slide = true;
235         _y_position = 0;
236         _height = 0;
237
238         group = new ArdourCanvas::Group (parent);
239         group->property_x() = 0.0;
240         group->property_y() = 0.0;
241
242         line = new ArdourCanvas::Line (*group);
243         line->property_width_pixels() = (guint)1;
244         line->set_data ("line", this);
245
246         line->signal_event().connect (mem_fun (*this, &AutomationLine::event_handler));
247
248         alist.StateChanged.connect (mem_fun(*this, &AutomationLine::list_changed));
249
250         trackview.session().register_with_memento_command_factory(alist.id(), this);
251
252 }
253
254 AutomationLine::~AutomationLine ()
255 {
256         vector_delete (&control_points);
257         delete group;
258 }
259
260 bool
261 AutomationLine::event_handler (GdkEvent* event)
262 {
263         return PublicEditor::instance().canvas_line_event (event, line, this);
264 }
265
266 void
267 AutomationLine::queue_reset ()
268 {
269         if (!update_pending) {
270                 update_pending = true;
271                 Gtkmm2ext::UI::instance()->call_slot (mem_fun(*this, &AutomationLine::reset));
272         }
273 }
274
275 void
276 AutomationLine::show () 
277 {
278         line->show();
279
280         if (points_visible) {
281                 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
282                         (*i)->show ();
283                 }
284         }
285
286         _visible = true;
287 }
288
289 void
290 AutomationLine::hide () 
291 {
292         line->hide();
293         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
294                 (*i)->hide();
295         }
296         _visible = false;
297 }
298
299 double
300 AutomationLine::control_point_box_size ()
301 {
302         if (_height > TimeAxisView::hLarger) {
303                 return 8.0;
304         } else if (_height > (guint32) TimeAxisView::hNormal) {
305                 return 6.0;
306         } 
307         return 4.0;
308 }
309
310 void
311 AutomationLine::set_y_position_and_height (guint32 y, guint32 h)
312 {
313         bool changed = false;
314         
315         if (y != _y_position) {
316                 _y_position = y;
317                 changed = true;
318         }
319                 
320         if (h != _height) {
321                 _height = h;
322
323                 double const bsz = control_point_box_size();
324
325                 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
326                         (*i)->set_size (bsz);
327                 }
328                 changed = true;
329         }
330
331         if (changed) {
332                 reset ();
333         }
334 }
335
336 void
337 AutomationLine::set_line_color (uint32_t color)
338 {
339         _line_color = color;
340         line->property_fill_color_rgba() = color;
341 }
342
343 void
344 AutomationLine::set_verbose_cursor_uses_gain_mapping (bool yn)
345 {
346         if (yn != _vc_uses_gain_mapping) {
347                 _vc_uses_gain_mapping = yn;
348                 reset ();
349         }
350 }
351
352 ControlPoint*
353 AutomationLine::nth (uint32_t n)
354 {
355         if (n < control_points.size()) {
356                 return control_points[n];
357         } else {
358                 return 0;
359         }
360 }
361
362 void
363 AutomationLine::modify_view_point (ControlPoint& cp, double x, double y, bool with_push)
364 {
365         double delta = 0.0;
366         uint32_t last_movable = UINT_MAX;
367         double x_limit = DBL_MAX;
368
369         /* this just changes the current view. it does not alter
370            the model in any way at all.
371         */
372
373         /* clamp y-coord appropriately. y is supposed to be a normalized fraction (0.0-1.0),
374            and needs to be converted to a canvas unit distance.
375         */
376
377         y = max (0.0, y);
378         y = min (1.0, y);
379         y = _y_position + _height - (y * _height);
380
381         if (cp.can_slide) {
382
383                 /* x-coord cannot move beyond adjacent points or the start/end, and is
384                    already in frames. it needs to be converted to canvas units.
385                 */
386                 
387                 x = trackview.editor.frame_to_unit (x);
388
389                 /* clamp x position using view coordinates */
390
391                 ControlPoint *before;
392                 ControlPoint *after;
393
394                 if (cp.view_index) {
395                         before = nth (cp.view_index - 1);
396                         x = max (x, before->get_x()+1.0);
397                 } else {
398                         before = &cp;
399                 }
400
401
402                 if (!with_push) {
403                         if (cp.view_index < control_points.size() - 1) {
404                 
405                                 after = nth (cp.view_index + 1);
406                 
407                                 /*if it is a "spike" leave the x alone */
408  
409                                 if (after->get_x() - before->get_x() < 2) {
410                                         x = cp.get_x();
411                                         
412                                 } else {
413                                         x = min (x, after->get_x()-1.0);
414                                 }
415                         } else {
416                                 after = &cp;
417                         }
418
419                 } else {
420
421                         ControlPoint* after;
422                         
423                         /* find the first point that can't move */
424
425                         for (uint32_t n = cp.view_index + 1; (after = nth (n)) != 0; ++n) {
426                                 if (!after->can_slide) {
427                                         x_limit = after->get_x() - 1.0;
428                                         last_movable = after->view_index;
429                                         break;
430                                 }
431                         }
432                         
433                         delta = x - cp.get_x();
434                 }
435                         
436         } else {
437
438                 /* leave the x-coordinate alone */
439
440                 x = trackview.editor.frame_to_unit ((*cp.model)->when);
441
442         }
443
444         if (!with_push) {
445
446                 cp.move_to (x, y, ControlPoint::Full);
447                 reset_line_coords (cp);
448
449         } else {
450
451                 uint32_t limit = min (control_points.size(), (size_t)last_movable);
452                 
453                 /* move the current point to wherever the user told it to go, subject
454                    to x_limit.
455                 */
456                 
457                 cp.move_to (min (x, x_limit), y, ControlPoint::Full);
458                 reset_line_coords (cp);
459                 
460                 /* now move all subsequent control points, to reflect the motion.
461                  */
462                 
463                 for (uint32_t i = cp.view_index + 1; i < limit; ++i) {
464                         ControlPoint *p = nth (i);
465                         double new_x;
466
467                         if (p->can_slide) {
468                                 new_x = min (p->get_x() + delta, x_limit);
469                                 p->move_to (new_x, p->get_y(), ControlPoint::Full);
470                                 reset_line_coords (*p);
471                         }
472                 }
473         }
474 }
475
476 void
477 AutomationLine::reset_line_coords (ControlPoint& cp)
478 {       
479         if (cp.view_index < line_points.size()) {
480                 line_points[cp.view_index].set_x (cp.get_x());
481                 line_points[cp.view_index].set_y (cp.get_y());
482         }
483 }
484
485 void
486 AutomationLine::sync_model_with_view_line (uint32_t start, uint32_t end)
487 {
488
489         ControlPoint *p;
490
491         update_pending = true;
492
493         for (uint32_t i = start; i <= end; ++i) {
494                 p = nth(i);
495                 sync_model_with_view_point (*p, false, 0);
496         }
497 }
498
499 void
500 AutomationLine::model_representation (ControlPoint& cp, ModelRepresentation& mr)
501 {
502         /* part one: find out where the visual control point is.
503            initial results are in canvas units. ask the
504            line to convert them to something relevant.
505         */
506         
507         mr.xval = (nframes_t) floor (cp.get_x());
508         mr.yval = 1.0 - ( (cp.get_y() - _y_position) / _height);
509
510         /* if xval has not changed, set it directly from the model to avoid rounding errors */
511
512         if (mr.xval == trackview.editor.frame_to_unit((*cp.model)->when)) {
513                 mr.xval = (nframes_t) (*cp.model)->when;
514         } else {
515                 mr.xval = trackview.editor.unit_to_frame (mr.xval);
516         }
517
518         /* virtual call: this will do the right thing
519            for whatever particular type of line we are.
520         */
521         
522         view_to_model_y (mr.yval);
523
524         /* part 2: find out where the model point is now
525          */
526
527         mr.xpos = (nframes_t) (*cp.model)->when;
528         mr.ypos = (*cp.model)->value;
529
530         /* part 3: get the position of the visual control
531            points before and after us.
532         */
533
534         ControlPoint* before;
535         ControlPoint* after;
536
537         if (cp.view_index) {
538                 before = nth (cp.view_index - 1);
539         } else {
540                 before = 0;
541         }
542
543         after = nth (cp.view_index + 1);
544
545         if (before) {
546                 mr.xmin = (nframes_t) (*before->model)->when;
547                 mr.ymin = (*before->model)->value;
548                 mr.start = before->model;
549                 ++mr.start;
550         } else {
551                 mr.xmin = mr.xpos;
552                 mr.ymin = mr.ypos;
553                 mr.start = cp.model;
554         }
555
556         if (after) {
557                 mr.end = after->model;
558         } else {
559                 mr.xmax = mr.xpos;
560                 mr.ymax = mr.ypos;
561                 mr.end = cp.model;
562                 ++mr.end;
563         }
564 }
565
566 void
567 AutomationLine::determine_visible_control_points (ALPoints& points)
568 {
569         uint32_t view_index, pi, n;
570         AutomationList::iterator model;
571         uint32_t npoints;
572         double last_control_point_x = 0.0;
573         double last_control_point_y = 0.0;
574         uint32_t this_rx = 0;
575         uint32_t prev_rx = 0;
576         uint32_t this_ry = 0;
577         uint32_t prev_ry = 0;   
578         double* slope;
579         uint32_t box_size;
580         uint32_t cpsize;
581
582         /* hide all existing points, and the line */
583
584         cpsize = 0;
585         
586         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
587                 (*i)->hide();
588                 ++cpsize;
589         }
590
591         line->hide ();
592
593         if (points.empty()) {
594                 return;
595         }
596
597         npoints = points.size();
598
599         /* compute derivative/slope for the entire line */
600
601         slope = new double[npoints];
602
603         for (n = 0; n < npoints - 1; ++n) {
604                 double xdelta = points[n+1].x - points[n].x;
605                 double ydelta = points[n+1].y - points[n].y;
606                 slope[n] = ydelta/xdelta;
607         }
608
609         box_size = (uint32_t) control_point_box_size ();
610
611         /* read all points and decide which ones to show as control points */
612
613         view_index = 0;
614
615         for (model = alist.begin(), pi = 0; pi < npoints; ++model, ++pi) {
616
617                 double tx = points[pi].x;
618                 double ty = points[pi].y;
619
620                 /* now ensure that the control_points vector reflects the current curve
621                    state, but don't plot control points too close together. also, don't
622                    plot a series of points all with the same value.
623
624                    always plot the first and last points, of course.
625                 */
626
627                 if (invalid_point (points, pi)) {
628                         /* for some reason, we are supposed to ignore this point,
629                            but still keep track of the model index.
630                         */
631                         continue;
632                 }
633
634                 if (pi > 0 && pi < npoints - 1) {
635                         if (slope[pi] == slope[pi-1]) {
636
637                                 /* no reason to display this point */
638                                 
639                                 continue;
640                         }
641                 }
642                 
643                 /* need to round here. the ultimate coordinates are integer
644                    pixels, so tiny deltas in the coords will be eliminated
645                    and we end up with "colinear" line segments. since the
646                    line rendering code in libart doesn't like this very
647                    much, we eliminate them here. don't do this for the first and last
648                    points.
649                 */
650
651                 this_rx = (uint32_t) rint (tx);
652                 this_ry = (uint32_t) rint (ty); 
653  
654                 if (view_index && pi != npoints && /* not the first, not the last */
655                     (((this_rx == prev_rx) && (this_ry == prev_ry)) || /* same point */
656                      (((this_rx - prev_rx) < (box_size + 2)) &&  /* not identical, but still too close horizontally */
657                       (abs ((int)(this_ry - prev_ry)) < (int) (box_size + 2))))) { /* too close vertically */
658                         continue;
659                 } 
660
661                 /* ok, we should display this point */
662
663                 if (view_index >= cpsize) {
664
665                         /* make sure we have enough control points */
666
667                         ControlPoint* ncp = new ControlPoint (*this);
668                         
669                         ncp->set_size (box_size); 
670
671                         control_points.push_back (ncp);
672                         ++cpsize;
673                 }
674
675                 ControlPoint::ShapeType shape;
676
677                 if (!terminal_points_can_slide) {
678                         if (pi == 0) {
679                                 control_points[view_index]->can_slide = false;
680                                 if (tx == 0) {
681                                         shape = ControlPoint::Start;
682                                 } else {
683                                         shape = ControlPoint::Full;
684                                 }
685                         } else if (pi == npoints - 1) {
686                                 control_points[view_index]->can_slide = false;
687                                 shape = ControlPoint::End;
688                         } else {
689                                 control_points[view_index]->can_slide = true;
690                                 shape = ControlPoint::Full;
691                         }
692                 } else {
693                         control_points[view_index]->can_slide = true;
694                         shape = ControlPoint::Full;
695                 }
696
697                 last_control_point_x = tx;
698                 last_control_point_y = ty;
699
700                 control_points[view_index]->reset (tx, ty, model, view_index, shape);
701
702                 prev_rx = this_rx;
703                 prev_ry = this_ry;
704
705                 /* finally, control visibility */
706                 
707                 if (_visible && points_visible) {
708                         control_points[view_index]->show ();
709                         control_points[view_index]->set_visible (true);
710                 } else {
711                         if (!points_visible) {
712                                 control_points[view_index]->set_visible (false);
713                         }
714                 }
715
716                 view_index++;
717         }
718         
719         /* discard extra CP's to avoid confusing ourselves */
720
721         while (control_points.size() > view_index) {
722                 ControlPoint* cp = control_points.back();
723                 control_points.pop_back ();
724                 delete cp;
725         }
726
727         if (!terminal_points_can_slide) {
728                 control_points.back()->can_slide = false;
729         }
730
731         delete [] slope;
732
733         if (view_index > 1) {
734
735                 npoints = view_index;
736                 
737                 /* reset the line coordinates */
738
739                 while (line_points.size() < npoints) {
740                         line_points.push_back (Art::Point (0,0));
741                 }
742
743                 while (line_points.size() > npoints) {
744                         line_points.pop_back ();
745                 }
746
747                 for (view_index = 0; view_index < npoints; ++view_index) {
748                         line_points[view_index].set_x (control_points[view_index]->get_x());
749                         line_points[view_index].set_y (control_points[view_index]->get_y());
750                 }
751                 
752                 line->property_points() = line_points;
753
754                 if (_visible) {
755                         line->show ();
756                 }
757
758         } 
759
760         set_selected_points (trackview.editor.get_selection().points);
761
762 }
763
764 string
765 AutomationLine::get_verbose_cursor_string (float fraction)
766 {
767         char buf[32];
768
769         if (_vc_uses_gain_mapping) {
770                 if (fraction == 0.0) {
771                         snprintf (buf, sizeof (buf), "-inf dB");
772                 } else {
773                         snprintf (buf, sizeof (buf), "%.1fdB", coefficient_to_dB (slider_position_to_gain (fraction)));
774                 }
775         } else {
776                 snprintf (buf, sizeof (buf), "%.2f", fraction);
777         }
778
779         return buf;
780 }
781
782 bool
783 AutomationLine::invalid_point (ALPoints& p, uint32_t index)
784 {
785         return p[index].x == max_frames && p[index].y == DBL_MAX;
786 }
787
788 void
789 AutomationLine::invalidate_point (ALPoints& p, uint32_t index)
790 {
791         p[index].x = max_frames;
792         p[index].y = DBL_MAX;
793 }
794
795 void
796 AutomationLine::start_drag (ControlPoint* cp, nframes_t x, float fraction) 
797 {
798         if (trackview.editor.current_session() == 0) { /* how? */
799                 return;
800         }
801
802         string str;
803
804         if (cp) {
805                 str = _("automation event move");
806         } else {
807                 str = _("automation range drag");
808         }
809
810         trackview.editor.current_session()->begin_reversible_command (str);
811         trackview.editor.current_session()->add_command (new MementoCommand<AutomationList>(alist, &get_state(), 0));
812         
813         drag_x = x;
814         drag_distance = 0;
815         first_drag_fraction = fraction;
816         last_drag_fraction = fraction;
817         drags = 0;
818         did_push = false;
819 }
820
821 void
822 AutomationLine::point_drag (ControlPoint& cp, nframes_t x, float fraction, bool with_push) 
823 {
824         if (x > drag_x) {
825                 drag_distance += (x - drag_x);
826         } else {
827                 drag_distance -= (drag_x - x);
828         }
829
830         drag_x = x;
831
832         modify_view_point (cp, x, fraction, with_push);
833
834         if (line_points.size() > 1) {
835                 line->property_points() = line_points;
836         }
837
838         drags++;
839         did_push = with_push;
840 }
841
842 void
843 AutomationLine::line_drag (uint32_t i1, uint32_t i2, float fraction, bool with_push) 
844 {
845         double const ydelta = fraction - last_drag_fraction;
846
847         did_push = with_push;
848         
849         last_drag_fraction = fraction;
850
851         line_drag_cp1 = i1;
852         line_drag_cp2 = i2;
853         
854         ControlPoint *cp;
855
856         for (uint32_t i = i1 ; i <= i2; i++) {
857                 cp = nth (i);
858                 modify_view_point (*cp, trackview.editor.unit_to_frame (cp->get_x()), ((_height - cp->get_y() + _y_position) /_height) + ydelta, with_push);
859         }
860
861         if (line_points.size() > 1) {
862                 line->property_points() = line_points;
863         }
864
865         drags++;
866 }
867
868 void
869 AutomationLine::end_drag (ControlPoint* cp) 
870 {
871         if (!drags) {
872                 return;
873         }
874
875         alist.freeze ();
876
877         if (cp) {
878                 sync_model_with_view_point (*cp, did_push, drag_distance);
879         } else {
880                 sync_model_with_view_line (line_drag_cp1, line_drag_cp2);
881         }
882         
883         alist.thaw ();
884
885         update_pending = false;
886
887         trackview.editor.current_session()->add_command (new MementoCommand<AutomationList>(alist, 0, &alist.get_state()));
888         trackview.editor.current_session()->commit_reversible_command ();
889         trackview.editor.current_session()->set_dirty ();
890 }
891
892
893 void
894 AutomationLine::sync_model_with_view_point (ControlPoint& cp, bool did_push, int64_t distance)
895 {
896         ModelRepresentation mr;
897         double ydelta;
898
899         model_representation (cp, mr);
900
901         /* how much are we changing the central point by */ 
902
903         ydelta = mr.yval - mr.ypos;
904
905         /*
906            apply the full change to the central point, and interpolate
907            on both axes to cover all model points represented
908            by the control point.
909         */
910
911         /* change all points before the primary point */
912
913         for (AutomationList::iterator i = mr.start; i != cp.model; ++i) {
914                 
915                 double fract = ((*i)->when - mr.xmin) / (mr.xpos - mr.xmin);
916                 double y_delta = ydelta * fract;
917                 double x_delta = distance * fract;
918
919                 /* interpolate */
920                 
921                 if (y_delta || x_delta) {
922                         alist.modify (i, (*i)->when + x_delta, mr.ymin + y_delta);
923                 }
924         }
925
926         /* change the primary point */
927
928         update_pending = true;
929         alist.modify (cp.model, mr.xval, mr.yval);
930
931
932         /* change later points */
933         
934         AutomationList::iterator i = cp.model;
935         
936         ++i;
937         
938         while (i != mr.end) {
939                 
940                 double delta = ydelta * (mr.xmax - (*i)->when) / (mr.xmax - mr.xpos);
941
942                 /* all later points move by the same distance along the x-axis as the main point */
943                 
944                 if (delta) {
945                         alist.modify (i, (*i)->when + distance, (*i)->value + delta);
946                 }
947                 
948                 ++i;
949         }
950                 
951         if (did_push) {
952
953                 /* move all points after the range represented by the view by the same distance
954                    as the main point moved.
955                 */
956
957                 alist.slide (mr.end, drag_distance);
958         }
959
960 }
961
962 bool 
963 AutomationLine::control_points_adjacent (double xval, uint32_t & before, uint32_t& after)
964 {
965         ControlPoint *bcp = 0;
966         ControlPoint *acp = 0;
967         double unit_xval;
968
969         /* xval is in frames */
970
971         unit_xval = trackview.editor.frame_to_unit (xval);
972
973         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
974                 
975                 if ((*i)->get_x() <= unit_xval) {
976
977                         if (!bcp || (*i)->get_x() > bcp->get_x()) {
978                                 bcp = *i;
979                                 before = bcp->view_index;
980                         } 
981
982                 } else if ((*i)->get_x() > unit_xval) {
983                         acp = *i;
984                         after = acp->view_index;
985                         break;
986                 }
987         }
988
989         return bcp && acp;
990 }
991
992 bool
993 AutomationLine::is_last_point (ControlPoint& cp)
994 {
995         ModelRepresentation mr;
996
997         model_representation (cp, mr);
998
999         // If the list is not empty, and the point is the last point in the list
1000
1001         if (!alist.empty() && mr.end == alist.end()) {
1002                 return true;
1003         }
1004         
1005         return false;
1006 }
1007
1008 bool
1009 AutomationLine::is_first_point (ControlPoint& cp)
1010 {
1011         ModelRepresentation mr;
1012
1013         model_representation (cp, mr);
1014
1015         // If the list is not empty, and the point is the first point in the list
1016
1017         if (!alist.empty() && mr.start == alist.begin()) {
1018                 return true;
1019         }
1020         
1021         return false;
1022 }
1023
1024 // This is copied into AudioRegionGainLine
1025 void
1026 AutomationLine::remove_point (ControlPoint& cp)
1027 {
1028         ModelRepresentation mr;
1029
1030         model_representation (cp, mr);
1031
1032         trackview.editor.current_session()->begin_reversible_command (_("remove control point"));
1033         XMLNode &before = alist.get_state();
1034
1035         alist.erase (mr.start, mr.end);
1036
1037         trackview.editor.current_session()->add_command(new MementoCommand<AutomationList>(alist, &before, &alist.get_state()));
1038         trackview.editor.current_session()->commit_reversible_command ();
1039         trackview.editor.current_session()->set_dirty ();
1040 }
1041
1042 void
1043 AutomationLine::get_selectables (nframes_t& start, nframes_t& end,
1044                                  double botfrac, double topfrac, list<Selectable*>& results)
1045 {
1046
1047         double top;
1048         double bot;
1049         nframes_t nstart;
1050         nframes_t nend;
1051         bool collecting = false;
1052
1053         /* Curse X11 and its inverted coordinate system! */
1054         
1055         bot = _y_position + (1.0 - topfrac) * _height;
1056         top = _y_position + (1.0 - botfrac) * _height;
1057         
1058         nstart = max_frames;
1059         nend = 0;
1060
1061         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1062                 
1063                 nframes_t when = (nframes_t) (*(*i)->model)->when;
1064
1065                 if (when >= start && when <= end) {
1066                         
1067                         if ((*i)->get_y() >= bot && (*i)->get_y() <= top) {
1068
1069                                 (*i)->show();
1070                                 (*i)->set_visible(true);
1071                                 collecting = true;
1072                                 nstart = min (nstart, when);
1073                                 nend = max (nend, when);
1074
1075                         } else {
1076                                 
1077                                 if (collecting) {
1078
1079                                         results.push_back (new AutomationSelectable (nstart, nend, botfrac, topfrac, trackview));
1080                                         collecting = false;
1081                                         nstart = max_frames;
1082                                         nend = 0;
1083                                 }
1084                         }
1085                 }
1086         }
1087
1088         if (collecting) {
1089                 results.push_back (new AutomationSelectable (nstart, nend, botfrac, topfrac, trackview));
1090         }
1091
1092 }
1093
1094 void
1095 AutomationLine::get_inverted_selectables (Selection&, list<Selectable*>& results)
1096 {
1097         // hmmm ....
1098 }
1099
1100 void
1101 AutomationLine::set_selected_points (PointSelection& points)
1102 {
1103         double top;
1104         double bot;
1105
1106         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1107                         (*i)->selected = false;
1108         }
1109
1110         if (points.empty()) {
1111                 goto out;
1112         } 
1113         
1114         for (PointSelection::iterator r = points.begin(); r != points.end(); ++r) {
1115                 
1116                 if (&(*r).track != &trackview) {
1117                         continue;
1118                 }
1119
1120                 /* Curse X11 and its inverted coordinate system! */
1121
1122                 bot = _y_position + (1.0 - (*r).high_fract) * _height;
1123                 top = _y_position + (1.0 - (*r).low_fract) * _height;
1124
1125                 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1126                         
1127                         double rstart, rend;
1128                         
1129                         rstart = trackview.editor.frame_to_unit ((*r).start);
1130                         rend = trackview.editor.frame_to_unit ((*r).end);
1131                         
1132                         if ((*i)->get_x() >= rstart && (*i)->get_x() <= rend) {
1133                                 
1134                                 if ((*i)->get_y() >= bot && (*i)->get_y() <= top) {
1135                                         
1136                                         (*i)->selected = true;
1137                                 }
1138                         }
1139
1140                 }
1141         }
1142
1143   out:
1144         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1145                 (*i)->show_color (false, !points_visible);
1146         }
1147
1148 }
1149
1150 void AutomationLine::set_colors() {
1151         set_line_color( color_map[cAutomationLine] );
1152         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1153                 (*i)->show_color (false, !points_visible);
1154         }
1155 }
1156
1157 void
1158 AutomationLine::show_selection ()
1159 {
1160         TimeSelection& time (trackview.editor.get_selection().time);
1161
1162         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1163                 
1164                 (*i)->selected = false;
1165
1166                 for (list<AudioRange>::iterator r = time.begin(); r != time.end(); ++r) {
1167                         double rstart, rend;
1168                         
1169                         rstart = trackview.editor.frame_to_unit ((*r).start);
1170                         rend = trackview.editor.frame_to_unit ((*r).end);
1171                         
1172                         if ((*i)->get_x() >= rstart && (*i)->get_x() <= rend) {
1173                                 (*i)->selected = true;
1174                                 break;
1175                         }
1176                 }
1177                 
1178                 (*i)->show_color (false, !points_visible);
1179         }
1180 }
1181
1182 void
1183 AutomationLine::hide_selection ()
1184 {
1185 //      show_selection ();
1186 }
1187
1188 void
1189 AutomationLine::list_changed ()
1190 {
1191         queue_reset ();
1192 }
1193
1194 void
1195 AutomationLine::reset_callback (const AutomationList& events)
1196 {
1197         ALPoints tmp_points;
1198         uint32_t npoints = events.size();
1199
1200         if (npoints == 0) {
1201                 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1202                         delete *i;
1203                 }
1204                 control_points.clear ();
1205                 line->hide();
1206                 return;
1207         }
1208
1209         AutomationList::const_iterator ai;
1210
1211         for (ai = events.const_begin(); ai != events.const_end(); ++ai) {
1212
1213                 double translated_y = (*ai)->value;
1214                 model_to_view_y (translated_y);
1215
1216                 tmp_points.push_back (ALPoint (trackview.editor.frame_to_unit ((*ai)->when),
1217                                                _y_position + _height - (translated_y * _height)));
1218         }
1219         
1220         determine_visible_control_points (tmp_points);
1221 }
1222
1223 void
1224 AutomationLine::reset ()
1225 {
1226         update_pending = false;
1227
1228         if (no_draw) {
1229                 return;
1230         }
1231
1232         alist.apply_to_points (*this, &AutomationLine::reset_callback);
1233 }
1234
1235 void
1236 AutomationLine::clear ()
1237 {
1238         /* parent must create command */
1239         XMLNode &before = get_state();
1240         alist.clear();
1241         trackview.editor.current_session()->add_command (new MementoCommand<AutomationLine>(*this, &before, &get_state()));
1242         trackview.editor.current_session()->commit_reversible_command ();
1243         trackview.editor.current_session()->set_dirty ();
1244 }
1245
1246 void
1247 AutomationLine::change_model (AutomationList::iterator i, double x, double y)
1248 {
1249 }
1250
1251 void
1252 AutomationLine::change_model_range (AutomationList::iterator start, AutomationList::iterator end, double xdelta, float ydelta)
1253 {
1254         alist.move_range (start, end, xdelta, ydelta);
1255 }
1256
1257 void
1258 AutomationLine::show_all_control_points ()
1259 {
1260         points_visible = true;
1261
1262         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1263                 (*i)->show ();
1264                 (*i)->set_visible (true);
1265         }
1266 }
1267
1268 void
1269 AutomationLine::hide_all_but_selected_control_points ()
1270 {
1271         points_visible = false;
1272
1273         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1274                 if (!(*i)->selected) {
1275                         (*i)->set_visible (false);
1276                 }
1277         }
1278 }
1279
1280 XMLNode &
1281 AutomationLine::get_state (void)
1282 {
1283         /* function as a proxy for the model */
1284         return alist.get_state();
1285 }
1286
1287 int 
1288 AutomationLine::set_state (const XMLNode &node)
1289 {
1290         /* function as a proxy for the model */
1291         return alist.set_state (node);
1292 }