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