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