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