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