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