introduce GUIObjectState; massive, pervasive changes in visibility and height managem...
[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 <utility>
21 #include <gtkmm2ext/barcontroller.h>
22
23 #include "pbd/memento_command.h"
24 #include "pbd/stacktrace.h"
25
26 #include "ardour/automation_control.h"
27 #include "ardour/event_type_map.h"
28 #include "ardour/route.h"
29 #include "ardour/session.h"
30
31 #include "ardour_ui.h"
32 #include "automation_time_axis.h"
33 #include "automation_streamview.h"
34 #include "global_signals.h"
35 #include "gui_thread.h"
36 #include "route_time_axis.h"
37 #include "automation_line.h"
38 #include "public_editor.h"
39 #include "simplerect.h"
40 #include "selection.h"
41 #include "rgb_macros.h"
42 #include "point_selection.h"
43 #include "canvas_impl.h"
44 #include "utils.h"
45
46 #include "i18n.h"
47
48 using namespace std;
49 using namespace ARDOUR;
50 using namespace PBD;
51 using namespace Gtk;
52 using namespace Gtkmm2ext;
53 using namespace Editing;
54
55 Pango::FontDescription AutomationTimeAxisView::name_font;
56 bool AutomationTimeAxisView::have_name_font = false;
57
58
59 /** \a a the automatable object this time axis is to display data for.
60  * For route/track automation (e.g. gain) pass the route for both \r and \a.
61  * For route child (e.g. plugin) automation, pass the child for \a.
62  * For region automation (e.g. MIDI CC), pass null for \a.
63  */
64 AutomationTimeAxisView::AutomationTimeAxisView (
65         Session* s,
66         boost::shared_ptr<Route> r,
67         boost::shared_ptr<Automatable> a,
68         boost::shared_ptr<AutomationControl> c,
69         Evoral::Parameter p,
70         PublicEditor& e,
71         TimeAxisView& parent,
72         bool show_regions,
73         ArdourCanvas::Canvas& canvas,
74         const string & nom,
75         const string & nomparent
76         )
77         : AxisView (s)
78         , TimeAxisView (s, e, &parent, canvas)
79         , _route (r)
80         , _control (c)
81         , _automatable (a)
82         , _parameter (p)
83         , _base_rect (0)
84         , _view (show_regions ? new AutomationStreamView (*this) : 0)
85         , _name (nom)
86         , auto_button (X_("")) /* force addition of a label */
87 {
88         if (!have_name_font) {
89                 name_font = get_font_for_style (X_("AutomationTrackName"));
90                 have_name_font = true;
91         }
92
93         if (_automatable && _control) {
94                 _controller = AutomationController::create (_automatable, _control->parameter(), _control);
95         }
96
97         automation_menu = 0;
98         auto_off_item = 0;
99         auto_touch_item = 0;
100         auto_write_item = 0;
101         auto_play_item = 0;
102         mode_discrete_item = 0;
103         mode_line_item = 0;
104
105         ignore_state_request = false;
106         first_call_to_set_height = true;
107
108         _base_rect = new SimpleRect(*_canvas_display);
109         _base_rect->property_x1() = 0.0;
110         _base_rect->property_y1() = 0.0;
111         /** gnomecanvas sometimes converts this value to int or adds 2 to it, so it must be
112             set correctly to avoid overflow.
113         */
114         _base_rect->property_x2() = INT_MAX - 2;
115         _base_rect->property_outline_color_rgba() = ARDOUR_UI::config()->canvasvar_AutomationTrackOutline.get();
116
117         /* outline ends and bottom */
118         _base_rect->property_outline_what() = (guint32) (0x1|0x2|0x8);
119         _base_rect->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_AutomationTrackFill.get();
120
121         _base_rect->set_data ("trackview", this);
122
123         _base_rect->signal_event().connect (sigc::bind (
124                         sigc::mem_fun (_editor, &PublicEditor::canvas_automation_track_event),
125                         _base_rect, this));
126
127         if (!a) {
128                 _base_rect->lower_to_bottom();
129         }
130
131         hide_button.add (*(manage (new Gtk::Image (::get_icon("hide")))));
132
133         auto_button.set_name ("TrackVisualButton");
134         hide_button.set_name ("TrackRemoveButton");
135
136         auto_button.unset_flags (Gtk::CAN_FOCUS);
137         hide_button.unset_flags (Gtk::CAN_FOCUS);
138
139         controls_table.set_no_show_all();
140
141         ARDOUR_UI::instance()->set_tip(auto_button, _("automation state"));
142         ARDOUR_UI::instance()->set_tip(hide_button, _("hide track"));
143
144         string str = gui_property ("height");
145         if (!str.empty()) {
146                 set_height (atoi (str));
147         } else {
148                 set_height (preset_height (HeightNormal));
149         }
150
151         /* rearrange the name display */
152
153         /* we never show these for automation tracks, so make
154            life easier and remove them.
155         */
156
157         hide_name_entry();
158
159         /* keep the parameter name short */
160
161         string shortpname = _name;
162         int ignore_width;
163         shortpname = fit_to_pixels (_name, 60, name_font, ignore_width, true);
164
165         name_label.set_text (shortpname);
166         name_label.set_alignment (Gtk::ALIGN_CENTER, Gtk::ALIGN_CENTER);
167         name_label.set_name (X_("TrackParameterName"));
168
169         string tipname = nomparent;
170         if (!tipname.empty()) {
171                 tipname += ": ";
172         }
173         tipname += _name;
174         ARDOUR_UI::instance()->set_tip(controls_ebox, tipname);
175
176         /* add the buttons */
177         controls_table.attach (hide_button, 0, 1, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
178
179         controls_table.attach (auto_button, 5, 8, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
180
181         if (_controller) {
182                 /* add bar controller */
183                 controls_table.attach (*_controller.get(), 0, 8, 1, 2, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
184         }
185
186         controls_table.show_all ();
187
188         hide_button.signal_clicked().connect (sigc::mem_fun(*this, &AutomationTimeAxisView::hide_clicked));
189         auto_button.signal_clicked().connect (sigc::mem_fun(*this, &AutomationTimeAxisView::auto_clicked));
190
191         controls_base_selected_name = X_("AutomationTrackControlsBaseSelected");
192         controls_base_unselected_name = X_("AutomationTrackControlsBase");
193         controls_ebox.set_name (controls_base_unselected_name);
194
195         /* ask for notifications of any new RegionViews */
196         if (show_regions) {
197
198                 assert(_view);
199                 _view->attach ();
200
201         } else {
202                 /* no regions, just a single line for the entire track (e.g. bus gain) */
203
204                 assert (_control);
205
206                 boost::shared_ptr<AutomationLine> line (
207                         new AutomationLine (
208                                 ARDOUR::EventTypeMap::instance().to_symbol(_parameter),
209                                 *this,
210                                 *_canvas_display,
211                                 _control->alist()
212                                 )
213                         );
214
215                 line->set_line_color (ARDOUR_UI::config()->canvasvar_ProcessorAutomationLine.get());
216                 line->queue_reset ();
217                 add_line (line);
218         }
219
220         /* make sure labels etc. are correct */
221
222         automation_state_changed ();
223         ColorsChanged.connect (sigc::mem_fun (*this, &AutomationTimeAxisView::color_handler));
224
225         _route->DropReferences.connect (
226                 _route_connections, invalidator (*this), ui_bind (&AutomationTimeAxisView::route_going_away, this), gui_context ()
227                 );
228 }
229
230 AutomationTimeAxisView::~AutomationTimeAxisView ()
231 {
232 }
233
234 void
235 AutomationTimeAxisView::route_going_away ()
236 {
237         _route.reset ();
238 }
239
240 void
241 AutomationTimeAxisView::auto_clicked ()
242 {
243         using namespace Menu_Helpers;
244
245         if (automation_menu == 0) {
246                 automation_menu = manage (new Menu);
247                 automation_menu->set_name ("ArdourContextMenu");
248                 MenuList& items (automation_menu->items());
249
250                 items.push_back (MenuElem (_("Manual"), sigc::bind (sigc::mem_fun(*this,
251                                 &AutomationTimeAxisView::set_automation_state), (AutoState) Off)));
252                 items.push_back (MenuElem (_("Play"), sigc::bind (sigc::mem_fun(*this,
253                                 &AutomationTimeAxisView::set_automation_state), (AutoState) Play)));
254                 items.push_back (MenuElem (_("Write"), sigc::bind (sigc::mem_fun(*this,
255                                 &AutomationTimeAxisView::set_automation_state), (AutoState) Write)));
256                 items.push_back (MenuElem (_("Touch"), sigc::bind (sigc::mem_fun(*this,
257                                 &AutomationTimeAxisView::set_automation_state), (AutoState) Touch)));
258         }
259
260         automation_menu->popup (1, gtk_get_current_event_time());
261 }
262
263 void
264 AutomationTimeAxisView::set_automation_state (AutoState state)
265 {
266         if (ignore_state_request) {
267                 return;
268         }
269
270         if (_automatable) {
271                 _automatable->set_parameter_automation_state (_parameter, state);
272         }
273
274         if (_view) {
275                 _view->set_automation_state (state);
276
277                 /* AutomationStreamViews don't signal when their automation state changes, so handle
278                    our updates `manually'.
279                 */
280                 automation_state_changed ();
281         }
282 }
283
284 void
285 AutomationTimeAxisView::automation_state_changed ()
286 {
287         AutoState state;
288
289         /* update button label */
290
291         if (_view) {
292                 state = _view->automation_state ();
293         } else if (_line) {
294                 assert (_control);
295                 state = _control->alist()->automation_state ();
296         } else {
297                 state = Off;
298         }
299
300         switch (state & (Off|Play|Touch|Write)) {
301         case Off:
302                 auto_button.set_label (_("Manual"));
303                 if (auto_off_item) {
304                         ignore_state_request = true;
305                         auto_off_item->set_active (true);
306                         auto_play_item->set_active (false);
307                         auto_touch_item->set_active (false);
308                         auto_write_item->set_active (false);
309                         ignore_state_request = false;
310                 }
311                 break;
312         case Play:
313                 auto_button.set_label (_("Play"));
314                 if (auto_play_item) {
315                         ignore_state_request = true;
316                         auto_play_item->set_active (true);
317                         auto_off_item->set_active (false);
318                         auto_touch_item->set_active (false);
319                         auto_write_item->set_active (false);
320                         ignore_state_request = false;
321                 }
322                 break;
323         case Write:
324                 auto_button.set_label (_("Write"));
325                 if (auto_write_item) {
326                         ignore_state_request = true;
327                         auto_write_item->set_active (true);
328                         auto_off_item->set_active (false);
329                         auto_play_item->set_active (false);
330                         auto_touch_item->set_active (false);
331                         ignore_state_request = false;
332                 }
333                 break;
334         case Touch:
335                 auto_button.set_label (_("Touch"));
336                 if (auto_touch_item) {
337                         ignore_state_request = true;
338                         auto_touch_item->set_active (true);
339                         auto_off_item->set_active (false);
340                         auto_play_item->set_active (false);
341                         auto_write_item->set_active (false);
342                         ignore_state_request = false;
343                 }
344                 break;
345         default:
346                 auto_button.set_label (_("???"));
347                 break;
348         }
349 }
350
351 /** The interpolation style of our AutomationList has changed, so update */
352 void
353 AutomationTimeAxisView::interpolation_changed (AutomationList::InterpolationStyle s)
354 {
355         if (mode_line_item && mode_discrete_item) {
356                 if (s == AutomationList::Discrete) {
357                         mode_discrete_item->set_active(true);
358                         mode_line_item->set_active(false);
359                 } else {
360                         mode_line_item->set_active(true);
361                         mode_discrete_item->set_active(false);
362                 }
363         }
364 }
365
366 /** A menu item has been selected to change our interpolation mode */
367 void
368 AutomationTimeAxisView::set_interpolation (AutomationList::InterpolationStyle style)
369 {
370         /* Tell our view's list, if we have one, otherwise tell our own.
371          * Everything else will be signalled back from that.
372          */
373
374         if (_view) {
375                 _view->set_interpolation (style);
376         } else {
377                 assert (_control);
378                 _control->list()->set_interpolation (style);
379         }
380 }
381
382 void
383 AutomationTimeAxisView::clear_clicked ()
384 {
385         assert (_line || _view);
386
387         _session->begin_reversible_command (_("clear automation"));
388
389         if (_line) {
390                 _line->clear ();
391         } else if (_view) {
392                 _view->clear ();
393         }
394
395         _session->commit_reversible_command ();
396         _session->set_dirty ();
397 }
398
399 void
400 AutomationTimeAxisView::set_height (uint32_t h)
401 {
402         bool const changed = (height != (uint32_t) h) || first_call_to_set_height;
403         uint32_t const normal = preset_height (HeightNormal);
404         bool const changed_between_small_and_normal = ( (height < normal && h >= normal) || (height >= normal || h < normal) );
405
406         TimeAxisView::set_height (h);
407
408         _base_rect->property_y2() = h;
409
410         if (_line) {
411                 _line->set_height(h);
412         }
413
414         if (_view) {
415                 _view->set_height(h);
416                 _view->update_contents_height();
417         }
418
419         if (changed_between_small_and_normal || first_call_to_set_height) {
420
421                 first_call_to_set_height = false;
422
423                 if (h >= preset_height (HeightNormal)) {
424                         hide_name_entry ();
425                         show_name_label ();
426                         name_hbox.show_all ();
427
428                         auto_button.show();
429                         hide_button.show_all();
430
431                 } else if (h >= preset_height (HeightSmall)) {
432                         controls_table.hide_all ();
433                         hide_name_entry ();
434                         show_name_label ();
435                         name_hbox.show_all ();
436
437                         auto_button.hide();
438                         hide_button.hide();
439                 }
440         } else if (h >= preset_height (HeightNormal)) {
441                 cerr << "track grown, but neither changed_between_small_and_normal nor first_call_to_set_height set!" << endl;
442         }
443
444         if (changed) {
445                 if (canvas_item_visible (_canvas_display) && _route) {
446                         /* only emit the signal if the height really changed and we were visible */
447                         _route->gui_changed ("visible_tracks", (void *) 0); /* EMIT_SIGNAL */
448                 }
449         }
450 }
451
452 void
453 AutomationTimeAxisView::set_samples_per_unit (double spu)
454 {
455         TimeAxisView::set_samples_per_unit (spu);
456
457         if (_line) {
458                 _line->reset ();
459         }
460
461         if (_view) {
462                 _view->set_samples_per_unit (spu);
463         }
464 }
465
466 void
467 AutomationTimeAxisView::hide_clicked ()
468 {
469         hide_button.set_sensitive(false);
470         set_marked_for_display (false);
471         RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*>(parent);
472         if (rtv) {
473                 rtv->request_redraw ();
474         }
475         hide_button.set_sensitive(true);
476 }
477
478 void
479 AutomationTimeAxisView::build_display_menu ()
480 {
481         using namespace Menu_Helpers;
482
483         /* prepare it */
484
485         TimeAxisView::build_display_menu ();
486
487         /* now fill it with our stuff */
488
489         MenuList& items = display_menu->items();
490
491         items.push_back (MenuElem (_("Hide"), sigc::mem_fun(*this, &AutomationTimeAxisView::hide_clicked)));
492         items.push_back (SeparatorElem());
493         items.push_back (MenuElem (_("Clear"), sigc::mem_fun(*this, &AutomationTimeAxisView::clear_clicked)));
494         items.push_back (SeparatorElem());
495
496         /* state menu */
497
498         Menu* auto_state_menu = manage (new Menu);
499         auto_state_menu->set_name ("ArdourContextMenu");
500         MenuList& as_items = auto_state_menu->items();
501
502         as_items.push_back (CheckMenuElem (_("Manual"), sigc::bind (
503                         sigc::mem_fun(*this, &AutomationTimeAxisView::set_automation_state),
504                         (AutoState) Off)));
505         auto_off_item = dynamic_cast<CheckMenuItem*>(&as_items.back());
506
507         as_items.push_back (CheckMenuElem (_("Play"), sigc::bind (
508                         sigc::mem_fun(*this, &AutomationTimeAxisView::set_automation_state),
509                         (AutoState) Play)));
510         auto_play_item = dynamic_cast<CheckMenuItem*>(&as_items.back());
511
512         as_items.push_back (CheckMenuElem (_("Write"), sigc::bind (
513                         sigc::mem_fun(*this, &AutomationTimeAxisView::set_automation_state),
514                         (AutoState) Write)));
515         auto_write_item = dynamic_cast<CheckMenuItem*>(&as_items.back());
516
517         as_items.push_back (CheckMenuElem (_("Touch"), sigc::bind (
518                         sigc::mem_fun(*this, &AutomationTimeAxisView::set_automation_state),
519                         (AutoState) Touch)));
520         auto_touch_item = dynamic_cast<CheckMenuItem*>(&as_items.back());
521
522         items.push_back (MenuElem (_("State"), *auto_state_menu));
523
524         /* mode menu */
525
526         /* current interpolation state */
527         AutomationList::InterpolationStyle const s = _view ? _view->interpolation() : _control->list()->interpolation ();
528
529         if (EventTypeMap::instance().is_midi_parameter(_parameter)) {
530
531                 Menu* auto_mode_menu = manage (new Menu);
532                 auto_mode_menu->set_name ("ArdourContextMenu");
533                 MenuList& am_items = auto_mode_menu->items();
534
535                 RadioMenuItem::Group group;
536
537                 am_items.push_back (RadioMenuElem (group, _("Discrete"), sigc::bind (
538                                 sigc::mem_fun(*this, &AutomationTimeAxisView::set_interpolation),
539                                 AutomationList::Discrete)));
540                 mode_discrete_item = dynamic_cast<CheckMenuItem*>(&am_items.back());
541                 mode_discrete_item->set_active (s == AutomationList::Discrete);
542
543                 am_items.push_back (RadioMenuElem (group, _("Linear"), sigc::bind (
544                                 sigc::mem_fun(*this, &AutomationTimeAxisView::set_interpolation),
545                                 AutomationList::Linear)));
546                 mode_line_item = dynamic_cast<CheckMenuItem*>(&am_items.back());
547                 mode_line_item->set_active (s == AutomationList::Linear);
548
549                 items.push_back (MenuElem (_("Mode"), *auto_mode_menu));
550         }
551
552         /* make sure the automation menu state is correct */
553
554         automation_state_changed ();
555         interpolation_changed (s);
556 }
557
558 void
559 AutomationTimeAxisView::add_automation_event (ArdourCanvas::Item* /*item*/, GdkEvent* /*event*/, framepos_t when, double y)
560 {
561         if (!_line) {
562                 return;
563         }
564
565         double x = 0;
566
567         _canvas_display->w2i (x, y);
568
569         /* compute vertical fractional position */
570
571         y = 1.0 - (y / height);
572
573         /* map using line */
574
575         _line->view_to_model_coord (x, y);
576
577         boost::shared_ptr<AutomationList> list = _line->the_list ();
578
579         _session->begin_reversible_command (_("add automation event"));
580         XMLNode& before = list->get_state();
581
582         list->add (when, y);
583
584         XMLNode& after = list->get_state();
585         _session->commit_reversible_command (new MementoCommand<ARDOUR::AutomationList> (*list, &before, &after));
586         _session->set_dirty ();
587 }
588
589 void
590 AutomationTimeAxisView::cut_copy_clear (Selection& selection, CutCopyOp op)
591 {
592         list<boost::shared_ptr<AutomationLine> > lines;
593         if (_line) {
594                 lines.push_back (_line);
595         } else if (_view) {
596                 lines = _view->get_lines ();
597         }
598
599         for (list<boost::shared_ptr<AutomationLine> >::iterator i = lines.begin(); i != lines.end(); ++i) {
600                 cut_copy_clear_one (**i, selection, op);
601         }
602 }
603
604 void
605 AutomationTimeAxisView::cut_copy_clear_one (AutomationLine& line, Selection& selection, CutCopyOp op)
606 {
607         boost::shared_ptr<Evoral::ControlList> what_we_got;
608         boost::shared_ptr<AutomationList> alist (line.the_list());
609
610         XMLNode &before = alist->get_state();
611
612         /* convert time selection to automation list model coordinates */
613         const Evoral::TimeConverter<double, ARDOUR::framepos_t>& tc = line.time_converter ();
614         double const start = tc.from (selection.time.front().start - tc.origin_b ());
615         double const end = tc.from (selection.time.front().end - tc.origin_b ());
616
617         switch (op) {
618         case Delete:
619                 if (alist->cut (start, end) != 0) {
620                         _session->add_command(new MementoCommand<AutomationList>(*alist.get(), &before, &alist->get_state()));
621                 }
622                 break;
623
624         case Cut:
625
626                 if ((what_we_got = alist->cut (start, end)) != 0) {
627                         _editor.get_cut_buffer().add (what_we_got);
628                         _session->add_command(new MementoCommand<AutomationList>(*alist.get(), &before, &alist->get_state()));
629                 }
630                 break;
631         case Copy:
632                 if ((what_we_got = alist->copy (start, end)) != 0) {
633                         _editor.get_cut_buffer().add (what_we_got);
634                 }
635                 break;
636
637         case Clear:
638                 if ((what_we_got = alist->cut (start, end)) != 0) {
639                         _session->add_command(new MementoCommand<AutomationList>(*alist.get(), &before, &alist->get_state()));
640                 }
641                 break;
642         }
643
644         if (what_we_got) {
645                 for (AutomationList::iterator x = what_we_got->begin(); x != what_we_got->end(); ++x) {
646                         double when = (*x)->when;
647                         double val  = (*x)->value;
648                         line.model_to_view_coord (when, val);
649                         (*x)->when = when;
650                         (*x)->value = val;
651                 }
652         }
653 }
654
655 void
656 AutomationTimeAxisView::reset_objects (PointSelection& selection)
657 {
658         list<boost::shared_ptr<AutomationLine> > lines;
659         if (_line) {
660                 lines.push_back (_line);
661         } else if (_view) {
662                 lines = _view->get_lines ();
663         }
664
665         for (list<boost::shared_ptr<AutomationLine> >::iterator i = lines.begin(); i != lines.end(); ++i) {
666                 reset_objects_one (**i, selection);
667         }
668 }
669
670 void
671 AutomationTimeAxisView::reset_objects_one (AutomationLine& line, PointSelection& selection)
672 {
673         boost::shared_ptr<AutomationList> alist(line.the_list());
674
675         _session->add_command (new MementoCommand<AutomationList>(*alist.get(), &alist->get_state(), 0));
676
677         for (PointSelection::iterator i = selection.begin(); i != selection.end(); ++i) {
678
679                 if ((*i).track != this) {
680                         continue;
681                 }
682
683                 alist->reset_range ((*i).start, (*i).end);
684         }
685 }
686
687 void
688 AutomationTimeAxisView::cut_copy_clear_objects (PointSelection& selection, CutCopyOp op)
689 {
690         list<boost::shared_ptr<AutomationLine> > lines;
691         if (_line) {
692                 lines.push_back (_line);
693         } else if (_view) {
694                 lines = _view->get_lines ();
695         }
696
697         for (list<boost::shared_ptr<AutomationLine> >::iterator i = lines.begin(); i != lines.end(); ++i) {
698                 cut_copy_clear_objects_one (**i, selection, op);
699         }
700 }
701
702 void
703 AutomationTimeAxisView::cut_copy_clear_objects_one (AutomationLine& line, PointSelection& selection, CutCopyOp op)
704 {
705         boost::shared_ptr<Evoral::ControlList> what_we_got;
706         boost::shared_ptr<AutomationList> alist(line.the_list());
707
708         XMLNode &before = alist->get_state();
709
710         for (PointSelection::iterator i = selection.begin(); i != selection.end(); ++i) {
711
712                 if ((*i).track != this) {
713                         continue;
714                 }
715
716                 switch (op) {
717                 case Delete:
718                         if (alist->cut ((*i).start, (*i).end) != 0) {
719                                 _session->add_command (new MementoCommand<AutomationList>(*alist.get(), new XMLNode (before), &alist->get_state()));
720                         }
721                         break;
722                 case Cut:
723                         if ((what_we_got = alist->cut ((*i).start, (*i).end)) != 0) {
724                                 _editor.get_cut_buffer().add (what_we_got);
725                                 _session->add_command (new MementoCommand<AutomationList>(*alist.get(), new XMLNode (before), &alist->get_state()));
726                         }
727                         break;
728                 case Copy:
729                         if ((what_we_got = alist->copy ((*i).start, (*i).end)) != 0) {
730                                 _editor.get_cut_buffer().add (what_we_got);
731                         }
732                         break;
733
734                 case Clear:
735                         if ((what_we_got = alist->cut ((*i).start, (*i).end)) != 0) {
736                                 _session->add_command (new MementoCommand<AutomationList>(*alist.get(), new XMLNode (before), &alist->get_state()));
737                         }
738                         break;
739                 }
740         }
741
742         delete &before;
743
744         if (what_we_got) {
745                 for (AutomationList::iterator x = what_we_got->begin(); x != what_we_got->end(); ++x) {
746                         double when = (*x)->when;
747                         double val  = (*x)->value;
748                         line.model_to_view_coord (when, val);
749                         (*x)->when = when;
750                         (*x)->value = val;
751                 }
752         }
753 }
754
755 /** Paste a selection.
756  *  @param pos Position to paste to (session frames).
757  *  @param times Number of times to paste.
758  *  @param selection Selection to paste.
759  *  @param nth Index of the AutomationList within the selection to paste from.
760  */
761 bool
762 AutomationTimeAxisView::paste (framepos_t pos, float times, Selection& selection, size_t nth)
763 {
764         boost::shared_ptr<AutomationLine> line;
765
766         if (_line) {
767                 line = _line;
768         } else if (_view) {
769                 line = _view->paste_line (pos);
770         }
771
772         if (!line) {
773                 return false;
774         }
775
776         return paste_one (*line, pos, times, selection, nth);
777 }
778
779 bool
780 AutomationTimeAxisView::paste_one (AutomationLine& line, framepos_t pos, float times, Selection& selection, size_t nth)
781 {
782         AutomationSelection::iterator p;
783         boost::shared_ptr<AutomationList> alist(line.the_list());
784
785         for (p = selection.lines.begin(); p != selection.lines.end() && nth; ++p, --nth) {}
786
787         if (p == selection.lines.end()) {
788                 return false;
789         }
790
791         /* Make a copy of the list because we have to scale the
792            values from view coordinates to model coordinates, and we're
793            not supposed to modify the points in the selection.
794         */
795
796         AutomationList copy (**p);
797
798         for (AutomationList::iterator x = copy.begin(); x != copy.end(); ++x) {
799                 double when = (*x)->when;
800                 double val  = (*x)->value;
801                 line.view_to_model_coord (when, val);
802                 (*x)->when = when;
803                 (*x)->value = val;
804         }
805
806         double const model_pos = line.time_converter().from (pos - line.time_converter().origin_b ());
807
808         XMLNode &before = alist->get_state();
809         alist->paste (copy, model_pos, times);
810         _session->add_command (new MementoCommand<AutomationList>(*alist.get(), &before, &alist->get_state()));
811
812         return true;
813 }
814
815 void
816 AutomationTimeAxisView::get_selectables (framepos_t start, framepos_t end, double top, double bot, list<Selectable*>& results)
817 {
818         if (!_line && !_view) {
819                 return;
820         }
821
822         if (touched (top, bot)) {
823
824                 /* remember: this is X Window - coordinate space starts in upper left and moves down.
825                    _y_position is the "origin" or "top" of the track.
826                 */
827
828                 /* bottom of our track */
829                 double const mybot = _y_position + height;
830
831                 double topfrac;
832                 double botfrac;
833
834                 if (_y_position >= top && mybot <= bot) {
835
836                         /* _y_position is below top, mybot is above bot, so we're fully
837                            covered vertically.
838                         */
839
840                         topfrac = 1.0;
841                         botfrac = 0.0;
842
843                 } else {
844
845                         /* top and bot are within _y_position .. mybot */
846
847                         topfrac = 1.0 - ((top - _y_position) / height);
848                         botfrac = 1.0 - ((bot - _y_position) / height);
849
850                 }
851
852                 if (_line) {
853                         _line->get_selectables (start, end, botfrac, topfrac, results);
854                 } else if (_view) {
855                         _view->get_selectables (start, end, botfrac, topfrac, results);
856                 }
857         }
858 }
859
860 void
861 AutomationTimeAxisView::get_inverted_selectables (Selection& sel, list<Selectable*>& result)
862 {
863         if (_line) {
864                 _line->get_inverted_selectables (sel, result);
865         }
866 }
867
868 void
869 AutomationTimeAxisView::set_selected_points (PointSelection& points)
870 {
871         if (_line) {
872                 _line->set_selected_points (points);
873         } else if (_view) {
874                 _view->set_selected_points (points);
875         }
876 }
877
878 void
879 AutomationTimeAxisView::clear_lines ()
880 {
881         _line.reset();
882         _list_connections.drop_connections ();
883 }
884
885 void
886 AutomationTimeAxisView::add_line (boost::shared_ptr<AutomationLine> line)
887 {
888         assert(line);
889         assert(!_line);
890         if (_control) {
891                 assert(line->the_list() == _control->list());
892                 
893                 _control->alist()->automation_state_changed.connect (
894                         _list_connections, invalidator (*this), boost::bind (&AutomationTimeAxisView::automation_state_changed, this), gui_context()
895                         );
896                 
897                 _control->alist()->InterpolationChanged.connect (
898                         _list_connections, invalidator (*this), boost::bind (&AutomationTimeAxisView::interpolation_changed, this, _1), gui_context()
899                         );
900         }
901
902         _line = line;
903
904         line->set_height (height);
905
906         /* pick up the current state */
907         automation_state_changed ();
908
909         line->show();
910 }
911
912 void
913 AutomationTimeAxisView::entered()
914 {
915         if (_line) {
916                 _line->track_entered();
917         }
918 }
919
920 void
921 AutomationTimeAxisView::exited ()
922 {
923         if (_line) {
924                 _line->track_exited();
925         }
926 }
927
928 void
929 AutomationTimeAxisView::color_handler ()
930 {
931         if (_line) {
932                 _line->set_colors();
933         }
934 }
935
936 int
937 AutomationTimeAxisView::set_state_2X (const XMLNode& node, int /*version*/)
938 {
939         if (node.name() == X_("gain") && _parameter == Evoral::Parameter (GainAutomation)) {
940                 XMLProperty const * shown = node.property (X_("shown"));
941                 if (shown) {
942                         bool yn = string_is_affirmative (shown->value ());
943                         if (yn) {
944                                 _canvas_display->show (); /* FIXME: necessary? show_at? */
945                         }
946                         set_gui_property ("visible", (yn ? "yes" : "no"));
947                 } else {
948                         set_gui_property ("visible", "no");
949                 }
950         }
951
952         return 0;
953 }
954
955 int
956 AutomationTimeAxisView::set_state (const XMLNode& node, int /*version*/)
957 {
958         return 0;
959 }
960
961 void
962 AutomationTimeAxisView::what_has_visible_automation (const boost::shared_ptr<Automatable>& automatable, set<Evoral::Parameter>& visible)
963 {
964         /* this keeps "knowledge" of how we store visibility information
965            in XML private to this class.
966         */
967
968         assert (automatable);
969
970         Automatable::Controls& controls (automatable->controls());
971         
972         for (Automatable::Controls::iterator i = controls.begin(); i != controls.end(); ++i) {
973                 
974                 boost::shared_ptr<AutomationControl> ac = boost::dynamic_pointer_cast<AutomationControl> (i->second);
975
976                 if (ac) {
977                         
978                         const XMLNode* gui_node = ac->extra_xml ("GUI");
979                         
980                         if (gui_node) {
981                                 const XMLProperty* prop = gui_node->property ("shown");
982                                 if (prop) {
983                                         if (string_is_affirmative (prop->value())) {
984                                                 visible.insert (i->first);
985                                         }
986                                 }
987                         }
988                 }
989         }
990 }
991
992
993 /** @return true if this view has any automation data to display */
994 bool
995 AutomationTimeAxisView::has_automation () const
996 {
997         return ( (_line && _line->npoints() > 0) || (_view && _view->has_automation()) );
998 }
999
1000 list<boost::shared_ptr<AutomationLine> >
1001 AutomationTimeAxisView::lines () const
1002 {
1003         list<boost::shared_ptr<AutomationLine> > lines;
1004
1005         if (_line) {
1006                 lines.push_back (_line);
1007         } else if (_view) {
1008                 lines = _view->get_lines ();
1009         }
1010
1011         return lines;
1012 }
1013
1014 string
1015 AutomationTimeAxisView::state_id() const
1016 {
1017         if (_control) {
1018                 return string_compose ("automation %1", _control->id().to_s());
1019         } else {
1020                 assert (_parameter);
1021                 return string_compose ("automation %1 %2/%3/%4", 
1022                                        _route->id(), 
1023                                        _parameter.type(),
1024                                        _parameter.id(),
1025                                        _parameter.channel());
1026         }
1027 }