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