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