use new keybindings; update InfoPlist.strings
[ardour.git] / gtk2_ardour / automation_time_axis.cc
1 /*
2     Copyright (C) 2000-2007 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 */
19
20 #include <ardour/route.h>
21 #include <pbd/memento_command.h>
22
23 #include "ardour_ui.h"
24 #include "automation_time_axis.h"
25 #include "automation_line.h"
26 #include "public_editor.h"
27 #include "simplerect.h"
28 #include "selection.h"
29 #include "ghostregion.h"
30 #include "rgb_macros.h"
31 #include "automation_selectable.h"
32 #include "point_selection.h"
33 #include "canvas_impl.h"
34 #include "utils.h"
35
36 #include "i18n.h"
37
38 using namespace ARDOUR;
39 using namespace PBD;
40 using namespace Gtk;
41 using namespace Editing;
42
43 Pango::FontDescription* AutomationTimeAxisView::name_font = 0;
44 bool AutomationTimeAxisView::have_name_font = false;
45
46 AutomationTimeAxisView::AutomationTimeAxisView (Session& s, boost::shared_ptr<Route> r, PublicEditor& e, TimeAxisView& rent, 
47                                                 ArdourCanvas::Canvas& canvas, const string & nom, 
48                                                 const string & state_name, const string & nomparent)
49
50         : AxisView (s), 
51           TimeAxisView (s, e, &rent, canvas),
52           route (r),
53           _name (nom),
54           _state_name (state_name),
55           height_button (_("h")),
56           clear_button (_("clear")),
57           auto_button (X_("")) /* force addition of a label */
58 {
59         if (!have_name_font) {
60                 name_font = get_font_for_style (X_("AutomationTrackName"));
61                 have_name_font = true;
62         }
63
64         automation_menu = 0;
65         in_destructor = false;
66         auto_off_item = 0;
67         auto_touch_item = 0;
68         auto_write_item = 0;
69         auto_play_item = 0;
70         ignore_state_request = false;
71         first_call_to_set_height = true;
72
73         base_rect = new SimpleRect(*canvas_display);
74         base_rect->property_x1() = 0.0;
75         base_rect->property_y1() = 0.0;
76         base_rect->property_x2() = LONG_MAX - 2;
77         base_rect->property_outline_color_rgba() = ARDOUR_UI::config()->canvasvar_AutomationTrackOutline.get();
78         /* outline ends and bottom */
79         base_rect->property_outline_what() = (guint32) (0x1|0x2|0x8);
80         base_rect->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_AutomationTrackFill.get();
81         //base_rect->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_EnteredControlPoint.get();
82         
83         base_rect->set_data ("trackview", this);
84
85         base_rect->signal_event().connect (bind (mem_fun (editor, &PublicEditor::canvas_automation_track_event),
86                                                  base_rect, this));
87
88         hide_button.add (*(manage (new Gtk::Image (::get_icon("hide")))));
89
90         height_button.set_name ("TrackSizeButton");
91         auto_button.set_name ("TrackVisualButton");
92         clear_button.set_name ("TrackVisualButton");
93         hide_button.set_name ("TrackRemoveButton");
94
95         height_button.unset_flags (Gtk::CAN_FOCUS);
96         auto_button.unset_flags (Gtk::CAN_FOCUS);
97         clear_button.unset_flags (Gtk::CAN_FOCUS);
98         hide_button.unset_flags (Gtk::CAN_FOCUS);
99
100         controls_table.set_no_show_all();
101
102         ARDOUR_UI::instance()->tooltips().set_tip(height_button, _("track height"));
103         ARDOUR_UI::instance()->tooltips().set_tip(auto_button, _("automation state"));
104         ARDOUR_UI::instance()->tooltips().set_tip(clear_button, _("clear track"));
105         ARDOUR_UI::instance()->tooltips().set_tip(hide_button, _("hide track"));
106
107         /* rearrange the name display */
108
109         /* we never show these for automation tracks, so make
110            life easier and remove them.
111         */
112
113         hide_name_entry();
114
115         /* move the name label over a bit */
116
117         string shortpname = _name;
118         bool shortened = false;
119
120         int ignore_width;
121         shortpname = fit_to_pixels (_name, 60, *name_font, ignore_width, true);
122
123         if (shortpname != _name ){
124                 shortened = true;
125         }
126
127         name_label.set_text (shortpname);
128         name_label.set_alignment (Gtk::ALIGN_CENTER, Gtk::ALIGN_CENTER);
129
130         if (nomparent.length()) {
131
132                 /* limit the plug name string */
133
134                 string pname = fit_to_pixels (nomparent, 60, *name_font, ignore_width, true);
135                 if (pname != nomparent) {
136                         shortened = true;
137                 }
138
139                 plugname = new Label (pname);
140                 plugname->set_name (X_("TrackPlugName"));
141                 plugname->show();
142                 name_label.set_name (X_("TrackParameterName"));
143                 controls_table.remove (name_hbox);
144                 controls_table.attach (*plugname, 1, 5, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
145                 plugname_packed = true;
146                 controls_table.attach (name_hbox, 1, 5, 1, 2, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
147         } else {
148                 plugname = 0;
149                 plugname_packed = false;
150         }
151
152         if (shortened) {
153                 string tipname = nomparent;
154                 if (!tipname.empty()) {
155                         tipname += ": ";
156                 }
157                 tipname += _name;
158                 ARDOUR_UI::instance()->tooltips().set_tip(controls_ebox, tipname);
159         }
160         
161         /* add the buttons */
162         controls_table.attach (hide_button, 0, 1, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
163         controls_table.attach (height_button, 0, 1, 1, 2, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
164
165         controls_table.attach (auto_button, 5, 8, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
166         controls_table.attach (clear_button, 5, 8, 1, 2, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
167
168         controls_table.show_all ();
169
170         height_button.signal_clicked().connect (mem_fun(*this, &AutomationTimeAxisView::height_clicked));
171         clear_button.signal_clicked().connect (mem_fun(*this, &AutomationTimeAxisView::clear_clicked));
172         hide_button.signal_clicked().connect (mem_fun(*this, &AutomationTimeAxisView::hide_clicked));
173         auto_button.signal_clicked().connect (mem_fun(*this, &AutomationTimeAxisView::auto_clicked));
174
175         controls_base_selected_name = X_("AutomationTrackControlsBaseSelected");
176         controls_base_unselected_name = X_("AutomationTrackControlsBase");
177         controls_ebox.set_name (controls_base_unselected_name);
178
179         controls_frame.set_shadow_type (Gtk::SHADOW_ETCHED_OUT);
180
181         XMLNode* xml_node = get_parent_with_state()->get_child_xml_node (_state_name);
182
183         if (xml_node) {
184                 set_state (*xml_node);
185         } 
186
187         /* make sure labels etc. are correct */
188
189         automation_state_changed ();
190         ColorsChanged.connect (mem_fun (*this, &AutomationTimeAxisView::color_handler));
191 }
192
193 AutomationTimeAxisView::~AutomationTimeAxisView ()
194 {
195         in_destructor = true;
196
197         for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
198                 delete *i;
199         }
200 }
201
202 void
203 AutomationTimeAxisView::auto_clicked ()
204 {
205         using namespace Menu_Helpers;
206
207         if (automation_menu == 0) {
208                 automation_menu = manage (new Menu);
209                 automation_menu->set_name ("ArdourContextMenu");
210                 MenuList& items (automation_menu->items());
211
212                 items.push_back (MenuElem (_("Manual"), 
213                                            bind (mem_fun(*this, &AutomationTimeAxisView::set_automation_state), (AutoState) Off)));
214                 items.push_back (MenuElem (_("Play"),
215                                            bind (mem_fun(*this, &AutomationTimeAxisView::set_automation_state), (AutoState) Play)));
216                 items.push_back (MenuElem (_("Write"),
217                                            bind (mem_fun(*this, &AutomationTimeAxisView::set_automation_state), (AutoState) Write)));
218                 items.push_back (MenuElem (_("Touch"),
219                                            bind (mem_fun(*this, &AutomationTimeAxisView::set_automation_state), (AutoState) Touch)));
220         }
221
222         automation_menu->popup (1, gtk_get_current_event_time());
223 }
224
225
226 void
227 AutomationTimeAxisView::automation_state_changed ()
228 {
229         AutoState state;
230
231         /* update button label */
232
233         if (lines.empty()) {
234                 state = Off;
235         } else {
236                 state = lines.front()->the_list().automation_state ();
237         }
238
239         switch (state & (Off|Play|Touch|Write)) {
240         case Off:
241                 auto_button.set_label (_("Manual"));
242                 if (auto_off_item) {
243                         ignore_state_request = true;
244                         auto_off_item->set_active (true);
245                         auto_play_item->set_active (false);
246                         auto_touch_item->set_active (false);
247                         auto_write_item->set_active (false);
248                         ignore_state_request = false;
249                 }
250                 break;
251         case Play:
252                 auto_button.set_label (_("Play"));
253                 if (auto_play_item) {
254                         ignore_state_request = true;
255                         auto_play_item->set_active (true);
256                         auto_off_item->set_active (false);
257                         auto_touch_item->set_active (false);
258                         auto_write_item->set_active (false);
259                         ignore_state_request = false;
260                 }
261                 break;
262         case Write:
263                 auto_button.set_label (_("Write"));
264                 if (auto_write_item) {
265                         ignore_state_request = true;
266                         auto_write_item->set_active (true);
267                         auto_off_item->set_active (false);
268                         auto_play_item->set_active (false);
269                         auto_touch_item->set_active (false);
270                         ignore_state_request = false;
271                 }
272                 break;
273         case Touch:
274                 auto_button.set_label (_("Touch"));
275                 if (auto_touch_item) {
276                         ignore_state_request = true;
277                         auto_touch_item->set_active (true);
278                         auto_off_item->set_active (false);
279                         auto_play_item->set_active (false);
280                         auto_write_item->set_active (false);
281                         ignore_state_request = false;
282                 }
283                 break;
284         default:
285                 auto_button.set_label (_("???"));
286                 break;
287         }
288 }
289
290 void
291 AutomationTimeAxisView::height_clicked ()
292 {
293         popup_size_menu (0);
294 }
295
296 void
297 AutomationTimeAxisView::clear_clicked ()
298 {
299         _session.begin_reversible_command (_("clear automation"));
300         for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
301                 (*i)->clear ();
302         }
303         _session.commit_reversible_command ();
304 }
305
306 void
307 AutomationTimeAxisView::set_height (uint32_t h)
308 {
309         bool changed = (height != (uint32_t) h);
310         bool changed_between_small_and_normal = ( (h == hSmall || h == hSmaller) ^ (height == hSmall || height == hSmaller) );
311
312         TimeAxisView* state_parent = get_parent_with_state ();
313         XMLNode* xml_node = (state_parent ? state_parent->get_child_xml_node (_state_name) : NULL);
314
315         TimeAxisView::set_height (h);
316         base_rect->property_y2() = h;
317
318         for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
319                 (*i)->set_height (h);
320         }
321
322         for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
323                 (*i)->set_height ();
324         }
325
326         char buf[32];
327         snprintf (buf, sizeof (buf), "%u", height);
328         if (xml_node) {
329                 xml_node->add_property ("height", buf);
330         }
331
332         if (changed_between_small_and_normal || first_call_to_set_height) {
333                 first_call_to_set_height = false;
334
335                 if (h >= hNormal) {
336                         controls_table.remove (name_hbox);
337                         
338                         if (plugname) {
339                                 if (plugname_packed) {
340                                         controls_table.remove (*plugname);
341                                         plugname_packed = false;
342                                 }
343                                 controls_table.attach (*plugname, 1, 5, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
344                                 plugname_packed = true;
345                                 controls_table.attach (name_hbox, 1, 5, 1, 2, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
346                         } else {
347                                 controls_table.attach (name_hbox, 1, 5, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
348                         }
349                         hide_name_entry ();
350                         show_name_label ();
351                         name_hbox.show_all ();
352                         
353                         auto_button.show();
354                         height_button.show();
355                         clear_button.show();
356                         hide_button.show_all();
357
358                 } else if (h >= hSmall) {
359                         controls_table.remove (name_hbox);
360                         if (plugname) {
361                                 if (plugname_packed) {
362                                         controls_table.remove (*plugname);
363                                         plugname_packed = false;
364                                 }
365                         }
366                         controls_table.attach (name_hbox, 1, 5, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
367                         controls_table.hide_all ();
368                         hide_name_entry ();
369                         show_name_label ();
370                         name_hbox.show_all ();
371                         
372                         auto_button.hide();
373                         height_button.hide();
374                         clear_button.hide();
375                         hide_button.hide();
376                 }
377         } else if (h >= hNormal){
378                 auto_button.show();
379                 height_button.show();
380                 clear_button.show();
381                 hide_button.show_all();
382         }
383
384         if (changed) {
385                 /* only emit the signal if the height really changed */
386                 route->gui_changed ("visible_tracks", (void *) 0); /* EMIT_SIGNAL */
387         }
388 }
389
390 void
391 AutomationTimeAxisView::set_samples_per_unit (double spu)
392 {
393         TimeAxisView::set_samples_per_unit (editor.get_current_zoom());
394
395         for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
396                 (*i)->reset ();
397         }
398 }
399  
400 void
401 AutomationTimeAxisView::hide_clicked ()
402 {
403         // LAME fix for refreshing the hide button
404         hide_button.set_sensitive(false);
405         
406         set_marked_for_display (false);
407         hide ();
408         
409         hide_button.set_sensitive(true);
410 }
411
412 void
413 AutomationTimeAxisView::build_display_menu ()
414 {
415         using namespace Menu_Helpers;
416
417         /* get the size menu ready */
418
419         build_size_menu ();
420
421         /* prepare it */
422
423         TimeAxisView::build_display_menu ();
424
425         /* now fill it with our stuff */
426
427         MenuList& items = display_menu->items();
428
429         items.push_back (MenuElem (_("Height"), *size_menu));
430         items.push_back (SeparatorElem());
431         items.push_back (MenuElem (_("Hide"), mem_fun(*this, &AutomationTimeAxisView::hide_clicked)));
432         items.push_back (SeparatorElem());
433         items.push_back (MenuElem (_("Clear"), mem_fun(*this, &AutomationTimeAxisView::clear_clicked)));
434         items.push_back (SeparatorElem());
435
436         Menu* auto_state_menu = manage (new Menu);
437         auto_state_menu->set_name ("ArdourContextMenu");
438         MenuList& as_items = auto_state_menu->items();
439         
440         as_items.push_back (CheckMenuElem (_("Manual"), 
441                                            bind (mem_fun(*this, &AutomationTimeAxisView::set_automation_state), (AutoState) Off)));
442         auto_off_item = dynamic_cast<CheckMenuItem*>(&as_items.back());
443
444         as_items.push_back (CheckMenuElem (_("Play"),
445                                            bind (mem_fun(*this, &AutomationTimeAxisView::set_automation_state), (AutoState) Play)));
446         auto_play_item = dynamic_cast<CheckMenuItem*>(&as_items.back());
447
448         as_items.push_back (CheckMenuElem (_("Write"),
449                                            bind (mem_fun(*this, &AutomationTimeAxisView::set_automation_state), (AutoState) Write)));
450         auto_write_item = dynamic_cast<CheckMenuItem*>(&as_items.back());
451
452         as_items.push_back (CheckMenuElem (_("Touch"),
453                                            bind (mem_fun(*this, &AutomationTimeAxisView::set_automation_state), (AutoState) Touch)));
454         auto_touch_item = dynamic_cast<CheckMenuItem*>(&as_items.back());
455
456         items.push_back (MenuElem (_("State"), *auto_state_menu));
457
458         /* make sure the automation menu state is correct */
459
460         automation_state_changed ();
461 }
462
463 bool
464 AutomationTimeAxisView::cut_copy_clear (Selection& selection, CutCopyOp op)
465 {
466         bool ret = false;
467
468         for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
469                 ret = cut_copy_clear_one ((**i), selection, op);
470         }
471
472         return ret;
473 }
474
475 bool
476 AutomationTimeAxisView::cut_copy_clear_one (AutomationLine& line, Selection& selection, CutCopyOp op)
477 {
478         AutomationList* what_we_got = 0;
479         AutomationList& alist (line.the_list());
480         bool ret = false;
481
482         XMLNode &before = alist.get_state();
483
484         switch (op) {
485         case Cut:
486                 if ((what_we_got = alist.cut (selection.time.front().start, selection.time.front().end)) != 0) {
487                         editor.get_cut_buffer().add (what_we_got);
488                         _session.add_command(new MementoCommand<AutomationList>(alist, &before, &alist.get_state()));
489                         ret = true;
490                 }
491                 break;
492         case Copy:
493                 if ((what_we_got = alist.copy (selection.time.front().start, selection.time.front().end)) != 0) {
494                         editor.get_cut_buffer().add (what_we_got);
495                 }
496                 break;
497
498         case Clear:
499                 if ((what_we_got = alist.cut (selection.time.front().start, selection.time.front().end)) != 0) {
500                         _session.add_command(new MementoCommand<AutomationList>(alist, &before, &alist.get_state()));
501                         delete what_we_got;
502                         what_we_got = 0;
503                         ret = true;
504                 }
505                 break;
506         }
507
508         if (what_we_got) {
509                 for (AutomationList::iterator x = what_we_got->begin(); x != what_we_got->end(); ++x) {
510                         double foo = (*x)->value;
511                         line.model_to_view_y (foo);
512                         (*x)->value = foo;
513                 }
514         }
515
516         return ret;
517 }
518
519 void
520 AutomationTimeAxisView::reset_objects (PointSelection& selection)
521 {
522         for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
523                 reset_objects_one ((**i), selection);
524         }
525 }
526
527 void
528 AutomationTimeAxisView::reset_objects_one (AutomationLine& line, PointSelection& selection)
529 {
530         AutomationList& alist (line.the_list());
531
532         _session.add_command (new MementoCommand<AutomationList>(alist, &alist.get_state(), 0));
533
534         for (PointSelection::iterator i = selection.begin(); i != selection.end(); ++i) {
535
536                 if (&(*i).track != this) {
537                         continue;
538                 }
539                 
540                 alist.reset_range ((*i).start, (*i).end);
541         }
542 }
543
544 bool
545 AutomationTimeAxisView::cut_copy_clear_objects (PointSelection& selection, CutCopyOp op)
546 {
547         bool ret = false;
548
549         for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
550                 ret = cut_copy_clear_objects_one ((**i), selection, op);
551         }
552
553         return ret;
554 }
555
556 bool
557 AutomationTimeAxisView::cut_copy_clear_objects_one (AutomationLine& line, PointSelection& selection, CutCopyOp op)
558 {
559         AutomationList* what_we_got = 0;
560         AutomationList& alist (line.the_list());
561         bool ret = false;
562
563         XMLNode &before = alist.get_state();
564
565         for (PointSelection::iterator i = selection.begin(); i != selection.end(); ++i) {
566
567                 if (&(*i).track != this) {
568                         continue;
569                 }
570
571                 switch (op) {
572                 case Cut:
573                         if ((what_we_got = alist.cut ((*i).start, (*i).end)) != 0) {
574                                 editor.get_cut_buffer().add (what_we_got);
575                                 _session.add_command (new MementoCommand<AutomationList>(alist, new XMLNode (before), &alist.get_state()));
576                                 ret = true;
577                         }
578                         break;
579                 case Copy:
580                         if ((what_we_got = alist.copy ((*i).start, (*i).end)) != 0) {
581                                 editor.get_cut_buffer().add (what_we_got);
582                         }
583                         break;
584                         
585                 case Clear:
586                         if ((what_we_got = alist.cut ((*i).start, (*i).end)) != 0) {
587                                 _session.add_command (new MementoCommand<AutomationList>(alist, new XMLNode (before), &alist.get_state()));
588                                 delete what_we_got;
589                                 what_we_got = 0;
590                                 ret = true;
591                         }
592                         break;
593                 }
594         }
595
596         delete &before;
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;
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_colors () {
791
792     for( list<GhostRegion *>::iterator i=ghosts.begin(); i != ghosts.end(); i++ ) {
793         (*i)->set_colors();
794     }
795     
796     for( vector<AutomationLine *>::iterator i=lines.begin(); i != lines.end(); i++ ) {
797         (*i)->set_colors();
798     }
799
800 }
801
802 void
803 AutomationTimeAxisView::color_handler () 
804 {
805     set_colors();
806 }
807
808
809
810 int
811 AutomationTimeAxisView::set_state (const XMLNode& node)
812 {
813         return TimeAxisView::set_state (node);
814 }
815
816 XMLNode*
817 AutomationTimeAxisView::get_state_node ()
818 {
819         TimeAxisView* state_parent = get_parent_with_state ();
820
821         if (state_parent) {
822                 return state_parent->get_child_xml_node (_state_name);
823         } else {
824                 return 0;
825         }
826 }