Fix crash on moving back beyond the region start in the step editor (#4113).
[ardour2.git] / gtk2_ardour / automation_time_axis.cc
blob85ba4b378cc50906d7cea176c3d5dc1395f4e7b7
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>
23 #include "pbd/memento_command.h"
24 #include "pbd/stacktrace.h"
26 #include "ardour/automation_control.h"
27 #include "ardour/event_type_map.h"
28 #include "ardour/route.h"
29 #include "ardour/session.h"
31 #include "ardour_ui.h"
32 #include "automation_time_axis.h"
33 #include "automation_streamview.h"
34 #include "global_signals.h"
35 #include "gui_thread.h"
36 #include "route_time_axis.h"
37 #include "automation_line.h"
38 #include "public_editor.h"
39 #include "simplerect.h"
40 #include "selection.h"
41 #include "rgb_macros.h"
42 #include "point_selection.h"
43 #include "canvas_impl.h"
44 #include "utils.h"
46 #include "i18n.h"
48 using namespace std;
49 using namespace ARDOUR;
50 using namespace PBD;
51 using namespace Gtk;
52 using namespace Gtkmm2ext;
53 using namespace Editing;
55 Pango::FontDescription AutomationTimeAxisView::name_font;
56 bool AutomationTimeAxisView::have_name_font = false;
57 const string AutomationTimeAxisView::state_node_name = "AutomationChild";
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.
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
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 */
89 if (!have_name_font) {
90 name_font = get_font_for_style (X_("AutomationTrackName"));
91 have_name_font = true;
94 if (_automatable && _control) {
95 _controller = AutomationController::create (_automatable, _control->parameter(), _control);
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;
106 ignore_state_request = false;
107 first_call_to_set_height = true;
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.
115 _base_rect->property_x2() = INT_MAX - 2;
116 _base_rect->property_outline_color_rgba() = ARDOUR_UI::config()->canvasvar_AutomationTrackOutline.get();
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();
122 _base_rect->set_data ("trackview", this);
124 _base_rect->signal_event().connect (sigc::bind (
125 sigc::mem_fun (_editor, &PublicEditor::canvas_automation_track_event),
126 _base_rect, this));
128 if (!a) {
129 _base_rect->lower_to_bottom();
132 hide_button.add (*(manage (new Gtk::Image (::get_icon("hide")))));
134 auto_button.set_name ("TrackVisualButton");
135 hide_button.set_name ("TrackRemoveButton");
137 auto_button.unset_flags (Gtk::CAN_FOCUS);
138 hide_button.unset_flags (Gtk::CAN_FOCUS);
140 controls_table.set_no_show_all();
142 ARDOUR_UI::instance()->set_tip(auto_button, _("automation state"));
143 ARDOUR_UI::instance()->set_tip(hide_button, _("hide track"));
145 /* rearrange the name display */
147 /* we never show these for automation tracks, so make
148 life easier and remove them.
151 hide_name_entry();
153 /* keep the parameter name short */
155 string shortpname = _name;
156 int ignore_width;
157 shortpname = fit_to_pixels (_name, 60, name_font, ignore_width, true);
159 name_label.set_text (shortpname);
160 name_label.set_alignment (Gtk::ALIGN_CENTER, Gtk::ALIGN_CENTER);
161 name_label.set_name (X_("TrackParameterName"));
163 string tipname = nomparent;
164 if (!tipname.empty()) {
165 tipname += ": ";
167 tipname += _name;
168 ARDOUR_UI::instance()->set_tip(controls_ebox, tipname);
170 /* add the buttons */
171 controls_table.attach (hide_button, 0, 1, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
173 controls_table.attach (auto_button, 5, 8, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
175 if (_controller) {
176 /* add bar controller */
177 controls_table.attach (*_controller.get(), 0, 8, 1, 2, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
180 controls_table.show_all ();
182 hide_button.signal_clicked().connect (sigc::mem_fun(*this, &AutomationTimeAxisView::hide_clicked));
183 auto_button.signal_clicked().connect (sigc::mem_fun(*this, &AutomationTimeAxisView::auto_clicked));
185 controls_base_selected_name = X_("AutomationTrackControlsBaseSelected");
186 controls_base_unselected_name = X_("AutomationTrackControlsBase");
187 controls_ebox.set_name (controls_base_unselected_name);
189 XMLNode* xml_node = get_state_node ();
191 if (xml_node) {
192 set_state (*xml_node, Stateful::loading_state_version);
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 boost::shared_ptr<AutomationLine> line (
205 new AutomationLine (
206 ARDOUR::EventTypeMap::instance().to_symbol(_parameter),
207 *this,
208 *_canvas_display,
209 _control->alist()
213 line->set_line_color (ARDOUR_UI::config()->canvasvar_ProcessorAutomationLine.get());
214 line->queue_reset ();
215 add_line (line);
218 /* make sure labels etc. are correct */
220 automation_state_changed ();
221 ColorsChanged.connect (sigc::mem_fun (*this, &AutomationTimeAxisView::color_handler));
223 _route->DropReferences.connect (
224 _route_connections, invalidator (*this), ui_bind (&AutomationTimeAxisView::route_going_away, this), gui_context ()
228 AutomationTimeAxisView::~AutomationTimeAxisView ()
232 void
233 AutomationTimeAxisView::route_going_away ()
235 _route.reset ();
238 void
239 AutomationTimeAxisView::auto_clicked ()
241 using namespace Menu_Helpers;
243 if (automation_menu == 0) {
244 automation_menu = manage (new Menu);
245 automation_menu->set_name ("ArdourContextMenu");
246 MenuList& items (automation_menu->items());
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)));
258 automation_menu->popup (1, gtk_get_current_event_time());
261 void
262 AutomationTimeAxisView::set_automation_state (AutoState state)
264 if (ignore_state_request) {
265 return;
268 if (_automatable) {
269 _automatable->set_parameter_automation_state (_parameter, state);
271 #if 0
272 if (_route == _automatable) { // This is a time axis for route (not region) automation
273 _route->set_parameter_automation_state (_parameter, state);
276 if (_control->list()) {
277 _control->alist()->set_automation_state(state);
279 #endif
280 if (_view) {
281 _view->set_automation_state (state);
283 /* AutomationStreamViews don't signal when their automation state changes, so handle
284 our updates `manually'.
286 automation_state_changed ();
290 void
291 AutomationTimeAxisView::automation_state_changed ()
293 AutoState state;
295 /* update button label */
297 if (_line) {
298 state = _control->alist()->automation_state ();
299 } else if (_view) {
300 state = _view->automation_state ();
301 } else {
302 state = Off;
305 switch (state & (Off|Play|Touch|Write)) {
306 case Off:
307 auto_button.set_label (_("Manual"));
308 if (auto_off_item) {
309 ignore_state_request = true;
310 auto_off_item->set_active (true);
311 auto_play_item->set_active (false);
312 auto_touch_item->set_active (false);
313 auto_write_item->set_active (false);
314 ignore_state_request = false;
316 break;
317 case Play:
318 auto_button.set_label (_("Play"));
319 if (auto_play_item) {
320 ignore_state_request = true;
321 auto_play_item->set_active (true);
322 auto_off_item->set_active (false);
323 auto_touch_item->set_active (false);
324 auto_write_item->set_active (false);
325 ignore_state_request = false;
327 break;
328 case Write:
329 auto_button.set_label (_("Write"));
330 if (auto_write_item) {
331 ignore_state_request = true;
332 auto_write_item->set_active (true);
333 auto_off_item->set_active (false);
334 auto_play_item->set_active (false);
335 auto_touch_item->set_active (false);
336 ignore_state_request = false;
338 break;
339 case Touch:
340 auto_button.set_label (_("Touch"));
341 if (auto_touch_item) {
342 ignore_state_request = true;
343 auto_touch_item->set_active (true);
344 auto_off_item->set_active (false);
345 auto_play_item->set_active (false);
346 auto_write_item->set_active (false);
347 ignore_state_request = false;
349 break;
350 default:
351 auto_button.set_label (_("???"));
352 break;
356 /** The interpolation style of our AutomationList has changed, so update */
357 void
358 AutomationTimeAxisView::interpolation_changed (AutomationList::InterpolationStyle s)
360 if (mode_line_item && mode_discrete_item) {
361 if (s == AutomationList::Discrete) {
362 mode_discrete_item->set_active(true);
363 mode_line_item->set_active(false);
364 } else {
365 mode_line_item->set_active(true);
366 mode_discrete_item->set_active(false);
371 /** A menu item has been selected to change our interpolation mode */
372 void
373 AutomationTimeAxisView::set_interpolation (AutomationList::InterpolationStyle style)
375 /* Tell our view's list, if we have one, otherwise tell our own.
376 * Everything else will be signalled back from that.
379 if (_view) {
380 _view->set_interpolation (style);
381 } else {
382 _control->list()->set_interpolation (style);
386 void
387 AutomationTimeAxisView::clear_clicked ()
389 assert (_line || _view);
391 _session->begin_reversible_command (_("clear automation"));
393 if (_line) {
394 _line->clear ();
395 } else if (_view) {
396 _view->clear ();
399 _session->commit_reversible_command ();
400 _session->set_dirty ();
403 void
404 AutomationTimeAxisView::set_height (uint32_t h)
406 bool const changed = (height != (uint32_t) h) || first_call_to_set_height;
407 uint32_t const normal = preset_height (HeightNormal);
408 bool const changed_between_small_and_normal = ( (height < normal && h >= normal) || (height >= normal || h < normal) );
410 TimeAxisView* state_parent = get_parent_with_state ();
411 assert(state_parent);
412 XMLNode* xml_node = _control->extra_xml ("GUI");
414 TimeAxisView::set_height (h);
415 _base_rect->property_y2() = h;
417 if (_line) {
418 _line->set_height(h);
421 if (_view) {
422 _view->set_height(h);
423 _view->update_contents_height();
426 char buf[32];
427 snprintf (buf, sizeof (buf), "%u", height);
428 if (xml_node) {
429 xml_node->add_property ("height", buf);
432 if (changed_between_small_and_normal || first_call_to_set_height) {
434 first_call_to_set_height = false;
436 if (h >= preset_height (HeightNormal)) {
437 hide_name_entry ();
438 show_name_label ();
439 name_hbox.show_all ();
441 auto_button.show();
442 hide_button.show_all();
444 } else if (h >= preset_height (HeightSmall)) {
445 controls_table.hide_all ();
446 hide_name_entry ();
447 show_name_label ();
448 name_hbox.show_all ();
450 auto_button.hide();
451 hide_button.hide();
453 } else if (h >= preset_height (HeightNormal)) {
454 cerr << "track grown, but neither changed_between_small_and_normal nor first_call_to_set_height set!" << endl;
457 if (changed) {
458 if (canvas_item_visible (_canvas_display) && _route) {
459 /* only emit the signal if the height really changed and we were visible */
460 _route->gui_changed ("visible_tracks", (void *) 0); /* EMIT_SIGNAL */
465 void
466 AutomationTimeAxisView::set_samples_per_unit (double spu)
468 TimeAxisView::set_samples_per_unit (spu);
470 if (_line) {
471 _line->reset ();
474 if (_view) {
475 _view->set_samples_per_unit (spu);
479 void
480 AutomationTimeAxisView::hide_clicked ()
482 // LAME fix for refreshing the hide button
483 hide_button.set_sensitive(false);
485 set_marked_for_display (false);
486 hide ();
488 hide_button.set_sensitive(true);
491 void
492 AutomationTimeAxisView::build_display_menu ()
494 using namespace Menu_Helpers;
496 /* prepare it */
498 TimeAxisView::build_display_menu ();
500 /* now fill it with our stuff */
502 MenuList& items = display_menu->items();
504 items.push_back (MenuElem (_("Hide"), sigc::mem_fun(*this, &AutomationTimeAxisView::hide_clicked)));
505 items.push_back (SeparatorElem());
506 items.push_back (MenuElem (_("Clear"), sigc::mem_fun(*this, &AutomationTimeAxisView::clear_clicked)));
507 items.push_back (SeparatorElem());
509 /* state menu */
511 Menu* auto_state_menu = manage (new Menu);
512 auto_state_menu->set_name ("ArdourContextMenu");
513 MenuList& as_items = auto_state_menu->items();
515 as_items.push_back (CheckMenuElem (_("Manual"), sigc::bind (
516 sigc::mem_fun(*this, &AutomationTimeAxisView::set_automation_state),
517 (AutoState) Off)));
518 auto_off_item = dynamic_cast<CheckMenuItem*>(&as_items.back());
520 as_items.push_back (CheckMenuElem (_("Play"), sigc::bind (
521 sigc::mem_fun(*this, &AutomationTimeAxisView::set_automation_state),
522 (AutoState) Play)));
523 auto_play_item = dynamic_cast<CheckMenuItem*>(&as_items.back());
525 as_items.push_back (CheckMenuElem (_("Write"), sigc::bind (
526 sigc::mem_fun(*this, &AutomationTimeAxisView::set_automation_state),
527 (AutoState) Write)));
528 auto_write_item = dynamic_cast<CheckMenuItem*>(&as_items.back());
530 as_items.push_back (CheckMenuElem (_("Touch"), sigc::bind (
531 sigc::mem_fun(*this, &AutomationTimeAxisView::set_automation_state),
532 (AutoState) Touch)));
533 auto_touch_item = dynamic_cast<CheckMenuItem*>(&as_items.back());
535 items.push_back (MenuElem (_("State"), *auto_state_menu));
537 /* mode menu */
539 /* current interpolation state */
540 AutomationList::InterpolationStyle const s = _view ? _view->interpolation() : _control->list()->interpolation ();
542 if (EventTypeMap::instance().is_midi_parameter(_parameter)) {
544 Menu* auto_mode_menu = manage (new Menu);
545 auto_mode_menu->set_name ("ArdourContextMenu");
546 MenuList& am_items = auto_mode_menu->items();
548 RadioMenuItem::Group group;
550 am_items.push_back (RadioMenuElem (group, _("Discrete"), sigc::bind (
551 sigc::mem_fun(*this, &AutomationTimeAxisView::set_interpolation),
552 AutomationList::Discrete)));
553 mode_discrete_item = dynamic_cast<CheckMenuItem*>(&am_items.back());
554 mode_discrete_item->set_active (s == AutomationList::Discrete);
556 am_items.push_back (RadioMenuElem (group, _("Linear"), sigc::bind (
557 sigc::mem_fun(*this, &AutomationTimeAxisView::set_interpolation),
558 AutomationList::Linear)));
559 mode_line_item = dynamic_cast<CheckMenuItem*>(&am_items.back());
560 mode_line_item->set_active (s == AutomationList::Linear);
562 items.push_back (MenuElem (_("Mode"), *auto_mode_menu));
565 /* make sure the automation menu state is correct */
567 automation_state_changed ();
568 interpolation_changed (s);
571 void
572 AutomationTimeAxisView::add_automation_event (ArdourCanvas::Item* /*item*/, GdkEvent* /*event*/, framepos_t when, double y)
574 if (!_line) {
575 return;
578 double x = 0;
580 _canvas_display->w2i (x, y);
582 /* compute vertical fractional position */
584 y = 1.0 - (y / height);
586 /* map using line */
588 _line->view_to_model_coord (x, y);
590 _session->begin_reversible_command (_("add automation event"));
591 XMLNode& before = _control->alist()->get_state();
593 _control->alist()->add (when, y);
595 XMLNode& after = _control->alist()->get_state();
596 _session->commit_reversible_command (new MementoCommand<ARDOUR::AutomationList>(*_control->alist(), &before, &after));
598 _session->set_dirty ();
601 void
602 AutomationTimeAxisView::cut_copy_clear (Selection& selection, CutCopyOp op)
604 list<boost::shared_ptr<AutomationLine> > lines;
605 if (_line) {
606 lines.push_back (_line);
607 } else if (_view) {
608 lines = _view->get_lines ();
611 for (list<boost::shared_ptr<AutomationLine> >::iterator i = lines.begin(); i != lines.end(); ++i) {
612 cut_copy_clear_one (**i, selection, op);
616 void
617 AutomationTimeAxisView::cut_copy_clear_one (AutomationLine& line, Selection& selection, CutCopyOp op)
619 boost::shared_ptr<Evoral::ControlList> what_we_got;
620 boost::shared_ptr<AutomationList> alist (line.the_list());
622 XMLNode &before = alist->get_state();
624 /* convert time selection to automation list model coordinates */
625 const Evoral::TimeConverter<double, ARDOUR::framepos_t>& tc = line.time_converter ();
626 double const start = tc.from (selection.time.front().start - tc.origin_b ());
627 double const end = tc.from (selection.time.front().end - tc.origin_b ());
629 switch (op) {
630 case Delete:
631 if (alist->cut (start, end) != 0) {
632 _session->add_command(new MementoCommand<AutomationList>(*alist.get(), &before, &alist->get_state()));
634 break;
636 case Cut:
638 if ((what_we_got = alist->cut (start, end)) != 0) {
639 _editor.get_cut_buffer().add (what_we_got);
640 _session->add_command(new MementoCommand<AutomationList>(*alist.get(), &before, &alist->get_state()));
642 break;
643 case Copy:
644 if ((what_we_got = alist->copy (start, end)) != 0) {
645 _editor.get_cut_buffer().add (what_we_got);
647 break;
649 case Clear:
650 if ((what_we_got = alist->cut (start, end)) != 0) {
651 _session->add_command(new MementoCommand<AutomationList>(*alist.get(), &before, &alist->get_state()));
653 break;
656 if (what_we_got) {
657 for (AutomationList::iterator x = what_we_got->begin(); x != what_we_got->end(); ++x) {
658 double when = (*x)->when;
659 double val = (*x)->value;
660 line.model_to_view_coord (when, val);
661 (*x)->when = when;
662 (*x)->value = val;
667 void
668 AutomationTimeAxisView::reset_objects (PointSelection& selection)
670 list<boost::shared_ptr<AutomationLine> > lines;
671 if (_line) {
672 lines.push_back (_line);
673 } else if (_view) {
674 lines = _view->get_lines ();
677 for (list<boost::shared_ptr<AutomationLine> >::iterator i = lines.begin(); i != lines.end(); ++i) {
678 reset_objects_one (**i, selection);
682 void
683 AutomationTimeAxisView::reset_objects_one (AutomationLine& line, PointSelection& selection)
685 boost::shared_ptr<AutomationList> alist(line.the_list());
687 _session->add_command (new MementoCommand<AutomationList>(*alist.get(), &alist->get_state(), 0));
689 for (PointSelection::iterator i = selection.begin(); i != selection.end(); ++i) {
691 if ((*i).track != this) {
692 continue;
695 alist->reset_range ((*i).start, (*i).end);
699 void
700 AutomationTimeAxisView::cut_copy_clear_objects (PointSelection& selection, CutCopyOp op)
702 list<boost::shared_ptr<AutomationLine> > lines;
703 if (_line) {
704 lines.push_back (_line);
705 } else if (_view) {
706 lines = _view->get_lines ();
709 for (list<boost::shared_ptr<AutomationLine> >::iterator i = lines.begin(); i != lines.end(); ++i) {
710 cut_copy_clear_objects_one (**i, selection, op);
714 void
715 AutomationTimeAxisView::cut_copy_clear_objects_one (AutomationLine& line, PointSelection& selection, CutCopyOp op)
717 boost::shared_ptr<Evoral::ControlList> what_we_got;
718 boost::shared_ptr<AutomationList> alist(line.the_list());
720 XMLNode &before = alist->get_state();
722 for (PointSelection::iterator i = selection.begin(); i != selection.end(); ++i) {
724 if ((*i).track != this) {
725 continue;
728 switch (op) {
729 case Delete:
730 if (alist->cut ((*i).start, (*i).end) != 0) {
731 _session->add_command (new MementoCommand<AutomationList>(*alist.get(), new XMLNode (before), &alist->get_state()));
733 break;
734 case Cut:
735 if ((what_we_got = alist->cut ((*i).start, (*i).end)) != 0) {
736 _editor.get_cut_buffer().add (what_we_got);
737 _session->add_command (new MementoCommand<AutomationList>(*alist.get(), new XMLNode (before), &alist->get_state()));
739 break;
740 case Copy:
741 if ((what_we_got = alist->copy ((*i).start, (*i).end)) != 0) {
742 _editor.get_cut_buffer().add (what_we_got);
744 break;
746 case Clear:
747 if ((what_we_got = alist->cut ((*i).start, (*i).end)) != 0) {
748 _session->add_command (new MementoCommand<AutomationList>(*alist.get(), new XMLNode (before), &alist->get_state()));
750 break;
754 delete &before;
756 if (what_we_got) {
757 for (AutomationList::iterator x = what_we_got->begin(); x != what_we_got->end(); ++x) {
758 double when = (*x)->when;
759 double val = (*x)->value;
760 line.model_to_view_coord (when, val);
761 (*x)->when = when;
762 (*x)->value = val;
767 /** Paste a selection.
768 * @param pos Position to paste to (session frames).
769 * @param times Number of times to paste.
770 * @param selection Selection to paste.
771 * @param nth Index of the AutomationList within the selection to paste from.
773 bool
774 AutomationTimeAxisView::paste (framepos_t pos, float times, Selection& selection, size_t nth)
776 boost::shared_ptr<AutomationLine> line;
778 if (_line) {
779 line = _line;
780 } else if (_view) {
781 line = _view->paste_line (pos);
784 if (!line) {
785 return false;
788 return paste_one (*line, pos, times, selection, nth);
791 bool
792 AutomationTimeAxisView::paste_one (AutomationLine& line, framepos_t pos, float times, Selection& selection, size_t nth)
794 AutomationSelection::iterator p;
795 boost::shared_ptr<AutomationList> alist(line.the_list());
797 for (p = selection.lines.begin(); p != selection.lines.end() && nth; ++p, --nth) {}
799 if (p == selection.lines.end()) {
800 return false;
803 /* Make a copy of the list because we have to scale the
804 values from view coordinates to model coordinates, and we're
805 not supposed to modify the points in the selection.
808 AutomationList copy (**p);
810 for (AutomationList::iterator x = copy.begin(); x != copy.end(); ++x) {
811 double when = (*x)->when;
812 double val = (*x)->value;
813 line.view_to_model_coord (when, val);
814 (*x)->when = when;
815 (*x)->value = val;
818 double const model_pos = line.time_converter().from (pos - line.time_converter().origin_b ());
820 XMLNode &before = alist->get_state();
821 alist->paste (copy, model_pos, times);
822 _session->add_command (new MementoCommand<AutomationList>(*alist.get(), &before, &alist->get_state()));
824 return true;
827 void
828 AutomationTimeAxisView::get_selectables (framepos_t start, framepos_t end, double top, double bot, list<Selectable*>& results)
830 if (!_line && !_view) {
831 return;
834 if (touched (top, bot)) {
836 /* remember: this is X Window - coordinate space starts in upper left and moves down.
837 _y_position is the "origin" or "top" of the track.
840 /* bottom of our track */
841 double const mybot = _y_position + height;
843 double topfrac;
844 double botfrac;
846 if (_y_position >= top && mybot <= bot) {
848 /* _y_position is below top, mybot is above bot, so we're fully
849 covered vertically.
852 topfrac = 1.0;
853 botfrac = 0.0;
855 } else {
857 /* top and bot are within _y_position .. mybot */
859 topfrac = 1.0 - ((top - _y_position) / height);
860 botfrac = 1.0 - ((bot - _y_position) / height);
864 if (_line) {
865 _line->get_selectables (start, end, botfrac, topfrac, results);
866 } else if (_view) {
867 _view->get_selectables (start, end, botfrac, topfrac, results);
872 void
873 AutomationTimeAxisView::get_inverted_selectables (Selection& sel, list<Selectable*>& result)
875 if (_line) {
876 _line->get_inverted_selectables (sel, result);
880 void
881 AutomationTimeAxisView::set_selected_points (PointSelection& points)
883 if (_line) {
884 _line->set_selected_points (points);
885 } else if (_view) {
886 _view->set_selected_points (points);
890 void
891 AutomationTimeAxisView::clear_lines ()
893 _line.reset();
894 _list_connections.drop_connections ();
897 void
898 AutomationTimeAxisView::add_line (boost::shared_ptr<AutomationLine> line)
900 assert(line);
901 assert(!_line);
902 assert(line->the_list() == _control->list());
904 _control->alist()->automation_state_changed.connect (
905 _list_connections, invalidator (*this), boost::bind (&AutomationTimeAxisView::automation_state_changed, this), gui_context()
908 _control->alist()->InterpolationChanged.connect (
909 _list_connections, invalidator (*this), boost::bind (&AutomationTimeAxisView::interpolation_changed, this, _1), gui_context()
912 _line = line;
913 //_controller = AutomationController::create(_session, line->the_list(), _control);
915 line->set_height (height);
917 /* pick up the current state */
918 automation_state_changed ();
920 line->show();
923 void
924 AutomationTimeAxisView::entered()
926 if (_line) {
927 _line->track_entered();
931 void
932 AutomationTimeAxisView::exited ()
934 if (_line) {
935 _line->track_exited();
939 void
940 AutomationTimeAxisView::color_handler ()
942 if (_line) {
943 _line->set_colors();
948 AutomationTimeAxisView::set_state (const XMLNode& node, int version)
950 TimeAxisView::set_state (node, version);
952 if (version < 3000) {
953 return set_state_2X (node, version);
956 XMLProperty const * prop = node.property ("shown");
958 if (prop) {
959 set_visibility (string_is_affirmative (prop->value()));
960 } else {
961 set_visibility (false);
964 return 0;
968 AutomationTimeAxisView::set_state_2X (const XMLNode& node, int /*version*/)
970 if (node.name() == X_("gain") && _parameter == Evoral::Parameter (GainAutomation)) {
971 XMLProperty const * shown = node.property (X_("shown"));
972 if (shown && string_is_affirmative (shown->value ())) {
973 set_marked_for_display (true);
974 _canvas_display->show (); /* FIXME: necessary? show_at? */
978 if (!_marked_for_display) {
979 hide ();
982 return 0;
985 XMLNode*
986 AutomationTimeAxisView::get_state_node ()
988 return _control->extra_xml ("GUI", true);
991 void
992 AutomationTimeAxisView::update_extra_xml_shown (bool shown)
994 XMLNode* xml_node = get_state_node();
995 if (xml_node) {
996 xml_node->add_property ("shown", shown ? "yes" : "no");
1000 void
1001 AutomationTimeAxisView::what_has_visible_automation (const boost::shared_ptr<Automatable>& automatable, set<Evoral::Parameter>& visible)
1003 /* this keeps "knowledge" of how we store visibility information
1004 in XML private to this class.
1007 assert (automatable);
1009 Automatable::Controls& controls (automatable->controls());
1011 for (Automatable::Controls::iterator i = controls.begin(); i != controls.end(); ++i) {
1013 boost::shared_ptr<AutomationControl> ac = boost::dynamic_pointer_cast<AutomationControl> (i->second);
1015 if (ac) {
1017 const XMLNode* gui_node = ac->extra_xml ("GUI");
1019 if (gui_node) {
1020 const XMLProperty* prop = gui_node->property ("shown");
1021 if (prop) {
1022 if (string_is_affirmative (prop->value())) {
1023 visible.insert (i->first);
1031 guint32
1032 AutomationTimeAxisView::show_at (double y, int& nth, Gtk::VBox *parent)
1034 if (!canvas_item_visible (_canvas_display)) {
1035 update_extra_xml_shown (true);
1038 return TimeAxisView::show_at (y, nth, parent);
1041 void
1042 AutomationTimeAxisView::show ()
1044 if (!canvas_item_visible (_canvas_display)) {
1045 update_extra_xml_shown (true);
1048 return TimeAxisView::show ();
1051 void
1052 AutomationTimeAxisView::hide ()
1054 if (canvas_item_visible (_canvas_display)) {
1055 update_extra_xml_shown (false);
1058 TimeAxisView::hide ();
1061 /** @return true if this view has any automation data to display */
1062 bool
1063 AutomationTimeAxisView::has_automation () const
1065 return ( (_line && _line->npoints() > 0) || (_view && _view->has_automation()) );
1068 list<boost::shared_ptr<AutomationLine> >
1069 AutomationTimeAxisView::lines () const
1071 list<boost::shared_ptr<AutomationLine> > lines;
1073 if (_line) {
1074 lines.push_back (_line);
1075 } else if (_view) {
1076 lines = _view->get_lines ();
1079 return lines;