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