add_option() after addings its choices so that it gets
[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 (_("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 (_("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 (_("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 (ArdourCanvas::Item* /*item*/, 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         _session->begin_reversible_command (_("add automation event"));
582         XMLNode& before = list->get_state();
583
584         list->add (when, y);
585
586         XMLNode& after = list->get_state();
587         _session->commit_reversible_command (new MementoCommand<ARDOUR::AutomationList> (*list, &before, &after));
588         _session->set_dirty ();
589 }
590
591 void
592 AutomationTimeAxisView::cut_copy_clear (Selection& selection, CutCopyOp op)
593 {
594         list<boost::shared_ptr<AutomationLine> > lines;
595         if (_line) {
596                 lines.push_back (_line);
597         } else if (_view) {
598                 lines = _view->get_lines ();
599         }
600
601         for (list<boost::shared_ptr<AutomationLine> >::iterator i = lines.begin(); i != lines.end(); ++i) {
602                 cut_copy_clear_one (**i, selection, op);
603         }
604 }
605
606 void
607 AutomationTimeAxisView::cut_copy_clear_one (AutomationLine& line, Selection& selection, CutCopyOp op)
608 {
609         boost::shared_ptr<Evoral::ControlList> what_we_got;
610         boost::shared_ptr<AutomationList> alist (line.the_list());
611
612         XMLNode &before = alist->get_state();
613
614         /* convert time selection to automation list model coordinates */
615         const Evoral::TimeConverter<double, ARDOUR::framepos_t>& tc = line.time_converter ();
616         double const start = tc.from (selection.time.front().start - tc.origin_b ());
617         double const end = tc.from (selection.time.front().end - tc.origin_b ());
618
619         switch (op) {
620         case Delete:
621                 if (alist->cut (start, end) != 0) {
622                         _session->add_command(new MementoCommand<AutomationList>(*alist.get(), &before, &alist->get_state()));
623                 }
624                 break;
625
626         case Cut:
627
628                 if ((what_we_got = alist->cut (start, end)) != 0) {
629                         _editor.get_cut_buffer().add (what_we_got);
630                         _session->add_command(new MementoCommand<AutomationList>(*alist.get(), &before, &alist->get_state()));
631                 }
632                 break;
633         case Copy:
634                 if ((what_we_got = alist->copy (start, end)) != 0) {
635                         _editor.get_cut_buffer().add (what_we_got);
636                 }
637                 break;
638
639         case Clear:
640                 if ((what_we_got = alist->cut (start, end)) != 0) {
641                         _session->add_command(new MementoCommand<AutomationList>(*alist.get(), &before, &alist->get_state()));
642                 }
643                 break;
644         }
645
646         if (what_we_got) {
647                 for (AutomationList::iterator x = what_we_got->begin(); x != what_we_got->end(); ++x) {
648                         double when = (*x)->when;
649                         double val  = (*x)->value;
650                         line.model_to_view_coord (when, val);
651                         (*x)->when = when;
652                         (*x)->value = val;
653                 }
654         }
655 }
656
657 void
658 AutomationTimeAxisView::reset_objects (PointSelection& selection)
659 {
660         list<boost::shared_ptr<AutomationLine> > lines;
661         if (_line) {
662                 lines.push_back (_line);
663         } else if (_view) {
664                 lines = _view->get_lines ();
665         }
666
667         for (list<boost::shared_ptr<AutomationLine> >::iterator i = lines.begin(); i != lines.end(); ++i) {
668                 reset_objects_one (**i, selection);
669         }
670 }
671
672 void
673 AutomationTimeAxisView::reset_objects_one (AutomationLine& line, PointSelection& selection)
674 {
675         boost::shared_ptr<AutomationList> alist(line.the_list());
676
677         _session->add_command (new MementoCommand<AutomationList>(*alist.get(), &alist->get_state(), 0));
678
679         for (PointSelection::iterator i = selection.begin(); i != selection.end(); ++i) {
680
681                 if ((*i).track != this) {
682                         continue;
683                 }
684
685                 alist->reset_range ((*i).start, (*i).end);
686         }
687 }
688
689 void
690 AutomationTimeAxisView::cut_copy_clear_objects (PointSelection& selection, CutCopyOp op)
691 {
692         list<boost::shared_ptr<AutomationLine> > lines;
693         if (_line) {
694                 lines.push_back (_line);
695         } else if (_view) {
696                 lines = _view->get_lines ();
697         }
698
699         for (list<boost::shared_ptr<AutomationLine> >::iterator i = lines.begin(); i != lines.end(); ++i) {
700                 cut_copy_clear_objects_one (**i, selection, op);
701         }
702 }
703
704 void
705 AutomationTimeAxisView::cut_copy_clear_objects_one (AutomationLine& line, PointSelection& selection, CutCopyOp op)
706 {
707         boost::shared_ptr<Evoral::ControlList> what_we_got;
708         boost::shared_ptr<AutomationList> alist(line.the_list());
709
710         XMLNode &before = alist->get_state();
711
712         for (PointSelection::iterator i = selection.begin(); i != selection.end(); ++i) {
713
714                 if ((*i).track != this) {
715                         continue;
716                 }
717
718                 switch (op) {
719                 case Delete:
720                         if (alist->cut ((*i).start, (*i).end) != 0) {
721                                 _session->add_command (new MementoCommand<AutomationList>(*alist.get(), new XMLNode (before), &alist->get_state()));
722                         }
723                         break;
724                 case Cut:
725                         if ((what_we_got = alist->cut ((*i).start, (*i).end)) != 0) {
726                                 _editor.get_cut_buffer().add (what_we_got);
727                                 _session->add_command (new MementoCommand<AutomationList>(*alist.get(), new XMLNode (before), &alist->get_state()));
728                         }
729                         break;
730                 case Copy:
731                         if ((what_we_got = alist->copy ((*i).start, (*i).end)) != 0) {
732                                 _editor.get_cut_buffer().add (what_we_got);
733                         }
734                         break;
735
736                 case Clear:
737                         if ((what_we_got = alist->cut ((*i).start, (*i).end)) != 0) {
738                                 _session->add_command (new MementoCommand<AutomationList>(*alist.get(), new XMLNode (before), &alist->get_state()));
739                         }
740                         break;
741                 }
742         }
743
744         delete &before;
745
746         if (what_we_got) {
747                 for (AutomationList::iterator x = what_we_got->begin(); x != what_we_got->end(); ++x) {
748                         double when = (*x)->when;
749                         double val  = (*x)->value;
750                         line.model_to_view_coord (when, val);
751                         (*x)->when = when;
752                         (*x)->value = val;
753                 }
754         }
755 }
756
757 /** Paste a selection.
758  *  @param pos Position to paste to (session frames).
759  *  @param times Number of times to paste.
760  *  @param selection Selection to paste.
761  *  @param nth Index of the AutomationList within the selection to paste from.
762  */
763 bool
764 AutomationTimeAxisView::paste (framepos_t pos, float times, Selection& selection, size_t nth)
765 {
766         boost::shared_ptr<AutomationLine> line;
767
768         if (_line) {
769                 line = _line;
770         } else if (_view) {
771                 line = _view->paste_line (pos);
772         }
773
774         if (!line) {
775                 return false;
776         }
777
778         return paste_one (*line, pos, times, selection, nth);
779 }
780
781 bool
782 AutomationTimeAxisView::paste_one (AutomationLine& line, framepos_t pos, float times, Selection& selection, size_t nth)
783 {
784         AutomationSelection::iterator p;
785         boost::shared_ptr<AutomationList> alist(line.the_list());
786
787         for (p = selection.lines.begin(); p != selection.lines.end() && nth; ++p, --nth) {}
788
789         if (p == selection.lines.end()) {
790                 return false;
791         }
792
793         /* Make a copy of the list because we have to scale the
794            values from view coordinates to model coordinates, and we're
795            not supposed to modify the points in the selection.
796         */
797
798         AutomationList copy (**p);
799
800         for (AutomationList::iterator x = copy.begin(); x != copy.end(); ++x) {
801                 double when = (*x)->when;
802                 double val  = (*x)->value;
803                 line.view_to_model_coord (when, val);
804                 (*x)->when = when;
805                 (*x)->value = val;
806         }
807
808         double const model_pos = line.time_converter().from (pos - line.time_converter().origin_b ());
809
810         XMLNode &before = alist->get_state();
811         alist->paste (copy, model_pos, times);
812         _session->add_command (new MementoCommand<AutomationList>(*alist.get(), &before, &alist->get_state()));
813
814         return true;
815 }
816
817 void
818 AutomationTimeAxisView::get_selectables (framepos_t start, framepos_t end, double top, double bot, list<Selectable*>& results)
819 {
820         if (!_line && !_view) {
821                 return;
822         }
823
824         if (touched (top, bot)) {
825
826                 /* remember: this is X Window - coordinate space starts in upper left and moves down.
827                    _y_position is the "origin" or "top" of the track.
828                 */
829
830                 /* bottom of our track */
831                 double const mybot = _y_position + height;
832
833                 double topfrac;
834                 double botfrac;
835
836                 if (_y_position >= top && mybot <= bot) {
837
838                         /* _y_position is below top, mybot is above bot, so we're fully
839                            covered vertically.
840                         */
841
842                         topfrac = 1.0;
843                         botfrac = 0.0;
844
845                 } else {
846
847                         /* top and bot are within _y_position .. mybot */
848
849                         topfrac = 1.0 - ((top - _y_position) / height);
850                         botfrac = 1.0 - ((bot - _y_position) / height);
851
852                 }
853
854                 if (_line) {
855                         _line->get_selectables (start, end, botfrac, topfrac, results);
856                 } else if (_view) {
857                         _view->get_selectables (start, end, botfrac, topfrac, results);
858                 }
859         }
860 }
861
862 void
863 AutomationTimeAxisView::get_inverted_selectables (Selection& sel, list<Selectable*>& result)
864 {
865         if (_line) {
866                 _line->get_inverted_selectables (sel, result);
867         }
868 }
869
870 void
871 AutomationTimeAxisView::set_selected_points (PointSelection& points)
872 {
873         if (_line) {
874                 _line->set_selected_points (points);
875         } else if (_view) {
876                 _view->set_selected_points (points);
877         }
878 }
879
880 void
881 AutomationTimeAxisView::clear_lines ()
882 {
883         _line.reset();
884         _list_connections.drop_connections ();
885 }
886
887 void
888 AutomationTimeAxisView::add_line (boost::shared_ptr<AutomationLine> line)
889 {
890         assert(line);
891         assert(!_line);
892         if (_control) {
893                 assert(line->the_list() == _control->list());
894                 
895                 _control->alist()->automation_state_changed.connect (
896                         _list_connections, invalidator (*this), boost::bind (&AutomationTimeAxisView::automation_state_changed, this), gui_context()
897                         );
898                 
899                 _control->alist()->InterpolationChanged.connect (
900                         _list_connections, invalidator (*this), boost::bind (&AutomationTimeAxisView::interpolation_changed, this, _1), gui_context()
901                         );
902         }
903
904         _line = line;
905
906         line->set_height (height);
907
908         /* pick up the current state */
909         automation_state_changed ();
910
911         line->show();
912 }
913
914 void
915 AutomationTimeAxisView::entered()
916 {
917         if (_line) {
918                 _line->track_entered();
919         }
920 }
921
922 void
923 AutomationTimeAxisView::exited ()
924 {
925         if (_line) {
926                 _line->track_exited();
927         }
928 }
929
930 void
931 AutomationTimeAxisView::color_handler ()
932 {
933         if (_line) {
934                 _line->set_colors();
935         }
936 }
937
938 int
939 AutomationTimeAxisView::set_state_2X (const XMLNode& node, int /*version*/)
940 {
941         if (node.name() == X_("gain") && _parameter == Evoral::Parameter (GainAutomation)) {
942                 XMLProperty const * shown = node.property (X_("shown"));
943                 if (shown) {
944                         bool yn = string_is_affirmative (shown->value ());
945                         if (yn) {
946                                 _canvas_display->show (); /* FIXME: necessary? show_at? */
947                         }
948                         set_gui_property ("visible", yn);
949                 } else {
950                         set_gui_property ("visible", false);
951                 }
952         }
953
954         return 0;
955 }
956
957 int
958 AutomationTimeAxisView::set_state (const XMLNode&, int /*version*/)
959 {
960         return 0;
961 }
962
963 void
964 AutomationTimeAxisView::what_has_visible_automation (const boost::shared_ptr<Automatable>& automatable, set<Evoral::Parameter>& visible)
965 {
966         /* this keeps "knowledge" of how we store visibility information
967            in XML private to this class.
968         */
969
970         assert (automatable);
971
972         Automatable::Controls& controls (automatable->controls());
973         
974         for (Automatable::Controls::iterator i = controls.begin(); i != controls.end(); ++i) {
975                 
976                 boost::shared_ptr<AutomationControl> ac = boost::dynamic_pointer_cast<AutomationControl> (i->second);
977
978                 if (ac) {
979                         
980                         const XMLNode* gui_node = ac->extra_xml ("GUI");
981                         
982                         if (gui_node) {
983                                 const XMLProperty* prop = gui_node->property ("shown");
984                                 if (prop) {
985                                         if (string_is_affirmative (prop->value())) {
986                                                 visible.insert (i->first);
987                                         }
988                                 }
989                         }
990                 }
991         }
992 }
993
994
995 /** @return true if this view has any automation data to display */
996 bool
997 AutomationTimeAxisView::has_automation () const
998 {
999         return ( (_line && _line->npoints() > 0) || (_view && _view->has_automation()) );
1000 }
1001
1002 list<boost::shared_ptr<AutomationLine> >
1003 AutomationTimeAxisView::lines () const
1004 {
1005         list<boost::shared_ptr<AutomationLine> > lines;
1006
1007         if (_line) {
1008                 lines.push_back (_line);
1009         } else if (_view) {
1010                 lines = _view->get_lines ();
1011         }
1012
1013         return lines;
1014 }
1015
1016 string
1017 AutomationTimeAxisView::state_id() const
1018 {
1019         if (_control) {
1020                 return string_compose ("automation %1", _control->id().to_s());
1021         } else {
1022                 assert (_parameter);
1023                 return string_compose ("automation %1 %2/%3/%4", 
1024                                        _route->id(), 
1025                                        _parameter.type(),
1026                                        _parameter.id(),
1027                                        (int) _parameter.channel());
1028         }
1029 }
1030
1031 /** Given a state id string, see if it is one generated by
1032  *  this class.  If so, parse it into its components.
1033  *  @param state_id State ID string to parse.
1034  *  @param route_id Filled in with the route's ID if the state ID string is parsed.
1035  *  @param has_parameter Filled in with true if the state ID has a parameter, otherwise false.
1036  *  @param parameter Filled in with the state ID's parameter, if it has one.
1037  *  @return true if this is a state ID generated by this class, otherwise false.
1038  */
1039
1040 bool
1041 AutomationTimeAxisView::parse_state_id (
1042         string const & state_id,
1043         PBD::ID & route_id,
1044         bool & has_parameter,
1045         Evoral::Parameter & parameter)
1046 {
1047         stringstream s;
1048         s << state_id;
1049
1050         string a, b, c;
1051         s >> a >> b >> c;
1052
1053         if (a != X_("automation")) {
1054                 return false;
1055         }
1056
1057         route_id = PBD::ID (b);
1058
1059         if (c.empty ()) {
1060                 has_parameter = false;
1061                 return true;
1062         }
1063
1064         has_parameter = true;
1065
1066         vector<string> p;
1067         boost::split (p, c, boost::is_any_of ("/"));
1068
1069         assert (p.size() == 3);
1070
1071         parameter = Evoral::Parameter (
1072                 boost::lexical_cast<int> (p[0]),
1073                 boost::lexical_cast<int> (p[2]),
1074                 boost::lexical_cast<int> (p[1])
1075                 );
1076
1077         return true;
1078 }