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