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