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