cb0c85ae617754ab893243a3005d3f3d1e48116b
[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                 /* AutomationStreamViews don't signal when their automation state changes, so handle
277                    our updates `manually'.
278                 */
279                 automation_state_changed ();
280         }
281 }
282
283 void
284 AutomationTimeAxisView::automation_state_changed ()
285 {
286         AutoState state;
287
288         /* update button label */
289
290         if (_line) {
291                 state = _control->alist()->automation_state ();
292         } else if (_view) {
293                 state = _view->automation_state ();
294         } else {
295                 state = Off;
296         }
297
298         switch (state & (Off|Play|Touch|Write)) {
299         case Off:
300                 auto_button.set_label (_("Manual"));
301                 if (auto_off_item) {
302                         ignore_state_request = true;
303                         auto_off_item->set_active (true);
304                         auto_play_item->set_active (false);
305                         auto_touch_item->set_active (false);
306                         auto_write_item->set_active (false);
307                         ignore_state_request = false;
308                 }
309                 break;
310         case Play:
311                 auto_button.set_label (_("Play"));
312                 if (auto_play_item) {
313                         ignore_state_request = true;
314                         auto_play_item->set_active (true);
315                         auto_off_item->set_active (false);
316                         auto_touch_item->set_active (false);
317                         auto_write_item->set_active (false);
318                         ignore_state_request = false;
319                 }
320                 break;
321         case Write:
322                 auto_button.set_label (_("Write"));
323                 if (auto_write_item) {
324                         ignore_state_request = true;
325                         auto_write_item->set_active (true);
326                         auto_off_item->set_active (false);
327                         auto_play_item->set_active (false);
328                         auto_touch_item->set_active (false);
329                         ignore_state_request = false;
330                 }
331                 break;
332         case Touch:
333                 auto_button.set_label (_("Touch"));
334                 if (auto_touch_item) {
335                         ignore_state_request = true;
336                         auto_touch_item->set_active (true);
337                         auto_off_item->set_active (false);
338                         auto_play_item->set_active (false);
339                         auto_write_item->set_active (false);
340                         ignore_state_request = false;
341                 }
342                 break;
343         default:
344                 auto_button.set_label (_("???"));
345                 break;
346         }
347 }
348
349 /** The interpolation style of our AutomationList has changed, so update */
350 void
351 AutomationTimeAxisView::interpolation_changed (AutomationList::InterpolationStyle s)
352 {
353         if (mode_line_item && mode_discrete_item) {
354                 if (s == AutomationList::Discrete) {
355                         mode_discrete_item->set_active(true);
356                         mode_line_item->set_active(false);
357                 } else {
358                         mode_line_item->set_active(true);
359                         mode_discrete_item->set_active(false);
360                 }
361         }
362 }
363
364 /** A menu item has been selected to change our interpolation mode */
365 void
366 AutomationTimeAxisView::set_interpolation (AutomationList::InterpolationStyle style)
367 {
368         /* Tell our view's list, if we have one, otherwise tell our own.
369          * Everything else will be signalled back from that.
370          */
371         
372         if (_view) {
373                 _view->set_interpolation (style);
374         } else {
375                 _control->list()->set_interpolation (style);
376         }
377 }
378
379 void
380 AutomationTimeAxisView::clear_clicked ()
381 {
382         assert (_line || _view);
383         
384         _session->begin_reversible_command (_("clear automation"));
385         
386         if (_line) {
387                 _line->clear ();
388         } else if (_view) {
389                 _view->clear ();
390         }
391
392         _session->commit_reversible_command ();
393         _session->set_dirty ();
394 }
395
396 void
397 AutomationTimeAxisView::set_height (uint32_t h)
398 {
399         bool const changed = (height != (uint32_t) h) || first_call_to_set_height;
400         uint32_t const normal = preset_height (HeightNormal);
401         bool const changed_between_small_and_normal = ( (height < normal && h >= normal) || (height >= normal || h < normal) );
402
403         TimeAxisView* state_parent = get_parent_with_state ();
404         assert(state_parent);
405         XMLNode* xml_node = state_parent->get_automation_child_xml_node (_control->parameter());
406
407         TimeAxisView::set_height (h);
408         _base_rect->property_y2() = h;
409
410         if (_line) {
411                 _line->set_height(h);
412         }
413
414         if (_view) {
415                 _view->set_height(h);
416                 _view->update_contents_height();
417         }
418
419         char buf[32];
420         snprintf (buf, sizeof (buf), "%u", height);
421         if (xml_node) {
422                 xml_node->add_property ("height", buf);
423         }
424
425         if (changed_between_small_and_normal || first_call_to_set_height) {
426
427                 first_call_to_set_height = false;
428
429                 if (h >= preset_height (HeightNormal)) {
430                         controls_table.remove (name_hbox);
431
432                         if (plugname) {
433                                 if (plugname_packed) {
434                                         controls_table.remove (*plugname);
435                                         plugname_packed = false;
436                                 }
437                                 controls_table.attach (*plugname, 1, 5, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
438                                 plugname_packed = true;
439                                 controls_table.attach (name_hbox, 1, 5, 1, 2, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
440                         } else {
441                                 controls_table.attach (name_hbox, 1, 5, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
442                         }
443                         hide_name_entry ();
444                         show_name_label ();
445                         name_hbox.show_all ();
446
447                         auto_button.show();
448                         hide_button.show_all();
449
450                 } else if (h >= preset_height (HeightSmall)) {
451                         controls_table.remove (name_hbox);
452                         if (plugname) {
453                                 if (plugname_packed) {
454                                         controls_table.remove (*plugname);
455                                         plugname_packed = false;
456                                 }
457                         }
458                         controls_table.attach (name_hbox, 1, 5, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
459                         controls_table.hide_all ();
460                         hide_name_entry ();
461                         show_name_label ();
462                         name_hbox.show_all ();
463
464                         auto_button.hide();
465                         hide_button.hide();
466                 }
467         } else if (h >= preset_height (HeightNormal)) {
468                 cerr << "track grown, but neither changed_between_small_and_normal nor first_call_to_set_height set!" << endl;
469         }
470
471         if (changed) {
472                 if (canvas_item_visible (_canvas_display)) {
473                         /* only emit the signal if the height really changed and we were visible */
474                         _route->gui_changed ("visible_tracks", (void *) 0); /* EMIT_SIGNAL */
475                 }
476         }
477 }
478
479 void
480 AutomationTimeAxisView::set_samples_per_unit (double spu)
481 {
482         TimeAxisView::set_samples_per_unit (spu);
483
484         if (_line) {
485                 _line->reset ();
486         }
487
488         if (_view) {
489                 _view->set_samples_per_unit (spu);
490         }
491 }
492
493 void
494 AutomationTimeAxisView::hide_clicked ()
495 {
496         // LAME fix for refreshing the hide button
497         hide_button.set_sensitive(false);
498
499         set_marked_for_display (false);
500         hide ();
501
502         hide_button.set_sensitive(true);
503 }
504
505 void
506 AutomationTimeAxisView::build_display_menu ()
507 {
508         using namespace Menu_Helpers;
509
510         /* prepare it */
511
512         TimeAxisView::build_display_menu ();
513
514         /* now fill it with our stuff */
515
516         MenuList& items = display_menu->items();
517
518         items.push_back (MenuElem (_("Hide"), sigc::mem_fun(*this, &AutomationTimeAxisView::hide_clicked)));
519         items.push_back (SeparatorElem());
520         items.push_back (MenuElem (_("Clear"), sigc::mem_fun(*this, &AutomationTimeAxisView::clear_clicked)));
521         items.push_back (SeparatorElem());
522
523         /* state menu */
524
525         Menu* auto_state_menu = manage (new Menu);
526         auto_state_menu->set_name ("ArdourContextMenu");
527         MenuList& as_items = auto_state_menu->items();
528
529         as_items.push_back (CheckMenuElem (_("Manual"), sigc::bind (
530                         sigc::mem_fun(*this, &AutomationTimeAxisView::set_automation_state),
531                         (AutoState) Off)));
532         auto_off_item = dynamic_cast<CheckMenuItem*>(&as_items.back());
533
534         as_items.push_back (CheckMenuElem (_("Play"), sigc::bind (
535                         sigc::mem_fun(*this, &AutomationTimeAxisView::set_automation_state),
536                         (AutoState) Play)));
537         auto_play_item = dynamic_cast<CheckMenuItem*>(&as_items.back());
538
539         as_items.push_back (CheckMenuElem (_("Write"), sigc::bind (
540                         sigc::mem_fun(*this, &AutomationTimeAxisView::set_automation_state),
541                         (AutoState) Write)));
542         auto_write_item = dynamic_cast<CheckMenuItem*>(&as_items.back());
543
544         as_items.push_back (CheckMenuElem (_("Touch"), sigc::bind (
545                         sigc::mem_fun(*this, &AutomationTimeAxisView::set_automation_state),
546                         (AutoState) Touch)));
547         auto_touch_item = dynamic_cast<CheckMenuItem*>(&as_items.back());
548
549         items.push_back (MenuElem (_("State"), *auto_state_menu));
550
551         /* mode menu */
552
553         /* current interpolation state */
554         AutomationList::InterpolationStyle const s = _view ? _view->interpolation() : _control->list()->interpolation ();
555
556         if (EventTypeMap::instance().is_midi_parameter(_control->parameter())) {
557
558                 Menu* auto_mode_menu = manage (new Menu);
559                 auto_mode_menu->set_name ("ArdourContextMenu");
560                 MenuList& am_items = auto_mode_menu->items();
561
562                 RadioMenuItem::Group group;
563
564                 am_items.push_back (RadioMenuElem (group, _("Discrete"), sigc::bind (
565                                 sigc::mem_fun(*this, &AutomationTimeAxisView::set_interpolation),
566                                 AutomationList::Discrete)));
567                 mode_discrete_item = dynamic_cast<CheckMenuItem*>(&am_items.back());
568                 mode_discrete_item->set_active (s == AutomationList::Discrete);
569
570                 am_items.push_back (RadioMenuElem (group, _("Linear"), sigc::bind (
571                                 sigc::mem_fun(*this, &AutomationTimeAxisView::set_interpolation),
572                                 AutomationList::Linear)));
573                 mode_line_item = dynamic_cast<CheckMenuItem*>(&am_items.back());
574                 mode_line_item->set_active (s == AutomationList::Linear);
575
576                 items.push_back (MenuElem (_("Mode"), *auto_mode_menu));
577         }
578
579         /* make sure the automation menu state is correct */
580
581         automation_state_changed ();
582         interpolation_changed (s);
583 }
584
585 void
586 AutomationTimeAxisView::add_automation_event (ArdourCanvas::Item* /*item*/, GdkEvent* /*event*/, nframes_t when, double y)
587 {
588         if (!_line)
589                 return;
590
591         double x = 0;
592
593         _canvas_display->w2i (x, y);
594
595         /* compute vertical fractional position */
596
597         y = 1.0 - (y / height);
598
599         /* map using line */
600
601         _line->view_to_model_coord (x, y);
602
603         _session->begin_reversible_command (_("add automation event"));
604         XMLNode& before = _control->alist()->get_state();
605
606         _control->alist()->add (when, y);
607
608         XMLNode& after = _control->alist()->get_state();
609         _session->commit_reversible_command (new MementoCommand<ARDOUR::AutomationList>(*_control->alist(), &before, &after));
610
611         _session->set_dirty ();
612 }
613
614 void
615 AutomationTimeAxisView::cut_copy_clear (Selection& selection, CutCopyOp op)
616 {
617         list<boost::shared_ptr<AutomationLine> > lines;
618         if (_line) {
619                 lines.push_back (_line);
620         } else if (_view) {
621                 lines = _view->get_lines ();
622         }
623
624         for (list<boost::shared_ptr<AutomationLine> >::iterator i = lines.begin(); i != lines.end(); ++i) {
625                 cut_copy_clear_one (**i, selection, op);
626         }
627 }
628
629 void
630 AutomationTimeAxisView::cut_copy_clear_one (AutomationLine& line, Selection& selection, CutCopyOp op)
631 {
632         boost::shared_ptr<Evoral::ControlList> what_we_got;
633         boost::shared_ptr<AutomationList> alist (line.the_list());
634
635         XMLNode &before = alist->get_state();
636
637         /* convert time selection to automation list model coordinates */
638         const Evoral::TimeConverter<double, ARDOUR::sframes_t>& tc = line.time_converter ();
639         double const start = tc.from (selection.time.front().start - tc.origin_b ());
640         double const end = tc.from (selection.time.front().end - tc.origin_b ());
641         
642         switch (op) {
643         case Cut:
644                 
645                 if ((what_we_got = alist->cut (start, end)) != 0) {
646                         _editor.get_cut_buffer().add (what_we_got);
647                         _session->add_command(new MementoCommand<AutomationList>(*alist.get(), &before, &alist->get_state()));
648                 }
649                 break;
650         case Copy:
651                 if ((what_we_got = alist->copy (start, end)) != 0) {
652                         _editor.get_cut_buffer().add (what_we_got);
653                 }
654                 break;
655
656         case Clear:
657                 if ((what_we_got = alist->cut (start, end)) != 0) {
658                         _session->add_command(new MementoCommand<AutomationList>(*alist.get(), &before, &alist->get_state()));
659                 }
660                 break;
661         }
662
663         if (what_we_got) {
664                 for (AutomationList::iterator x = what_we_got->begin(); x != what_we_got->end(); ++x) {
665                         double when = (*x)->when;
666                         double val  = (*x)->value;
667                         line.model_to_view_coord (when, val);
668                         (*x)->when = when;
669                         (*x)->value = val;
670                 }
671         }
672 }
673
674 void
675 AutomationTimeAxisView::reset_objects (PointSelection& selection)
676 {
677         reset_objects_one (*_line, selection);
678 }
679
680 void
681 AutomationTimeAxisView::reset_objects_one (AutomationLine& line, PointSelection& selection)
682 {
683         boost::shared_ptr<AutomationList> alist(line.the_list());
684
685         _session->add_command (new MementoCommand<AutomationList>(*alist.get(), &alist->get_state(), 0));
686
687         for (PointSelection::iterator i = selection.begin(); i != selection.end(); ++i) {
688
689                 if ((*i).track != this) {
690                         continue;
691                 }
692
693                 alist->reset_range ((*i).start, (*i).end);
694         }
695 }
696
697 void
698 AutomationTimeAxisView::cut_copy_clear_objects (PointSelection& selection, CutCopyOp op)
699 {
700         list<boost::shared_ptr<AutomationLine> > lines;
701         if (_line) {
702                 lines.push_back (_line);
703         } else if (_view) {
704                 lines = _view->get_lines ();
705         }
706
707         for (list<boost::shared_ptr<AutomationLine> >::iterator i = lines.begin(); i != lines.end(); ++i) {
708                 cut_copy_clear_objects_one (**i, selection, op);
709         }
710 }
711
712 void
713 AutomationTimeAxisView::cut_copy_clear_objects_one (AutomationLine& line, PointSelection& selection, CutCopyOp op)
714 {
715         boost::shared_ptr<Evoral::ControlList> what_we_got;
716         boost::shared_ptr<AutomationList> alist(line.the_list());
717
718         XMLNode &before = alist->get_state();
719
720         for (PointSelection::iterator i = selection.begin(); i != selection.end(); ++i) {
721
722                 if ((*i).track != this) {
723                         continue;
724                 }
725
726                 switch (op) {
727                 case Cut:
728                         if ((what_we_got = alist->cut ((*i).start, (*i).end)) != 0) {
729                                 _editor.get_cut_buffer().add (what_we_got);
730                                 _session->add_command (new MementoCommand<AutomationList>(*alist.get(), new XMLNode (before), &alist->get_state()));
731                         }
732                         break;
733                 case Copy:
734                         if ((what_we_got = alist->copy ((*i).start, (*i).end)) != 0) {
735                                 _editor.get_cut_buffer().add (what_we_got);
736                         }
737                         break;
738
739                 case Clear:
740                         if ((what_we_got = alist->cut ((*i).start, (*i).end)) != 0) {
741                                 _session->add_command (new MementoCommand<AutomationList>(*alist.get(), new XMLNode (before), &alist->get_state()));
742                         }
743                         break;
744                 }
745         }
746
747         delete &before;
748
749         if (what_we_got) {
750                 for (AutomationList::iterator x = what_we_got->begin(); x != what_we_got->end(); ++x) {
751                         double when = (*x)->when;
752                         double val  = (*x)->value;
753                         line.model_to_view_coord (when, val);
754                         (*x)->when = when;
755                         (*x)->value = val;
756                 }
757         }
758 }
759
760 /** Paste a selection.
761  *  @param pos Position to paste to (session frames).
762  *  @param times Number of times to paste.
763  *  @param selection Selection to paste.
764  *  @param nth Index of the AutomationList within the selection to paste from.
765  */
766 bool
767 AutomationTimeAxisView::paste (framepos_t pos, float times, Selection& selection, size_t nth)
768 {
769         boost::shared_ptr<AutomationLine> line;
770         
771         if (_line) {
772                 line = _line;
773         } else if (_view) {
774                 line = _view->paste_line (pos);
775         }
776
777         if (!line) {
778                 return false;
779         }
780         
781         return paste_one (*line, pos, times, selection, nth);
782 }
783
784 bool
785 AutomationTimeAxisView::paste_one (AutomationLine& line, framepos_t pos, float times, Selection& selection, size_t nth)
786 {
787         AutomationSelection::iterator p;
788         boost::shared_ptr<AutomationList> alist(line.the_list());
789
790         for (p = selection.lines.begin(); p != selection.lines.end() && nth; ++p, --nth) {}
791
792         if (p == selection.lines.end()) {
793                 return false;
794         }
795
796         /* Make a copy of the list because we have to scale the
797            values from view coordinates to model coordinates, and we're
798            not supposed to modify the points in the selection.
799         */
800
801         AutomationList copy (**p);
802
803         for (AutomationList::iterator x = copy.begin(); x != copy.end(); ++x) {
804                 double when = (*x)->when;
805                 double val  = (*x)->value;
806                 line.view_to_model_coord (when, val);
807                 (*x)->when = when;
808                 (*x)->value = val;
809         }
810
811         double const model_pos = line.time_converter().from (pos - line.time_converter().origin_b ());
812
813         XMLNode &before = alist->get_state();
814         alist->paste (copy, model_pos, times);
815         _session->add_command (new MementoCommand<AutomationList>(*alist.get(), &before, &alist->get_state()));
816
817         return true;
818 }
819
820 void
821 AutomationTimeAxisView::get_selectables (nframes_t start, nframes_t end, double top, double bot, list<Selectable*>& results)
822 {
823         if (!_line && !_view) {
824                 return;
825         }
826         
827         if (touched (top, bot)) {
828                 double topfrac;
829                 double botfrac;
830
831                 /* remember: this is X Window - coordinate space starts in upper left and moves down.
832                    _y_position is the "origin" or "top" of the track.
833                 */
834
835                 double mybot = _y_position + height;
836
837                 if (_y_position >= top && mybot <= bot) {
838
839                         /* _y_position is below top, mybot is above bot, so we're fully
840                            covered vertically.
841                         */
842
843                         topfrac = 1.0;
844                         botfrac = 0.0;
845
846                 } else {
847
848                         /* top and bot are within _y_position .. mybot */
849
850                         topfrac = 1.0 - ((top - _y_position) / height);
851                         botfrac = 1.0 - ((bot - _y_position) / height);
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         assert(line->the_list() == _control->list());
893
894         _control->alist()->automation_state_changed.connect (
895                 _list_connections, invalidator (*this), boost::bind (&AutomationTimeAxisView::automation_state_changed, this), gui_context()
896                 );
897         
898         _control->alist()->InterpolationChanged.connect (
899                 _list_connections, invalidator (*this), boost::bind (&AutomationTimeAxisView::interpolation_changed, this, _1), gui_context()
900                 );
901
902         _line = line;
903         //_controller = AutomationController::create(_session, line->the_list(), _control);
904
905         line->set_height (height);
906
907         /* pick up the current state */
908         automation_state_changed ();
909
910         line->show();
911 }
912
913 void
914 AutomationTimeAxisView::entered()
915 {
916         if (_line)
917                 _line->track_entered();
918 }
919
920 void
921 AutomationTimeAxisView::exited ()
922 {
923         if (_line)
924                 _line->track_exited();
925 }
926
927 void
928 AutomationTimeAxisView::color_handler ()
929 {
930         if (_line) {
931                 _line->set_colors();
932         }
933 }
934
935 int
936 AutomationTimeAxisView::set_state (const XMLNode& node, int version)
937 {
938         TimeAxisView::set_state (node, version);
939
940         XMLProperty const * type = node.property ("automation-id");
941         if (type && type->value () == ARDOUR::EventTypeMap::instance().to_symbol (_control->parameter())) {
942                 XMLProperty const * shown = node.property ("shown");
943                 if (shown && shown->value () == "yes") {
944                         set_marked_for_display (true);
945                         _canvas_display->show (); /* FIXME: necessary? show_at? */
946                 }
947         }
948
949         if (!_marked_for_display) {
950                 hide();
951         }
952
953         return 0;
954 }
955
956 XMLNode*
957 AutomationTimeAxisView::get_state_node ()
958 {
959         TimeAxisView* state_parent = get_parent_with_state ();
960
961         if (state_parent) {
962                 return state_parent->get_automation_child_xml_node (_control->parameter());
963         } else {
964                 return 0;
965         }
966 }
967
968 void
969 AutomationTimeAxisView::update_extra_xml_shown (bool editor_shown)
970 {
971         XMLNode* xml_node = get_state_node();
972         if (xml_node) {
973                 xml_node->add_property ("shown", editor_shown ? "yes" : "no");
974         }
975 }
976
977 guint32
978 AutomationTimeAxisView::show_at (double y, int& nth, Gtk::VBox *parent)
979 {
980         update_extra_xml_shown (true);
981
982         return TimeAxisView::show_at (y, nth, parent);
983 }
984
985 void
986 AutomationTimeAxisView::hide ()
987 {
988         update_extra_xml_shown (false);
989
990         TimeAxisView::hide ();
991 }
992
993 bool
994 AutomationTimeAxisView::set_visibility (bool yn)
995 {
996         bool changed = TimeAxisView::set_visibility (yn);
997
998         if (changed) {
999                 get_state_node()->add_property ("shown", yn ? X_("yes") : X_("no"));
1000         }
1001
1002         return changed;
1003 }
1004
1005 /** @return true if this view has any automation data to display */
1006 bool
1007 AutomationTimeAxisView::has_automation () const
1008 {
1009         return ( (_line && _line->npoints() > 0) || (_view && _view->has_automation()) );
1010 }