various fixes to MidiRegionView selection handling, key handling, drawing of ghost...
[ardour2.git] / gtk2_ardour / automation_time_axis.cc
blob7f70d3cd84929589fdbf5e5609d1dbd7f66dd0f8
1 /*
2 Copyright (C) 2000-2007 Paul Davis
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.
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.
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.
20 #include <utility>
21 #include <gtkmm2ext/barcontroller.h>
22 #include <gtkmm2ext/utils.h>
23 #include <boost/algorithm/string.hpp>
24 #include <boost/lexical_cast.hpp>
26 #include "pbd/memento_command.h"
27 #include "pbd/stacktrace.h"
29 #include "ardour/automation_control.h"
30 #include "ardour/event_type_map.h"
31 #include "ardour/route.h"
32 #include "ardour/session.h"
34 #include "ardour_ui.h"
35 #include "automation_time_axis.h"
36 #include "automation_streamview.h"
37 #include "global_signals.h"
38 #include "gui_thread.h"
39 #include "route_time_axis.h"
40 #include "automation_line.h"
41 #include "public_editor.h"
42 #include "simplerect.h"
43 #include "selection.h"
44 #include "rgb_macros.h"
45 #include "point_selection.h"
46 #include "canvas_impl.h"
47 #include "utils.h"
49 #include "i18n.h"
51 using namespace std;
52 using namespace ARDOUR;
53 using namespace PBD;
54 using namespace Gtk;
55 using namespace Gtkmm2ext;
56 using namespace Editing;
58 Pango::FontDescription AutomationTimeAxisView::name_font;
59 bool AutomationTimeAxisView::have_name_font = false;
62 /** \a a the automatable object this time axis is to display data for.
63 * For route/track automation (e.g. gain) pass the route for both \r and \a.
64 * For route child (e.g. plugin) automation, pass the child for \a.
65 * For region automation (e.g. MIDI CC), pass null for \a.
67 AutomationTimeAxisView::AutomationTimeAxisView (
68 Session* s,
69 boost::shared_ptr<Route> r,
70 boost::shared_ptr<Automatable> a,
71 boost::shared_ptr<AutomationControl> c,
72 Evoral::Parameter p,
73 PublicEditor& e,
74 TimeAxisView& parent,
75 bool show_regions,
76 ArdourCanvas::Canvas& canvas,
77 const string & nom,
78 const string & nomparent
80 : AxisView (s)
81 , TimeAxisView (s, e, &parent, canvas)
82 , _route (r)
83 , _control (c)
84 , _automatable (a)
85 , _parameter (p)
86 , _base_rect (0)
87 , _view (show_regions ? new AutomationStreamView (*this) : 0)
88 , _name (nom)
89 , auto_button (X_("")) /* force addition of a label */
91 if (!have_name_font) {
92 name_font = get_font_for_style (X_("AutomationTrackName"));
93 have_name_font = true;
96 if (_automatable && _control) {
97 _controller = AutomationController::create (_automatable, _control->parameter(), _control);
100 automation_menu = 0;
101 auto_off_item = 0;
102 auto_touch_item = 0;
103 auto_write_item = 0;
104 auto_play_item = 0;
105 mode_discrete_item = 0;
106 mode_line_item = 0;
108 ignore_state_request = false;
109 first_call_to_set_height = true;
111 _base_rect = new SimpleRect(*_canvas_display);
112 _base_rect->property_x1() = 0.0;
113 _base_rect->property_y1() = 0.0;
114 /** gnomecanvas sometimes converts this value to int or adds 2 to it, so it must be
115 set correctly to avoid overflow.
117 _base_rect->property_x2() = INT_MAX - 2;
118 _base_rect->property_outline_color_rgba() = ARDOUR_UI::config()->canvasvar_AutomationTrackOutline.get();
120 /* outline ends and bottom */
121 _base_rect->property_outline_what() = (guint32) (0x1|0x2|0x8);
122 _base_rect->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_AutomationTrackFill.get();
124 _base_rect->set_data ("trackview", this);
126 _base_rect->signal_event().connect (sigc::bind (
127 sigc::mem_fun (_editor, &PublicEditor::canvas_automation_track_event),
128 _base_rect, this));
130 if (!a) {
131 _base_rect->lower_to_bottom();
134 hide_button.add (*(manage (new Gtk::Image (::get_icon("hide")))));
136 auto_button.set_name ("TrackVisualButton");
137 hide_button.set_name ("TrackRemoveButton");
139 auto_button.unset_flags (Gtk::CAN_FOCUS);
140 hide_button.unset_flags (Gtk::CAN_FOCUS);
142 controls_table.set_no_show_all();
144 ARDOUR_UI::instance()->set_tip(auto_button, _("automation state"));
145 ARDOUR_UI::instance()->set_tip(hide_button, _("hide track"));
147 string str = gui_property ("height");
148 if (!str.empty()) {
149 set_height (atoi (str));
150 } else {
151 set_height (preset_height (HeightNormal));
154 /* rearrange the name display */
156 controls_table.remove (name_hbox);
157 controls_table.attach (name_hbox, 1, 6, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND, 3, 0);
159 /* we never show these for automation tracks, so make
160 life easier and remove them.
163 hide_name_entry();
165 name_label.set_text (_name);
166 name_label.set_alignment (Gtk::ALIGN_CENTER, Gtk::ALIGN_CENTER);
167 name_label.set_name (X_("TrackParameterName"));
168 name_label.set_ellipsize (Pango::ELLIPSIZE_END);
170 string tipname = nomparent;
171 if (!tipname.empty()) {
172 tipname += ": ";
174 tipname += _name;
175 ARDOUR_UI::instance()->set_tip(controls_ebox, tipname);
177 /* add the buttons */
178 controls_table.attach (hide_button, 0, 1, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
179 controls_table.attach (auto_button, 6, 8, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
181 if (_controller) {
182 /* add bar controller */
183 controls_table.attach (*_controller.get(), 0, 8, 1, 2, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
186 controls_table.show_all ();
188 hide_button.signal_clicked().connect (sigc::mem_fun(*this, &AutomationTimeAxisView::hide_clicked));
189 auto_button.signal_clicked().connect (sigc::mem_fun(*this, &AutomationTimeAxisView::auto_clicked));
191 controls_base_selected_name = X_("AutomationTrackControlsBaseSelected");
192 controls_base_unselected_name = X_("AutomationTrackControlsBase");
193 controls_ebox.set_name (controls_base_unselected_name);
195 /* ask for notifications of any new RegionViews */
196 if (show_regions) {
198 assert(_view);
199 _view->attach ();
201 } else {
202 /* no regions, just a single line for the entire track (e.g. bus gain) */
204 assert (_control);
206 boost::shared_ptr<AutomationLine> line (
207 new AutomationLine (
208 ARDOUR::EventTypeMap::instance().to_symbol(_parameter),
209 *this,
210 *_canvas_display,
211 _control->alist()
215 line->set_line_color (ARDOUR_UI::config()->canvasvar_ProcessorAutomationLine.get());
216 line->queue_reset ();
217 add_line (line);
220 /* make sure labels etc. are correct */
222 automation_state_changed ();
223 ColorsChanged.connect (sigc::mem_fun (*this, &AutomationTimeAxisView::color_handler));
225 _route->DropReferences.connect (
226 _route_connections, invalidator (*this), ui_bind (&AutomationTimeAxisView::route_going_away, this), gui_context ()
230 AutomationTimeAxisView::~AutomationTimeAxisView ()
232 delete _view;
235 void
236 AutomationTimeAxisView::route_going_away ()
238 _route.reset ();
241 void
242 AutomationTimeAxisView::auto_clicked ()
244 using namespace Menu_Helpers;
246 if (automation_menu == 0) {
247 automation_menu = manage (new Menu);
248 automation_menu->set_name ("ArdourContextMenu");
249 MenuList& items (automation_menu->items());
251 items.push_back (MenuElem (_("Manual"), sigc::bind (sigc::mem_fun(*this,
252 &AutomationTimeAxisView::set_automation_state), (AutoState) Off)));
253 items.push_back (MenuElem (_("Play"), sigc::bind (sigc::mem_fun(*this,
254 &AutomationTimeAxisView::set_automation_state), (AutoState) Play)));
255 items.push_back (MenuElem (_("Write"), sigc::bind (sigc::mem_fun(*this,
256 &AutomationTimeAxisView::set_automation_state), (AutoState) Write)));
257 items.push_back (MenuElem (_("Touch"), sigc::bind (sigc::mem_fun(*this,
258 &AutomationTimeAxisView::set_automation_state), (AutoState) Touch)));
261 automation_menu->popup (1, gtk_get_current_event_time());
264 void
265 AutomationTimeAxisView::set_automation_state (AutoState state)
267 if (ignore_state_request) {
268 return;
271 if (_automatable) {
272 _automatable->set_parameter_automation_state (_parameter, state);
275 if (_view) {
276 _view->set_automation_state (state);
278 /* AutomationStreamViews don't signal when their automation state changes, so handle
279 our updates `manually'.
281 automation_state_changed ();
285 void
286 AutomationTimeAxisView::automation_state_changed ()
288 AutoState state;
290 /* update button label */
292 if (_view) {
293 state = _view->automation_state ();
294 } else if (_line) {
295 assert (_control);
296 state = _control->alist()->automation_state ();
297 } else {
298 state = Off;
301 switch (state & (Off|Play|Touch|Write)) {
302 case Off:
303 auto_button.set_label (_("Manual"));
304 if (auto_off_item) {
305 ignore_state_request = true;
306 auto_off_item->set_active (true);
307 auto_play_item->set_active (false);
308 auto_touch_item->set_active (false);
309 auto_write_item->set_active (false);
310 ignore_state_request = false;
312 break;
313 case Play:
314 auto_button.set_label (_("Play"));
315 if (auto_play_item) {
316 ignore_state_request = true;
317 auto_play_item->set_active (true);
318 auto_off_item->set_active (false);
319 auto_touch_item->set_active (false);
320 auto_write_item->set_active (false);
321 ignore_state_request = false;
323 break;
324 case Write:
325 auto_button.set_label (_("Write"));
326 if (auto_write_item) {
327 ignore_state_request = true;
328 auto_write_item->set_active (true);
329 auto_off_item->set_active (false);
330 auto_play_item->set_active (false);
331 auto_touch_item->set_active (false);
332 ignore_state_request = false;
334 break;
335 case Touch:
336 auto_button.set_label (_("Touch"));
337 if (auto_touch_item) {
338 ignore_state_request = true;
339 auto_touch_item->set_active (true);
340 auto_off_item->set_active (false);
341 auto_play_item->set_active (false);
342 auto_write_item->set_active (false);
343 ignore_state_request = false;
345 break;
346 default:
347 auto_button.set_label (_("???"));
348 break;
352 /** The interpolation style of our AutomationList has changed, so update */
353 void
354 AutomationTimeAxisView::interpolation_changed (AutomationList::InterpolationStyle s)
356 if (mode_line_item && mode_discrete_item) {
357 if (s == AutomationList::Discrete) {
358 mode_discrete_item->set_active(true);
359 mode_line_item->set_active(false);
360 } else {
361 mode_line_item->set_active(true);
362 mode_discrete_item->set_active(false);
367 /** A menu item has been selected to change our interpolation mode */
368 void
369 AutomationTimeAxisView::set_interpolation (AutomationList::InterpolationStyle style)
371 /* Tell our view's list, if we have one, otherwise tell our own.
372 * Everything else will be signalled back from that.
375 if (_view) {
376 _view->set_interpolation (style);
377 } else {
378 assert (_control);
379 _control->list()->set_interpolation (style);
383 void
384 AutomationTimeAxisView::clear_clicked ()
386 assert (_line || _view);
388 _session->begin_reversible_command (_("clear automation"));
390 if (_line) {
391 _line->clear ();
392 } else if (_view) {
393 _view->clear ();
396 _session->commit_reversible_command ();
397 _session->set_dirty ();
400 void
401 AutomationTimeAxisView::set_height (uint32_t h)
403 bool const changed = (height != (uint32_t) h) || first_call_to_set_height;
404 uint32_t const normal = preset_height (HeightNormal);
405 bool const changed_between_small_and_normal = ( (height < normal && h >= normal) || (height >= normal || h < normal) );
407 TimeAxisView::set_height (h);
409 _base_rect->property_y2() = h;
411 if (_line) {
412 _line->set_height(h);
415 if (_view) {
416 _view->set_height(h);
417 _view->update_contents_height();
420 if (changed_between_small_and_normal || first_call_to_set_height) {
422 first_call_to_set_height = false;
424 if (h >= preset_height (HeightNormal)) {
425 hide_name_entry ();
426 show_name_label ();
427 name_hbox.show_all ();
429 auto_button.show();
430 hide_button.show_all();
432 } else if (h >= preset_height (HeightSmall)) {
433 controls_table.hide_all ();
434 hide_name_entry ();
435 show_name_label ();
436 name_hbox.show_all ();
438 auto_button.hide();
439 hide_button.hide();
441 } else if (h >= preset_height (HeightNormal)) {
442 cerr << "track grown, but neither changed_between_small_and_normal nor first_call_to_set_height set!" << endl;
445 if (changed) {
446 if (canvas_item_visible (_canvas_display) && _route) {
447 /* only emit the signal if the height really changed and we were visible */
448 _route->gui_changed ("visible_tracks", (void *) 0); /* EMIT_SIGNAL */
453 void
454 AutomationTimeAxisView::set_samples_per_unit (double spu)
456 TimeAxisView::set_samples_per_unit (spu);
458 if (_line) {
459 _line->reset ();
462 if (_view) {
463 _view->set_samples_per_unit (spu);
467 void
468 AutomationTimeAxisView::hide_clicked ()
470 hide_button.set_sensitive(false);
471 set_marked_for_display (false);
472 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*>(parent);
473 if (rtv) {
474 rtv->request_redraw ();
476 hide_button.set_sensitive(true);
479 void
480 AutomationTimeAxisView::build_display_menu ()
482 using namespace Menu_Helpers;
484 /* prepare it */
486 TimeAxisView::build_display_menu ();
488 /* now fill it with our stuff */
490 MenuList& items = display_menu->items();
492 items.push_back (MenuElem (_("Hide"), sigc::mem_fun(*this, &AutomationTimeAxisView::hide_clicked)));
493 items.push_back (SeparatorElem());
494 items.push_back (MenuElem (_("Clear"), sigc::mem_fun(*this, &AutomationTimeAxisView::clear_clicked)));
495 items.push_back (SeparatorElem());
497 /* state menu */
499 Menu* auto_state_menu = manage (new Menu);
500 auto_state_menu->set_name ("ArdourContextMenu");
501 MenuList& as_items = auto_state_menu->items();
503 as_items.push_back (CheckMenuElem (_("Manual"), sigc::bind (
504 sigc::mem_fun(*this, &AutomationTimeAxisView::set_automation_state),
505 (AutoState) Off)));
506 auto_off_item = dynamic_cast<CheckMenuItem*>(&as_items.back());
508 as_items.push_back (CheckMenuElem (_("Play"), sigc::bind (
509 sigc::mem_fun(*this, &AutomationTimeAxisView::set_automation_state),
510 (AutoState) Play)));
511 auto_play_item = dynamic_cast<CheckMenuItem*>(&as_items.back());
513 as_items.push_back (CheckMenuElem (_("Write"), sigc::bind (
514 sigc::mem_fun(*this, &AutomationTimeAxisView::set_automation_state),
515 (AutoState) Write)));
516 auto_write_item = dynamic_cast<CheckMenuItem*>(&as_items.back());
518 as_items.push_back (CheckMenuElem (_("Touch"), sigc::bind (
519 sigc::mem_fun(*this, &AutomationTimeAxisView::set_automation_state),
520 (AutoState) Touch)));
521 auto_touch_item = dynamic_cast<CheckMenuItem*>(&as_items.back());
523 items.push_back (MenuElem (_("State"), *auto_state_menu));
525 /* mode menu */
527 /* current interpolation state */
528 AutomationList::InterpolationStyle const s = _view ? _view->interpolation() : _control->list()->interpolation ();
530 if (EventTypeMap::instance().is_midi_parameter(_parameter)) {
532 Menu* auto_mode_menu = manage (new Menu);
533 auto_mode_menu->set_name ("ArdourContextMenu");
534 MenuList& am_items = auto_mode_menu->items();
536 RadioMenuItem::Group group;
538 am_items.push_back (RadioMenuElem (group, _("Discrete"), sigc::bind (
539 sigc::mem_fun(*this, &AutomationTimeAxisView::set_interpolation),
540 AutomationList::Discrete)));
541 mode_discrete_item = dynamic_cast<CheckMenuItem*>(&am_items.back());
542 mode_discrete_item->set_active (s == AutomationList::Discrete);
544 am_items.push_back (RadioMenuElem (group, _("Linear"), sigc::bind (
545 sigc::mem_fun(*this, &AutomationTimeAxisView::set_interpolation),
546 AutomationList::Linear)));
547 mode_line_item = dynamic_cast<CheckMenuItem*>(&am_items.back());
548 mode_line_item->set_active (s == AutomationList::Linear);
550 items.push_back (MenuElem (_("Mode"), *auto_mode_menu));
553 /* make sure the automation menu state is correct */
555 automation_state_changed ();
556 interpolation_changed (s);
559 void
560 AutomationTimeAxisView::add_automation_event (ArdourCanvas::Item* /*item*/, GdkEvent* /*event*/, framepos_t when, double y)
562 if (!_line) {
563 return;
566 double x = 0;
568 _canvas_display->w2i (x, y);
570 /* compute vertical fractional position */
572 y = 1.0 - (y / height);
574 /* map using line */
576 _line->view_to_model_coord (x, y);
578 boost::shared_ptr<AutomationList> list = _line->the_list ();
580 _session->begin_reversible_command (_("add automation event"));
581 XMLNode& before = list->get_state();
583 list->add (when, y);
585 XMLNode& after = list->get_state();
586 _session->commit_reversible_command (new MementoCommand<ARDOUR::AutomationList> (*list, &before, &after));
587 _session->set_dirty ();
590 void
591 AutomationTimeAxisView::cut_copy_clear (Selection& selection, CutCopyOp op)
593 list<boost::shared_ptr<AutomationLine> > lines;
594 if (_line) {
595 lines.push_back (_line);
596 } else if (_view) {
597 lines = _view->get_lines ();
600 for (list<boost::shared_ptr<AutomationLine> >::iterator i = lines.begin(); i != lines.end(); ++i) {
601 cut_copy_clear_one (**i, selection, op);
605 void
606 AutomationTimeAxisView::cut_copy_clear_one (AutomationLine& line, Selection& selection, CutCopyOp op)
608 boost::shared_ptr<Evoral::ControlList> what_we_got;
609 boost::shared_ptr<AutomationList> alist (line.the_list());
611 XMLNode &before = alist->get_state();
613 /* convert time selection to automation list model coordinates */
614 const Evoral::TimeConverter<double, ARDOUR::framepos_t>& tc = line.time_converter ();
615 double const start = tc.from (selection.time.front().start - tc.origin_b ());
616 double const end = tc.from (selection.time.front().end - tc.origin_b ());
618 switch (op) {
619 case Delete:
620 if (alist->cut (start, end) != 0) {
621 _session->add_command(new MementoCommand<AutomationList>(*alist.get(), &before, &alist->get_state()));
623 break;
625 case Cut:
627 if ((what_we_got = alist->cut (start, end)) != 0) {
628 _editor.get_cut_buffer().add (what_we_got);
629 _session->add_command(new MementoCommand<AutomationList>(*alist.get(), &before, &alist->get_state()));
631 break;
632 case Copy:
633 if ((what_we_got = alist->copy (start, end)) != 0) {
634 _editor.get_cut_buffer().add (what_we_got);
636 break;
638 case Clear:
639 if ((what_we_got = alist->cut (start, end)) != 0) {
640 _session->add_command(new MementoCommand<AutomationList>(*alist.get(), &before, &alist->get_state()));
642 break;
645 if (what_we_got) {
646 for (AutomationList::iterator x = what_we_got->begin(); x != what_we_got->end(); ++x) {
647 double when = (*x)->when;
648 double val = (*x)->value;
649 line.model_to_view_coord (when, val);
650 (*x)->when = when;
651 (*x)->value = val;
656 void
657 AutomationTimeAxisView::reset_objects (PointSelection& selection)
659 list<boost::shared_ptr<AutomationLine> > lines;
660 if (_line) {
661 lines.push_back (_line);
662 } else if (_view) {
663 lines = _view->get_lines ();
666 for (list<boost::shared_ptr<AutomationLine> >::iterator i = lines.begin(); i != lines.end(); ++i) {
667 reset_objects_one (**i, selection);
671 void
672 AutomationTimeAxisView::reset_objects_one (AutomationLine& line, PointSelection& selection)
674 boost::shared_ptr<AutomationList> alist(line.the_list());
676 _session->add_command (new MementoCommand<AutomationList>(*alist.get(), &alist->get_state(), 0));
678 for (PointSelection::iterator i = selection.begin(); i != selection.end(); ++i) {
680 if ((*i).track != this) {
681 continue;
684 alist->reset_range ((*i).start, (*i).end);
688 void
689 AutomationTimeAxisView::cut_copy_clear_objects (PointSelection& selection, CutCopyOp op)
691 list<boost::shared_ptr<AutomationLine> > lines;
692 if (_line) {
693 lines.push_back (_line);
694 } else if (_view) {
695 lines = _view->get_lines ();
698 for (list<boost::shared_ptr<AutomationLine> >::iterator i = lines.begin(); i != lines.end(); ++i) {
699 cut_copy_clear_objects_one (**i, selection, op);
703 void
704 AutomationTimeAxisView::cut_copy_clear_objects_one (AutomationLine& line, PointSelection& selection, CutCopyOp op)
706 boost::shared_ptr<Evoral::ControlList> what_we_got;
707 boost::shared_ptr<AutomationList> alist(line.the_list());
709 XMLNode &before = alist->get_state();
711 for (PointSelection::iterator i = selection.begin(); i != selection.end(); ++i) {
713 if ((*i).track != this) {
714 continue;
717 switch (op) {
718 case Delete:
719 if (alist->cut ((*i).start, (*i).end) != 0) {
720 _session->add_command (new MementoCommand<AutomationList>(*alist.get(), new XMLNode (before), &alist->get_state()));
722 break;
723 case Cut:
724 if ((what_we_got = alist->cut ((*i).start, (*i).end)) != 0) {
725 _editor.get_cut_buffer().add (what_we_got);
726 _session->add_command (new MementoCommand<AutomationList>(*alist.get(), new XMLNode (before), &alist->get_state()));
728 break;
729 case Copy:
730 if ((what_we_got = alist->copy ((*i).start, (*i).end)) != 0) {
731 _editor.get_cut_buffer().add (what_we_got);
733 break;
735 case Clear:
736 if ((what_we_got = alist->cut ((*i).start, (*i).end)) != 0) {
737 _session->add_command (new MementoCommand<AutomationList>(*alist.get(), new XMLNode (before), &alist->get_state()));
739 break;
743 delete &before;
745 if (what_we_got) {
746 for (AutomationList::iterator x = what_we_got->begin(); x != what_we_got->end(); ++x) {
747 double when = (*x)->when;
748 double val = (*x)->value;
749 line.model_to_view_coord (when, val);
750 (*x)->when = when;
751 (*x)->value = val;
756 /** Paste a selection.
757 * @param pos Position to paste to (session frames).
758 * @param times Number of times to paste.
759 * @param selection Selection to paste.
760 * @param nth Index of the AutomationList within the selection to paste from.
762 bool
763 AutomationTimeAxisView::paste (framepos_t pos, float times, Selection& selection, size_t nth)
765 boost::shared_ptr<AutomationLine> line;
767 if (_line) {
768 line = _line;
769 } else if (_view) {
770 line = _view->paste_line (pos);
773 if (!line) {
774 return false;
777 return paste_one (*line, pos, times, selection, nth);
780 bool
781 AutomationTimeAxisView::paste_one (AutomationLine& line, framepos_t pos, float times, Selection& selection, size_t nth)
783 AutomationSelection::iterator p;
784 boost::shared_ptr<AutomationList> alist(line.the_list());
786 for (p = selection.lines.begin(); p != selection.lines.end() && nth; ++p, --nth) {}
788 if (p == selection.lines.end()) {
789 return false;
792 /* Make a copy of the list because we have to scale the
793 values from view coordinates to model coordinates, and we're
794 not supposed to modify the points in the selection.
797 AutomationList copy (**p);
799 for (AutomationList::iterator x = copy.begin(); x != copy.end(); ++x) {
800 double when = (*x)->when;
801 double val = (*x)->value;
802 line.view_to_model_coord (when, val);
803 (*x)->when = when;
804 (*x)->value = val;
807 double const model_pos = line.time_converter().from (pos - line.time_converter().origin_b ());
809 XMLNode &before = alist->get_state();
810 alist->paste (copy, model_pos, times);
811 _session->add_command (new MementoCommand<AutomationList>(*alist.get(), &before, &alist->get_state()));
813 return true;
816 void
817 AutomationTimeAxisView::get_selectables (framepos_t start, framepos_t end, double top, double bot, list<Selectable*>& results)
819 if (!_line && !_view) {
820 return;
823 if (touched (top, bot)) {
825 /* remember: this is X Window - coordinate space starts in upper left and moves down.
826 _y_position is the "origin" or "top" of the track.
829 /* bottom of our track */
830 double const mybot = _y_position + height;
832 double topfrac;
833 double botfrac;
835 if (_y_position >= top && mybot <= bot) {
837 /* _y_position is below top, mybot is above bot, so we're fully
838 covered vertically.
841 topfrac = 1.0;
842 botfrac = 0.0;
844 } else {
846 /* top and bot are within _y_position .. mybot */
848 topfrac = 1.0 - ((top - _y_position) / height);
849 botfrac = 1.0 - ((bot - _y_position) / height);
853 if (_line) {
854 _line->get_selectables (start, end, botfrac, topfrac, results);
855 } else if (_view) {
856 _view->get_selectables (start, end, botfrac, topfrac, results);
861 void
862 AutomationTimeAxisView::get_inverted_selectables (Selection& sel, list<Selectable*>& result)
864 if (_line) {
865 _line->get_inverted_selectables (sel, result);
869 void
870 AutomationTimeAxisView::set_selected_points (PointSelection& points)
872 if (_line) {
873 _line->set_selected_points (points);
874 } else if (_view) {
875 _view->set_selected_points (points);
879 void
880 AutomationTimeAxisView::clear_lines ()
882 _line.reset();
883 _list_connections.drop_connections ();
886 void
887 AutomationTimeAxisView::add_line (boost::shared_ptr<AutomationLine> line)
889 assert(line);
890 assert(!_line);
891 if (_control) {
892 assert(line->the_list() == _control->list());
894 _control->alist()->automation_state_changed.connect (
895 _list_connections, invalidator (*this), boost::bind (&AutomationTimeAxisView::automation_state_changed, this), gui_context()
898 _control->alist()->InterpolationChanged.connect (
899 _list_connections, invalidator (*this), boost::bind (&AutomationTimeAxisView::interpolation_changed, this, _1), gui_context()
903 _line = line;
905 line->set_height (height);
907 /* pick up the current state */
908 automation_state_changed ();
910 line->show();
913 void
914 AutomationTimeAxisView::entered()
916 if (_line) {
917 _line->track_entered();
921 void
922 AutomationTimeAxisView::exited ()
924 if (_line) {
925 _line->track_exited();
929 void
930 AutomationTimeAxisView::color_handler ()
932 if (_line) {
933 _line->set_colors();
938 AutomationTimeAxisView::set_state_2X (const XMLNode& node, int /*version*/)
940 if (node.name() == X_("gain") && _parameter == Evoral::Parameter (GainAutomation)) {
941 XMLProperty const * shown = node.property (X_("shown"));
942 if (shown) {
943 bool yn = string_is_affirmative (shown->value ());
944 if (yn) {
945 _canvas_display->show (); /* FIXME: necessary? show_at? */
947 set_gui_property ("visible", yn);
948 } else {
949 set_gui_property ("visible", false);
953 return 0;
957 AutomationTimeAxisView::set_state (const XMLNode& node, int /*version*/)
959 return 0;
962 void
963 AutomationTimeAxisView::what_has_visible_automation (const boost::shared_ptr<Automatable>& automatable, set<Evoral::Parameter>& visible)
965 /* this keeps "knowledge" of how we store visibility information
966 in XML private to this class.
969 assert (automatable);
971 Automatable::Controls& controls (automatable->controls());
973 for (Automatable::Controls::iterator i = controls.begin(); i != controls.end(); ++i) {
975 boost::shared_ptr<AutomationControl> ac = boost::dynamic_pointer_cast<AutomationControl> (i->second);
977 if (ac) {
979 const XMLNode* gui_node = ac->extra_xml ("GUI");
981 if (gui_node) {
982 const XMLProperty* prop = gui_node->property ("shown");
983 if (prop) {
984 if (string_is_affirmative (prop->value())) {
985 visible.insert (i->first);
994 /** @return true if this view has any automation data to display */
995 bool
996 AutomationTimeAxisView::has_automation () const
998 return ( (_line && _line->npoints() > 0) || (_view && _view->has_automation()) );
1001 list<boost::shared_ptr<AutomationLine> >
1002 AutomationTimeAxisView::lines () const
1004 list<boost::shared_ptr<AutomationLine> > lines;
1006 if (_line) {
1007 lines.push_back (_line);
1008 } else if (_view) {
1009 lines = _view->get_lines ();
1012 return lines;
1015 string
1016 AutomationTimeAxisView::state_id() const
1018 if (_control) {
1019 return string_compose ("automation %1", _control->id().to_s());
1020 } else {
1021 assert (_parameter);
1022 return string_compose ("automation %1 %2/%3/%4",
1023 _route->id(),
1024 _parameter.type(),
1025 _parameter.id(),
1026 (int) _parameter.channel());
1030 /** Given a state id string, see if it is one generated by
1031 * this class. If so, parse it into its components.
1032 * @param state_id State ID string to parse.
1033 * @param route_id Filled in with the route's ID if the state ID string is parsed.
1034 * @param has_parameter Filled in with true if the state ID has a parameter, otherwise false.
1035 * @param parameter Filled in with the state ID's parameter, if it has one.
1036 * @return true if this is a state ID generated by this class, otherwise false.
1039 bool
1040 AutomationTimeAxisView::parse_state_id (
1041 string const & state_id,
1042 PBD::ID & route_id,
1043 bool & has_parameter,
1044 Evoral::Parameter & parameter)
1046 stringstream s;
1047 s << state_id;
1049 string a, b, c;
1050 s >> a >> b >> c;
1052 if (a != X_("automation")) {
1053 return false;
1056 route_id = PBD::ID (b);
1058 if (c.empty ()) {
1059 has_parameter = false;
1060 return true;
1063 has_parameter = true;
1065 vector<string> p;
1066 boost::split (p, c, boost::is_any_of ("/"));
1068 assert (p.size() == 3);
1070 parameter = Evoral::Parameter (
1071 boost::lexical_cast<int> (p[0]),
1072 boost::lexical_cast<int> (p[2]),
1073 boost::lexical_cast<int> (p[1])
1076 return true;