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