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