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