fix bad behaviour by +/- buttons in edit group list. yes, i was supposed to be in...
[ardour.git] / gtk2_ardour / automation_time_axis.cc
1 #include <ardour/route.h>
2 #include <pbd/memento_command.h>
3
4 #include "ardour_ui.h"
5 #include "automation_time_axis.h"
6 #include "automation_line.h"
7 #include "public_editor.h"
8 #include "simplerect.h"
9 #include "selection.h"
10 #include "ghostregion.h"
11 #include "rgb_macros.h"
12 #include "automation_selectable.h"
13 #include "point_selection.h"
14 #include "canvas_impl.h"
15 #include "utils.h"
16
17 #include "i18n.h"
18
19 using namespace ARDOUR;
20 using namespace PBD;
21 using namespace Gtk;
22 using namespace Editing;
23
24 AutomationTimeAxisView::AutomationTimeAxisView (Session& s, boost::shared_ptr<Route> r, PublicEditor& e, TimeAxisView& rent, 
25                                                 ArdourCanvas::Canvas& canvas, const string & nom, 
26                                                 const string & state_name, const string & nomparent)
27
28         : AxisView (s), 
29           TimeAxisView (s, e, &rent, canvas),
30           route (r),
31           _name (nom),
32           _state_name (state_name),
33           height_button (_("h")),
34           clear_button (_("clear")),
35           auto_button (X_("")) /* force addition of a label */
36 {
37         automation_menu = 0;
38         in_destructor = false;
39         auto_off_item = 0;
40         auto_touch_item = 0;
41         auto_write_item = 0;
42         auto_play_item = 0;
43         ignore_state_request = false;
44         first_call_to_set_height = true;
45
46         //      base_rect = gnome_canvas_item_new (GNOME_CANVAS_GROUP(canvas_display),
47         //                       gnome_canvas_simplerect_get_type(),
48         //                       "x1", 0.0,
49         //                       "y1", 0.0,
50         //                       "x2", 1000000.0,
51         //                       "outline_color_rgba", color_map[cAutomationTrackOutline],
52         //                       /* outline ends and bottom */
53         //                       "outline_what", (guint32) (0x1|0x2|0x8),
54         //                       "fill_color_rgba", color_map[cAutomationTrackFill],
55         //                       NULL);
56         base_rect = new SimpleRect(*canvas_display);
57         base_rect->property_x1() = 0.0;
58         base_rect->property_y1() = 0.0;
59         base_rect->property_x2() = 1000000.0;
60         base_rect->property_outline_color_rgba() = color_map[cAutomationTrackOutline];
61         /* outline ends and bottom */
62         base_rect->property_outline_what() = (guint32) (0x1|0x2|0x8);
63         base_rect->property_fill_color_rgba() = color_map[cAutomationTrackFill];
64         
65         base_rect->set_data ("trackview", this);
66
67         base_rect->signal_event().connect (bind (mem_fun (editor, &PublicEditor::canvas_automation_track_event),
68                                                  base_rect, this));
69
70         hide_button.add (*(manage (new Gtk::Image (get_xpm("small_x.xpm")))));
71
72         height_button.set_name ("TrackSizeButton");
73         auto_button.set_name ("TrackVisualButton");
74         clear_button.set_name ("TrackVisualButton");
75         hide_button.set_name ("TrackRemoveButton");
76
77         controls_table.set_no_show_all();
78
79         ARDOUR_UI::instance()->tooltips().set_tip(height_button, _("track height"));
80         ARDOUR_UI::instance()->tooltips().set_tip(auto_button, _("automation state"));
81         ARDOUR_UI::instance()->tooltips().set_tip(clear_button, _("clear track"));
82         ARDOUR_UI::instance()->tooltips().set_tip(hide_button, _("hide track"));
83
84         /* rearrange the name display */
85
86         /* we never show these for automation tracks, so make
87            life easier and remove them.
88         */
89
90         hide_name_entry();
91
92         /* move the name label over a bit */
93
94         string shortpname = _name;
95         bool shortened = false;
96         
97         if (_name.length()) {
98                 if (shortpname.length() > 18) {
99                         shortpname = shortpname.substr (0, 16);
100                         shortpname += "...";
101                         shortened = true;
102                 }
103         }
104         name_label.set_text (shortpname);
105         name_label.set_alignment (Gtk::ALIGN_CENTER, Gtk::ALIGN_CENTER);
106
107         if (nomparent.length()) {
108
109                 /* limit the plug name string */
110
111                 string pname = nomparent;
112
113                 if (pname.length() > 14) {
114                         pname = pname.substr (0, 11);
115                         pname += "...";
116                         shortened = true;
117                 }
118
119                 plugname = new Label (pname);
120                 plugname->set_name (X_("TrackPlugName"));
121                 plugname->show();
122                 name_label.set_name (X_("TrackParameterName"));
123                 controls_table.remove (name_hbox);
124                 controls_table.attach (*plugname, 1, 5, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
125                 plugname_packed = true;
126                 controls_table.attach (name_hbox, 1, 5, 1, 2, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
127         } else {
128                 plugname = 0;
129                 plugname_packed = false;
130         }
131
132         if (shortened) {
133                 string tipname = nomparent;
134                 if (!tipname.empty()) {
135                         tipname += ": ";
136                 }
137                 tipname += _name;
138                 ARDOUR_UI::instance()->tooltips().set_tip(controls_ebox, tipname);
139         }
140         
141         /* add the buttons */
142         controls_table.attach (hide_button, 0, 1, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
143         controls_table.attach (height_button, 0, 1, 1, 2, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
144
145         controls_table.attach (auto_button, 5, 8, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
146         controls_table.attach (clear_button, 5, 8, 1, 2, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
147
148         controls_table.show_all ();
149
150         height_button.signal_clicked().connect (mem_fun(*this, &AutomationTimeAxisView::height_clicked));
151         clear_button.signal_clicked().connect (mem_fun(*this, &AutomationTimeAxisView::clear_clicked));
152         hide_button.signal_clicked().connect (mem_fun(*this, &AutomationTimeAxisView::hide_clicked));
153         auto_button.signal_clicked().connect (mem_fun(*this, &AutomationTimeAxisView::auto_clicked));
154
155         controls_base_selected_name = X_("AutomationTrackControlsBaseSelected");
156         controls_base_unselected_name = X_("AutomationTrackControlsBase");
157         controls_ebox.set_name (controls_base_unselected_name);
158
159         controls_frame.set_shadow_type (Gtk::SHADOW_ETCHED_OUT);
160
161         XMLNode* xml_node = get_parent_with_state()->get_child_xml_node (_state_name);
162         set_state (*xml_node);
163
164         /* make sure labels etc. are correct */
165
166         automation_state_changed ();
167 }
168
169 AutomationTimeAxisView::~AutomationTimeAxisView ()
170 {
171         in_destructor = true;
172
173         for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
174                 delete *i;
175         }
176 }
177
178 void
179 AutomationTimeAxisView::auto_clicked ()
180 {
181         using namespace Menu_Helpers;
182
183         if (automation_menu == 0) {
184                 automation_menu = manage (new Menu);
185                 automation_menu->set_name ("ArdourContextMenu");
186                 MenuList& items (automation_menu->items());
187
188                 items.push_back (MenuElem (_("Manual"), 
189                                            bind (mem_fun(*this, &AutomationTimeAxisView::set_automation_state), (AutoState) Off)));
190                 items.push_back (MenuElem (_("Play"),
191                                            bind (mem_fun(*this, &AutomationTimeAxisView::set_automation_state), (AutoState) Play)));
192                 items.push_back (MenuElem (_("Write"),
193                                            bind (mem_fun(*this, &AutomationTimeAxisView::set_automation_state), (AutoState) Write)));
194                 items.push_back (MenuElem (_("Touch"),
195                                            bind (mem_fun(*this, &AutomationTimeAxisView::set_automation_state), (AutoState) Touch)));
196         }
197
198         automation_menu->popup (1, 0);
199 }
200
201
202 void
203 AutomationTimeAxisView::automation_state_changed ()
204 {
205         AutoState state;
206
207         /* update button label */
208
209         if (lines.empty()) {
210                 state = Off;
211         } else {
212                 state = lines.front()->the_list().automation_state ();
213         }
214
215         switch (state & (Off|Play|Touch|Write)) {
216         case Off:
217                 auto_button.set_label (_("Manual"));
218                 if (auto_off_item) {
219                         ignore_state_request = true;
220                         auto_off_item->set_active (true);
221                         auto_play_item->set_active (false);
222                         auto_touch_item->set_active (false);
223                         auto_write_item->set_active (false);
224                         ignore_state_request = false;
225                 }
226                 break;
227         case Play:
228                 auto_button.set_label (_("Play"));
229                 if (auto_play_item) {
230                         ignore_state_request = true;
231                         auto_play_item->set_active (true);
232                         auto_off_item->set_active (false);
233                         auto_touch_item->set_active (false);
234                         auto_write_item->set_active (false);
235                         ignore_state_request = false;
236                 }
237                 break;
238         case Write:
239                 auto_button.set_label (_("Write"));
240                 if (auto_write_item) {
241                         ignore_state_request = true;
242                         auto_write_item->set_active (true);
243                         auto_off_item->set_active (false);
244                         auto_play_item->set_active (false);
245                         auto_touch_item->set_active (false);
246                         ignore_state_request = false;
247                 }
248                 break;
249         case Touch:
250                 auto_button.set_label (_("Touch"));
251                 if (auto_touch_item) {
252                         ignore_state_request = true;
253                         auto_touch_item->set_active (true);
254                         auto_off_item->set_active (false);
255                         auto_play_item->set_active (false);
256                         auto_write_item->set_active (false);
257                         ignore_state_request = false;
258                 }
259                 break;
260         default:
261                 auto_button.set_label (_("???"));
262                 break;
263         }
264 }
265
266 void
267 AutomationTimeAxisView::height_clicked ()
268 {
269         popup_size_menu (0);
270 }
271
272 void
273 AutomationTimeAxisView::clear_clicked ()
274 {
275         _session.begin_reversible_command (_("clear automation"));
276         for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
277                 (*i)->clear ();
278         }
279         _session.commit_reversible_command ();
280 }
281
282 void
283 AutomationTimeAxisView::set_height (TrackHeight ht)
284 {
285         uint32_t h = height_to_pixels (ht);
286         bool changed = (height != (uint32_t) h);
287
288         bool changed_between_small_and_normal = ( (ht == Small || ht == Smaller) ^ (height_style == Small || height_style == Smaller) );
289
290         TimeAxisView* state_parent = get_parent_with_state ();
291         XMLNode* xml_node = state_parent->get_child_xml_node (_state_name);
292
293         TimeAxisView::set_height (ht);
294         base_rect->property_y2() = h;
295
296         for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
297                 (*i)->set_height (h);
298         }
299
300         for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
301                 (*i)->set_height ();
302         }
303
304
305         switch (ht) {
306         case Largest:
307                 xml_node->add_property ("track_height", "largest");
308                 break;
309
310         case Large:
311                 xml_node->add_property ("track_height", "large");
312                 break;
313
314         case Larger:
315                 xml_node->add_property ("track_height", "larger");
316                 break;
317
318         case Normal:
319                 xml_node->add_property ("track_height", "normal");
320                 break;
321
322         case Smaller:
323                 xml_node->add_property ("track_height", "smaller");
324                 break;
325
326         case Small:
327                 xml_node->add_property ("track_height", "small");
328                 break;
329         }
330
331         if (changed_between_small_and_normal || first_call_to_set_height) {
332                 first_call_to_set_height = false;
333                 switch (ht) {
334                         case Largest:
335                         case Large:
336                         case Larger:
337                         case Normal:
338
339                                 controls_table.remove (name_hbox);
340
341                                 if (plugname) {
342                                         if (plugname_packed) {
343                                                 controls_table.remove (*plugname);
344                                                 plugname_packed = false;
345                                         }
346                                         controls_table.attach (*plugname, 1, 5, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
347                                         plugname_packed = true;
348                                         controls_table.attach (name_hbox, 1, 5, 1, 2, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
349                                 } else {
350                                         controls_table.attach (name_hbox, 1, 5, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
351                                 }
352                                 hide_name_entry ();
353                                 show_name_label ();
354                                 name_hbox.show_all ();
355
356                                 auto_button.show();
357                                 height_button.show();
358                                 clear_button.show();
359                                 hide_button.show_all();
360                                 break;
361
362                         case Smaller:
363                         case Small:
364
365                                 controls_table.remove (name_hbox);
366                                 if (plugname) {
367                                         if (plugname_packed) {
368                                                 controls_table.remove (*plugname);
369                                                 plugname_packed = false;
370                                         }
371                                 }
372                                 controls_table.attach (name_hbox, 1, 5, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
373                                 controls_table.hide_all ();
374                                 hide_name_entry ();
375                                 show_name_label ();
376                                 name_hbox.show_all ();
377
378                                 auto_button.hide();
379                                 height_button.hide();
380                                 clear_button.hide();
381                                 hide_button.hide();
382                                 break;
383                 }
384         }
385
386         if (changed) {
387                 /* only emit the signal if the height really changed */
388                 route->gui_changed ("track_height", (void *) 0); /* EMIT_SIGNAL */
389         }
390 }
391
392 void
393 AutomationTimeAxisView::set_samples_per_unit (double spu)
394 {
395         TimeAxisView::set_samples_per_unit (editor.get_current_zoom());
396
397         for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
398                 (*i)->reset ();
399         }
400 }
401  
402 void
403 AutomationTimeAxisView::hide_clicked ()
404 {
405         // LAME fix for refreshing the hide button
406         hide_button.set_sensitive(false);
407         
408         set_marked_for_display (false);
409         hide ();
410         
411         hide_button.set_sensitive(true);
412 }
413
414 void
415 AutomationTimeAxisView::build_display_menu ()
416 {
417         using namespace Menu_Helpers;
418
419         /* get the size menu ready */
420
421         build_size_menu ();
422
423         /* prepare it */
424
425         TimeAxisView::build_display_menu ();
426
427         /* now fill it with our stuff */
428
429         MenuList& items = display_menu->items();
430
431         items.push_back (MenuElem (_("Height"), *size_menu));
432         items.push_back (SeparatorElem());
433         items.push_back (MenuElem (_("Hide"), mem_fun(*this, &AutomationTimeAxisView::hide_clicked)));
434         items.push_back (SeparatorElem());
435         items.push_back (MenuElem (_("Clear"), mem_fun(*this, &AutomationTimeAxisView::clear_clicked)));
436         items.push_back (SeparatorElem());
437
438         Menu* auto_state_menu = manage (new Menu);
439         auto_state_menu->set_name ("ArdourContextMenu");
440         MenuList& as_items = auto_state_menu->items();
441         
442         as_items.push_back (CheckMenuElem (_("Manual"), 
443                                            bind (mem_fun(*this, &AutomationTimeAxisView::set_automation_state), (AutoState) Off)));
444         auto_off_item = dynamic_cast<CheckMenuItem*>(&as_items.back());
445
446         as_items.push_back (CheckMenuElem (_("Play"),
447                                            bind (mem_fun(*this, &AutomationTimeAxisView::set_automation_state), (AutoState) Play)));
448         auto_play_item = dynamic_cast<CheckMenuItem*>(&as_items.back());
449
450         as_items.push_back (CheckMenuElem (_("Write"),
451                                            bind (mem_fun(*this, &AutomationTimeAxisView::set_automation_state), (AutoState) Write)));
452         auto_write_item = dynamic_cast<CheckMenuItem*>(&as_items.back());
453
454         as_items.push_back (CheckMenuElem (_("Touch"),
455                                            bind (mem_fun(*this, &AutomationTimeAxisView::set_automation_state), (AutoState) Touch)));
456         auto_touch_item = dynamic_cast<CheckMenuItem*>(&as_items.back());
457
458         items.push_back (MenuElem (_("State"), *auto_state_menu));
459
460         /* make sure the automation menu state is correct */
461
462         automation_state_changed ();
463 }
464
465 bool
466 AutomationTimeAxisView::cut_copy_clear (Selection& selection, CutCopyOp op)
467 {
468         bool ret = false;
469
470         for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
471                 ret = cut_copy_clear_one ((**i), selection, op);
472         }
473
474         return ret;
475 }
476
477 bool
478 AutomationTimeAxisView::cut_copy_clear_one (AutomationLine& line, Selection& selection, CutCopyOp op)
479 {
480         AutomationList* what_we_got = 0;
481         AutomationList& alist (line.the_list());
482         bool ret = false;
483
484         XMLNode &before = alist.get_state();
485
486         switch (op) {
487         case Cut:
488                 if ((what_we_got = alist.cut (selection.time.front().start, selection.time.front().end)) != 0) {
489                         editor.get_cut_buffer().add (what_we_got);
490                         _session.add_command(new MementoCommand<AutomationList>(alist, &before, &alist.get_state()));
491                         ret = true;
492                 }
493                 break;
494         case Copy:
495                 if ((what_we_got = alist.copy (selection.time.front().start, selection.time.front().end)) != 0) {
496                         editor.get_cut_buffer().add (what_we_got);
497                 }
498                 break;
499
500         case Clear:
501                 if ((what_we_got = alist.cut (selection.time.front().start, selection.time.front().end)) != 0) {
502                         _session.add_command(new MementoCommand<AutomationList>(alist, &before, &alist.get_state()));
503                         delete what_we_got;
504                         what_we_got = 0;
505                         ret = true;
506                 }
507                 break;
508         }
509
510         if (what_we_got) {
511                 for (AutomationList::iterator x = what_we_got->begin(); x != what_we_got->end(); ++x) {
512                         double foo = (*x)->value;
513                         line.model_to_view_y (foo);
514                         (*x)->value = foo;
515                 }
516         }
517
518         return ret;
519 }
520
521 void
522 AutomationTimeAxisView::reset_objects (PointSelection& selection)
523 {
524         for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
525                 reset_objects_one ((**i), selection);
526         }
527 }
528
529 void
530 AutomationTimeAxisView::reset_objects_one (AutomationLine& line, PointSelection& selection)
531 {
532         AutomationList& alist (line.the_list());
533
534         _session.add_command (new MementoCommand<AutomationList>(alist, &alist.get_state(), 0));
535
536         for (PointSelection::iterator i = selection.begin(); i != selection.end(); ++i) {
537
538                 if (&(*i).track != this) {
539                         continue;
540                 }
541                 
542                 alist.reset_range ((*i).start, (*i).end);
543         }
544 }
545
546 bool
547 AutomationTimeAxisView::cut_copy_clear_objects (PointSelection& selection, CutCopyOp op)
548 {
549         bool ret = false;
550
551         for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
552                 ret = cut_copy_clear_objects_one ((**i), selection, op);
553         }
554
555         return ret;
556 }
557
558 bool
559 AutomationTimeAxisView::cut_copy_clear_objects_one (AutomationLine& line, PointSelection& selection, CutCopyOp op)
560 {
561         AutomationList* what_we_got = 0;
562         AutomationList& alist (line.the_list());
563         bool ret = false;
564
565         XMLNode &before = alist.get_state();
566
567         for (PointSelection::iterator i = selection.begin(); i != selection.end(); ++i) {
568
569                 if (&(*i).track != this) {
570                         continue;
571                 }
572
573                 switch (op) {
574                 case Cut:
575                         if ((what_we_got = alist.cut ((*i).start, (*i).end)) != 0) {
576                                 editor.get_cut_buffer().add (what_we_got);
577                                 _session.add_command (new MementoCommand<AutomationList>(alist, &before, &alist.get_state()));
578                                 ret = true;
579                         }
580                         break;
581                 case Copy:
582                         if ((what_we_got = alist.copy ((*i).start, (*i).end)) != 0) {
583                                 editor.get_cut_buffer().add (what_we_got);
584                         }
585                         break;
586                         
587                 case Clear:
588                         if ((what_we_got = alist.cut ((*i).start, (*i).end)) != 0) {
589                                 _session.add_command (new MementoCommand<AutomationList>(alist, &before, &alist.get_state()));
590                                 delete what_we_got;
591                                 what_we_got = 0;
592                                 ret = true;
593                         }
594                         break;
595                 }
596         }
597                 
598         if (what_we_got) {
599                 for (AutomationList::iterator x = what_we_got->begin(); x != what_we_got->end(); ++x) {
600                         double foo = (*x)->value;
601                         line.model_to_view_y (foo);
602                         (*x)->value = foo;
603                 }
604         }
605
606         return ret;
607 }
608
609 bool
610 AutomationTimeAxisView::paste (nframes_t pos, float times, Selection& selection, size_t nth)
611 {
612         bool ret = true;
613
614         for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
615                 ret = paste_one (**i, pos, times, selection, nth);
616         }
617
618         return ret;
619 }
620
621 bool
622 AutomationTimeAxisView::paste_one (AutomationLine& line, nframes_t pos, float times, Selection& selection, size_t nth)
623 {
624         AutomationSelection::iterator p;
625         AutomationList& alist (line.the_list());
626         
627         for (p = selection.lines.begin(); p != selection.lines.end() && nth; ++p, --nth);
628
629         if (p == selection.lines.end()) {
630                 return false;
631         }
632
633         /* Make a copy of the list because we have to scale the
634            values from view coordinates to model coordinates, and we're
635            not supposed to modify the points in the selection.
636         */
637            
638         AutomationList copy (**p);
639
640         for (AutomationList::iterator x = copy.begin(); x != copy.end(); ++x) {
641                 double foo = (*x)->value;
642                 line.view_to_model_y (foo);
643                 (*x)->value = foo;
644         }
645
646         XMLNode &before = alist.get_state();
647         alist.paste (copy, pos, times);
648         _session.add_command (new MementoCommand<AutomationList>(alist, &before, &alist.get_state()));
649
650         return true;
651 }
652
653 void
654 AutomationTimeAxisView::add_ghost (GhostRegion* gr)
655 {
656         ghosts.push_back (gr);
657         gr->GoingAway.connect (mem_fun(*this, &AutomationTimeAxisView::remove_ghost));
658 }
659
660 void
661 AutomationTimeAxisView::remove_ghost (GhostRegion* gr)
662 {
663         if (in_destructor) {
664                 return;
665         }
666
667         list<GhostRegion*>::iterator i;
668
669         for (i = ghosts.begin(); i != ghosts.end(); ++i) {
670                 if ((*i) == gr) {
671                         ghosts.erase (i);
672                         break;
673                 }
674         }
675 }
676
677 void
678 AutomationTimeAxisView::get_selectables (nframes_t start, nframes_t end, double top, double bot, list<Selectable*>& results)
679 {
680         if (!lines.empty() && touched (top, bot)) {
681                 double topfrac;
682                 double botfrac;
683
684                 /* remember: this is X Window - coordinate space starts in upper left and moves down.
685                    y_position is the "origin" or "top" of the track.
686                 */
687
688                 double mybot = y_position + height; // XXX need to include Editor::track_spacing; 
689
690                 if (y_position >= top && mybot <= bot) {
691
692                         /* y_position is below top, mybot is above bot, so we're fully
693                            covered vertically.
694                         */
695
696                         topfrac = 1.0;
697                         botfrac = 0.0;
698
699                 } else {
700
701                         /* top and bot are within y_position .. mybot */
702
703                         topfrac = 1.0 - ((top - y_position) / height);
704                         botfrac = 1.0 - ((bot - y_position) / height);
705                 }
706
707                 for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
708                         (*i)->get_selectables (start, end, botfrac, topfrac, results);
709                 }
710         }
711 }
712
713 void
714 AutomationTimeAxisView::get_inverted_selectables (Selection& sel, list<Selectable*>& result)
715 {
716         for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
717                 (*i)->get_inverted_selectables (sel, result);
718         }
719 }
720
721 void
722 AutomationTimeAxisView::set_selected_points (PointSelection& points)
723 {
724         for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
725                 (*i)->set_selected_points (points);
726         }
727 }
728
729 void
730 AutomationTimeAxisView::clear_lines ()
731 {
732         for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
733                 delete *i;
734         }
735
736         lines.clear ();
737         automation_connection.disconnect ();
738 }
739
740 void
741 AutomationTimeAxisView::add_line (AutomationLine& line)
742 {
743         bool get = false;
744
745         if (lines.empty()) {
746                 /* first line is the Model for automation state */
747                 automation_connection = line.the_list().automation_state_changed.connect
748                         (mem_fun(*this, &AutomationTimeAxisView::automation_state_changed));
749                 get = true;
750         }
751
752         lines.push_back (&line);
753         line.set_height (height);
754
755         if (get) {
756                 /* pick up the current state */
757                 automation_state_changed ();
758         }
759 }
760
761 void
762 AutomationTimeAxisView::show_all_control_points ()
763 {
764         for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
765                 (*i)->show_all_control_points ();
766         }
767 }
768
769 void
770 AutomationTimeAxisView::hide_all_but_selected_control_points ()
771 {
772         for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
773                 (*i)->hide_all_but_selected_control_points ();
774         }
775 }
776
777 void
778 AutomationTimeAxisView::entered()
779 {
780         show_all_control_points ();
781 }
782
783 void
784 AutomationTimeAxisView::exited ()
785 {
786         hide_all_but_selected_control_points ();
787 }
788
789 void
790 AutomationTimeAxisView::set_state (const XMLNode& node)
791 {
792         TimeAxisView::set_state (node);
793 }
794
795 XMLNode*
796 AutomationTimeAxisView::get_state_node ()
797 {
798         TimeAxisView* state_parent = get_parent_with_state ();
799
800         if (state_parent) {
801                 return state_parent->get_child_xml_node (_state_name);
802         } else {
803                 return 0;
804         }
805 }