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