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