1 #include <ardour/route.h>
4 #include "automation_time_axis.h"
5 #include "automation_line.h"
6 #include "public_editor.h"
7 #include "simplerect.h"
9 #include "ghostregion.h"
10 #include "rgb_macros.h"
11 #include "automation_selectable.h"
12 #include "point_selection.h"
13 #include "canvas_impl.h"
18 using namespace ARDOUR;
21 using namespace Editing;
23 AutomationTimeAxisView::AutomationTimeAxisView (Session& s, Route& r, PublicEditor& e, TimeAxisView& rent,
24 ArdourCanvas::Canvas& canvas, const string & nom,
25 const string & state_name, const string & nomparent)
28 TimeAxisView (s, e, &rent, canvas),
31 _state_name (state_name),
32 height_button (_("h")),
33 clear_button (_("clear")),
34 auto_button (X_("")) /* force addition of a label */
37 in_destructor = false;
42 ignore_state_request = false;
44 // base_rect = gnome_canvas_item_new (GNOME_CANVAS_GROUP(canvas_display),
45 // gnome_canvas_simplerect_get_type(),
49 // "outline_color_rgba", color_map[cAutomationTrackOutline],
50 // /* outline ends and bottom */
51 // "outline_what", (guint32) (0x1|0x2|0x8),
52 // "fill_color_rgba", color_map[cAutomationTrackFill],
54 base_rect = new SimpleRect(*canvas_display);
55 base_rect->property_x1() = 0.0;
56 base_rect->property_y1() = 0.0;
57 base_rect->property_x2() = 1000000.0;
58 base_rect->property_outline_color_rgba() = color_map[cAutomationTrackOutline];
59 /* outline ends and bottom */
60 base_rect->property_outline_what() = (guint32) (0x1|0x2|0x8);
61 base_rect->property_fill_color_rgba() = color_map[cAutomationTrackFill];
63 base_rect->set_data ("trackview", this);
65 base_rect->signal_event().connect (bind (mem_fun (editor, &PublicEditor::canvas_automation_track_event),
68 hide_button.add (*(manage (new Gtk::Image (get_xpm("small_x.xpm")))));
70 height_button.set_name ("TrackSizeButton");
71 auto_button.set_name ("TrackVisualButton");
72 clear_button.set_name ("TrackVisualButton");
73 hide_button.set_name ("TrackRemoveButton");
75 ARDOUR_UI::instance()->tooltips().set_tip(height_button, _("track height"));
76 ARDOUR_UI::instance()->tooltips().set_tip(auto_button, _("automation state"));
77 ARDOUR_UI::instance()->tooltips().set_tip(clear_button, _("clear track"));
78 ARDOUR_UI::instance()->tooltips().set_tip(hide_button, _("hide track"));
80 /* rearrange the name display */
82 /* we never show these for automation tracks, so make
83 life easier and remove them.
88 /* move the name label over a bit */
90 string shortpname = _name;
91 bool shortened = false;
94 if (shortpname.length() > 18) {
95 shortpname = shortpname.substr (0, 16);
100 name_label.set_text (shortpname);
101 name_label.set_alignment (1.0, 0.5);
103 if (nomparent.length()) {
105 /* limit the plug name string */
107 string pname = nomparent;
109 if (pname.length() > 14) {
110 pname = pname.substr (0, 11);
115 plugname = new Label (pname);
116 plugname->set_name (X_("TrackPlugName"));
117 plugname->set_alignment (1.0, 0.5);
118 name_label.set_name (X_("TrackParameterName"));
119 controls_table.remove (name_hbox);
120 controls_table.attach (*plugname, 1, 5, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
121 plugname_packed = true;
122 controls_table.attach (name_hbox, 1, 5, 1, 2, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
125 plugname_packed = false;
129 string tipname = nomparent;
130 if (!tipname.empty()) {
134 ARDOUR_UI::instance()->tooltips().set_tip(controls_ebox, tipname);
137 /* add the buttons */
138 controls_table.attach (hide_button, 0, 1, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
139 controls_table.attach (height_button, 0, 1, 1, 2, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
141 controls_table.attach (auto_button, 6, 8, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
142 controls_table.attach (clear_button, 6, 8, 1, 2, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
144 controls_table.show_all ();
146 height_button.signal_clicked().connect (mem_fun(*this, &AutomationTimeAxisView::height_clicked));
147 clear_button.signal_clicked().connect (mem_fun(*this, &AutomationTimeAxisView::clear_clicked));
148 hide_button.signal_clicked().connect (mem_fun(*this, &AutomationTimeAxisView::hide_clicked));
149 auto_button.signal_clicked().connect (mem_fun(*this, &AutomationTimeAxisView::auto_clicked));
151 controls_base_selected_name = X_("AutomationTrackControlsBaseSelected");
152 controls_base_unselected_name = X_("AutomationTrackControlsBase");
153 controls_ebox.set_name (controls_base_unselected_name);
155 controls_frame.set_shadow_type (Gtk::SHADOW_ETCHED_OUT);
157 XMLNode* xml_node = get_parent_with_state()->get_child_xml_node (_state_name);
158 set_state (*xml_node);
160 /* make sure labels etc. are correct */
162 automation_state_changed ();
165 AutomationTimeAxisView::~AutomationTimeAxisView ()
167 in_destructor = true;
169 for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
175 AutomationTimeAxisView::auto_clicked ()
177 using namespace Menu_Helpers;
179 if (automation_menu == 0) {
180 automation_menu = manage (new Menu);
181 automation_menu->set_name ("ArdourContextMenu");
182 MenuList& items (automation_menu->items());
184 items.push_back (MenuElem (_("Manual"),
185 bind (mem_fun(*this, &AutomationTimeAxisView::set_automation_state), (AutoState) Off)));
186 items.push_back (MenuElem (_("Play"),
187 bind (mem_fun(*this, &AutomationTimeAxisView::set_automation_state), (AutoState) Play)));
188 items.push_back (MenuElem (_("Write"),
189 bind (mem_fun(*this, &AutomationTimeAxisView::set_automation_state), (AutoState) Write)));
190 items.push_back (MenuElem (_("Touch"),
191 bind (mem_fun(*this, &AutomationTimeAxisView::set_automation_state), (AutoState) Touch)));
194 automation_menu->popup (1, 0);
199 AutomationTimeAxisView::automation_state_changed ()
203 /* update button label */
208 state = lines.front()->the_list().automation_state ();
211 switch (state & (Off|Play|Touch|Write)) {
213 auto_button.set_label (_("Manual"));
215 ignore_state_request = true;
216 auto_off_item->set_active (true);
217 auto_play_item->set_active (false);
218 auto_touch_item->set_active (false);
219 auto_write_item->set_active (false);
220 ignore_state_request = false;
224 auto_button.set_label (_("Play"));
225 if (auto_play_item) {
226 ignore_state_request = true;
227 auto_play_item->set_active (true);
228 auto_off_item->set_active (false);
229 auto_touch_item->set_active (false);
230 auto_write_item->set_active (false);
231 ignore_state_request = false;
235 auto_button.set_label (_("Write"));
236 if (auto_write_item) {
237 ignore_state_request = true;
238 auto_write_item->set_active (true);
239 auto_off_item->set_active (false);
240 auto_play_item->set_active (false);
241 auto_touch_item->set_active (false);
242 ignore_state_request = false;
246 auto_button.set_label (_("Touch"));
247 if (auto_touch_item) {
248 ignore_state_request = true;
249 auto_touch_item->set_active (true);
250 auto_off_item->set_active (false);
251 auto_play_item->set_active (false);
252 auto_write_item->set_active (false);
253 ignore_state_request = false;
257 auto_button.set_label (_("???"));
263 AutomationTimeAxisView::height_clicked ()
269 AutomationTimeAxisView::clear_clicked ()
271 _session.begin_reversible_command (_("clear automation"));
272 for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
275 _session.commit_reversible_command ();
279 AutomationTimeAxisView::set_height (TrackHeight ht)
281 uint32_t h = height_to_pixels (ht);
282 bool changed = (height != (uint32_t) h);
284 TimeAxisView* state_parent = get_parent_with_state ();
285 XMLNode* xml_node = state_parent->get_child_xml_node (_state_name);
287 controls_table.show_all ();
289 TimeAxisView::set_height (ht);
290 base_rect->property_y2() = h;
292 for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
293 (*i)->set_height (h);
296 for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
302 xml_node->add_property ("track_height", "largest");
303 controls_table.remove (name_hbox);
305 if (plugname_packed) {
306 controls_table.remove (*plugname);
307 plugname_packed = false;
309 controls_table.attach (*plugname, 1, 5, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
310 plugname_packed = true;
311 controls_table.attach (name_hbox, 1, 5, 1, 2, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
313 controls_table.attach (name_hbox, 1, 5, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
315 controls_table.show_all ();
321 xml_node->add_property ("track_height", "large");
322 controls_table.remove (name_hbox);
324 if (plugname_packed) {
325 controls_table.remove (*plugname);
326 plugname_packed = false;
328 controls_table.attach (*plugname, 1, 5, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
329 plugname_packed = true;
331 controls_table.attach (name_hbox, 1, 5, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
333 controls_table.show_all ();
339 xml_node->add_property ("track_height", "larger");
340 controls_table.remove (name_hbox);
342 if (plugname_packed) {
343 controls_table.remove (*plugname);
344 plugname_packed = false;
346 controls_table.attach (*plugname, 1, 5, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
347 plugname_packed = true;
349 controls_table.attach (name_hbox, 1, 5, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
351 controls_table.show_all ();
357 xml_node->add_property ("track_height", "normal");
358 controls_table.remove (name_hbox);
360 if (plugname_packed) {
361 controls_table.remove (*plugname);
362 plugname_packed = false;
364 controls_table.attach (*plugname, 1, 5, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
365 plugname_packed = true;
366 controls_table.attach (name_hbox, 1, 5, 1, 2, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
368 controls_table.attach (name_hbox, 1, 5, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
370 controls_table.show_all ();
376 xml_node->add_property ("track_height", "smaller");
377 controls_table.remove (name_hbox);
379 if (plugname_packed) {
380 controls_table.remove (*plugname);
381 plugname_packed = false;
384 controls_table.attach (name_hbox, 1, 5, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
385 controls_table.hide_all ();
388 name_hbox.show_all ();
389 controls_table.show ();
393 xml_node->add_property ("track_height", "small");
394 controls_table.remove (name_hbox);
396 if (plugname_packed) {
397 controls_table.remove (*plugname);
398 plugname_packed = false;
401 controls_table.attach (name_hbox, 1, 5, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
402 controls_table.hide_all ();
405 name_hbox.show_all ();
406 controls_table.show ();
411 /* only emit the signal if the height really changed */
412 route.gui_changed ("track_height", (void *) 0); /* EMIT_SIGNAL */
417 AutomationTimeAxisView::set_samples_per_unit (double spu)
419 TimeAxisView::set_samples_per_unit (editor.get_current_zoom());
421 for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
427 AutomationTimeAxisView::hide_clicked ()
429 set_marked_for_display (false);
434 AutomationTimeAxisView::build_display_menu ()
436 using namespace Menu_Helpers;
438 /* get the size menu ready */
444 TimeAxisView::build_display_menu ();
446 /* now fill it with our stuff */
448 MenuList& items = display_menu->items();
450 items.push_back (MenuElem (_("Height"), *size_menu));
451 items.push_back (SeparatorElem());
452 items.push_back (MenuElem (_("Hide"), mem_fun(*this, &AutomationTimeAxisView::hide_clicked)));
453 items.push_back (SeparatorElem());
454 items.push_back (MenuElem (_("Clear"), mem_fun(*this, &AutomationTimeAxisView::clear_clicked)));
455 items.push_back (SeparatorElem());
457 Menu* auto_state_menu = manage (new Menu);
458 auto_state_menu->set_name ("ArdourContextMenu");
459 MenuList& as_items = auto_state_menu->items();
461 as_items.push_back (CheckMenuElem (_("Manual"),
462 bind (mem_fun(*this, &AutomationTimeAxisView::set_automation_state), (AutoState) Off)));
463 auto_off_item = dynamic_cast<CheckMenuItem*>(&as_items.back());
465 as_items.push_back (CheckMenuElem (_("Play"),
466 bind (mem_fun(*this, &AutomationTimeAxisView::set_automation_state), (AutoState) Play)));
467 auto_play_item = dynamic_cast<CheckMenuItem*>(&as_items.back());
469 as_items.push_back (CheckMenuElem (_("Write"),
470 bind (mem_fun(*this, &AutomationTimeAxisView::set_automation_state), (AutoState) Write)));
471 auto_write_item = dynamic_cast<CheckMenuItem*>(&as_items.back());
473 as_items.push_back (CheckMenuElem (_("Touch"),
474 bind (mem_fun(*this, &AutomationTimeAxisView::set_automation_state), (AutoState) Touch)));
475 auto_touch_item = dynamic_cast<CheckMenuItem*>(&as_items.back());
477 items.push_back (MenuElem (_("State"), *auto_state_menu));
479 /* make sure the automation menu state is correct */
481 automation_state_changed ();
485 AutomationTimeAxisView::cut_copy_clear (Selection& selection, CutCopyOp op)
489 for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
490 ret = cut_copy_clear_one ((**i), selection, op);
497 AutomationTimeAxisView::cut_copy_clear_one (AutomationLine& line, Selection& selection, CutCopyOp op)
499 AutomationList* what_we_got = 0;
500 AutomationList& alist (line.the_list());
503 XMLNode &before, &after;
504 before = alist.get_state();
508 if ((what_we_got = alist.cut (selection.time.front().start, selection.time.front().end)) != 0) {
509 editor.get_cut_buffer().add (what_we_got);
510 _session.add_command(MementoCommand<AutomationList>(alist, before, alist.get_state()));
515 if ((what_we_got = alist.copy (selection.time.front().start, selection.time.front().end)) != 0) {
516 editor.get_cut_buffer().add (what_we_got);
521 if ((what_we_got = alist.cut (selection.time.front().start, selection.time.front().end)) != 0) {
522 _session.add_command(MementoCommand<AutomationList>(alist, before, alist.get_state()));
531 for (AutomationList::iterator x = what_we_got->begin(); x != what_we_got->end(); ++x) {
532 double foo = (*x)->value;
533 line.model_to_view_y (foo);
542 AutomationTimeAxisView::reset_objects (PointSelection& selection)
544 for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
545 reset_objects_one ((**i), selection);
550 AutomationTimeAxisView::reset_objects_one (AutomationLine& line, PointSelection& selection)
552 AutomationList& alist (line.the_list());
554 _session.add_command (MementoUndoCommand<AutomationList>(alist, alist.get_state()));
556 for (PointSelection::iterator i = selection.begin(); i != selection.end(); ++i) {
558 if (&(*i).track != this) {
562 alist.reset_range ((*i).start, (*i).end);
567 AutomationTimeAxisView::cut_copy_clear_objects (PointSelection& selection, CutCopyOp op)
571 for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
572 ret = cut_copy_clear_objects_one ((**i), selection, op);
579 AutomationTimeAxisView::cut_copy_clear_objects_one (AutomationLine& line, PointSelection& selection, CutCopyOp op)
581 AutomationList* what_we_got = 0;
582 AutomationList& alist (line.the_list());
584 XMLNode &before, &after;
586 before = alist.get_state();
588 for (PointSelection::iterator i = selection.begin(); i != selection.end(); ++i) {
590 if (&(*i).track != this) {
596 if ((what_we_got = alist.cut ((*i).start, (*i).end)) != 0) {
597 editor.get_cut_buffer().add (what_we_got);
598 _session.add_command (MementoCommand<AutomationList>(alist, before, alist.get_state()));
603 if ((what_we_got = alist.copy ((*i).start, (*i).end)) != 0) {
604 editor.get_cut_buffer().add (what_we_got);
609 if ((what_we_got = alist.cut ((*i).start, (*i).end)) != 0) {
610 _session.add_command (MementoCommand<AutomationList>(alist, before, alist.get_state()));
620 for (AutomationList::iterator x = what_we_got->begin(); x != what_we_got->end(); ++x) {
621 double foo = (*x)->value;
622 line.model_to_view_y (foo);
631 AutomationTimeAxisView::paste (jack_nframes_t pos, float times, Selection& selection, size_t nth)
635 for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
636 ret = paste_one (**i, pos, times, selection, nth);
643 AutomationTimeAxisView::paste_one (AutomationLine& line, jack_nframes_t pos, float times, Selection& selection, size_t nth)
645 AutomationSelection::iterator p;
646 AutomationList& alist (line.the_list());
648 for (p = selection.lines.begin(); p != selection.lines.end() && nth; ++p, --nth);
650 if (p == selection.lines.end()) {
654 /* Make a copy of the list because we have to scale the
655 values from view coordinates to model coordinates, and we're
656 not supposed to modify the points in the selection.
659 AutomationList copy (**p);
661 for (AutomationList::iterator x = copy.begin(); x != copy.end(); ++x) {
662 double foo = (*x)->value;
663 line.view_to_model_y (foo);
667 XMLNode &before = alist.get_state();
668 alist.paste (copy, pos, times);
669 _session.add_command (MementoCommand<AutomationList>(alist, before, alist.get_state()));
675 AutomationTimeAxisView::add_ghost (GhostRegion* gr)
677 ghosts.push_back (gr);
678 gr->GoingAway.connect (mem_fun(*this, &AutomationTimeAxisView::remove_ghost));
682 AutomationTimeAxisView::remove_ghost (GhostRegion* gr)
688 list<GhostRegion*>::iterator i;
690 for (i = ghosts.begin(); i != ghosts.end(); ++i) {
699 AutomationTimeAxisView::get_selectables (jack_nframes_t start, jack_nframes_t end, double top, double bot, list<Selectable*>& results)
701 if (!lines.empty() && touched (top, bot)) {
705 /* remember: this is X Window - coordinate space starts in upper left and moves down.
706 y_position is the "origin" or "top" of the track.
709 double mybot = y_position + height; // XXX need to include Editor::track_spacing;
711 if (y_position >= top && mybot <= bot) {
713 /* y_position is below top, mybot is above bot, so we're fully
722 /* top and bot are within y_position .. mybot */
724 topfrac = 1.0 - ((top - y_position) / height);
725 botfrac = 1.0 - ((bot - y_position) / height);
728 for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
729 (*i)->get_selectables (start, end, botfrac, topfrac, results);
735 AutomationTimeAxisView::get_inverted_selectables (Selection& sel, list<Selectable*>& result)
737 for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
738 (*i)->get_inverted_selectables (sel, result);
743 AutomationTimeAxisView::set_selected_points (PointSelection& points)
745 for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
746 (*i)->set_selected_points (points);
751 AutomationTimeAxisView::clear_lines ()
753 for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
758 automation_connection.disconnect ();
762 AutomationTimeAxisView::add_line (AutomationLine& line)
767 /* first line is the Model for automation state */
768 automation_connection = line.the_list().automation_state_changed.connect
769 (mem_fun(*this, &AutomationTimeAxisView::automation_state_changed));
773 lines.push_back (&line);
774 line.set_height (height);
777 /* pick up the current state */
778 automation_state_changed ();
783 AutomationTimeAxisView::show_all_control_points ()
785 for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
786 (*i)->show_all_control_points ();
791 AutomationTimeAxisView::hide_all_but_selected_control_points ()
793 for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
794 (*i)->hide_all_but_selected_control_points ();
799 AutomationTimeAxisView::entered()
801 show_all_control_points ();
805 AutomationTimeAxisView::exited ()
807 hide_all_but_selected_control_points ();
811 AutomationTimeAxisView::set_state (const XMLNode& node)
813 TimeAxisView::set_state (node);
817 AutomationTimeAxisView::get_state_node ()
819 TimeAxisView* state_parent = get_parent_with_state ();
822 return state_parent->get_child_xml_node (_state_name);