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