make X on track hide button visible; fix signed/unsigned warning
[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                     (((this_rx == prev_rx) && (this_ry == prev_ry)) || /* same point */
751                      (((this_rx - prev_rx) < (box_size + 2)) &&  /* too close horizontally */
752                       ((abs ((int)(this_ry - prev_ry)) < (int) (box_size + 2)))))) { /* too close vertically */
753                         continue;
754                 }
755
756                 /* ok, we should display this point */
757
758                 if (view_index >= cpsize) {
759
760                         /* make sure we have enough control points */
761
762                         ControlPoint* ncp = new ControlPoint (*this);
763                         
764                         ncp->set_size (box_size); 
765
766                         control_points.push_back (ncp);
767                         ++cpsize;
768                 }
769
770                 ControlPoint::ShapeType shape;
771
772                 if (!terminal_points_can_slide) {
773                         if (pi == 0) {
774                                 control_points[view_index]->can_slide = false;
775                                 if (tx == 0) {
776                                         shape = ControlPoint::Start;
777                                 } else {
778                                         shape = ControlPoint::Full;
779                                 }
780                         } else if (pi == npoints - 1) {
781                                 control_points[view_index]->can_slide = false;
782                                 shape = ControlPoint::End;
783                         } else {
784                                 control_points[view_index]->can_slide = true;
785                                 shape = ControlPoint::Full;
786                         }
787                 } else {
788                         control_points[view_index]->can_slide = true;
789                         shape = ControlPoint::Full;
790                 }
791
792                 last_control_point_x = tx;
793                 last_control_point_y = ty;
794
795                 control_points[view_index]->reset (tx, ty, model, view_index, shape);
796
797                 prev_rx = this_rx;
798                 prev_ry = this_ry;
799
800                 /* finally, control visibility */
801                 
802                 if (_visible && points_visible) {
803                         control_points[view_index]->show ();
804                         control_points[view_index]->set_visible (true);
805                 } else {
806                         if (!points_visible) {
807                                 control_points[view_index]->set_visible (false);
808                         }
809                 }
810
811                 view_index++;
812         }
813
814         /* discard extra CP's to avoid confusing ourselves */
815
816         while (control_points.size() > view_index) {
817                 ControlPoint* cp = control_points.back();
818                 control_points.pop_back ();
819                 delete cp;
820         }
821
822         if (!terminal_points_can_slide) {
823                 control_points.back()->can_slide = false;
824         }
825
826         delete [] slope;
827
828         if (view_index > 1) {
829
830                 npoints = view_index;
831                 
832                 /* reset the line coordinates */
833
834                 while (line_points.size() < npoints) {
835                         line_points.push_back (Art::Point (0,0));
836                 }
837
838                 while (line_points.size() > npoints) {
839                         line_points.pop_back ();
840                 }
841
842                 for (view_index = 0; view_index < npoints; ++view_index) {
843                         line_points[view_index].set_x (control_points[view_index]->get_x());
844                         line_points[view_index].set_y (control_points[view_index]->get_y());
845                 }
846                 
847                 line->property_points() = line_points;
848
849                 if (_visible) {
850                         line->show ();
851                 }
852         } 
853
854         set_selected_points (trackview.editor.get_selection().points);
855 }
856
857 string
858 AutomationLine::get_verbose_cursor_string (float fraction)
859 {
860         char buf[32];
861
862         if (_vc_uses_gain_mapping) {
863                 if (fraction == 0.0) {
864                         snprintf (buf, sizeof (buf), "-inf dB");
865                 } else {
866                         snprintf (buf, sizeof (buf), "%.1fdB", coefficient_to_dB (slider_position_to_gain (fraction)));
867                 }
868         } else {
869                 snprintf (buf, sizeof (buf), "%.2f", fraction);
870         }
871
872         return buf;
873 }
874
875 bool
876 AutomationLine::invalid_point (ALPoints& p, uint32_t index)
877 {
878         return p[index].x == max_frames && p[index].y == DBL_MAX;
879 }
880
881 void
882 AutomationLine::invalidate_point (ALPoints& p, uint32_t index)
883 {
884         p[index].x = max_frames;
885         p[index].y = DBL_MAX;
886 }
887
888 void
889 AutomationLine::start_drag (ControlPoint* cp, float fraction) 
890 {
891         if (trackview.editor.current_session() == 0) { /* how? */
892                 return;
893         }
894
895         string str;
896
897         if (cp) {
898                 str = _("automation event move");
899         } else {
900                 str = _("automation range drag");
901         }
902
903         trackview.editor.current_session()->begin_reversible_command (str);
904         trackview.editor.current_session()->add_command (new MementoCommand<AutomationLine>(*this, &get_state(), 0));
905         
906         first_drag_fraction = fraction;
907         last_drag_fraction = fraction;
908         drags = 0;
909 }
910
911 void
912 AutomationLine::point_drag (ControlPoint& cp, nframes_t x, float fraction, bool with_push) 
913 {
914         modify_view (cp, x, fraction, with_push);
915         drags++;
916 }
917
918 void
919 AutomationLine::line_drag (uint32_t i1, uint32_t i2, float fraction, bool with_push) 
920 {
921         double ydelta = fraction - last_drag_fraction;
922         
923         last_drag_fraction = fraction;
924
925         line_drag_cp1 = i1;
926         line_drag_cp2 = i2;
927         
928         ControlPoint *cp;
929
930         for (uint32_t i = i1 ; i <= i2; i++) {
931                 cp = nth (i);
932                 modify_view_point (*cp, trackview.editor.unit_to_frame (cp->get_x()), ((_height - cp->get_y()) /_height) + ydelta, with_push);
933         }
934
935         update_line ();
936
937         drags++;
938 }
939
940 void
941 AutomationLine::end_drag (ControlPoint* cp) 
942 {
943         if (drags) {
944
945                 if (cp) {
946                         sync_model_from (*cp);
947                 } else {
948                         sync_model_with_view_line (line_drag_cp1, line_drag_cp2);
949                 }
950
951                 update_pending = false;
952
953                 trackview.editor.current_session()->add_command (new MementoCommand<AutomationLine>(*this, 0, &get_state()));
954                 trackview.editor.current_session()->commit_reversible_command ();
955                 trackview.editor.current_session()->set_dirty ();
956         }
957 }
958
959 bool 
960 AutomationLine::control_points_adjacent (double xval, uint32_t & before, uint32_t& after)
961 {
962         ControlPoint *bcp = 0;
963         ControlPoint *acp = 0;
964         double unit_xval;
965
966         /* xval is in frames */
967
968         unit_xval = trackview.editor.frame_to_unit (xval);
969
970         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
971                 
972                 if ((*i)->get_x() <= unit_xval) {
973
974                         if (!bcp || (*i)->get_x() > bcp->get_x()) {
975                                 bcp = *i;
976                                 before = bcp->view_index;
977                         } 
978
979                 } else if ((*i)->get_x() > unit_xval) {
980                         acp = *i;
981                         after = acp->view_index;
982                         break;
983                 }
984         }
985
986         return bcp && acp;
987 }
988
989 bool
990 AutomationLine::is_last_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 last point in the list
997
998         if (!alist.empty() && mr.end == alist.end()) {
999                 return true;
1000         }
1001         
1002         return false;
1003 }
1004
1005 bool
1006 AutomationLine::is_first_point (ControlPoint& cp)
1007 {
1008         ModelRepresentation mr;
1009
1010         model_representation (cp, mr);
1011
1012         // If the list is not empty, and the point is the first point in the list
1013
1014         if (!alist.empty() && mr.start == alist.begin()) {
1015                 return true;
1016         }
1017         
1018         return false;
1019 }
1020
1021 // This is copied into AudioRegionGainLine
1022 void
1023 AutomationLine::remove_point (ControlPoint& cp)
1024 {
1025         ModelRepresentation mr;
1026
1027         model_representation (cp, mr);
1028
1029         trackview.editor.current_session()->begin_reversible_command (_("remove control point"));
1030         XMLNode &before = get_state();
1031
1032         alist.erase (mr.start, mr.end);
1033
1034         trackview.editor.current_session()->add_command(new MementoCommand<AutomationLine>(*this, &before, &get_state()));
1035         trackview.editor.current_session()->commit_reversible_command ();
1036         trackview.editor.current_session()->set_dirty ();
1037 }
1038
1039 void
1040 AutomationLine::get_selectables (nframes_t& start, nframes_t& end,
1041                                  double botfrac, double topfrac, list<Selectable*>& results)
1042 {
1043
1044         double top;
1045         double bot;
1046         nframes_t nstart;
1047         nframes_t nend;
1048         bool collecting = false;
1049
1050         /* Curse X11 and its inverted coordinate system! */
1051         
1052         bot = (1.0 - topfrac) * _height;
1053         top = (1.0 - botfrac) * _height;
1054         
1055         nstart = max_frames;
1056         nend = 0;
1057
1058         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1059                 
1060                 nframes_t when = (nframes_t) (*(*i)->model)->when;
1061
1062                 if (when >= start && when <= end) {
1063                         
1064                         if ((*i)->get_y() >= bot && (*i)->get_y() <= top) {
1065
1066                                 (*i)->show();
1067                                 (*i)->set_visible(true);
1068                                 collecting = true;
1069                                 nstart = min (nstart, when);
1070                                 nend = max (nend, when);
1071
1072                         } else {
1073                                 
1074                                 if (collecting) {
1075
1076                                         results.push_back (new AutomationSelectable (nstart, nend, botfrac, topfrac, trackview));
1077                                         collecting = false;
1078                                         nstart = max_frames;
1079                                         nend = 0;
1080                                 }
1081                         }
1082                 }
1083         }
1084
1085         if (collecting) {
1086                 results.push_back (new AutomationSelectable (nstart, nend, botfrac, topfrac, trackview));
1087         }
1088
1089 }
1090
1091 void
1092 AutomationLine::get_inverted_selectables (Selection&, list<Selectable*>& results)
1093 {
1094         // hmmm ....
1095 }
1096
1097 void
1098 AutomationLine::set_selected_points (PointSelection& points)
1099 {
1100         double top;
1101         double bot;
1102
1103         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1104                         (*i)->selected = false;
1105         }
1106
1107         if (points.empty()) {
1108                 goto out;
1109         } 
1110         
1111         for (PointSelection::iterator r = points.begin(); r != points.end(); ++r) {
1112                 
1113                 if (&(*r).track != &trackview) {
1114                         continue;
1115                 }
1116
1117                 /* Curse X11 and its inverted coordinate system! */
1118
1119                 bot = (1.0 - (*r).high_fract) * _height;
1120                 top = (1.0 - (*r).low_fract) * _height;
1121
1122                 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1123                         
1124                         double rstart, rend;
1125                         
1126                         rstart = trackview.editor.frame_to_unit ((*r).start);
1127                         rend = trackview.editor.frame_to_unit ((*r).end);
1128                         
1129                         if ((*i)->get_x() >= rstart && (*i)->get_x() <= rend) {
1130                                 
1131                                 if ((*i)->get_y() >= bot && (*i)->get_y() <= top) {
1132                                         
1133                                         (*i)->selected = true;
1134                                 }
1135                         }
1136
1137                 }
1138         }
1139
1140   out:
1141         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1142                 (*i)->show_color (false, !points_visible);
1143         }
1144
1145 }
1146
1147 void
1148 AutomationLine::show_selection ()
1149 {
1150         TimeSelection& time (trackview.editor.get_selection().time);
1151
1152         // cerr << "show selection\n";
1153
1154         for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1155                 
1156                 (*i)->selected = false;
1157
1158                 for (list<AudioRange>::iterator r = time.begin(); r != time.end(); ++r) {
1159                         double rstart, rend;
1160                         
1161                         rstart = trackview.editor.frame_to_unit ((*r).start);
1162                         rend = trackview.editor.frame_to_unit ((*r).end);
1163                         
1164                         if ((*i)->get_x() >= rstart && (*i)->get_x() <= rend) {
1165                                 (*i)->selected = true;
1166                                 break;
1167                         }
1168                 }
1169                 
1170                 (*i)->show_color (false, !points_visible);
1171         }
1172 }
1173
1174 void
1175 AutomationLine::hide_selection ()
1176 {
1177         // cerr << "hide selection\n";
1178 //      show_selection ();
1179 }
1180
1181 void
1182 AutomationLine::list_changed (Change ignored)
1183 {
1184         queue_reset ();
1185 }
1186
1187 void
1188 AutomationLine::reset_callback (const AutomationList& events)
1189 {
1190         ALPoints tmp_points;
1191         uint32_t npoints = events.size();
1192
1193         if (npoints == 0) {
1194                 for (vector<ControlPoint*>::iterator i = control_points.begin(); i != control_points.end(); ++i) {
1195                         delete *i;
1196                 }
1197                 control_points.clear ();
1198                 line->hide();
1199                 return;
1200         }
1201
1202         AutomationList::const_iterator ai;
1203
1204         for (ai = events.const_begin(); ai != events.const_end(); ++ai) {
1205
1206                 double 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 &
1275 AutomationLine::get_state (void)
1276 {
1277         /* function as a proxy for the model */
1278         return alist.get_state();
1279 }
1280
1281 int 
1282 AutomationLine::set_state (const XMLNode &node)
1283 {
1284         /* function as a proxy for the model */
1285         return alist.set_state (node);
1286 }