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"
17 using namespace ARDOUR;
19 using namespace Editing;
21 static const gchar * small_x_xpm[] = {
37 AutomationTimeAxisView::AutomationTimeAxisView (Session& s, Route& r, PublicEditor& e, TimeAxisView& rent,
38 ArdourCanvas::Canvas& canvas, const string & nom,
39 const string & state_name, const string & nomparent)
42 TimeAxisView (s, e, &rent, canvas),
45 _state_name (state_name),
46 height_button (_("h")),
47 clear_button (_("clear")),
48 auto_button (X_("")) /* force addition of a label */
51 in_destructor = false;
56 ignore_state_request = false;
58 // base_rect = gnome_canvas_item_new (GNOME_CANVAS_GROUP(canvas_display),
59 // gnome_canvas_simplerect_get_type(),
63 // "outline_color_rgba", color_map[cAutomationTrackOutline],
64 // /* outline ends and bottom */
65 // "outline_what", (guint32) (0x1|0x2|0x8),
66 // "fill_color_rgba", color_map[cAutomationTrackFill],
68 base_rect = new SimpleRect(*canvas_display);
69 base_rect->property_x1() = 0.0;
70 base_rect->property_y1() = 0.0;
71 base_rect->property_x2() = 1000000.0;
72 base_rect->property_outline_color_rgba() = color_map[cAutomationTrackOutline];
73 /* outline ends and bottom */
74 base_rect->property_outline_what() = (guint32) (0x1|0x2|0x8);
75 base_rect->property_fill_color_rgba() = color_map[cAutomationTrackFill];
77 base_rect->set_data ("trackview", this);
79 base_rect->signal_event().connect (bind (mem_fun (editor, &PublicEditor::canvas_automation_track_event),
82 hide_button.add (*(manage (new Gtk::Image (Gdk::Pixbuf::create_from_xpm_data(small_x_xpm)))));
84 height_button.set_name ("TrackSizeButton");
85 auto_button.set_name ("TrackVisualButton");
86 clear_button.set_name ("TrackVisualButton");
87 hide_button.set_name ("TrackRemoveButton");
89 ARDOUR_UI::instance()->tooltips().set_tip(height_button, _("track height"));
90 ARDOUR_UI::instance()->tooltips().set_tip(auto_button, _("automation state"));
91 ARDOUR_UI::instance()->tooltips().set_tip(clear_button, _("clear track"));
92 ARDOUR_UI::instance()->tooltips().set_tip(hide_button, _("hide track"));
94 /* rearrange the name display */
96 /* we never show these for automation tracks, so make
97 life easier and remove them.
102 /* move the name label over a bit */
104 string shortpname = _name;
105 bool shortened = false;
107 if (_name.length()) {
108 if (shortpname.length() > 18) {
109 shortpname = shortpname.substr (0, 16);
114 name_label.set_text (shortpname);
115 name_label.set_alignment (1.0, 0.5);
117 if (nomparent.length()) {
119 /* limit the plug name string */
121 string pname = nomparent;
123 if (pname.length() > 14) {
124 pname = pname.substr (0, 11);
129 plugname = new Label (pname);
130 plugname->set_name (X_("TrackPlugName"));
131 plugname->set_alignment (1.0, 0.5);
132 name_label.set_name (X_("TrackParameterName"));
133 controls_table.remove (name_hbox);
134 controls_table.attach (*plugname, 1, 5, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
135 plugname_packed = true;
136 controls_table.attach (name_hbox, 1, 5, 1, 2, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
139 plugname_packed = false;
143 string tipname = nomparent;
144 if (!tipname.empty()) {
148 ARDOUR_UI::instance()->tooltips().set_tip(controls_ebox, tipname);
151 /* add the buttons */
152 controls_table.attach (hide_button, 0, 1, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
153 controls_table.attach (height_button, 0, 1, 1, 2, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
155 controls_table.attach (auto_button, 6, 8, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
156 controls_table.attach (clear_button, 6, 8, 1, 2, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
158 controls_table.show_all ();
160 height_button.signal_clicked().connect (mem_fun(*this, &AutomationTimeAxisView::height_clicked));
161 clear_button.signal_clicked().connect (mem_fun(*this, &AutomationTimeAxisView::clear_clicked));
162 hide_button.signal_clicked().connect (mem_fun(*this, &AutomationTimeAxisView::hide_clicked));
163 auto_button.signal_clicked().connect (mem_fun(*this, &AutomationTimeAxisView::auto_clicked));
165 controls_base_selected_name = X_("AutomationTrackControlsBaseSelected");
166 controls_base_unselected_name = X_("AutomationTrackControlsBase");
167 controls_ebox.set_name (controls_base_unselected_name);
169 controls_frame.set_shadow_type (Gtk::SHADOW_ETCHED_OUT);
171 XMLNode* xml_node = get_parent_with_state()->get_child_xml_node (_state_name);
172 set_state (*xml_node);
174 /* make sure labels etc. are correct */
176 automation_state_changed ();
179 AutomationTimeAxisView::~AutomationTimeAxisView ()
181 in_destructor = true;
183 for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
189 AutomationTimeAxisView::auto_clicked ()
191 using namespace Menu_Helpers;
193 if (automation_menu == 0) {
194 automation_menu = manage (new Menu);
195 automation_menu->set_name ("ArdourContextMenu");
196 MenuList& items (automation_menu->items());
198 items.push_back (MenuElem (_("off"),
199 bind (mem_fun(*this, &AutomationTimeAxisView::set_automation_state), (AutoState) Off)));
200 items.push_back (MenuElem (_("play"),
201 bind (mem_fun(*this, &AutomationTimeAxisView::set_automation_state), (AutoState) Play)));
202 items.push_back (MenuElem (_("write"),
203 bind (mem_fun(*this, &AutomationTimeAxisView::set_automation_state), (AutoState) Write)));
204 items.push_back (MenuElem (_("touch"),
205 bind (mem_fun(*this, &AutomationTimeAxisView::set_automation_state), (AutoState) Touch)));
208 automation_menu->popup (1, 0);
213 AutomationTimeAxisView::automation_state_changed ()
217 /* update button label */
222 state = lines.front()->the_list().automation_state ();
225 switch (state & (Off|Play|Touch|Write)) {
227 static_cast<Gtk::Label*>(auto_button.get_child())->set_text (_("off"));
229 ignore_state_request = true;
230 auto_off_item->set_active (true);
231 auto_play_item->set_active (false);
232 auto_touch_item->set_active (false);
233 auto_write_item->set_active (false);
234 ignore_state_request = false;
238 static_cast<Gtk::Label*>(auto_button.get_child())->set_text (_("play"));
239 if (auto_play_item) {
240 ignore_state_request = true;
241 auto_play_item->set_active (true);
242 auto_off_item->set_active (false);
243 auto_touch_item->set_active (false);
244 auto_write_item->set_active (false);
245 ignore_state_request = false;
249 static_cast<Gtk::Label*>(auto_button.get_child())->set_text (_("write"));
250 if (auto_write_item) {
251 ignore_state_request = true;
252 auto_write_item->set_active (true);
253 auto_off_item->set_active (false);
254 auto_play_item->set_active (false);
255 auto_touch_item->set_active (false);
256 ignore_state_request = false;
260 static_cast<Gtk::Label*>(auto_button.get_child())->set_text (_("touch"));
261 if (auto_touch_item) {
262 ignore_state_request = true;
263 auto_touch_item->set_active (true);
264 auto_off_item->set_active (false);
265 auto_play_item->set_active (false);
266 auto_write_item->set_active (false);
267 ignore_state_request = false;
271 static_cast<Gtk::Label*>(auto_button.get_child())->set_text (_("???"));
277 AutomationTimeAxisView::height_clicked ()
283 AutomationTimeAxisView::clear_clicked ()
285 _session.begin_reversible_command (_("clear automation"));
286 for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
289 _session.commit_reversible_command ();
293 AutomationTimeAxisView::set_height (TrackHeight h)
295 bool changed = (height != (uint32_t) h);
297 TimeAxisView* state_parent = get_parent_with_state ();
298 XMLNode* xml_node = state_parent->get_child_xml_node (_state_name);
300 controls_table.show_all ();
302 TimeAxisView::set_height (h);
303 base_rect->property_y2() = h;
305 for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
306 (*i)->set_height (h);
309 for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
315 xml_node->add_property ("track_height", "largest");
316 controls_table.remove (name_hbox);
318 if (plugname_packed) {
319 controls_table.remove (*plugname);
320 plugname_packed = false;
322 controls_table.attach (*plugname, 1, 5, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
323 plugname_packed = true;
324 controls_table.attach (name_hbox, 1, 5, 1, 2, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
326 controls_table.attach (name_hbox, 1, 5, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
328 controls_table.show_all ();
334 xml_node->add_property ("track_height", "large");
335 controls_table.remove (name_hbox);
337 if (plugname_packed) {
338 controls_table.remove (*plugname);
339 plugname_packed = false;
341 controls_table.attach (*plugname, 1, 5, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
342 plugname_packed = true;
344 controls_table.attach (name_hbox, 1, 5, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
346 controls_table.show_all ();
352 xml_node->add_property ("track_height", "larger");
353 controls_table.remove (name_hbox);
355 if (plugname_packed) {
356 controls_table.remove (*plugname);
357 plugname_packed = false;
359 controls_table.attach (*plugname, 1, 5, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
360 plugname_packed = true;
362 controls_table.attach (name_hbox, 1, 5, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
364 controls_table.show_all ();
370 xml_node->add_property ("track_height", "normal");
371 controls_table.remove (name_hbox);
373 if (plugname_packed) {
374 controls_table.remove (*plugname);
375 plugname_packed = false;
377 controls_table.attach (*plugname, 1, 5, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
378 plugname_packed = true;
379 controls_table.attach (name_hbox, 1, 5, 1, 2, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
381 controls_table.attach (name_hbox, 1, 5, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
383 controls_table.show_all ();
389 xml_node->add_property ("track_height", "smaller");
390 controls_table.remove (name_hbox);
392 if (plugname_packed) {
393 controls_table.remove (*plugname);
394 plugname_packed = false;
397 controls_table.attach (name_hbox, 1, 5, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
398 controls_table.hide_all ();
401 name_hbox.show_all ();
402 controls_table.show ();
406 xml_node->add_property ("track_height", "small");
407 controls_table.remove (name_hbox);
409 if (plugname_packed) {
410 controls_table.remove (*plugname);
411 plugname_packed = false;
414 controls_table.attach (name_hbox, 1, 5, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
415 controls_table.hide_all ();
418 name_hbox.show_all ();
419 controls_table.show ();
424 /* only emit the signal if the height really changed */
425 route.gui_changed ("track_height", (void *) 0); /* EMIT_SIGNAL */
430 AutomationTimeAxisView::set_samples_per_unit (double spu)
432 TimeAxisView::set_samples_per_unit (editor.get_current_zoom());
434 for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
440 AutomationTimeAxisView::hide_clicked ()
442 set_marked_for_display (false);
447 AutomationTimeAxisView::build_display_menu ()
449 using namespace Menu_Helpers;
451 /* get the size menu ready */
457 TimeAxisView::build_display_menu ();
459 /* now fill it with our stuff */
461 MenuList& items = display_menu->items();
463 items.push_back (MenuElem (_("Height"), *size_menu));
464 items.push_back (SeparatorElem());
465 items.push_back (MenuElem (_("Hide"), mem_fun(*this, &AutomationTimeAxisView::hide_clicked)));
466 items.push_back (SeparatorElem());
467 items.push_back (MenuElem (_("Clear"), mem_fun(*this, &AutomationTimeAxisView::clear_clicked)));
468 items.push_back (SeparatorElem());
470 Menu* auto_state_menu = manage (new Menu);
471 auto_state_menu->set_name ("ArdourContextMenu");
472 MenuList& as_items = auto_state_menu->items();
474 as_items.push_back (CheckMenuElem (_("off"),
475 bind (mem_fun(*this, &AutomationTimeAxisView::set_automation_state), (AutoState) Off)));
476 auto_off_item = dynamic_cast<CheckMenuItem*>(&as_items.back());
478 as_items.push_back (CheckMenuElem (_("play"),
479 bind (mem_fun(*this, &AutomationTimeAxisView::set_automation_state), (AutoState) Play)));
480 auto_play_item = dynamic_cast<CheckMenuItem*>(&as_items.back());
482 as_items.push_back (CheckMenuElem (_("write"),
483 bind (mem_fun(*this, &AutomationTimeAxisView::set_automation_state), (AutoState) Write)));
484 auto_write_item = dynamic_cast<CheckMenuItem*>(&as_items.back());
486 as_items.push_back (CheckMenuElem (_("touch"),
487 bind (mem_fun(*this, &AutomationTimeAxisView::set_automation_state), (AutoState) Touch)));
488 auto_touch_item = dynamic_cast<CheckMenuItem*>(&as_items.back());
490 items.push_back (MenuElem (_("State"), *auto_state_menu));
492 /* make sure the automation menu state is correct */
494 automation_state_changed ();
498 AutomationTimeAxisView::cut_copy_clear (Selection& selection, CutCopyOp op)
502 for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
503 ret = cut_copy_clear_one ((**i), selection, op);
510 AutomationTimeAxisView::cut_copy_clear_one (AutomationLine& line, Selection& selection, CutCopyOp op)
512 AutomationList* what_we_got = 0;
513 AutomationList& alist (line.the_list());
516 _session.add_undo (alist.get_memento());
520 if ((what_we_got = alist.cut (selection.time.front().start, selection.time.front().end)) != 0) {
521 editor.get_cut_buffer().add (what_we_got);
522 _session.add_redo_no_execute (alist.get_memento());
527 if ((what_we_got = alist.copy (selection.time.front().start, selection.time.front().end)) != 0) {
528 editor.get_cut_buffer().add (what_we_got);
533 if ((what_we_got = alist.cut (selection.time.front().start, selection.time.front().end)) != 0) {
534 _session.add_redo_no_execute (alist.get_memento());
543 for (AutomationList::iterator x = what_we_got->begin(); x != what_we_got->end(); ++x) {
544 double foo = (*x)->value;
545 line.model_to_view_y (foo);
554 AutomationTimeAxisView::cut_copy_clear_objects (PointSelection& selection, CutCopyOp op)
558 for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
559 ret = cut_copy_clear_objects_one ((**i), selection, op);
566 AutomationTimeAxisView::cut_copy_clear_objects_one (AutomationLine& line, PointSelection& selection, CutCopyOp op)
568 AutomationList* what_we_got = 0;
569 AutomationList& alist (line.the_list());
572 _session.add_undo (alist.get_memento());
574 for (PointSelection::iterator i = selection.begin(); i != selection.end(); ++i) {
576 if (&(*i).track != this) {
582 if ((what_we_got = alist.cut ((*i).start, (*i).end)) != 0) {
583 editor.get_cut_buffer().add (what_we_got);
584 _session.add_redo_no_execute (alist.get_memento());
589 if ((what_we_got = alist.copy ((*i).start, (*i).end)) != 0) {
590 editor.get_cut_buffer().add (what_we_got);
595 if ((what_we_got = alist.cut ((*i).start, (*i).end)) != 0) {
596 _session.add_redo_no_execute (alist.get_memento());
606 for (AutomationList::iterator x = what_we_got->begin(); x != what_we_got->end(); ++x) {
607 double foo = (*x)->value;
608 line.model_to_view_y (foo);
617 AutomationTimeAxisView::paste (jack_nframes_t pos, float times, Selection& selection, size_t nth)
621 for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
622 ret = paste_one (**i, pos, times, selection, nth);
629 AutomationTimeAxisView::paste_one (AutomationLine& line, jack_nframes_t pos, float times, Selection& selection, size_t nth)
631 AutomationSelection::iterator p;
632 AutomationList& alist (line.the_list());
634 for (p = selection.lines.begin(); p != selection.lines.end() && nth; ++p, --nth);
636 if (p == selection.lines.end()) {
640 /* Make a copy of the list because we have to scale the
641 values from view coordinates to model coordinates, and we're
642 not supposed to modify the points in the selection.
645 AutomationList copy (**p);
647 for (AutomationList::iterator x = copy.begin(); x != copy.end(); ++x) {
648 double foo = (*x)->value;
649 line.view_to_model_y (foo);
653 _session.add_undo (alist.get_memento());
654 alist.paste (copy, pos, times);
655 _session.add_redo_no_execute (alist.get_memento());
661 AutomationTimeAxisView::add_ghost (GhostRegion* gr)
663 ghosts.push_back (gr);
664 gr->GoingAway.connect (mem_fun(*this, &AutomationTimeAxisView::remove_ghost));
668 AutomationTimeAxisView::remove_ghost (GhostRegion* gr)
674 list<GhostRegion*>::iterator i;
676 for (i = ghosts.begin(); i != ghosts.end(); ++i) {
685 AutomationTimeAxisView::get_selectables (jack_nframes_t start, jack_nframes_t end, double top, double bot, list<Selectable*>& results)
687 if (!lines.empty() && touched (top, bot)) {
691 /* remember: this is X Window - coordinate space starts in upper left and moves down.
692 y_position is the "origin" or "top" of the track.
695 double mybot = y_position + height; // XXX need to include Editor::track_spacing;
697 if (y_position >= top && mybot <= bot) {
699 /* y_position is below top, mybot is above bot, so we're fully
708 /* top and bot are within y_position .. mybot */
710 topfrac = 1.0 - ((top - y_position) / height);
711 botfrac = 1.0 - ((bot - y_position) / height);
714 for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
715 (*i)->get_selectables (start, end, botfrac, topfrac, results);
721 AutomationTimeAxisView::get_inverted_selectables (Selection& sel, list<Selectable*>& result)
723 for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
724 (*i)->get_inverted_selectables (sel, result);
729 AutomationTimeAxisView::set_selected_points (PointSelection& points)
731 for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
732 (*i)->set_selected_points (points);
737 AutomationTimeAxisView::clear_lines ()
739 for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
744 automation_connection.disconnect ();
748 AutomationTimeAxisView::add_line (AutomationLine& line)
753 /* first line is the Model for automation state */
754 automation_connection = line.the_list().automation_state_changed.connect
755 (mem_fun(*this, &AutomationTimeAxisView::automation_state_changed));
759 lines.push_back (&line);
760 line.set_height (height);
763 /* pick up the current state */
764 automation_state_changed ();
769 AutomationTimeAxisView::show_all_control_points ()
771 for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
772 (*i)->show_all_control_points ();
777 AutomationTimeAxisView::hide_all_but_selected_control_points ()
779 for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
780 (*i)->hide_all_but_selected_control_points ();
785 AutomationTimeAxisView::entered()
787 show_all_control_points ();
791 AutomationTimeAxisView::exited ()
793 hide_all_but_selected_control_points ();
797 AutomationTimeAxisView::set_state (const XMLNode& node)
799 TimeAxisView::set_state (node);
803 AutomationTimeAxisView::get_state_node ()
805 TimeAxisView* state_parent = get_parent_with_state ();
808 return state_parent->get_child_xml_node (_state_name);