Allow to send MIDI data directly to a plugin
[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
22 #include <boost/algorithm/string.hpp>
23
24 #include <gtkmm/separator.h>
25
26 #include "pbd/error.h"
27 #include "pbd/memento_command.h"
28 #include "pbd/stacktrace.h"
29 #include "pbd/string_convert.h"
30 #include "pbd/types_convert.h"
31 #include "pbd/unwind.h"
32
33 #include "ardour/automation_control.h"
34 #include "ardour/beats_frames_converter.h"
35 #include "ardour/event_type_map.h"
36 #include "ardour/parameter_types.h"
37 #include "ardour/profile.h"
38 #include "ardour/route.h"
39 #include "ardour/session.h"
40
41 #include "gtkmm2ext/utils.h"
42
43 #include "canvas/debug.h"
44
45 #include "widgets/tooltips.h"
46
47 #include "automation_time_axis.h"
48 #include "automation_streamview.h"
49 #include "gui_thread.h"
50 #include "route_time_axis.h"
51 #include "automation_line.h"
52 #include "paste_context.h"
53 #include "public_editor.h"
54 #include "selection.h"
55 #include "rgb_macros.h"
56 #include "point_selection.h"
57 #include "control_point.h"
58 #include "utils.h"
59 #include "item_counts.h"
60 #include "ui_config.h"
61
62 #include "pbd/i18n.h"
63
64 using namespace std;
65 using namespace ARDOUR;
66 using namespace ArdourWidgets;
67 using namespace ARDOUR_UI_UTILS;
68 using namespace PBD;
69 using namespace Gtk;
70 using namespace Gtkmm2ext;
71 using namespace Editing;
72
73 Pango::FontDescription AutomationTimeAxisView::name_font;
74 bool AutomationTimeAxisView::have_name_font = false;
75
76
77 /** \a a the automatable object this time axis is to display data for.
78  * For route/track automation (e.g. gain) pass the route for both \r and \a.
79  * For route child (e.g. plugin) automation, pass the child for \a.
80  * For region automation (e.g. MIDI CC), pass null for \a.
81  */
82 AutomationTimeAxisView::AutomationTimeAxisView (
83         Session* s,
84         boost::shared_ptr<Stripable> strip,
85         boost::shared_ptr<Automatable> a,
86         boost::shared_ptr<AutomationControl> c,
87         Evoral::Parameter p,
88         PublicEditor& e,
89         TimeAxisView& parent,
90         bool show_regions,
91         ArdourCanvas::Canvas& canvas,
92         const string & nom,
93         const string & nomparent
94         )
95         : SessionHandlePtr (s)
96         , TimeAxisView (s, e, &parent, canvas)
97         , _stripable (strip)
98         , _control (c)
99         , _automatable (a)
100         , _parameter (p)
101         , _base_rect (new ArdourCanvas::Rectangle (_canvas_display))
102         , _view (show_regions ? new AutomationStreamView (*this) : 0)
103         , auto_dropdown ()
104         , _show_regions (show_regions)
105 {
106         //concatenate plugin name and param name into the tooltip
107         string tipname = nomparent;
108         if (!tipname.empty()) {
109                 tipname += ": ";
110         }
111         tipname += nom;
112         set_tooltip(controls_ebox, tipname);
113
114         //plugin name and param name appear on 2 separate lines in the track header
115         tipname = nomparent;
116         if (!tipname.empty()) {
117                 tipname += "\n";
118         }
119         tipname += nom;
120         _name = tipname;
121
122         CANVAS_DEBUG_NAME (_canvas_display, string_compose ("main for auto %2/%1", _name, strip->name()));
123         CANVAS_DEBUG_NAME (selection_group, string_compose ("selections for auto %2/%1", _name, strip->name()));
124         CANVAS_DEBUG_NAME (_ghost_group, string_compose ("ghosts for auto %2/%1", _name, strip->name()));
125
126         if (!have_name_font) {
127                 name_font = get_font_for_style (X_("AutomationTrackName"));
128                 have_name_font = true;
129         }
130
131         if (_control) {
132                 _controller = AutomationController::create (_control->parameter(), _control->desc(), _control);
133         }
134
135         const std::string fill_color_name = (dynamic_cast<MidiTimeAxisView*>(&parent)
136                                              ? "midi automation track fill"
137                                              : "audio automation track fill");
138
139         auto_off_item = 0;
140         auto_touch_item = 0;
141         auto_write_item = 0;
142         auto_play_item = 0;
143         mode_discrete_item = 0;
144         mode_line_item = 0;
145         mode_log_item = 0;
146         mode_exp_item = 0;
147
148         ignore_state_request = false;
149         ignore_mode_request = false;
150         first_call_to_set_height = true;
151
152         CANVAS_DEBUG_NAME (_base_rect, string_compose ("base rect for %1", _name));
153         _base_rect->set_x1 (ArdourCanvas::COORD_MAX);
154         _base_rect->set_outline (false);
155         _base_rect->set_fill_color (UIConfiguration::instance().color_mod (fill_color_name, "automation track fill"));
156         _base_rect->set_data ("trackview", this);
157         _base_rect->Event.connect (sigc::bind (sigc::mem_fun (_editor, &PublicEditor::canvas_automation_track_event), _base_rect, this));
158         if (!a) {
159                 _base_rect->lower_to_bottom();
160         }
161
162         using namespace Menu_Helpers;
163
164         auto_dropdown.AddMenuElem (MenuElem (automation_state_off_string(), sigc::bind (sigc::mem_fun(*this,
165                                                 &AutomationTimeAxisView::set_automation_state), (AutoState) ARDOUR::Off)));
166         auto_dropdown.AddMenuElem (MenuElem (_("Play"), sigc::bind (sigc::mem_fun(*this,
167                                                 &AutomationTimeAxisView::set_automation_state), (AutoState) Play)));
168
169         if (!(_parameter.type() >= MidiCCAutomation &&
170               _parameter.type() <= MidiChannelPressureAutomation)) {
171                 auto_dropdown.AddMenuElem (MenuElem (_("Write"), sigc::bind (sigc::mem_fun(*this, &AutomationTimeAxisView::set_automation_state), (AutoState) Write)));
172                 auto_dropdown.AddMenuElem (MenuElem (_("Touch"), sigc::bind (sigc::mem_fun(*this, &AutomationTimeAxisView::set_automation_state), (AutoState) Touch)));
173         }
174
175         /* XXX translators: use a string here that will be at least as long
176            as the longest automation label (see ::automation_state_changed()
177            below). be sure to include a descender.
178         */
179         auto_dropdown.set_sizing_text(_("Mgnual"));
180
181         hide_button.set_icon (ArdourIcon::CloseCross);
182         hide_button.set_tweaks(ArdourButton::TrackHeader);
183
184         auto_dropdown.set_name ("route button");
185         hide_button.set_name ("route button");
186
187         auto_dropdown.unset_flags (Gtk::CAN_FOCUS);
188         hide_button.unset_flags (Gtk::CAN_FOCUS);
189
190         controls_table.set_no_show_all();
191
192         set_tooltip(auto_dropdown, _("automation state"));
193         set_tooltip(hide_button, _("hide track"));
194
195         uint32_t height;
196         if (get_gui_property ("height", height)) {
197                 set_height (height);
198         } else {
199                 set_height (preset_height (HeightNormal));
200         }
201
202         //name label isn't editable on an automation track; remove the tooltip
203         set_tooltip (name_label, X_(""));
204
205         /* repack the name label, which TimeAxisView has already attached to
206          * the controls_table
207          */
208
209         if (name_label.get_parent()) {
210                 name_label.get_parent()->remove (name_label);
211         }
212
213         name_label.set_text (_name);
214         name_label.set_alignment (Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER);
215         name_label.set_name (X_("TrackParameterName"));
216         name_label.set_ellipsize (Pango::ELLIPSIZE_END);
217
218         /* add the buttons */
219         controls_table.set_border_width (1);
220         controls_table.attach (hide_button, 1, 2, 0, 1, Gtk::SHRINK, Gtk::SHRINK, 0, 0);
221         controls_table.attach (name_label,  2, 3, 1, 3, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND, 2, 0);
222         controls_table.attach (auto_dropdown, 3, 4, 2, 3, Gtk::SHRINK, Gtk::SHRINK, 0, 0);
223
224         Gtk::DrawingArea *blank0 = manage (new Gtk::DrawingArea());
225         Gtk::DrawingArea *blank1 = manage (new Gtk::DrawingArea());
226
227         RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*>(&parent);
228         // TODO use rtv->controls_base_unselected_name
229         // subscribe to route_active_changed, ...
230         if (rtv && rtv->is_audio_track()) {
231                 blank0->set_name ("AudioTrackControlsBaseUnselected");
232         } else if (rtv && rtv->is_midi_track()) {
233                 blank0->set_name ("MidiTrackControlsBaseUnselected");
234         } else if (rtv) {
235                 blank0->set_name ("AudioBusControlsBaseUnselected");
236         } else {
237                 blank0->set_name ("UnknownControlsBaseUnselected");
238         }
239         blank0->set_size_request (-1, -1);
240         blank1->set_size_request (1, 0);
241         VSeparator* separator = manage (new VSeparator());
242         separator->set_name("TrackSeparator");
243         separator->set_size_request (1, -1);
244
245         controls_button_size_group->add_widget(hide_button);
246         controls_button_size_group->add_widget(*blank0);
247
248         time_axis_hbox.pack_start (*blank0, false, false);
249         time_axis_hbox.pack_start (*separator, false, false);
250         time_axis_hbox.reorder_child (*blank0, 0);
251         time_axis_hbox.reorder_child (*separator, 1);
252         time_axis_hbox.reorder_child (time_axis_vbox, 2);
253
254         if (!ARDOUR::Profile->get_mixbus() ) {
255                 time_axis_hbox.pack_start (*blank1, false, false);
256         }
257
258         blank0->show();
259         separator->show();
260         name_label.show ();
261         hide_button.show ();
262
263         if (_controller) {
264                 _controller->disable_vertical_scroll ();
265                 controls_table.attach (*_controller.get(), 2, 4, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND, 0, 0);
266         }
267
268         controls_table.show_all ();
269
270         hide_button.signal_clicked.connect (sigc::mem_fun(*this, &AutomationTimeAxisView::hide_clicked));
271
272         controls_base_selected_name = X_("AutomationTrackControlsBaseSelected");
273         controls_base_unselected_name = X_("AutomationTrackControlsBase");
274
275         controls_ebox.set_name (controls_base_unselected_name);
276         time_axis_frame.set_name (controls_base_unselected_name);
277
278         /* ask for notifications of any new RegionViews */
279         if (show_regions) {
280
281                 if (_view) {
282                         _view->attach ();
283                 }
284
285         } else {
286                 /* no regions, just a single line for the entire track (e.g. bus gain) */
287
288                 assert (_control);
289
290                 boost::shared_ptr<AutomationLine> line (
291                         new AutomationLine (
292                                 ARDOUR::EventTypeMap::instance().to_symbol(_parameter),
293                                 *this,
294                                 *_canvas_display,
295                                 _control->alist(),
296                                 _control->desc()
297                                 )
298                         );
299
300                 line->set_line_color (UIConfiguration::instance().color ("processor automation line"));
301                 line->set_fill (true);
302                 line->queue_reset ();
303                 add_line (line);
304         }
305
306         /* make sure labels etc. are correct */
307
308         automation_state_changed ();
309         UIConfiguration::instance().ColorsChanged.connect (sigc::mem_fun (*this, &AutomationTimeAxisView::color_handler));
310
311         _stripable->DropReferences.connect (
312                 _stripable_connections, invalidator (*this), boost::bind (&AutomationTimeAxisView::route_going_away, this), gui_context ()
313                 );
314 }
315
316 AutomationTimeAxisView::~AutomationTimeAxisView ()
317 {
318         if (_stripable) {
319                 cleanup_gui_properties ();
320         }
321         delete _view;
322 }
323
324 void
325 AutomationTimeAxisView::route_going_away ()
326 {
327         cleanup_gui_properties ();
328         _stripable.reset ();
329 }
330
331 void
332 AutomationTimeAxisView::set_automation_state (AutoState state)
333 {
334         if (ignore_state_request) {
335                 return;
336         }
337
338         if (_automatable) {
339                 _automatable->set_parameter_automation_state (_parameter, state);
340         }
341         else if (_control) {
342                 _control->set_automation_state (state);
343                 _session->set_dirty ();
344         }
345
346         if (_view) {
347                 _view->set_automation_state (state);
348
349                 /* AutomationStreamViews don't signal when their automation state changes, so handle
350                    our updates `manually'.
351                 */
352                 automation_state_changed ();
353         }
354 }
355
356 void
357 AutomationTimeAxisView::automation_state_changed ()
358 {
359         AutoState state;
360
361         /* update button label */
362
363         if (_view) {
364                 state = _view->automation_state ();
365         } else if (_line) {
366                 assert (_control);
367                 state = _control->alist()->automation_state ();
368         } else {
369                 state = ARDOUR::Off;
370         }
371
372         switch (state & (ARDOUR::Off|Play|Touch|Write)) {
373         case ARDOUR::Off:
374                 auto_dropdown.set_text (automation_state_off_string());
375                 ignore_state_request = true;
376                 if (auto_off_item) {
377                         auto_off_item->set_active (true);
378                         auto_play_item->set_active (false);
379                 }
380                 if (auto_touch_item) {
381                         auto_touch_item->set_active (false);
382                         auto_write_item->set_active (false);
383                 }
384                 ignore_state_request = false;
385                 break;
386         case Play:
387                 auto_dropdown.set_text (_("Play"));
388                 ignore_state_request = true;
389                 if (auto_off_item) {
390                         auto_play_item->set_active (true);
391                         auto_off_item->set_active (false);
392                 }
393                 if (auto_touch_item) {
394                         auto_touch_item->set_active (false);
395                         auto_write_item->set_active (false);
396                 }
397                 ignore_state_request = false;
398                 break;
399         case Write:
400                 auto_dropdown.set_text (_("Write"));
401                 ignore_state_request = true;
402                 if (auto_off_item) {
403                         auto_off_item->set_active (false);
404                         auto_play_item->set_active (false);
405                 }
406                 if (auto_touch_item) {
407                         auto_write_item->set_active (true);
408                         auto_touch_item->set_active (false);
409                 }
410                 ignore_state_request = false;
411                 break;
412         case Touch:
413                 auto_dropdown.set_text (_("Touch"));
414                 ignore_state_request = true;
415                 if (auto_off_item) {
416                         auto_off_item->set_active (false);
417                         auto_play_item->set_active (false);
418                 }
419                 if (auto_touch_item) {
420                         auto_touch_item->set_active (true);
421                         auto_write_item->set_active (false);
422                 }
423                 ignore_state_request = false;
424                 break;
425         default:
426                 auto_dropdown.set_text (_("???"));
427                 break;
428         }
429 }
430
431 /** The interpolation style of our AutomationList has changed, so update */
432 void
433 AutomationTimeAxisView::interpolation_changed (AutomationList::InterpolationStyle s)
434 {
435         if (ignore_mode_request) {
436                 return;
437         }
438         PBD::Unwinder<bool> uw (ignore_mode_request, true);
439         switch (s) {
440                 case AutomationList::Discrete:
441                         if (mode_discrete_item) {
442                                 mode_discrete_item->set_active(true);
443                         }
444                         break;
445                 case AutomationList::Linear:
446                         if (mode_line_item) {
447                                 mode_line_item->set_active(true);
448                         }
449                         break;
450                 case AutomationList::Logarithmic:
451                         if (mode_log_item) {
452                                 mode_log_item->set_active(true);
453                         }
454                         break;
455                 case AutomationList::Exponential:
456                         if (mode_exp_item) {
457                                 mode_exp_item->set_active(true);
458                         }
459                         break;
460                 default:
461                         break;
462         }
463 }
464
465 /** A menu item has been selected to change our interpolation mode */
466 void
467 AutomationTimeAxisView::set_interpolation (AutomationList::InterpolationStyle style)
468 {
469         /* Tell our view's list, if we have one, otherwise tell our own.
470          * Everything else will be signalled back from that.
471          */
472
473         if (_view) {
474                 _view->set_interpolation (style);
475         } else {
476                 assert (_control);
477                 _control->list()->set_interpolation (style);
478         }
479 }
480
481 void
482 AutomationTimeAxisView::clear_clicked ()
483 {
484         assert (_line || _view);
485
486         _editor.begin_reversible_command (_("clear automation"));
487
488         if (_line) {
489                 _line->clear ();
490         } else if (_view) {
491                 _view->clear ();
492         }
493         if (!EventTypeMap::instance().type_is_midi (_control->parameter().type())) {
494                 set_automation_state ((AutoState) ARDOUR::Off);
495         }
496         _editor.commit_reversible_command ();
497         _session->set_dirty ();
498 }
499
500 void
501 AutomationTimeAxisView::set_height (uint32_t h, TrackHeightMode m)
502 {
503         bool const changed = (height != (uint32_t) h) || first_call_to_set_height;
504         uint32_t const normal = preset_height (HeightNormal);
505         bool const changed_between_small_and_normal = ( (height < normal && h >= normal) || (height >= normal || h < normal) );
506
507         TimeAxisView::set_height (h, m);
508
509         _base_rect->set_y1 (h);
510
511         if (_line) {
512                 _line->set_height(h - 2.5);
513         }
514
515         if (_view) {
516                 _view->set_height(h);
517                 _view->update_contents_height();
518         }
519
520         if (changed_between_small_and_normal || first_call_to_set_height) {
521
522                 first_call_to_set_height = false;
523
524                 if (h >= preset_height (HeightNormal)) {
525                         auto_dropdown.show();
526                         name_label.show();
527                         hide_button.show();
528
529                 } else if (h >= preset_height (HeightSmall)) {
530                         controls_table.hide_all ();
531                         auto_dropdown.hide();
532                         name_label.hide();
533                 }
534         }
535
536         if (changed) {
537                 if (_canvas_display->visible() && _stripable) {
538                         /* only emit the signal if the height really changed and we were visible */
539                         _stripable->gui_changed ("visible_tracks", (void *) 0); /* EMIT_SIGNAL */
540                 }
541         }
542 }
543
544 void
545 AutomationTimeAxisView::set_samples_per_pixel (double fpp)
546 {
547         TimeAxisView::set_samples_per_pixel (fpp);
548
549         if (_line) {
550                 _line->reset ();
551         }
552
553         if (_view) {
554                 _view->set_samples_per_pixel (fpp);
555         }
556 }
557
558 void
559 AutomationTimeAxisView::hide_clicked ()
560 {
561         hide_button.set_sensitive(false);
562         set_marked_for_display (false);
563         StripableTimeAxisView* stv = dynamic_cast<StripableTimeAxisView*>(parent);
564         if (stv) {
565                 stv->request_redraw ();
566         }
567         hide_button.set_sensitive(true);
568 }
569
570 string
571 AutomationTimeAxisView::automation_state_off_string () const
572 {
573         if (_parameter.type() >= MidiCCAutomation && _parameter.type() <= MidiChannelPressureAutomation) {
574                 return S_("Automation|Off");
575         }
576
577         return S_("Automation|Manual");
578 }
579
580 void
581 AutomationTimeAxisView::build_display_menu ()
582 {
583         using namespace Menu_Helpers;
584
585         /* prepare it */
586
587         TimeAxisView::build_display_menu ();
588
589         /* now fill it with our stuff */
590
591         MenuList& items = display_menu->items();
592
593         items.push_back (MenuElem (_("Hide"), sigc::mem_fun(*this, &AutomationTimeAxisView::hide_clicked)));
594         items.push_back (SeparatorElem());
595         items.push_back (MenuElem (_("Clear"), sigc::mem_fun(*this, &AutomationTimeAxisView::clear_clicked)));
596         items.push_back (SeparatorElem());
597
598         /* state menu */
599
600         Menu* auto_state_menu = manage (new Menu);
601         auto_state_menu->set_name ("ArdourContextMenu");
602         MenuList& as_items = auto_state_menu->items();
603
604         as_items.push_back (CheckMenuElem (automation_state_off_string(), sigc::bind (
605                         sigc::mem_fun(*this, &AutomationTimeAxisView::set_automation_state),
606                         (AutoState) ARDOUR::Off)));
607         auto_off_item = dynamic_cast<Gtk::CheckMenuItem*>(&as_items.back());
608
609         as_items.push_back (CheckMenuElem (_("Play"), sigc::bind (
610                         sigc::mem_fun(*this, &AutomationTimeAxisView::set_automation_state),
611                         (AutoState) Play)));
612         auto_play_item = dynamic_cast<Gtk::CheckMenuItem*>(&as_items.back());
613
614         if (!(_parameter.type() >= MidiCCAutomation &&
615               _parameter.type() <= MidiChannelPressureAutomation)) {
616                 as_items.push_back (CheckMenuElem (_("Write"), sigc::bind (
617                                                            sigc::mem_fun(*this, &AutomationTimeAxisView::set_automation_state),
618                                                            (AutoState) Write)));
619                 auto_write_item = dynamic_cast<Gtk::CheckMenuItem*>(&as_items.back());
620
621                 as_items.push_back (CheckMenuElem (_("Touch"), sigc::bind (
622                                                            sigc::mem_fun(*this, &AutomationTimeAxisView::set_automation_state),
623                         (AutoState) Touch)));
624                 auto_touch_item = dynamic_cast<Gtk::CheckMenuItem*>(&as_items.back());
625         }
626
627         items.push_back (MenuElem (_("State"), *auto_state_menu));
628
629         /* mode menu */
630
631         /* current interpolation state */
632         AutomationList::InterpolationStyle const s = _view ? _view->interpolation() : _control->list()->interpolation ();
633
634         if (ARDOUR::parameter_is_midi((AutomationType)_parameter.type())) {
635
636                 Menu* auto_mode_menu = manage (new Menu);
637                 auto_mode_menu->set_name ("ArdourContextMenu");
638                 MenuList& am_items = auto_mode_menu->items();
639
640                 RadioMenuItem::Group group;
641
642                 am_items.push_back (RadioMenuElem (group, _("Discrete"), sigc::bind (
643                                 sigc::mem_fun(*this, &AutomationTimeAxisView::set_interpolation),
644                                 AutomationList::Discrete)));
645                 mode_discrete_item = dynamic_cast<Gtk::CheckMenuItem*>(&am_items.back());
646
647                 am_items.push_back (RadioMenuElem (group, _("Linear"), sigc::bind (
648                                 sigc::mem_fun(*this, &AutomationTimeAxisView::set_interpolation),
649                                 AutomationList::Linear)));
650                 mode_line_item = dynamic_cast<Gtk::CheckMenuItem*>(&am_items.back());
651
652                 items.push_back (MenuElem (_("Mode"), *auto_mode_menu));
653
654         } else {
655 #ifdef XXX_NEW_INTERPOLATON__BREAK_SESSION_FORMAT_XXX
656                 Menu* auto_mode_menu = manage (new Menu);
657                 auto_mode_menu->set_name ("ArdourContextMenu");
658                 MenuList& am_items = auto_mode_menu->items();
659                 bool have_options = false;
660
661                 RadioMenuItem::Group group;
662
663                 am_items.push_back (RadioMenuElem (group, _("Linear"), sigc::bind (
664                                 sigc::mem_fun(*this, &AutomationTimeAxisView::set_interpolation),
665                                 AutomationList::Linear)));
666                 mode_line_item = dynamic_cast<Gtk::CheckMenuItem*>(&am_items.back());
667
668                 if (_control->desc().logarithmic) {
669                         am_items.push_back (RadioMenuElem (group, _("Logarithmic"), sigc::bind (
670                                                         sigc::mem_fun(*this, &AutomationTimeAxisView::set_interpolation),
671                                                         AutomationList::Logarithmic)));
672                         mode_log_item = dynamic_cast<Gtk::CheckMenuItem*>(&am_items.back());
673                         have_options = true;
674                 } else {
675                         mode_log_item = 0;
676                 }
677
678                 if (_line->get_uses_gain_mapping () && !_control->desc().logarithmic) {
679                         am_items.push_back (RadioMenuElem (group, _("Exponential"), sigc::bind (
680                                                         sigc::mem_fun(*this, &AutomationTimeAxisView::set_interpolation),
681                                                         AutomationList::Exponential)));
682                         mode_exp_item = dynamic_cast<Gtk::CheckMenuItem*>(&am_items.back());
683                         have_options = true;
684                 } else {
685                         mode_exp_item = 0;
686                 }
687
688                 if (have_options) {
689                         items.push_back (MenuElem (_("Interpolation"), *auto_mode_menu));
690                 } else {
691                         mode_line_item = 0;
692                         delete auto_mode_menu;
693                         auto_mode_menu = 0;
694                 }
695 #endif
696         }
697
698         /* make sure the automation menu state is correct */
699
700         automation_state_changed ();
701         interpolation_changed (s);
702 }
703
704 void
705 AutomationTimeAxisView::add_automation_event (GdkEvent* event, framepos_t frame, double y, bool with_guard_points)
706 {
707         if (!_line) {
708                 return;
709         }
710
711         boost::shared_ptr<AutomationList> list = _line->the_list ();
712
713         if (list->in_write_pass()) {
714                 /* do not allow the GUI to add automation events during an
715                    automation write pass.
716                 */
717                 return;
718         }
719
720         MusicFrame when (frame, 0);
721         _editor.snap_to_with_modifier (when, event);
722
723         if (UIConfiguration::instance().get_new_automation_points_on_lane()) {
724                 if (_control->list()->size () == 0) {
725                         y = _control->get_value ();
726                 } else {
727                         y = _control->list()->eval (when.frame);
728                 }
729         } else {
730                 double x = 0;
731                 _line->grab_item().canvas_to_item (x, y);
732                 /* compute vertical fractional position */
733                 y = 1.0 - (y / _line->height());
734                 /* map using line */
735                 _line->view_to_model_coord (x, y);
736         }
737
738         XMLNode& before = list->get_state();
739         std::list<Selectable*> results;
740
741         if (list->editor_add (when.frame, y, with_guard_points)) {
742                 XMLNode& after = list->get_state();
743                 _editor.begin_reversible_command (_("add automation event"));
744                 _session->add_command (new MementoCommand<ARDOUR::AutomationList> (*list.get (), &before, &after));
745
746                 _line->get_selectables (when.frame, when.frame, 0.0, 1.0, results);
747                 _editor.get_selection ().set (results);
748
749                 _editor.commit_reversible_command ();
750                 _session->set_dirty ();
751         }
752 }
753
754 bool
755 AutomationTimeAxisView::paste (framepos_t pos, const Selection& selection, PasteContext& ctx, const int32_t divisions)
756 {
757         if (_line) {
758                 return paste_one (pos, ctx.count, ctx.times, selection, ctx.counts, ctx.greedy);
759         } else if (_view) {
760                 AutomationSelection::const_iterator l = selection.lines.get_nth(_parameter, ctx.counts.n_lines(_parameter));
761                 if (l == selection.lines.end()) {
762                         if (ctx.greedy && selection.lines.size() == 1) {
763                                 l = selection.lines.begin();
764                         }
765                 }
766                 if (l != selection.lines.end() && _view->paste (pos, ctx.count, ctx.times, *l)) {
767                         ctx.counts.increase_n_lines(_parameter);
768                         return true;
769                 }
770         }
771
772         return false;
773 }
774
775 bool
776 AutomationTimeAxisView::paste_one (framepos_t pos, unsigned paste_count, float times, const Selection& selection, ItemCounts& counts, bool greedy)
777 {
778         boost::shared_ptr<AutomationList> alist(_line->the_list());
779
780         if (_session->transport_rolling() && alist->automation_write()) {
781                 /* do not paste if this control is in write mode and we're rolling */
782                 return false;
783         }
784
785         /* Get appropriate list from selection. */
786         AutomationSelection::const_iterator p = selection.lines.get_nth(_parameter, counts.n_lines(_parameter));
787         if (p == selection.lines.end()) {
788                 if (greedy && selection.lines.size() == 1) {
789                         p = selection.lines.begin();
790                 } else {
791                         return false;
792                 }
793         }
794         counts.increase_n_lines(_parameter);
795
796         /* add multi-paste offset if applicable */
797
798         AutomationType src_type = (AutomationType)(*p)->parameter().type ();
799         double len = (*p)->length();
800
801         if (parameter_is_midi (src_type)) {
802                 // convert length to samples (incl tempo-ramps)
803                 len = DoubleBeatsFramesConverter (_session->tempo_map(), pos).to (len * paste_count);
804                 pos += _editor.get_paste_offset (pos, paste_count > 0 ? 1 : 0, len);
805         } else {
806                 pos += _editor.get_paste_offset (pos, paste_count, len);
807         }
808
809         /* convert sample-position to model's unit and position */
810         double const model_pos = _line->time_converter().from (pos - _line->time_converter().origin_b ());
811
812         XMLNode &before = alist->get_state();
813         alist->paste (**p, model_pos, DoubleBeatsFramesConverter (_session->tempo_map(), pos));
814         _session->add_command (new MementoCommand<AutomationList>(*alist.get(), &before, &alist->get_state()));
815
816         return true;
817 }
818
819 void
820 AutomationTimeAxisView::get_selectables (framepos_t start, framepos_t end, double top, double bot, list<Selectable*>& results, bool /*within*/)
821 {
822         if (!_line && !_view) {
823                 return;
824         }
825
826         if (touched (top, bot)) {
827
828                 /* remember: this is X Window - coordinate space starts in upper left and moves down.
829                    _y_position is the "origin" or "top" of the track.
830                 */
831
832                 /* bottom of our track */
833                 double const mybot = _y_position + height;
834
835                 double topfrac;
836                 double botfrac;
837
838                 if (_y_position >= top && mybot <= bot) {
839
840                         /* _y_position is below top, mybot is above bot, so we're fully
841                            covered vertically.
842                         */
843
844                         topfrac = 1.0;
845                         botfrac = 0.0;
846
847                 } else {
848
849                         /* top and bot are within _y_position .. mybot */
850
851                         topfrac = 1.0 - ((top - _y_position) / height);
852                         botfrac = 1.0 - ((bot - _y_position) / height);
853
854                 }
855
856                 if (_line) {
857                         _line->get_selectables (start, end, botfrac, topfrac, results);
858                 } else if (_view) {
859                         _view->get_selectables (start, end, botfrac, topfrac, results);
860                 }
861         }
862 }
863
864 void
865 AutomationTimeAxisView::get_inverted_selectables (Selection& sel, list<Selectable*>& result)
866 {
867         if (_line) {
868                 _line->get_inverted_selectables (sel, result);
869         }
870 }
871
872 void
873 AutomationTimeAxisView::set_selected_points (PointSelection& points)
874 {
875         if (_line) {
876                 _line->set_selected_points (points);
877         } else if (_view) {
878                 _view->set_selected_points (points);
879         }
880 }
881
882 void
883 AutomationTimeAxisView::clear_lines ()
884 {
885         _line.reset();
886         _list_connections.drop_connections ();
887 }
888
889 void
890 AutomationTimeAxisView::add_line (boost::shared_ptr<AutomationLine> line)
891 {
892         if (_control && line) {
893                 assert(line->the_list() == _control->list());
894
895                 _control->alist()->automation_state_changed.connect (
896                         _list_connections, invalidator (*this), boost::bind (&AutomationTimeAxisView::automation_state_changed, this), gui_context()
897                         );
898
899                 _control->alist()->InterpolationChanged.connect (
900                         _list_connections, invalidator (*this), boost::bind (&AutomationTimeAxisView::interpolation_changed, this, _1), gui_context()
901                         );
902         }
903
904         _line = line;
905
906         line->set_height (height - 2.5);
907
908         /* pick up the current state */
909         automation_state_changed ();
910
911         line->add_visibility (AutomationLine::Line);
912 }
913
914 bool
915 AutomationTimeAxisView::propagate_time_selection () const
916 {
917         /* MIDI automation is part of the MIDI region. It is always
918          * implicily selected with the parent, regardless of TAV selection
919          */
920         if (_parameter.type() >= MidiCCAutomation &&
921             _parameter.type() <= MidiChannelPressureAutomation) {
922                 return true;
923         }
924         return false;
925 }
926
927 void
928 AutomationTimeAxisView::entered()
929 {
930         if (_line) {
931                 _line->track_entered();
932         }
933 }
934
935 void
936 AutomationTimeAxisView::exited ()
937 {
938         if (_line) {
939                 _line->track_exited();
940         }
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_2X (const XMLNode& node, int /*version*/)
953 {
954         if (node.name() == X_("gain") && _parameter == Evoral::Parameter (GainAutomation)) {
955
956                 bool shown;
957                 if (node.get_property (X_("shown"), shown)) {
958                         if (shown) {
959                                 _canvas_display->show (); /* FIXME: necessary? show_at? */
960                                 set_gui_property ("visible", shown);
961                         }
962                 } else {
963                         set_gui_property ("visible", false);
964                 }
965         }
966
967         return 0;
968 }
969
970 int
971 AutomationTimeAxisView::set_state (const XMLNode&, int /*version*/)
972 {
973         return 0;
974 }
975
976 void
977 AutomationTimeAxisView::what_has_visible_automation (const boost::shared_ptr<Automatable>& automatable, set<Evoral::Parameter>& visible)
978 {
979         /* this keeps "knowledge" of how we store visibility information
980            in XML private to this class.
981         */
982
983         assert (automatable);
984
985         Automatable::Controls& controls (automatable->controls());
986
987         for (Automatable::Controls::iterator i = controls.begin(); i != controls.end(); ++i) {
988
989                 boost::shared_ptr<AutomationControl> ac = boost::dynamic_pointer_cast<AutomationControl> (i->second);
990
991                 if (ac && ac->alist()) {
992
993                         const XMLNode* gui_node = ac->extra_xml ("GUI");
994
995                         if (gui_node) {
996                                 bool shown;
997                                 if (gui_node->get_property ("shown", shown) && shown) {
998                                         visible.insert (i->first);
999                                 }
1000                         }
1001                 }
1002         }
1003 }
1004
1005
1006 /** @return true if this view has any automation data to display */
1007 bool
1008 AutomationTimeAxisView::has_automation () const
1009 {
1010         return ( (_line && _line->npoints() > 0) || (_view && _view->has_automation()) );
1011 }
1012
1013 list<boost::shared_ptr<AutomationLine> >
1014 AutomationTimeAxisView::lines () const
1015 {
1016         list<boost::shared_ptr<AutomationLine> > lines;
1017
1018         if (_line) {
1019                 lines.push_back (_line);
1020         } else if (_view) {
1021                 lines = _view->get_lines ();
1022         }
1023
1024         return lines;
1025 }
1026
1027 string
1028 AutomationTimeAxisView::state_id() const
1029 {
1030         if (_parameter && _stripable && _automatable == _stripable) {
1031                 const string parameter_str = PBD::to_string (_parameter.type()) + "/" +
1032                                              PBD::to_string (_parameter.id()) + "/" +
1033                                              PBD::to_string (_parameter.channel ());
1034
1035                 return string("automation ") + PBD::to_string(_stripable->id()) + " " + parameter_str;
1036         } else if (_automatable != _stripable && _control) {
1037                 return string("automation ") + _control->id().to_s();
1038         } else {
1039                 error << "Automation time axis has no state ID" << endmsg;
1040                 return "";
1041         }
1042 }
1043
1044 /** Given a state id string, see if it is one generated by
1045  *  this class.  If so, parse it into its components.
1046  *  @param state_id State ID string to parse.
1047  *  @param route_id Filled in with the route's ID if the state ID string is parsed.
1048  *  @param has_parameter Filled in with true if the state ID has a parameter, otherwise false.
1049  *  @param parameter Filled in with the state ID's parameter, if it has one.
1050  *  @return true if this is a state ID generated by this class, otherwise false.
1051  */
1052
1053 bool
1054 AutomationTimeAxisView::parse_state_id (
1055         string const & state_id,
1056         PBD::ID & route_id,
1057         bool & has_parameter,
1058         Evoral::Parameter & parameter)
1059 {
1060         stringstream ss;
1061         ss << state_id;
1062
1063         string a, b, c;
1064         ss >> a >> b >> c;
1065
1066         if (a != X_("automation")) {
1067                 return false;
1068         }
1069
1070         route_id = PBD::ID (b);
1071
1072         if (c.empty ()) {
1073                 has_parameter = false;
1074                 return true;
1075         }
1076
1077         has_parameter = true;
1078
1079         vector<string> p;
1080         boost::split (p, c, boost::is_any_of ("/"));
1081
1082         assert (p.size() == 3);
1083
1084         parameter = Evoral::Parameter (
1085                 PBD::string_to<uint32_t>(p[0]), // type
1086                 PBD::string_to<uint8_t>(p[2]), // channel
1087                 PBD::string_to<uint32_t>(p[1]) // id
1088                 );
1089
1090         return true;
1091 }
1092
1093 void
1094 AutomationTimeAxisView::cut_copy_clear (Selection& selection, CutCopyOp op)
1095 {
1096         list<boost::shared_ptr<AutomationLine> > lines;
1097         if (_line) {
1098                 lines.push_back (_line);
1099         } else if (_view) {
1100                 lines = _view->get_lines ();
1101         }
1102
1103         for (list<boost::shared_ptr<AutomationLine> >::iterator i = lines.begin(); i != lines.end(); ++i) {
1104                 cut_copy_clear_one (**i, selection, op);
1105         }
1106 }
1107
1108 void
1109 AutomationTimeAxisView::cut_copy_clear_one (AutomationLine& line, Selection& selection, CutCopyOp op)
1110 {
1111         boost::shared_ptr<Evoral::ControlList> what_we_got;
1112         boost::shared_ptr<AutomationList> alist (line.the_list());
1113
1114         XMLNode &before = alist->get_state();
1115
1116         /* convert time selection to automation list model coordinates */
1117         const Evoral::TimeConverter<double, ARDOUR::framepos_t>& tc = line.time_converter ();
1118         double const start = tc.from (selection.time.front().start - tc.origin_b ());
1119         double const end = tc.from (selection.time.front().end - tc.origin_b ());
1120
1121         switch (op) {
1122         case Delete:
1123                 if (alist->cut (start, end) != 0) {
1124                         _session->add_command(new MementoCommand<AutomationList>(*alist.get(), &before, &alist->get_state()));
1125                 }
1126                 break;
1127
1128         case Cut:
1129
1130                 if ((what_we_got = alist->cut (start, end)) != 0) {
1131                         _editor.get_cut_buffer().add (what_we_got);
1132                         _session->add_command(new MementoCommand<AutomationList>(*alist.get(), &before, &alist->get_state()));
1133                 }
1134                 break;
1135         case Copy:
1136                 if ((what_we_got = alist->copy (start, end)) != 0) {
1137                         _editor.get_cut_buffer().add (what_we_got);
1138                 }
1139                 break;
1140
1141         case Clear:
1142                 if ((what_we_got = alist->cut (start, end)) != 0) {
1143                         _session->add_command(new MementoCommand<AutomationList>(*alist.get(), &before, &alist->get_state()));
1144                 }
1145                 break;
1146         }
1147
1148         if (what_we_got) {
1149                 for (AutomationList::iterator x = what_we_got->begin(); x != what_we_got->end(); ++x) {
1150                         double when = (*x)->when;
1151                         double val  = (*x)->value;
1152                         line.model_to_view_coord (when, val);
1153                         (*x)->when = when;
1154                         (*x)->value = val;
1155                 }
1156         }
1157 }
1158
1159 PresentationInfo const &
1160 AutomationTimeAxisView::presentation_info () const
1161 {
1162         return _stripable->presentation_info();
1163 }
1164
1165 boost::shared_ptr<Stripable>
1166 AutomationTimeAxisView::stripable () const
1167 {
1168         return _stripable;
1169 }
1170
1171 Gdk::Color
1172 AutomationTimeAxisView::color () const
1173 {
1174         return gdk_color_from_rgb (_stripable->presentation_info().color());
1175 }