Zoom session when the mouse pointer is moved up and down during a playhead drag.
[ardour2.git] / gtk2_ardour / automation_time_axis.cc
blob9cf1598010ccb83e99997a60ec9a0526fdf88816
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 "pbd/memento_command.h"
23 #include "ardour/automation_control.h"
24 #include "ardour/event_type_map.h"
25 #include "ardour/route.h"
26 #include "ardour/session.h"
28 #include "ardour_ui.h"
29 #include "automation_time_axis.h"
30 #include "automation_streamview.h"
31 #include "global_signals.h"
32 #include "gui_thread.h"
33 #include "route_time_axis.h"
34 #include "automation_line.h"
35 #include "public_editor.h"
36 #include "simplerect.h"
37 #include "selection.h"
38 #include "rgb_macros.h"
39 #include "point_selection.h"
40 #include "canvas_impl.h"
41 #include "utils.h"
43 #include "i18n.h"
45 using namespace std;
46 using namespace ARDOUR;
47 using namespace PBD;
48 using namespace Gtk;
49 using namespace Gtkmm2ext;
50 using namespace Editing;
52 Pango::FontDescription* AutomationTimeAxisView::name_font = 0;
53 bool AutomationTimeAxisView::have_name_font = false;
54 const string AutomationTimeAxisView::state_node_name = "AutomationChild";
57 /** \a a the automatable object this time axis is to display data for.
58 * For route/track automation (e.g. gain) pass the route for both \r and \a.
59 * For route child (e.g. plugin) automation, pass the child for \a.
60 * For region automation (e.g. MIDI CC), pass null for \a.
62 AutomationTimeAxisView::AutomationTimeAxisView (
63 Session* s,
64 boost::shared_ptr<Route> r,
65 boost::shared_ptr<Automatable> a,
66 boost::shared_ptr<AutomationControl> c,
67 Evoral::Parameter p,
68 PublicEditor& e,
69 TimeAxisView& parent,
70 bool show_regions,
71 ArdourCanvas::Canvas& canvas,
72 const string & nom,
73 const string & nomparent
75 : AxisView (s)
76 , TimeAxisView (s, e, &parent, canvas)
77 , _route (r)
78 , _control (c)
79 , _automatable (a)
80 , _parameter (p)
81 , _base_rect (0)
82 , _view (show_regions ? new AutomationStreamView (*this) : 0)
83 , _name (nom)
84 , auto_button (X_("")) /* force addition of a label */
86 if (!have_name_font) {
87 name_font = get_font_for_style (X_("AutomationTrackName"));
88 have_name_font = true;
91 if (_automatable && _control) {
92 _controller = AutomationController::create (_automatable, _control->parameter(), _control);
95 automation_menu = 0;
96 auto_off_item = 0;
97 auto_touch_item = 0;
98 auto_write_item = 0;
99 auto_play_item = 0;
100 mode_discrete_item = 0;
101 mode_line_item = 0;
103 ignore_state_request = false;
104 first_call_to_set_height = true;
106 _base_rect = new SimpleRect(*_canvas_display);
107 _base_rect->property_x1() = 0.0;
108 _base_rect->property_y1() = 0.0;
109 /** gnomecanvas sometimes converts this value to int or adds 2 to it, so it must be
110 set correctly to avoid overflow.
112 _base_rect->property_x2() = INT_MAX - 2;
113 _base_rect->property_outline_color_rgba() = ARDOUR_UI::config()->canvasvar_AutomationTrackOutline.get();
115 /* outline ends and bottom */
116 _base_rect->property_outline_what() = (guint32) (0x1|0x2|0x8);
117 _base_rect->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_AutomationTrackFill.get();
119 _base_rect->set_data ("trackview", this);
121 _base_rect->signal_event().connect (sigc::bind (
122 sigc::mem_fun (_editor, &PublicEditor::canvas_automation_track_event),
123 _base_rect, this));
125 if (!a) {
126 _base_rect->lower_to_bottom();
129 hide_button.add (*(manage (new Gtk::Image (::get_icon("hide")))));
131 auto_button.set_name ("TrackVisualButton");
132 hide_button.set_name ("TrackRemoveButton");
134 auto_button.unset_flags (Gtk::CAN_FOCUS);
135 hide_button.unset_flags (Gtk::CAN_FOCUS);
137 controls_table.set_no_show_all();
139 ARDOUR_UI::instance()->set_tip(auto_button, _("automation state"));
140 ARDOUR_UI::instance()->set_tip(hide_button, _("hide track"));
142 /* rearrange the name display */
144 /* we never show these for automation tracks, so make
145 life easier and remove them.
148 hide_name_entry();
150 /* move the name label over a bit */
152 string shortpname = _name;
153 bool shortened = false;
155 int ignore_width;
156 shortpname = fit_to_pixels (_name, 60, *name_font, ignore_width, true);
158 if (shortpname != _name ){
159 shortened = true;
162 name_label.set_text (shortpname);
163 name_label.set_alignment (Gtk::ALIGN_CENTER, Gtk::ALIGN_CENTER);
164 name_label.set_name (X_("TrackParameterName"));
166 if (nomparent.length()) {
168 /* limit the plug name string */
170 string pname = fit_to_pixels (nomparent, 60, *name_font, ignore_width, true);
171 if (pname != nomparent) {
172 shortened = true;
175 plugname = new Label (pname);
176 plugname->set_name (X_("TrackPlugName"));
177 plugname->show();
178 controls_table.remove (name_hbox);
179 controls_table.attach (*plugname, 1, 5, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
180 plugname_packed = true;
181 controls_table.attach (name_hbox, 1, 5, 1, 2, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
182 } else {
183 plugname = 0;
184 plugname_packed = false;
187 if (shortened) {
188 string tipname = nomparent;
189 if (!tipname.empty()) {
190 tipname += ": ";
192 tipname += _name;
193 ARDOUR_UI::instance()->set_tip(controls_ebox, tipname);
196 /* add the buttons */
197 controls_table.attach (hide_button, 0, 1, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
199 controls_table.attach (auto_button, 5, 8, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
201 if (_controller) {
202 /* add bar controller */
203 controls_table.attach (*_controller.get(), 0, 8, 1, 2, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
206 controls_table.show_all ();
208 hide_button.signal_clicked().connect (sigc::mem_fun(*this, &AutomationTimeAxisView::hide_clicked));
209 auto_button.signal_clicked().connect (sigc::mem_fun(*this, &AutomationTimeAxisView::auto_clicked));
211 controls_base_selected_name = X_("AutomationTrackControlsBaseSelected");
212 controls_base_unselected_name = X_("AutomationTrackControlsBase");
213 controls_ebox.set_name (controls_base_unselected_name);
215 XMLNode* xml_node = get_parent_with_state()->get_automation_child_xml_node (_parameter);
217 if (xml_node) {
218 set_state (*xml_node, Stateful::loading_state_version);
221 /* ask for notifications of any new RegionViews */
222 if (show_regions) {
224 assert(_view);
225 _view->attach ();
227 } else {
228 /* no regions, just a single line for the entire track (e.g. bus gain) */
230 boost::shared_ptr<AutomationLine> line (
231 new AutomationLine (
232 ARDOUR::EventTypeMap::instance().to_symbol(_parameter),
233 *this,
234 *_canvas_display,
235 _control->alist()
239 line->set_line_color (ARDOUR_UI::config()->canvasvar_ProcessorAutomationLine.get());
240 line->queue_reset ();
241 add_line (line);
244 /* make sure labels etc. are correct */
246 automation_state_changed ();
247 ColorsChanged.connect (sigc::mem_fun (*this, &AutomationTimeAxisView::color_handler));
249 _route->DropReferences.connect (
250 _route_connections, invalidator (*this), ui_bind (&AutomationTimeAxisView::route_going_away, this), gui_context ()
254 AutomationTimeAxisView::~AutomationTimeAxisView ()
258 void
259 AutomationTimeAxisView::route_going_away ()
261 _route.reset ();
264 void
265 AutomationTimeAxisView::auto_clicked ()
267 using namespace Menu_Helpers;
269 if (automation_menu == 0) {
270 automation_menu = manage (new Menu);
271 automation_menu->set_name ("ArdourContextMenu");
272 MenuList& items (automation_menu->items());
274 items.push_back (MenuElem (_("Manual"), sigc::bind (sigc::mem_fun(*this,
275 &AutomationTimeAxisView::set_automation_state), (AutoState) Off)));
276 items.push_back (MenuElem (_("Play"), sigc::bind (sigc::mem_fun(*this,
277 &AutomationTimeAxisView::set_automation_state), (AutoState) Play)));
278 items.push_back (MenuElem (_("Write"), sigc::bind (sigc::mem_fun(*this,
279 &AutomationTimeAxisView::set_automation_state), (AutoState) Write)));
280 items.push_back (MenuElem (_("Touch"), sigc::bind (sigc::mem_fun(*this,
281 &AutomationTimeAxisView::set_automation_state), (AutoState) Touch)));
284 automation_menu->popup (1, gtk_get_current_event_time());
287 void
288 AutomationTimeAxisView::set_automation_state (AutoState state)
290 if (ignore_state_request) {
291 return;
294 if (_automatable) {
295 _automatable->set_parameter_automation_state (_parameter, state);
297 #if 0
298 if (_route == _automatable) { // This is a time axis for route (not region) automation
299 _route->set_parameter_automation_state (_parameter, state);
302 if (_control->list()) {
303 _control->alist()->set_automation_state(state);
305 #endif
306 if (_view) {
307 _view->set_automation_state (state);
309 /* AutomationStreamViews don't signal when their automation state changes, so handle
310 our updates `manually'.
312 automation_state_changed ();
316 void
317 AutomationTimeAxisView::automation_state_changed ()
319 AutoState state;
321 /* update button label */
323 if (_line) {
324 state = _control->alist()->automation_state ();
325 } else if (_view) {
326 state = _view->automation_state ();
327 } else {
328 state = Off;
331 switch (state & (Off|Play|Touch|Write)) {
332 case Off:
333 auto_button.set_label (_("Manual"));
334 if (auto_off_item) {
335 ignore_state_request = true;
336 auto_off_item->set_active (true);
337 auto_play_item->set_active (false);
338 auto_touch_item->set_active (false);
339 auto_write_item->set_active (false);
340 ignore_state_request = false;
342 break;
343 case Play:
344 auto_button.set_label (_("Play"));
345 if (auto_play_item) {
346 ignore_state_request = true;
347 auto_play_item->set_active (true);
348 auto_off_item->set_active (false);
349 auto_touch_item->set_active (false);
350 auto_write_item->set_active (false);
351 ignore_state_request = false;
353 break;
354 case Write:
355 auto_button.set_label (_("Write"));
356 if (auto_write_item) {
357 ignore_state_request = true;
358 auto_write_item->set_active (true);
359 auto_off_item->set_active (false);
360 auto_play_item->set_active (false);
361 auto_touch_item->set_active (false);
362 ignore_state_request = false;
364 break;
365 case Touch:
366 auto_button.set_label (_("Touch"));
367 if (auto_touch_item) {
368 ignore_state_request = true;
369 auto_touch_item->set_active (true);
370 auto_off_item->set_active (false);
371 auto_play_item->set_active (false);
372 auto_write_item->set_active (false);
373 ignore_state_request = false;
375 break;
376 default:
377 auto_button.set_label (_("???"));
378 break;
382 /** The interpolation style of our AutomationList has changed, so update */
383 void
384 AutomationTimeAxisView::interpolation_changed (AutomationList::InterpolationStyle s)
386 if (mode_line_item && mode_discrete_item) {
387 if (s == AutomationList::Discrete) {
388 mode_discrete_item->set_active(true);
389 mode_line_item->set_active(false);
390 } else {
391 mode_line_item->set_active(true);
392 mode_discrete_item->set_active(false);
397 /** A menu item has been selected to change our interpolation mode */
398 void
399 AutomationTimeAxisView::set_interpolation (AutomationList::InterpolationStyle style)
401 /* Tell our view's list, if we have one, otherwise tell our own.
402 * Everything else will be signalled back from that.
405 if (_view) {
406 _view->set_interpolation (style);
407 } else {
408 _control->list()->set_interpolation (style);
412 void
413 AutomationTimeAxisView::clear_clicked ()
415 assert (_line || _view);
417 _session->begin_reversible_command (_("clear automation"));
419 if (_line) {
420 _line->clear ();
421 } else if (_view) {
422 _view->clear ();
425 _session->commit_reversible_command ();
426 _session->set_dirty ();
429 void
430 AutomationTimeAxisView::set_height (uint32_t h)
432 bool const changed = (height != (uint32_t) h) || first_call_to_set_height;
433 uint32_t const normal = preset_height (HeightNormal);
434 bool const changed_between_small_and_normal = ( (height < normal && h >= normal) || (height >= normal || h < normal) );
436 TimeAxisView* state_parent = get_parent_with_state ();
437 assert(state_parent);
438 XMLNode* xml_node = state_parent->get_automation_child_xml_node (_parameter);
440 TimeAxisView::set_height (h);
441 _base_rect->property_y2() = h;
443 if (_line) {
444 _line->set_height(h);
447 if (_view) {
448 _view->set_height(h);
449 _view->update_contents_height();
452 char buf[32];
453 snprintf (buf, sizeof (buf), "%u", height);
454 if (xml_node) {
455 xml_node->add_property ("height", buf);
458 if (changed_between_small_and_normal || first_call_to_set_height) {
460 first_call_to_set_height = false;
462 if (h >= preset_height (HeightNormal)) {
463 controls_table.remove (name_hbox);
465 if (plugname) {
466 if (plugname_packed) {
467 controls_table.remove (*plugname);
468 plugname_packed = false;
470 controls_table.attach (*plugname, 1, 5, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
471 plugname_packed = true;
472 controls_table.attach (name_hbox, 1, 5, 1, 2, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
473 } else {
474 controls_table.attach (name_hbox, 1, 5, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
476 hide_name_entry ();
477 show_name_label ();
478 name_hbox.show_all ();
480 auto_button.show();
481 hide_button.show_all();
483 } else if (h >= preset_height (HeightSmall)) {
484 controls_table.remove (name_hbox);
485 if (plugname) {
486 if (plugname_packed) {
487 controls_table.remove (*plugname);
488 plugname_packed = false;
491 controls_table.attach (name_hbox, 1, 5, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
492 controls_table.hide_all ();
493 hide_name_entry ();
494 show_name_label ();
495 name_hbox.show_all ();
497 auto_button.hide();
498 hide_button.hide();
500 } else if (h >= preset_height (HeightNormal)) {
501 cerr << "track grown, but neither changed_between_small_and_normal nor first_call_to_set_height set!" << endl;
504 if (changed) {
505 if (canvas_item_visible (_canvas_display) && _route) {
506 /* only emit the signal if the height really changed and we were visible */
507 _route->gui_changed ("visible_tracks", (void *) 0); /* EMIT_SIGNAL */
512 void
513 AutomationTimeAxisView::set_samples_per_unit (double spu)
515 TimeAxisView::set_samples_per_unit (spu);
517 if (_line) {
518 _line->reset ();
521 if (_view) {
522 _view->set_samples_per_unit (spu);
526 void
527 AutomationTimeAxisView::hide_clicked ()
529 // LAME fix for refreshing the hide button
530 hide_button.set_sensitive(false);
532 set_marked_for_display (false);
533 hide ();
535 hide_button.set_sensitive(true);
538 void
539 AutomationTimeAxisView::build_display_menu ()
541 using namespace Menu_Helpers;
543 /* prepare it */
545 TimeAxisView::build_display_menu ();
547 /* now fill it with our stuff */
549 MenuList& items = display_menu->items();
551 items.push_back (MenuElem (_("Hide"), sigc::mem_fun(*this, &AutomationTimeAxisView::hide_clicked)));
552 items.push_back (SeparatorElem());
553 items.push_back (MenuElem (_("Clear"), sigc::mem_fun(*this, &AutomationTimeAxisView::clear_clicked)));
554 items.push_back (SeparatorElem());
556 /* state menu */
558 Menu* auto_state_menu = manage (new Menu);
559 auto_state_menu->set_name ("ArdourContextMenu");
560 MenuList& as_items = auto_state_menu->items();
562 as_items.push_back (CheckMenuElem (_("Manual"), sigc::bind (
563 sigc::mem_fun(*this, &AutomationTimeAxisView::set_automation_state),
564 (AutoState) Off)));
565 auto_off_item = dynamic_cast<CheckMenuItem*>(&as_items.back());
567 as_items.push_back (CheckMenuElem (_("Play"), sigc::bind (
568 sigc::mem_fun(*this, &AutomationTimeAxisView::set_automation_state),
569 (AutoState) Play)));
570 auto_play_item = dynamic_cast<CheckMenuItem*>(&as_items.back());
572 as_items.push_back (CheckMenuElem (_("Write"), sigc::bind (
573 sigc::mem_fun(*this, &AutomationTimeAxisView::set_automation_state),
574 (AutoState) Write)));
575 auto_write_item = dynamic_cast<CheckMenuItem*>(&as_items.back());
577 as_items.push_back (CheckMenuElem (_("Touch"), sigc::bind (
578 sigc::mem_fun(*this, &AutomationTimeAxisView::set_automation_state),
579 (AutoState) Touch)));
580 auto_touch_item = dynamic_cast<CheckMenuItem*>(&as_items.back());
582 items.push_back (MenuElem (_("State"), *auto_state_menu));
584 /* mode menu */
586 /* current interpolation state */
587 AutomationList::InterpolationStyle const s = _view ? _view->interpolation() : _control->list()->interpolation ();
589 if (EventTypeMap::instance().is_midi_parameter(_parameter)) {
591 Menu* auto_mode_menu = manage (new Menu);
592 auto_mode_menu->set_name ("ArdourContextMenu");
593 MenuList& am_items = auto_mode_menu->items();
595 RadioMenuItem::Group group;
597 am_items.push_back (RadioMenuElem (group, _("Discrete"), sigc::bind (
598 sigc::mem_fun(*this, &AutomationTimeAxisView::set_interpolation),
599 AutomationList::Discrete)));
600 mode_discrete_item = dynamic_cast<CheckMenuItem*>(&am_items.back());
601 mode_discrete_item->set_active (s == AutomationList::Discrete);
603 am_items.push_back (RadioMenuElem (group, _("Linear"), sigc::bind (
604 sigc::mem_fun(*this, &AutomationTimeAxisView::set_interpolation),
605 AutomationList::Linear)));
606 mode_line_item = dynamic_cast<CheckMenuItem*>(&am_items.back());
607 mode_line_item->set_active (s == AutomationList::Linear);
609 items.push_back (MenuElem (_("Mode"), *auto_mode_menu));
612 /* make sure the automation menu state is correct */
614 automation_state_changed ();
615 interpolation_changed (s);
618 void
619 AutomationTimeAxisView::add_automation_event (ArdourCanvas::Item* /*item*/, GdkEvent* /*event*/, framepos_t when, double y)
621 if (!_line) {
622 return;
625 double x = 0;
627 _canvas_display->w2i (x, y);
629 /* compute vertical fractional position */
631 y = 1.0 - (y / height);
633 /* map using line */
635 _line->view_to_model_coord (x, y);
637 _session->begin_reversible_command (_("add automation event"));
638 XMLNode& before = _control->alist()->get_state();
640 _control->alist()->add (when, y);
642 XMLNode& after = _control->alist()->get_state();
643 _session->commit_reversible_command (new MementoCommand<ARDOUR::AutomationList>(*_control->alist(), &before, &after));
645 _session->set_dirty ();
648 void
649 AutomationTimeAxisView::cut_copy_clear (Selection& selection, CutCopyOp op)
651 list<boost::shared_ptr<AutomationLine> > lines;
652 if (_line) {
653 lines.push_back (_line);
654 } else if (_view) {
655 lines = _view->get_lines ();
658 for (list<boost::shared_ptr<AutomationLine> >::iterator i = lines.begin(); i != lines.end(); ++i) {
659 cut_copy_clear_one (**i, selection, op);
663 void
664 AutomationTimeAxisView::cut_copy_clear_one (AutomationLine& line, Selection& selection, CutCopyOp op)
666 boost::shared_ptr<Evoral::ControlList> what_we_got;
667 boost::shared_ptr<AutomationList> alist (line.the_list());
669 XMLNode &before = alist->get_state();
671 /* convert time selection to automation list model coordinates */
672 const Evoral::TimeConverter<double, ARDOUR::framepos_t>& tc = line.time_converter ();
673 double const start = tc.from (selection.time.front().start - tc.origin_b ());
674 double const end = tc.from (selection.time.front().end - tc.origin_b ());
676 switch (op) {
677 case Cut:
679 if ((what_we_got = alist->cut (start, end)) != 0) {
680 _editor.get_cut_buffer().add (what_we_got);
681 _session->add_command(new MementoCommand<AutomationList>(*alist.get(), &before, &alist->get_state()));
683 break;
684 case Copy:
685 if ((what_we_got = alist->copy (start, end)) != 0) {
686 _editor.get_cut_buffer().add (what_we_got);
688 break;
690 case Clear:
691 if ((what_we_got = alist->cut (start, end)) != 0) {
692 _session->add_command(new MementoCommand<AutomationList>(*alist.get(), &before, &alist->get_state()));
694 break;
697 if (what_we_got) {
698 for (AutomationList::iterator x = what_we_got->begin(); x != what_we_got->end(); ++x) {
699 double when = (*x)->when;
700 double val = (*x)->value;
701 line.model_to_view_coord (when, val);
702 (*x)->when = when;
703 (*x)->value = val;
708 void
709 AutomationTimeAxisView::reset_objects (PointSelection& selection)
711 list<boost::shared_ptr<AutomationLine> > lines;
712 if (_line) {
713 lines.push_back (_line);
714 } else if (_view) {
715 lines = _view->get_lines ();
718 for (list<boost::shared_ptr<AutomationLine> >::iterator i = lines.begin(); i != lines.end(); ++i) {
719 reset_objects_one (**i, selection);
723 void
724 AutomationTimeAxisView::reset_objects_one (AutomationLine& line, PointSelection& selection)
726 boost::shared_ptr<AutomationList> alist(line.the_list());
728 _session->add_command (new MementoCommand<AutomationList>(*alist.get(), &alist->get_state(), 0));
730 for (PointSelection::iterator i = selection.begin(); i != selection.end(); ++i) {
732 if ((*i).track != this) {
733 continue;
736 alist->reset_range ((*i).start, (*i).end);
740 void
741 AutomationTimeAxisView::cut_copy_clear_objects (PointSelection& selection, CutCopyOp op)
743 list<boost::shared_ptr<AutomationLine> > lines;
744 if (_line) {
745 lines.push_back (_line);
746 } else if (_view) {
747 lines = _view->get_lines ();
750 for (list<boost::shared_ptr<AutomationLine> >::iterator i = lines.begin(); i != lines.end(); ++i) {
751 cut_copy_clear_objects_one (**i, selection, op);
755 void
756 AutomationTimeAxisView::cut_copy_clear_objects_one (AutomationLine& line, PointSelection& selection, CutCopyOp op)
758 boost::shared_ptr<Evoral::ControlList> what_we_got;
759 boost::shared_ptr<AutomationList> alist(line.the_list());
761 XMLNode &before = alist->get_state();
763 for (PointSelection::iterator i = selection.begin(); i != selection.end(); ++i) {
765 if ((*i).track != this) {
766 continue;
769 switch (op) {
770 case Cut:
771 if ((what_we_got = alist->cut ((*i).start, (*i).end)) != 0) {
772 _editor.get_cut_buffer().add (what_we_got);
773 _session->add_command (new MementoCommand<AutomationList>(*alist.get(), new XMLNode (before), &alist->get_state()));
775 break;
776 case Copy:
777 if ((what_we_got = alist->copy ((*i).start, (*i).end)) != 0) {
778 _editor.get_cut_buffer().add (what_we_got);
780 break;
782 case Clear:
783 if ((what_we_got = alist->cut ((*i).start, (*i).end)) != 0) {
784 _session->add_command (new MementoCommand<AutomationList>(*alist.get(), new XMLNode (before), &alist->get_state()));
786 break;
790 delete &before;
792 if (what_we_got) {
793 for (AutomationList::iterator x = what_we_got->begin(); x != what_we_got->end(); ++x) {
794 double when = (*x)->when;
795 double val = (*x)->value;
796 line.model_to_view_coord (when, val);
797 (*x)->when = when;
798 (*x)->value = val;
803 /** Paste a selection.
804 * @param pos Position to paste to (session frames).
805 * @param times Number of times to paste.
806 * @param selection Selection to paste.
807 * @param nth Index of the AutomationList within the selection to paste from.
809 bool
810 AutomationTimeAxisView::paste (framepos_t pos, float times, Selection& selection, size_t nth)
812 boost::shared_ptr<AutomationLine> line;
814 if (_line) {
815 line = _line;
816 } else if (_view) {
817 line = _view->paste_line (pos);
820 if (!line) {
821 return false;
824 return paste_one (*line, pos, times, selection, nth);
827 bool
828 AutomationTimeAxisView::paste_one (AutomationLine& line, framepos_t pos, float times, Selection& selection, size_t nth)
830 AutomationSelection::iterator p;
831 boost::shared_ptr<AutomationList> alist(line.the_list());
833 for (p = selection.lines.begin(); p != selection.lines.end() && nth; ++p, --nth) {}
835 if (p == selection.lines.end()) {
836 return false;
839 /* Make a copy of the list because we have to scale the
840 values from view coordinates to model coordinates, and we're
841 not supposed to modify the points in the selection.
844 AutomationList copy (**p);
846 for (AutomationList::iterator x = copy.begin(); x != copy.end(); ++x) {
847 double when = (*x)->when;
848 double val = (*x)->value;
849 line.view_to_model_coord (when, val);
850 (*x)->when = when;
851 (*x)->value = val;
854 double const model_pos = line.time_converter().from (pos - line.time_converter().origin_b ());
856 XMLNode &before = alist->get_state();
857 alist->paste (copy, model_pos, times);
858 _session->add_command (new MementoCommand<AutomationList>(*alist.get(), &before, &alist->get_state()));
860 return true;
863 void
864 AutomationTimeAxisView::get_selectables (framepos_t start, framepos_t end, double top, double bot, list<Selectable*>& results)
866 if (!_line && !_view) {
867 return;
870 if (touched (top, bot)) {
872 /* remember: this is X Window - coordinate space starts in upper left and moves down.
873 _y_position is the "origin" or "top" of the track.
876 /* bottom of our track */
877 double const mybot = _y_position + height;
879 double topfrac;
880 double botfrac;
882 if (_y_position >= top && mybot <= bot) {
884 /* _y_position is below top, mybot is above bot, so we're fully
885 covered vertically.
888 topfrac = 1.0;
889 botfrac = 0.0;
891 } else {
893 /* top and bot are within _y_position .. mybot */
895 topfrac = 1.0 - ((top - _y_position) / height);
896 botfrac = 1.0 - ((bot - _y_position) / height);
900 if (_line) {
901 _line->get_selectables (start, end, botfrac, topfrac, results);
902 } else if (_view) {
903 _view->get_selectables (start, end, botfrac, topfrac, results);
908 void
909 AutomationTimeAxisView::get_inverted_selectables (Selection& sel, list<Selectable*>& result)
911 if (_line) {
912 _line->get_inverted_selectables (sel, result);
916 void
917 AutomationTimeAxisView::set_selected_points (PointSelection& points)
919 if (_line) {
920 _line->set_selected_points (points);
921 } else if (_view) {
922 _view->set_selected_points (points);
926 void
927 AutomationTimeAxisView::clear_lines ()
929 _line.reset();
930 _list_connections.drop_connections ();
933 void
934 AutomationTimeAxisView::add_line (boost::shared_ptr<AutomationLine> line)
936 assert(line);
937 assert(!_line);
938 assert(line->the_list() == _control->list());
940 _control->alist()->automation_state_changed.connect (
941 _list_connections, invalidator (*this), boost::bind (&AutomationTimeAxisView::automation_state_changed, this), gui_context()
944 _control->alist()->InterpolationChanged.connect (
945 _list_connections, invalidator (*this), boost::bind (&AutomationTimeAxisView::interpolation_changed, this, _1), gui_context()
948 _line = line;
949 //_controller = AutomationController::create(_session, line->the_list(), _control);
951 line->set_height (height);
953 /* pick up the current state */
954 automation_state_changed ();
956 line->show();
959 void
960 AutomationTimeAxisView::entered()
962 if (_line) {
963 _line->track_entered();
967 void
968 AutomationTimeAxisView::exited ()
970 if (_line) {
971 _line->track_exited();
975 void
976 AutomationTimeAxisView::color_handler ()
978 if (_line) {
979 _line->set_colors();
984 AutomationTimeAxisView::set_state (const XMLNode& node, int version)
986 TimeAxisView::set_state (node, version);
988 if (version < 3000) {
989 return set_state_2X (node, version);
992 XMLProperty const * type = node.property ("automation-id");
993 if (type && type->value () == ARDOUR::EventTypeMap::instance().to_symbol (_parameter)) {
994 XMLProperty const * shown = node.property ("shown");
995 if (shown && shown->value () == "yes") {
996 set_marked_for_display (true);
997 _canvas_display->show (); /* FIXME: necessary? show_at? */
1001 if (!_marked_for_display) {
1002 hide();
1005 return 0;
1010 AutomationTimeAxisView::set_state_2X (const XMLNode& node, int /*version*/)
1012 if (node.name() == X_("gain") && _parameter == Evoral::Parameter (GainAutomation)) {
1013 XMLProperty const * shown = node.property (X_("shown"));
1014 if (shown && string_is_affirmative (shown->value ())) {
1015 set_marked_for_display (true);
1016 _canvas_display->show (); /* FIXME: necessary? show_at? */
1020 if (!_marked_for_display) {
1021 hide ();
1024 return 0;
1027 XMLNode*
1028 AutomationTimeAxisView::get_state_node ()
1030 TimeAxisView* state_parent = get_parent_with_state ();
1032 if (state_parent) {
1033 return state_parent->get_automation_child_xml_node (_parameter);
1034 } else {
1035 return 0;
1039 void
1040 AutomationTimeAxisView::update_extra_xml_shown (bool editor_shown)
1042 XMLNode* xml_node = get_state_node();
1043 if (xml_node) {
1044 xml_node->add_property ("shown", editor_shown ? "yes" : "no");
1048 guint32
1049 AutomationTimeAxisView::show_at (double y, int& nth, Gtk::VBox *parent)
1051 update_extra_xml_shown (true);
1053 return TimeAxisView::show_at (y, nth, parent);
1056 void
1057 AutomationTimeAxisView::hide ()
1059 update_extra_xml_shown (false);
1061 TimeAxisView::hide ();
1064 bool
1065 AutomationTimeAxisView::set_visibility (bool yn)
1067 bool changed = TimeAxisView::set_visibility (yn);
1069 if (changed) {
1070 get_state_node()->add_property ("shown", yn ? X_("yes") : X_("no"));
1073 return changed;
1076 /** @return true if this view has any automation data to display */
1077 bool
1078 AutomationTimeAxisView::has_automation () const
1080 return ( (_line && _line->npoints() > 0) || (_view && _view->has_automation()) );
1083 list<boost::shared_ptr<AutomationLine> >
1084 AutomationTimeAxisView::lines () const
1086 list<boost::shared_ptr<AutomationLine> > lines;
1088 if (_line) {
1089 lines.push_back (_line);
1090 } else if (_view) {
1091 lines = _view->get_lines ();
1094 return lines;