Add a couple of missing attach_buffers() calls after _ports has been changed. I...
[ardour2.git] / gtk2_ardour / automation_time_axis.cc
blobdef810f74e4c86d9e1342c87a4e2729982b4d52a
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>
24 #include "pbd/memento_command.h"
25 #include "pbd/stacktrace.h"
27 #include "ardour/automation_control.h"
28 #include "ardour/event_type_map.h"
29 #include "ardour/route.h"
30 #include "ardour/session.h"
32 #include "ardour_ui.h"
33 #include "automation_time_axis.h"
34 #include "automation_streamview.h"
35 #include "global_signals.h"
36 #include "gui_thread.h"
37 #include "route_time_axis.h"
38 #include "automation_line.h"
39 #include "public_editor.h"
40 #include "simplerect.h"
41 #include "selection.h"
42 #include "rgb_macros.h"
43 #include "point_selection.h"
44 #include "canvas_impl.h"
45 #include "utils.h"
47 #include "i18n.h"
49 using namespace std;
50 using namespace ARDOUR;
51 using namespace PBD;
52 using namespace Gtk;
53 using namespace Gtkmm2ext;
54 using namespace Editing;
56 Pango::FontDescription AutomationTimeAxisView::name_font;
57 bool AutomationTimeAxisView::have_name_font = false;
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 string str = gui_property ("height");
146 if (!str.empty()) {
147 set_height (atoi (str));
148 } else {
149 set_height (preset_height (HeightNormal));
152 /* rearrange the name display */
154 controls_table.remove (name_hbox);
155 controls_table.attach (name_hbox, 1, 6, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND, 3, 0);
157 /* we never show these for automation tracks, so make
158 life easier and remove them.
161 hide_name_entry();
163 name_label.set_text (_name);
164 name_label.set_alignment (Gtk::ALIGN_CENTER, Gtk::ALIGN_CENTER);
165 name_label.set_name (X_("TrackParameterName"));
166 name_label.set_ellipsize (Pango::ELLIPSIZE_END);
168 string tipname = nomparent;
169 if (!tipname.empty()) {
170 tipname += ": ";
172 tipname += _name;
173 ARDOUR_UI::instance()->set_tip(controls_ebox, tipname);
175 /* add the buttons */
176 controls_table.attach (hide_button, 0, 1, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
177 controls_table.attach (auto_button, 6, 8, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
179 if (_controller) {
180 /* add bar controller */
181 controls_table.attach (*_controller.get(), 0, 8, 1, 2, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
184 controls_table.show_all ();
186 hide_button.signal_clicked().connect (sigc::mem_fun(*this, &AutomationTimeAxisView::hide_clicked));
187 auto_button.signal_clicked().connect (sigc::mem_fun(*this, &AutomationTimeAxisView::auto_clicked));
189 controls_base_selected_name = X_("AutomationTrackControlsBaseSelected");
190 controls_base_unselected_name = X_("AutomationTrackControlsBase");
191 controls_ebox.set_name (controls_base_unselected_name);
193 /* ask for notifications of any new RegionViews */
194 if (show_regions) {
196 assert(_view);
197 _view->attach ();
199 } else {
200 /* no regions, just a single line for the entire track (e.g. bus gain) */
202 assert (_control);
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 ()
230 delete _view;
233 void
234 AutomationTimeAxisView::route_going_away ()
236 _route.reset ();
239 void
240 AutomationTimeAxisView::auto_clicked ()
242 using namespace Menu_Helpers;
244 if (automation_menu == 0) {
245 automation_menu = manage (new Menu);
246 automation_menu->set_name ("ArdourContextMenu");
247 MenuList& items (automation_menu->items());
249 items.push_back (MenuElem (_("Manual"), sigc::bind (sigc::mem_fun(*this,
250 &AutomationTimeAxisView::set_automation_state), (AutoState) Off)));
251 items.push_back (MenuElem (_("Play"), sigc::bind (sigc::mem_fun(*this,
252 &AutomationTimeAxisView::set_automation_state), (AutoState) Play)));
253 items.push_back (MenuElem (_("Write"), sigc::bind (sigc::mem_fun(*this,
254 &AutomationTimeAxisView::set_automation_state), (AutoState) Write)));
255 items.push_back (MenuElem (_("Touch"), sigc::bind (sigc::mem_fun(*this,
256 &AutomationTimeAxisView::set_automation_state), (AutoState) Touch)));
259 automation_menu->popup (1, gtk_get_current_event_time());
262 void
263 AutomationTimeAxisView::set_automation_state (AutoState state)
265 if (ignore_state_request) {
266 return;
269 if (_automatable) {
270 _automatable->set_parameter_automation_state (_parameter, state);
273 if (_view) {
274 _view->set_automation_state (state);
276 /* AutomationStreamViews don't signal when their automation state changes, so handle
277 our updates `manually'.
279 automation_state_changed ();
283 void
284 AutomationTimeAxisView::automation_state_changed ()
286 AutoState state;
288 /* update button label */
290 if (_view) {
291 state = _view->automation_state ();
292 } else if (_line) {
293 assert (_control);
294 state = _control->alist()->automation_state ();
295 } else {
296 state = Off;
299 switch (state & (Off|Play|Touch|Write)) {
300 case Off:
301 auto_button.set_label (_("Manual"));
302 if (auto_off_item) {
303 ignore_state_request = true;
304 auto_off_item->set_active (true);
305 auto_play_item->set_active (false);
306 auto_touch_item->set_active (false);
307 auto_write_item->set_active (false);
308 ignore_state_request = false;
310 break;
311 case Play:
312 auto_button.set_label (_("Play"));
313 if (auto_play_item) {
314 ignore_state_request = true;
315 auto_play_item->set_active (true);
316 auto_off_item->set_active (false);
317 auto_touch_item->set_active (false);
318 auto_write_item->set_active (false);
319 ignore_state_request = false;
321 break;
322 case Write:
323 auto_button.set_label (_("Write"));
324 if (auto_write_item) {
325 ignore_state_request = true;
326 auto_write_item->set_active (true);
327 auto_off_item->set_active (false);
328 auto_play_item->set_active (false);
329 auto_touch_item->set_active (false);
330 ignore_state_request = false;
332 break;
333 case Touch:
334 auto_button.set_label (_("Touch"));
335 if (auto_touch_item) {
336 ignore_state_request = true;
337 auto_touch_item->set_active (true);
338 auto_off_item->set_active (false);
339 auto_play_item->set_active (false);
340 auto_write_item->set_active (false);
341 ignore_state_request = false;
343 break;
344 default:
345 auto_button.set_label (_("???"));
346 break;
350 /** The interpolation style of our AutomationList has changed, so update */
351 void
352 AutomationTimeAxisView::interpolation_changed (AutomationList::InterpolationStyle s)
354 if (mode_line_item && mode_discrete_item) {
355 if (s == AutomationList::Discrete) {
356 mode_discrete_item->set_active(true);
357 mode_line_item->set_active(false);
358 } else {
359 mode_line_item->set_active(true);
360 mode_discrete_item->set_active(false);
365 /** A menu item has been selected to change our interpolation mode */
366 void
367 AutomationTimeAxisView::set_interpolation (AutomationList::InterpolationStyle style)
369 /* Tell our view's list, if we have one, otherwise tell our own.
370 * Everything else will be signalled back from that.
373 if (_view) {
374 _view->set_interpolation (style);
375 } else {
376 assert (_control);
377 _control->list()->set_interpolation (style);
381 void
382 AutomationTimeAxisView::clear_clicked ()
384 assert (_line || _view);
386 _session->begin_reversible_command (_("clear automation"));
388 if (_line) {
389 _line->clear ();
390 } else if (_view) {
391 _view->clear ();
394 _session->commit_reversible_command ();
395 _session->set_dirty ();
398 void
399 AutomationTimeAxisView::set_height (uint32_t h)
401 bool const changed = (height != (uint32_t) h) || first_call_to_set_height;
402 uint32_t const normal = preset_height (HeightNormal);
403 bool const changed_between_small_and_normal = ( (height < normal && h >= normal) || (height >= normal || h < normal) );
405 TimeAxisView::set_height (h);
407 _base_rect->property_y2() = h;
409 if (_line) {
410 _line->set_height(h);
413 if (_view) {
414 _view->set_height(h);
415 _view->update_contents_height();
418 if (changed_between_small_and_normal || first_call_to_set_height) {
420 first_call_to_set_height = false;
422 if (h >= preset_height (HeightNormal)) {
423 hide_name_entry ();
424 show_name_label ();
425 name_hbox.show_all ();
427 auto_button.show();
428 hide_button.show_all();
430 } else if (h >= preset_height (HeightSmall)) {
431 controls_table.hide_all ();
432 hide_name_entry ();
433 show_name_label ();
434 name_hbox.show_all ();
436 auto_button.hide();
437 hide_button.hide();
439 } else if (h >= preset_height (HeightNormal)) {
440 cerr << "track grown, but neither changed_between_small_and_normal nor first_call_to_set_height set!" << endl;
443 if (changed) {
444 if (canvas_item_visible (_canvas_display) && _route) {
445 /* only emit the signal if the height really changed and we were visible */
446 _route->gui_changed ("visible_tracks", (void *) 0); /* EMIT_SIGNAL */
451 void
452 AutomationTimeAxisView::set_samples_per_unit (double spu)
454 TimeAxisView::set_samples_per_unit (spu);
456 if (_line) {
457 _line->reset ();
460 if (_view) {
461 _view->set_samples_per_unit (spu);
465 void
466 AutomationTimeAxisView::hide_clicked ()
468 hide_button.set_sensitive(false);
469 set_marked_for_display (false);
470 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*>(parent);
471 if (rtv) {
472 rtv->request_redraw ();
474 hide_button.set_sensitive(true);
477 void
478 AutomationTimeAxisView::build_display_menu ()
480 using namespace Menu_Helpers;
482 /* prepare it */
484 TimeAxisView::build_display_menu ();
486 /* now fill it with our stuff */
488 MenuList& items = display_menu->items();
490 items.push_back (MenuElem (_("Hide"), sigc::mem_fun(*this, &AutomationTimeAxisView::hide_clicked)));
491 items.push_back (SeparatorElem());
492 items.push_back (MenuElem (_("Clear"), sigc::mem_fun(*this, &AutomationTimeAxisView::clear_clicked)));
493 items.push_back (SeparatorElem());
495 /* state menu */
497 Menu* auto_state_menu = manage (new Menu);
498 auto_state_menu->set_name ("ArdourContextMenu");
499 MenuList& as_items = auto_state_menu->items();
501 as_items.push_back (CheckMenuElem (_("Manual"), sigc::bind (
502 sigc::mem_fun(*this, &AutomationTimeAxisView::set_automation_state),
503 (AutoState) Off)));
504 auto_off_item = dynamic_cast<CheckMenuItem*>(&as_items.back());
506 as_items.push_back (CheckMenuElem (_("Play"), sigc::bind (
507 sigc::mem_fun(*this, &AutomationTimeAxisView::set_automation_state),
508 (AutoState) Play)));
509 auto_play_item = dynamic_cast<CheckMenuItem*>(&as_items.back());
511 as_items.push_back (CheckMenuElem (_("Write"), sigc::bind (
512 sigc::mem_fun(*this, &AutomationTimeAxisView::set_automation_state),
513 (AutoState) Write)));
514 auto_write_item = dynamic_cast<CheckMenuItem*>(&as_items.back());
516 as_items.push_back (CheckMenuElem (_("Touch"), sigc::bind (
517 sigc::mem_fun(*this, &AutomationTimeAxisView::set_automation_state),
518 (AutoState) Touch)));
519 auto_touch_item = dynamic_cast<CheckMenuItem*>(&as_items.back());
521 items.push_back (MenuElem (_("State"), *auto_state_menu));
523 /* mode menu */
525 /* current interpolation state */
526 AutomationList::InterpolationStyle const s = _view ? _view->interpolation() : _control->list()->interpolation ();
528 if (EventTypeMap::instance().is_midi_parameter(_parameter)) {
530 Menu* auto_mode_menu = manage (new Menu);
531 auto_mode_menu->set_name ("ArdourContextMenu");
532 MenuList& am_items = auto_mode_menu->items();
534 RadioMenuItem::Group group;
536 am_items.push_back (RadioMenuElem (group, _("Discrete"), sigc::bind (
537 sigc::mem_fun(*this, &AutomationTimeAxisView::set_interpolation),
538 AutomationList::Discrete)));
539 mode_discrete_item = dynamic_cast<CheckMenuItem*>(&am_items.back());
540 mode_discrete_item->set_active (s == AutomationList::Discrete);
542 am_items.push_back (RadioMenuElem (group, _("Linear"), sigc::bind (
543 sigc::mem_fun(*this, &AutomationTimeAxisView::set_interpolation),
544 AutomationList::Linear)));
545 mode_line_item = dynamic_cast<CheckMenuItem*>(&am_items.back());
546 mode_line_item->set_active (s == AutomationList::Linear);
548 items.push_back (MenuElem (_("Mode"), *auto_mode_menu));
551 /* make sure the automation menu state is correct */
553 automation_state_changed ();
554 interpolation_changed (s);
557 void
558 AutomationTimeAxisView::add_automation_event (ArdourCanvas::Item* /*item*/, GdkEvent* /*event*/, framepos_t when, double y)
560 if (!_line) {
561 return;
564 double x = 0;
566 _canvas_display->w2i (x, y);
568 /* compute vertical fractional position */
570 y = 1.0 - (y / height);
572 /* map using line */
574 _line->view_to_model_coord (x, y);
576 boost::shared_ptr<AutomationList> list = _line->the_list ();
578 _session->begin_reversible_command (_("add automation event"));
579 XMLNode& before = list->get_state();
581 list->add (when, y);
583 XMLNode& after = list->get_state();
584 _session->commit_reversible_command (new MementoCommand<ARDOUR::AutomationList> (*list, &before, &after));
585 _session->set_dirty ();
588 void
589 AutomationTimeAxisView::cut_copy_clear (Selection& selection, CutCopyOp op)
591 list<boost::shared_ptr<AutomationLine> > lines;
592 if (_line) {
593 lines.push_back (_line);
594 } else if (_view) {
595 lines = _view->get_lines ();
598 for (list<boost::shared_ptr<AutomationLine> >::iterator i = lines.begin(); i != lines.end(); ++i) {
599 cut_copy_clear_one (**i, selection, op);
603 void
604 AutomationTimeAxisView::cut_copy_clear_one (AutomationLine& line, Selection& selection, CutCopyOp op)
606 boost::shared_ptr<Evoral::ControlList> what_we_got;
607 boost::shared_ptr<AutomationList> alist (line.the_list());
609 XMLNode &before = alist->get_state();
611 /* convert time selection to automation list model coordinates */
612 const Evoral::TimeConverter<double, ARDOUR::framepos_t>& tc = line.time_converter ();
613 double const start = tc.from (selection.time.front().start - tc.origin_b ());
614 double const end = tc.from (selection.time.front().end - tc.origin_b ());
616 switch (op) {
617 case Delete:
618 if (alist->cut (start, end) != 0) {
619 _session->add_command(new MementoCommand<AutomationList>(*alist.get(), &before, &alist->get_state()));
621 break;
623 case Cut:
625 if ((what_we_got = alist->cut (start, end)) != 0) {
626 _editor.get_cut_buffer().add (what_we_got);
627 _session->add_command(new MementoCommand<AutomationList>(*alist.get(), &before, &alist->get_state()));
629 break;
630 case Copy:
631 if ((what_we_got = alist->copy (start, end)) != 0) {
632 _editor.get_cut_buffer().add (what_we_got);
634 break;
636 case Clear:
637 if ((what_we_got = alist->cut (start, end)) != 0) {
638 _session->add_command(new MementoCommand<AutomationList>(*alist.get(), &before, &alist->get_state()));
640 break;
643 if (what_we_got) {
644 for (AutomationList::iterator x = what_we_got->begin(); x != what_we_got->end(); ++x) {
645 double when = (*x)->when;
646 double val = (*x)->value;
647 line.model_to_view_coord (when, val);
648 (*x)->when = when;
649 (*x)->value = val;
654 void
655 AutomationTimeAxisView::reset_objects (PointSelection& selection)
657 list<boost::shared_ptr<AutomationLine> > lines;
658 if (_line) {
659 lines.push_back (_line);
660 } else if (_view) {
661 lines = _view->get_lines ();
664 for (list<boost::shared_ptr<AutomationLine> >::iterator i = lines.begin(); i != lines.end(); ++i) {
665 reset_objects_one (**i, selection);
669 void
670 AutomationTimeAxisView::reset_objects_one (AutomationLine& line, PointSelection& selection)
672 boost::shared_ptr<AutomationList> alist(line.the_list());
674 _session->add_command (new MementoCommand<AutomationList>(*alist.get(), &alist->get_state(), 0));
676 for (PointSelection::iterator i = selection.begin(); i != selection.end(); ++i) {
678 if ((*i).track != this) {
679 continue;
682 alist->reset_range ((*i).start, (*i).end);
686 void
687 AutomationTimeAxisView::cut_copy_clear_objects (PointSelection& selection, CutCopyOp op)
689 list<boost::shared_ptr<AutomationLine> > lines;
690 if (_line) {
691 lines.push_back (_line);
692 } else if (_view) {
693 lines = _view->get_lines ();
696 for (list<boost::shared_ptr<AutomationLine> >::iterator i = lines.begin(); i != lines.end(); ++i) {
697 cut_copy_clear_objects_one (**i, selection, op);
701 void
702 AutomationTimeAxisView::cut_copy_clear_objects_one (AutomationLine& line, PointSelection& selection, CutCopyOp op)
704 boost::shared_ptr<Evoral::ControlList> what_we_got;
705 boost::shared_ptr<AutomationList> alist(line.the_list());
707 XMLNode &before = alist->get_state();
709 for (PointSelection::iterator i = selection.begin(); i != selection.end(); ++i) {
711 if ((*i).track != this) {
712 continue;
715 switch (op) {
716 case Delete:
717 if (alist->cut ((*i).start, (*i).end) != 0) {
718 _session->add_command (new MementoCommand<AutomationList>(*alist.get(), new XMLNode (before), &alist->get_state()));
720 break;
721 case Cut:
722 if ((what_we_got = alist->cut ((*i).start, (*i).end)) != 0) {
723 _editor.get_cut_buffer().add (what_we_got);
724 _session->add_command (new MementoCommand<AutomationList>(*alist.get(), new XMLNode (before), &alist->get_state()));
726 break;
727 case Copy:
728 if ((what_we_got = alist->copy ((*i).start, (*i).end)) != 0) {
729 _editor.get_cut_buffer().add (what_we_got);
731 break;
733 case Clear:
734 if ((what_we_got = alist->cut ((*i).start, (*i).end)) != 0) {
735 _session->add_command (new MementoCommand<AutomationList>(*alist.get(), new XMLNode (before), &alist->get_state()));
737 break;
741 delete &before;
743 if (what_we_got) {
744 for (AutomationList::iterator x = what_we_got->begin(); x != what_we_got->end(); ++x) {
745 double when = (*x)->when;
746 double val = (*x)->value;
747 line.model_to_view_coord (when, val);
748 (*x)->when = when;
749 (*x)->value = val;
754 /** Paste a selection.
755 * @param pos Position to paste to (session frames).
756 * @param times Number of times to paste.
757 * @param selection Selection to paste.
758 * @param nth Index of the AutomationList within the selection to paste from.
760 bool
761 AutomationTimeAxisView::paste (framepos_t pos, float times, Selection& selection, size_t nth)
763 boost::shared_ptr<AutomationLine> line;
765 if (_line) {
766 line = _line;
767 } else if (_view) {
768 line = _view->paste_line (pos);
771 if (!line) {
772 return false;
775 return paste_one (*line, pos, times, selection, nth);
778 bool
779 AutomationTimeAxisView::paste_one (AutomationLine& line, framepos_t pos, float times, Selection& selection, size_t nth)
781 AutomationSelection::iterator p;
782 boost::shared_ptr<AutomationList> alist(line.the_list());
784 for (p = selection.lines.begin(); p != selection.lines.end() && nth; ++p, --nth) {}
786 if (p == selection.lines.end()) {
787 return false;
790 /* Make a copy of the list because we have to scale the
791 values from view coordinates to model coordinates, and we're
792 not supposed to modify the points in the selection.
795 AutomationList copy (**p);
797 for (AutomationList::iterator x = copy.begin(); x != copy.end(); ++x) {
798 double when = (*x)->when;
799 double val = (*x)->value;
800 line.view_to_model_coord (when, val);
801 (*x)->when = when;
802 (*x)->value = val;
805 double const model_pos = line.time_converter().from (pos - line.time_converter().origin_b ());
807 XMLNode &before = alist->get_state();
808 alist->paste (copy, model_pos, times);
809 _session->add_command (new MementoCommand<AutomationList>(*alist.get(), &before, &alist->get_state()));
811 return true;
814 void
815 AutomationTimeAxisView::get_selectables (framepos_t start, framepos_t end, double top, double bot, list<Selectable*>& results)
817 if (!_line && !_view) {
818 return;
821 if (touched (top, bot)) {
823 /* remember: this is X Window - coordinate space starts in upper left and moves down.
824 _y_position is the "origin" or "top" of the track.
827 /* bottom of our track */
828 double const mybot = _y_position + height;
830 double topfrac;
831 double botfrac;
833 if (_y_position >= top && mybot <= bot) {
835 /* _y_position is below top, mybot is above bot, so we're fully
836 covered vertically.
839 topfrac = 1.0;
840 botfrac = 0.0;
842 } else {
844 /* top and bot are within _y_position .. mybot */
846 topfrac = 1.0 - ((top - _y_position) / height);
847 botfrac = 1.0 - ((bot - _y_position) / height);
851 if (_line) {
852 _line->get_selectables (start, end, botfrac, topfrac, results);
853 } else if (_view) {
854 _view->get_selectables (start, end, botfrac, topfrac, results);
859 void
860 AutomationTimeAxisView::get_inverted_selectables (Selection& sel, list<Selectable*>& result)
862 if (_line) {
863 _line->get_inverted_selectables (sel, result);
867 void
868 AutomationTimeAxisView::set_selected_points (PointSelection& points)
870 if (_line) {
871 _line->set_selected_points (points);
872 } else if (_view) {
873 _view->set_selected_points (points);
877 void
878 AutomationTimeAxisView::clear_lines ()
880 _line.reset();
881 _list_connections.drop_connections ();
884 void
885 AutomationTimeAxisView::add_line (boost::shared_ptr<AutomationLine> line)
887 assert(line);
888 assert(!_line);
889 if (_control) {
890 assert(line->the_list() == _control->list());
892 _control->alist()->automation_state_changed.connect (
893 _list_connections, invalidator (*this), boost::bind (&AutomationTimeAxisView::automation_state_changed, this), gui_context()
896 _control->alist()->InterpolationChanged.connect (
897 _list_connections, invalidator (*this), boost::bind (&AutomationTimeAxisView::interpolation_changed, this, _1), gui_context()
901 _line = line;
903 line->set_height (height);
905 /* pick up the current state */
906 automation_state_changed ();
908 line->show();
911 void
912 AutomationTimeAxisView::entered()
914 if (_line) {
915 _line->track_entered();
919 void
920 AutomationTimeAxisView::exited ()
922 if (_line) {
923 _line->track_exited();
927 void
928 AutomationTimeAxisView::color_handler ()
930 if (_line) {
931 _line->set_colors();
936 AutomationTimeAxisView::set_state_2X (const XMLNode& node, int /*version*/)
938 if (node.name() == X_("gain") && _parameter == Evoral::Parameter (GainAutomation)) {
939 XMLProperty const * shown = node.property (X_("shown"));
940 if (shown) {
941 bool yn = string_is_affirmative (shown->value ());
942 if (yn) {
943 _canvas_display->show (); /* FIXME: necessary? show_at? */
945 set_gui_property ("visible", yn);
946 } else {
947 set_gui_property ("visible", false);
951 return 0;
955 AutomationTimeAxisView::set_state (const XMLNode& node, int /*version*/)
957 return 0;
960 void
961 AutomationTimeAxisView::what_has_visible_automation (const boost::shared_ptr<Automatable>& automatable, set<Evoral::Parameter>& visible)
963 /* this keeps "knowledge" of how we store visibility information
964 in XML private to this class.
967 assert (automatable);
969 Automatable::Controls& controls (automatable->controls());
971 for (Automatable::Controls::iterator i = controls.begin(); i != controls.end(); ++i) {
973 boost::shared_ptr<AutomationControl> ac = boost::dynamic_pointer_cast<AutomationControl> (i->second);
975 if (ac) {
977 const XMLNode* gui_node = ac->extra_xml ("GUI");
979 if (gui_node) {
980 const XMLProperty* prop = gui_node->property ("shown");
981 if (prop) {
982 if (string_is_affirmative (prop->value())) {
983 visible.insert (i->first);
992 /** @return true if this view has any automation data to display */
993 bool
994 AutomationTimeAxisView::has_automation () const
996 return ( (_line && _line->npoints() > 0) || (_view && _view->has_automation()) );
999 list<boost::shared_ptr<AutomationLine> >
1000 AutomationTimeAxisView::lines () const
1002 list<boost::shared_ptr<AutomationLine> > lines;
1004 if (_line) {
1005 lines.push_back (_line);
1006 } else if (_view) {
1007 lines = _view->get_lines ();
1010 return lines;
1013 string
1014 AutomationTimeAxisView::state_id() const
1016 if (_control) {
1017 return string_compose ("automation %1", _control->id().to_s());
1018 } else {
1019 assert (_parameter);
1020 return string_compose ("automation %1 %2/%3/%4",
1021 _route->id(),
1022 _parameter.type(),
1023 _parameter.id(),
1024 (int) _parameter.channel());