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