Merged with trunk R992.
[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(_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::set_point_size (double sz)
275 {
276         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
277                 (*i)->set_size (sz);
278         }
279 }       
280
281 void
282 AutomationLine::show () 
283 {
284         line->show();
285
286         if (points_visible) {
287                 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
288                         (*i)->show ();
289                 }
290         }
291
292         _visible = true;
293 }
294
295 void
296 AutomationLine::hide () 
297 {
298         line->hide();
299         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
300                 (*i)->hide();
301         }
302         _visible = false;
303 }
304
305 void
306 AutomationLine::set_height (guint32 h)
307 {
308         if (h != _height) {
309                 _height = h;
310
311                 if (_height > (guint32) TimeAxisView::Larger) {
312                         set_point_size (8.0);
313                 } else if (_height > (guint32) TimeAxisView::Normal) {
314                         set_point_size (6.0);
315                 } else {
316                         set_point_size (4.0);
317                 }
318
319                 reset ();
320         }
321 }
322
323 void
324 AutomationLine::set_line_color (uint32_t color)
325 {
326         _line_color = color;
327         line->property_fill_color_rgba() = color;
328 }
329
330 void
331 AutomationLine::set_verbose_cursor_uses_gain_mapping (bool yn)
332 {
333         if (yn != _vc_uses_gain_mapping) {
334                 _vc_uses_gain_mapping = yn;
335                 reset ();
336         }
337 }
338
339 ControlPoint*
340 AutomationLine::nth (uint32_t n)
341 {
342         if (n < control_points.size()) {
343                 return control_points[n];
344         } else {
345                 return 0;
346         }
347 }
348
349 void
350 AutomationLine::modify_view (ControlPoint& cp, double x, double y, bool with_push)
351 {
352         modify_view_point (cp, x, y, with_push);
353         update_line ();
354 }
355
356 void
357 AutomationLine::modify_view_point (ControlPoint& cp, double x, double y, bool with_push)
358 {
359         double delta = 0.0;
360         uint32_t last_movable = UINT_MAX;
361         double x_limit = DBL_MAX;
362
363         /* this just changes the current view. it does not alter
364            the model in any way at all.
365         */
366
367         /* clamp y-coord appropriately. y is supposed to be a normalized fraction (0.0-1.0),
368            and needs to be converted to a canvas unit distance.
369         */
370
371         y = max (0.0, y);
372         y = min (1.0, y);
373         y = _height - (y * _height);
374
375         if (cp.can_slide) {
376
377                 /* x-coord cannot move beyond adjacent points or the start/end, and is
378                    already in frames. it needs to be converted to canvas units.
379                 */
380                 
381                 x = trackview.editor.frame_to_unit (x);
382
383                 /* clamp x position using view coordinates */
384
385                 ControlPoint *before;
386                 ControlPoint *after;
387
388                 if (cp.view_index) {
389                         before = nth (cp.view_index - 1);
390                         x = max (x, before->get_x()+1.0);
391                 } else {
392                         before = &cp;
393                 }
394
395
396                 if (!with_push) {
397                         if (cp.view_index < control_points.size() - 1) {
398                 
399                                 after = nth (cp.view_index + 1);
400                 
401                                 /*if it is a "spike" leave the x alone */
402  
403                                 if (after->get_x() - before->get_x() < 2) {
404                                         x = cp.get_x();
405                                         
406                                 } else {
407                                         x = min (x, after->get_x()-1.0);
408                                 }
409                         } else {
410                                 after = &cp;
411                         }
412
413                 } else {
414
415                         ControlPoint* after;
416                         
417                         /* find the first point that can't move */
418                         
419                         for (uint32_t n = cp.view_index + 1; (after = nth (n)) != 0; ++n) {
420                                 if (!after->can_slide) {
421                                         x_limit = after->get_x() - 1.0;
422                                         last_movable = after->view_index;
423                                         break;
424                                 }
425                         }
426                         
427                         delta = x - cp.get_x();
428                 }
429                         
430         } else {
431
432                 /* leave the x-coordinate alone */
433
434                 x = trackview.editor.frame_to_unit ((*cp.model)->when);
435
436         }
437
438         if (!with_push) {
439
440                 cp.move_to (x, y, ControlPoint::Full);
441                 reset_line_coords (cp);
442
443         } else {
444
445                 uint32_t limit = min (control_points.size(), (size_t)last_movable);
446                 
447                 /* move the current point to wherever the user told it to go, subject
448                    to x_limit.
449                 */
450                 
451                 cp.move_to (min (x, x_limit), y, ControlPoint::Full);
452                 reset_line_coords (cp);
453                 
454                 /* now move all subsequent control points, to reflect the motion.
455                  */
456                 
457                 for (uint32_t i = cp.view_index + 1; i < limit; ++i) {
458                         ControlPoint *p = nth (i);
459                         double new_x;
460
461                         if (p->can_slide) {
462                                 new_x = min (p->get_x() + delta, x_limit);
463                                 p->move_to (new_x, p->get_y(), ControlPoint::Full);
464                                 reset_line_coords (*p);
465                         }
466                 }
467         }
468 }
469
470 void
471 AutomationLine::reset_line_coords (ControlPoint& cp)
472 {       
473         line_points[cp.view_index].set_x (cp.get_x());
474         line_points[cp.view_index].set_y (cp.get_y());
475 }
476
477 void
478 AutomationLine::update_line ()
479 {
480         line->property_points() = line_points;
481 }
482
483 void
484 AutomationLine::sync_model_with_view_line (uint32_t start, uint32_t end)
485 {
486
487         ControlPoint *p;
488
489         update_pending = true;
490
491         for (uint32_t i = start; i <= end; ++i) {
492                 p = nth(i);
493                 sync_model_with_view_point(*p);
494         }
495         
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() / _height);
509
510
511         /* if xval has not changed, set it directly from the model to avoid rounding errors */
512
513         if (mr.xval == trackview.editor.frame_to_unit((*cp.model)->when)) {
514                 mr.xval = (nframes_t) (*cp.model)->when;
515         } else {
516                 mr.xval = trackview.editor.unit_to_frame (mr.xval);
517         }
518
519
520         /* virtual call: this will do the right thing
521            for whatever particular type of line we are.
522         */
523         
524         view_to_model_y (mr.yval);
525
526         /* part 2: find out where the model point is now
527          */
528
529         mr.xpos = (nframes_t) (*cp.model)->when;
530         mr.ypos = (*cp.model)->value;
531
532         /* part 3: get the position of the visual control
533            points before and after us.
534         */
535
536         ControlPoint* before;
537         ControlPoint* after;
538
539         if (cp.view_index) {
540                 before = nth (cp.view_index - 1);
541         } else {
542                 before = 0;
543         }
544
545         after = nth (cp.view_index + 1);
546
547         if (before) {
548                 mr.xmin = (nframes_t) (*before->model)->when;
549                 mr.ymin = (*before->model)->value;
550                 mr.start = before->model;
551                 ++mr.start;
552         } else {
553                 mr.xmin = mr.xpos;
554                 mr.ymin = mr.ypos;
555                 mr.start = cp.model;
556         }
557
558         if (after) {
559                 mr.end = after->model;
560         } else {
561                 mr.xmax = mr.xpos;
562                 mr.ymax = mr.ypos;
563                 mr.end = cp.model;
564                 ++mr.end;
565         }
566 }
567
568 void
569 AutomationLine::sync_model_from (ControlPoint& cp)
570 {
571         ControlPoint* p;
572         uint32_t lasti;
573
574         sync_model_with_view_point (cp);
575
576         /* we might have moved all points after `cp' by some amount
577            if we pressed the with_push modifyer some of the time during the drag
578            so all subsequent points have to be resynced
579         */
580
581         lasti = control_points.size() - 1;
582         p = nth (lasti);
583
584         update_pending = true;
585
586         while (p != &cp && lasti) {
587                 sync_model_with_view_point (*p);
588                 --lasti;
589                 p = nth (lasti);
590         }
591 }
592
593 void
594 AutomationLine::sync_model_with_view_point (ControlPoint& cp)
595 {
596         ModelRepresentation mr;
597         double ydelta;
598
599         model_representation (cp, mr);
600
601         /* part 4: how much are we changing the central point by */ 
602
603         ydelta = mr.yval - mr.ypos;
604
605         /* IMPORTANT: changing the model when the x-coordinate changes
606            may invalidate the iterators that we are using. this means that we have
607            to change the points before+after the one corresponding to the visual CP
608            first (their x-coordinate doesn't change). then we change the
609            "main" point.
610
611            apply the full change to the central point, and interpolate
612            in each direction to cover all model points represented
613            by the control point.
614         */
615
616         /* part 5: change all points before the primary point */
617
618         for (AutomationList::iterator i = mr.start; i != cp.model; ++i) {
619
620                 double delta;
621
622                 delta = ydelta * ((*i)->when - mr.xmin) / (mr.xpos - mr.xmin);
623
624                 /* x-coordinate (generally time) stays where it is,
625                    y-coordinate moves by a certain amount.
626                 */
627                 
628                 update_pending = true;
629                 change_model (i, (*i)->when, mr.yval + delta);
630         }
631
632         /* part 6: change later points */
633
634         AutomationList::iterator i = cp.model;
635
636         ++i;
637
638         while (i != mr.end) {
639
640                 double delta;
641                 
642                 delta = ydelta * (mr.xmax - (*i)->when) / (mr.xmax - mr.xpos);
643
644                 /* x-coordinate (generally time) stays where it is,
645                    y-coordinate moves by a certain amount.
646                 */
647                 
648                 update_pending = true;
649                 change_model (i, (*i)->when, (*i)->value + delta);
650                 
651                 ++i;
652         }
653
654         /* part 7: change the primary point */
655
656         update_pending = true;
657         change_model (cp.model, mr.xval, mr.yval);
658 }
659
660 void
661 AutomationLine::determine_visible_control_points (ALPoints& points)
662 {
663         uint32_t view_index, pi, n;
664         AutomationList::iterator model;
665         uint32_t npoints;
666         double last_control_point_x = 0.0;
667         double last_control_point_y = 0.0;
668         uint32_t this_rx = 0;
669         uint32_t prev_rx = 0;
670         uint32_t this_ry = 0;
671         uint32_t prev_ry = 0;   
672         double* slope;
673
674         /* hide all existing points, and the line */
675         
676         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
677                 (*i)->hide();
678         }
679
680         line->hide ();
681
682         if (points.empty()) {
683                 return;
684         }
685
686         npoints = points.size();
687
688         /* compute derivative/slope for the entire line */
689
690         slope = new double[npoints];
691
692         for (n = 0; n < npoints - 1; ++n) {
693                 double xdelta = points[n+1].x - points[n].x;
694                 double ydelta = points[n+1].y - points[n].y;
695                 slope[n] = ydelta/xdelta;
696         }
697
698         /* read all points and decide which ones to show as control points */
699
700         view_index = 0;
701
702         for (model = alist.begin(), pi = 0; pi < npoints; ++model, ++pi) {
703
704                 double tx = points[pi].x;
705                 double ty = points[pi].y;
706
707                 /* now ensure that the control_points vector reflects the current curve
708                    state, but don't plot control points too close together. also, don't
709                    plot a series of points all with the same value.
710
711                    always plot the first and last points, of course.
712                 */
713
714                 if (invalid_point (points, pi)) {
715                         /* for some reason, we are supposed to ignore this point,
716                            but still keep track of the model index.
717                         */
718                         continue;
719                 }
720
721                 if (pi > 0 && pi < npoints - 1) {
722                         if (slope[pi] == slope[pi-1]) {
723
724                                 /* no reason to display this point */
725                                 
726                                 continue;
727                         }
728                 }
729                 
730                 /* need to round here. the ultimate coordinates are integer
731                    pixels, so tiny deltas in the coords will be eliminated
732                    and we end up with "colinear" line segments. since the
733                    line rendering code in libart doesn't like this very
734                    much, we eliminate them here. don't do this for the first and last
735                    points.
736                 */
737
738                 this_rx = (uint32_t) rint (tx);
739                 this_ry = (unsigned long) rint (ty); 
740  
741                 if (view_index && pi != npoints && (this_rx == prev_rx) && (this_ry == prev_ry)) {
742  
743                         continue;
744                 } 
745
746                 /* ok, we should display this point */
747
748                 if (view_index >= control_points.size()) {
749                         /* make sure we have enough control points */
750
751                         ControlPoint* ncp = new ControlPoint (*this);
752
753                         if (_height > (guint32) TimeAxisView::Larger) {
754                                 ncp->set_size (8.0);
755                         } else if (_height > (guint32) TimeAxisView::Normal) {
756                                 ncp->set_size (6.0); 
757                         } else {
758                                 ncp->set_size (4.0);
759                         }
760
761                         control_points.push_back (ncp);
762                 }
763
764                 ControlPoint::ShapeType shape;
765
766                 if (!terminal_points_can_slide) {
767                         if (pi == 0) {
768                                 control_points[view_index]->can_slide = false;
769                                 if (tx == 0) {
770                                         shape = ControlPoint::Start;
771                                 } else {
772                                         shape = ControlPoint::Full;
773                                 }
774                         } else if (pi == npoints - 1) {
775                                 control_points[view_index]->can_slide = false;
776                                 shape = ControlPoint::End;
777                         } else {
778                                 control_points[view_index]->can_slide = true;
779                                 shape = ControlPoint::Full;
780                         }
781                 } else {
782                         control_points[view_index]->can_slide = true;
783                         shape = ControlPoint::Full;
784                 }
785
786                 last_control_point_x = tx;
787                 last_control_point_y = ty;
788
789                 control_points[view_index]->reset (tx, ty, model, view_index, shape);
790
791                 prev_rx = this_rx;
792                 prev_ry = this_ry;
793
794                 /* finally, control visibility */
795                 
796                 if (_visible && points_visible) {
797                         control_points[view_index]->show ();
798                         control_points[view_index]->set_visible (true);
799                 } else {
800                         if (!points_visible) {
801                                 control_points[view_index]->set_visible (false);
802                         }
803                 }
804
805                 view_index++;
806         }
807
808         /* discard extra CP's to avoid confusing ourselves */
809
810         while (control_points.size() > view_index) {
811                 ControlPoint* cp = control_points.back();
812                 control_points.pop_back ();
813                 delete cp;
814         }
815
816         if (!terminal_points_can_slide) {
817                 control_points.back()->can_slide = false;
818         }
819
820         delete [] slope;
821
822         if (view_index > 1) {
823
824                 npoints = view_index;
825                 
826                 /* reset the line coordinates */
827
828                 while (line_points.size() < npoints) {
829                         line_points.push_back (Art::Point (0,0));
830                 }
831
832                 for (view_index = 0; view_index < npoints; ++view_index) {
833                         line_points[view_index].set_x (control_points[view_index]->get_x());
834                         line_points[view_index].set_y (control_points[view_index]->get_y());
835                 }
836                 
837                 line->property_points() = line_points;
838
839                 if (_visible) {
840                         line->show ();
841                 }
842         } 
843
844         set_selected_points (trackview.editor.get_selection().points);
845 }
846
847 string
848 AutomationLine::get_verbose_cursor_string (float fraction)
849 {
850         char buf[32];
851
852         if (_vc_uses_gain_mapping) {
853                 if (fraction == 0.0) {
854                         snprintf (buf, sizeof (buf), "-inf dB");
855                 } else {
856                         snprintf (buf, sizeof (buf), "%.1fdB", coefficient_to_dB (slider_position_to_gain (fraction)));
857                 }
858         } else {
859                 snprintf (buf, sizeof (buf), "%.2f", fraction);
860         }
861
862         return buf;
863 }
864
865 bool
866 AutomationLine::invalid_point (ALPoints& p, uint32_t index)
867 {
868         return p[index].x == max_frames && p[index].y == DBL_MAX;
869 }
870
871 void
872 AutomationLine::invalidate_point (ALPoints& p, uint32_t index)
873 {
874         p[index].x = max_frames;
875         p[index].y = DBL_MAX;
876 }
877
878 void
879 AutomationLine::start_drag (ControlPoint* cp, float fraction) 
880 {
881         if (trackview.editor.current_session() == 0) { /* how? */
882                 return;
883         }
884
885         string str;
886
887         if (cp) {
888                 str = _("automation event move");
889         } else {
890                 str = _("automation range drag");
891         }
892
893         trackview.editor.current_session()->begin_reversible_command (str);
894         trackview.editor.current_session()->add_command (new MementoCommand<AutomationLine>(*this, &get_state(), 0));
895         
896         first_drag_fraction = fraction;
897         last_drag_fraction = fraction;
898         drags = 0;
899 }
900
901 void
902 AutomationLine::point_drag (ControlPoint& cp, nframes_t x, float fraction, bool with_push) 
903 {
904         modify_view (cp, x, fraction, with_push);
905         drags++;
906 }
907
908 void
909 AutomationLine::line_drag (uint32_t i1, uint32_t i2, float fraction, bool with_push) 
910 {
911         double ydelta = fraction - last_drag_fraction;
912         
913         last_drag_fraction = fraction;
914
915         line_drag_cp1 = i1;
916         line_drag_cp2 = i2;
917         
918         ControlPoint *cp;
919
920         for (uint32_t i = i1 ; i <= i2; i++) {
921                 cp = nth (i);
922                 modify_view_point (*cp, trackview.editor.unit_to_frame (cp->get_x()), ((_height - cp->get_y()) /_height) + ydelta, with_push);
923         }
924
925         update_line ();
926
927         drags++;
928 }
929
930 void
931 AutomationLine::end_drag (ControlPoint* cp) 
932 {
933         if (drags) {
934
935                 if (cp) {
936                         sync_model_from (*cp);
937                 } else {
938                         sync_model_with_view_line (line_drag_cp1, line_drag_cp2);
939                 }
940
941                 update_pending = false;
942
943                 trackview.editor.current_session()->add_command (new MementoCommand<AutomationLine>(*this, 0, &get_state()));
944                 trackview.editor.current_session()->commit_reversible_command ();
945                 trackview.editor.current_session()->set_dirty ();
946         }
947 }
948
949 bool 
950 AutomationLine::control_points_adjacent (double xval, uint32_t & before, uint32_t& after)
951 {
952         ControlPoint *bcp = 0;
953         ControlPoint *acp = 0;
954         double unit_xval;
955
956         /* xval is in frames */
957
958         unit_xval = trackview.editor.frame_to_unit (xval);
959
960         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
961                 
962                 if ((*i)->get_x() <= unit_xval) {
963
964                         if (!bcp || (*i)->get_x() > bcp->get_x()) {
965                                 bcp = *i;
966                                 before = bcp->view_index;
967                         } 
968
969                 } else if ((*i)->get_x() > unit_xval) {
970                         acp = *i;
971                         after = acp->view_index;
972                         break;
973                 }
974         }
975
976         return bcp && acp;
977 }
978
979 bool
980 AutomationLine::is_last_point (ControlPoint& cp)
981 {
982         ModelRepresentation mr;
983
984         model_representation (cp, mr);
985
986         // If the list is not empty, and the point is the last point in the list
987
988         if (!alist.empty() && mr.end == alist.end()) {
989                 return true;
990         }
991         
992         return false;
993 }
994
995 bool
996 AutomationLine::is_first_point (ControlPoint& cp)
997 {
998         ModelRepresentation mr;
999
1000         model_representation (cp, mr);
1001
1002         // If the list is not empty, and the point is the first point in the list
1003
1004         if (!alist.empty() && mr.start == alist.begin()) {
1005                 return true;
1006         }
1007         
1008         return false;
1009 }
1010
1011 // This is copied into AudioRegionGainLine
1012 void
1013 AutomationLine::remove_point (ControlPoint& cp)
1014 {
1015         ModelRepresentation mr;
1016
1017         model_representation (cp, mr);
1018
1019         trackview.editor.current_session()->begin_reversible_command (_("remove control point"));
1020         XMLNode &before = get_state();
1021
1022         alist.erase (mr.start, mr.end);
1023
1024         trackview.editor.current_session()->add_command(new MementoCommand<AutomationLine>(*this, &before, &get_state()));
1025         trackview.editor.current_session()->commit_reversible_command ();
1026         trackview.editor.current_session()->set_dirty ();
1027 }
1028
1029 void
1030 AutomationLine::get_selectables (nframes_t& start, nframes_t& end,
1031                                  double botfrac, double topfrac, list<Selectable*>& results)
1032 {
1033
1034         double top;
1035         double bot;
1036         nframes_t nstart;
1037         nframes_t nend;
1038         bool collecting = false;
1039
1040         /* Curse X11 and its inverted coordinate system! */
1041         
1042         bot = (1.0 - topfrac) * _height;
1043         top = (1.0 - botfrac) * _height;
1044         
1045         nstart = max_frames;
1046         nend = 0;
1047
1048         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1049                 
1050                 nframes_t when = (nframes_t) (*(*i)->model)->when;
1051
1052                 if (when >= start && when <= end) {
1053                         
1054                         if ((*i)->get_y() >= bot && (*i)->get_y() <= top) {
1055
1056                                 (*i)->show();
1057                                 (*i)->set_visible(true);
1058                                 collecting = true;
1059                                 nstart = min (nstart, when);
1060                                 nend = max (nend, when);
1061
1062                         } else {
1063                                 
1064                                 if (collecting) {
1065
1066                                         results.push_back (new AutomationSelectable (nstart, nend, botfrac, topfrac, trackview));
1067                                         collecting = false;
1068                                         nstart = max_frames;
1069                                         nend = 0;
1070                                 }
1071                         }
1072                 }
1073         }
1074
1075         if (collecting) {
1076                 results.push_back (new AutomationSelectable (nstart, nend, botfrac, topfrac, trackview));
1077         }
1078
1079 }
1080
1081 void
1082 AutomationLine::get_inverted_selectables (Selection&, list<Selectable*>& results)
1083 {
1084         // hmmm ....
1085 }
1086
1087 void
1088 AutomationLine::set_selected_points (PointSelection& points)
1089 {
1090         double top;
1091         double bot;
1092
1093         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1094                         (*i)->selected = false;
1095         }
1096
1097         if (points.empty()) {
1098                 goto out;
1099         } 
1100         
1101         for (PointSelection::iterator r = points.begin(); r != points.end(); ++r) {
1102                 
1103                 if (&(*r).track != &trackview) {
1104                         continue;
1105                 }
1106
1107                 /* Curse X11 and its inverted coordinate system! */
1108
1109                 bot = (1.0 - (*r).high_fract) * _height;
1110                 top = (1.0 - (*r).low_fract) * _height;
1111
1112                 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1113                         
1114                         double rstart, rend;
1115                         
1116                         rstart = trackview.editor.frame_to_unit ((*r).start);
1117                         rend = trackview.editor.frame_to_unit ((*r).end);
1118                         
1119                         if ((*i)->get_x() >= rstart && (*i)->get_x() <= rend) {
1120                                 
1121                                 if ((*i)->get_y() >= bot && (*i)->get_y() <= top) {
1122                                         
1123                                         (*i)->selected = true;
1124                                 }
1125                         }
1126
1127                 }
1128         }
1129
1130   out:
1131         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1132                 (*i)->show_color (false, !points_visible);
1133         }
1134
1135 }
1136
1137 void
1138 AutomationLine::show_selection ()
1139 {
1140         TimeSelection& time (trackview.editor.get_selection().time);
1141
1142         // cerr << "show selection\n";
1143
1144         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1145                 
1146                 (*i)->selected = false;
1147
1148                 for (list<AudioRange>::iterator r = time.begin(); r != time.end(); ++r) {
1149                         double rstart, rend;
1150                         
1151                         rstart = trackview.editor.frame_to_unit ((*r).start);
1152                         rend = trackview.editor.frame_to_unit ((*r).end);
1153                         
1154                         if ((*i)->get_x() >= rstart && (*i)->get_x() <= rend) {
1155                                 (*i)->selected = true;
1156                                 break;
1157                         }
1158                 }
1159                 
1160                 (*i)->show_color (false, !points_visible);
1161         }
1162 }
1163
1164 void
1165 AutomationLine::hide_selection ()
1166 {
1167         // cerr << "hide selection\n";
1168 //      show_selection ();
1169 }
1170
1171
1172 // This is copied into AudioRegionGainLine
1173 UndoAction
1174 AutomationLine::get_memento ()
1175 {
1176         return alist.get_memento();
1177 }
1178
1179 void
1180 AutomationLine::list_changed (Change ignored)
1181 {
1182         queue_reset ();
1183 }
1184
1185 void
1186 AutomationLine::reset_callback (const AutomationList& events)
1187 {
1188         ALPoints tmp_points;
1189         uint32_t npoints = events.size();
1190
1191         if (npoints == 0) {
1192                 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1193                         delete *i;
1194                 }
1195                 control_points.clear ();
1196                 line->hide();
1197                 return;
1198         }
1199
1200         AutomationList::const_iterator ai;
1201
1202         for (ai = events.const_begin(); ai != events.const_end(); ++ai) {
1203
1204                 double translated_y;
1205                 
1206                 translated_y = (*ai)->value;
1207                 model_to_view_y (translated_y);
1208
1209                 tmp_points.push_back (ALPoint (trackview.editor.frame_to_unit ((*ai)->when),
1210                                                _height - (translated_y * _height)));
1211         }
1212         
1213         determine_visible_control_points (tmp_points);
1214 }
1215
1216 void
1217 AutomationLine::reset ()
1218 {
1219         update_pending = false;
1220
1221         if (no_draw) {
1222                 return;
1223         }
1224
1225         alist.apply_to_points (*this, &AutomationLine::reset_callback);
1226 }
1227
1228 void
1229 AutomationLine::clear ()
1230 {
1231         /* parent must create command */
1232         XMLNode &before = get_state();
1233         alist.clear();
1234         trackview.editor.current_session()->add_command (new MementoCommand<AutomationLine>(*this, &before, &get_state()));
1235         trackview.editor.current_session()->commit_reversible_command ();
1236         trackview.editor.current_session()->set_dirty ();
1237 }
1238
1239 void
1240 AutomationLine::change_model (AutomationList::iterator i, double x, double y)
1241 {
1242         alist.modify (i, (nframes_t) x, y);
1243 }
1244
1245 void
1246 AutomationLine::change_model_range (AutomationList::iterator start, AutomationList::iterator end, double xdelta, float ydelta)
1247 {
1248         alist.move_range (start, end, xdelta, ydelta);
1249 }
1250
1251 void
1252 AutomationLine::show_all_control_points ()
1253 {
1254         points_visible = true;
1255
1256         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1257                 (*i)->show ();
1258                 (*i)->set_visible (true);
1259         }
1260 }
1261
1262 void
1263 AutomationLine::hide_all_but_selected_control_points ()
1264 {
1265         points_visible = false;
1266
1267         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1268                 if (!(*i)->selected) {
1269                         (*i)->set_visible (false);
1270                 }
1271         }
1272 }
1273
1274 XMLNode &AutomationLine::get_state(void)
1275 {
1276     XMLNode *node = new XMLNode("AutomationLine");
1277     node->add_child_nocopy(alist.get_state());
1278     return *node;
1279 }
1280
1281 int AutomationLine::set_state(const XMLNode &node)
1282 {
1283     // TODO
1284     //alist.set_state(node);
1285     return 0;
1286 }