1 #include <ardour/route.h>
4 #include "automation_time_axis.h"
5 #include "automation_line.h"
6 #include "public_editor.h"
7 #include "canvas-simplerect.h"
8 #include "canvas-waveview.h"
10 #include "ghostregion.h"
11 #include "rgb_macros.h"
12 #include "automation_selectable.h"
13 #include "point_selection.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, Widget* p, std::string nom, std::string state_name, std::string nomparent)
40 TimeAxisView (s, e, &rent, p),
43 _state_name (state_name),
44 height_button (_("h")),
45 clear_button (_("clear")),
46 auto_button (X_("")) /* force addition of a label */
49 in_destructor = false;
54 ignore_state_request = false;
56 // base_rect = gnome_canvas_item_new (GNOME_CANVAS_GROUP(canvas_display),
57 // gnome_canvas_simplerect_get_type(),
61 // "outline_color_rgba", color_map[cAutomationTrackOutline],
62 // /* outline ends and bottom */
63 // "outline_what", (guint32) (0x1|0x2|0x8),
64 // "fill_color_rgba", color_map[cAutomationTrackFill],
66 base_rect = new Gnome::Canvas::SimpleRect(*canvas_display);
67 base_rect->set_property ("x1", 0.0);
68 base_rect->set_property ("y1", 0.0);
69 base_rect->set_property ("x2", 1000000.0);
70 base_rect->set_property ("outline_color_rgba", color_map[cAutomationTrackOutline]);
71 /* outline ends and bottom */
72 base_rect->set_property ("outline_what", (guint32) (0x1|0x2|0x8));
73 base_rect->set_property ("fill_color_rgba", color_map[cAutomationTrackFill]);
75 base_rect->set_data ("trackview", this);
77 gtk_signal_connect (GTK_OBJECT(base_rect), "event",
78 (GtkSignalFunc) PublicEditor::canvas_automation_track_event,
81 hide_button.add (*(manage (new Gtk::Image (Gdk::Pixbuf::create_from_xpm_data(small_x_xpm)))));
83 height_button.set_name ("TrackSizeButton");
84 auto_button.set_name ("TrackVisualButton");
85 clear_button.set_name ("TrackVisualButton");
86 hide_button.set_name ("TrackRemoveButton");
88 ARDOUR_UI::instance()->tooltips().set_tip(height_button, _("track height"));
89 ARDOUR_UI::instance()->tooltips().set_tip(auto_button, _("automation state"));
90 ARDOUR_UI::instance()->tooltips().set_tip(clear_button, _("clear track"));
91 ARDOUR_UI::instance()->tooltips().set_tip(hide_button, _("hide track"));
93 /* rearrange the name display */
95 /* we never show these for automation tracks, so make
96 life easier and remove them.
99 name_hbox.remove (name_entry);
101 /* move the name label over a bit */
103 string shortpname = _name;
104 bool shortened = false;
106 if (_name.length()) {
107 if (shortpname.length() > 18) {
108 shortpname = shortpname.substr (0, 16);
113 name_label.set_text (shortpname);
114 name_label.set_alignment (1.0, 0.5);
116 if (nomparent.length()) {
118 /* limit the plug name string */
120 string pname = nomparent;
122 if (pname.length() > 14) {
123 pname = pname.substr (0, 11);
128 plugname = new Label (pname);
129 plugname->set_name (X_("TrackPlugName"));
130 plugname->set_alignment (1.0, 0.5);
131 name_label.set_name (X_("TrackParameterName"));
132 controls_table.remove (name_hbox);
133 controls_table.attach (*plugname, 1, 6, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
134 plugname_packed = true;
135 controls_table.attach (name_hbox, 1, 6, 1, 2, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
138 plugname_packed = false;
142 string tipname = nomparent;
143 if (!tipname.empty()) {
147 ARDOUR_UI::instance()->tooltips().set_tip(controls_ebox, tipname);
150 /* add the buttons */
151 controls_table.attach (hide_button, 0, 1, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
152 controls_table.attach (height_button, 0, 1, 1, 2, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
154 controls_table.attach (auto_button, 7, 9, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
155 controls_table.attach (clear_button, 7, 9, 1, 2, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
157 controls_table.show_all ();
159 height_button.signal_clicked().connect (mem_fun(*this, &AutomationTimeAxisView::height_clicked));
160 clear_button.signal_clicked().connect (mem_fun(*this, &AutomationTimeAxisView::clear_clicked));
161 hide_button.signal_clicked().connect (mem_fun(*this, &AutomationTimeAxisView::hide_clicked));
162 auto_button.signal_clicked().connect (mem_fun(*this, &AutomationTimeAxisView::auto_clicked));
164 controls_base_selected_name = X_("AutomationTrackControlsBaseSelected");
165 controls_base_unselected_name = X_("AutomationTrackControlsBase");
166 controls_ebox.set_name (controls_base_unselected_name);
168 controls_frame.set_shadow_type (Gtk::SHADOW_ETCHED_OUT);
170 XMLNode* xml_node = get_parent_with_state()->get_child_xml_node (_state_name);
171 set_state (*xml_node);
173 /* make sure labels etc. are correct */
175 automation_state_changed ();
178 AutomationTimeAxisView::~AutomationTimeAxisView ()
180 in_destructor = true;
182 for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
188 AutomationTimeAxisView::auto_clicked ()
190 using namespace Menu_Helpers;
192 if (automation_menu == 0) {
193 automation_menu = manage (new Menu);
194 automation_menu->set_name ("ArdourContextMenu");
195 MenuList& items (automation_menu->items());
197 items.push_back (MenuElem (_("off"),
198 bind (mem_fun(*this, &AutomationTimeAxisView::set_automation_state), (AutoState) Off)));
199 items.push_back (MenuElem (_("play"),
200 bind (mem_fun(*this, &AutomationTimeAxisView::set_automation_state), (AutoState) Play)));
201 items.push_back (MenuElem (_("write"),
202 bind (mem_fun(*this, &AutomationTimeAxisView::set_automation_state), (AutoState) Write)));
203 items.push_back (MenuElem (_("touch"),
204 bind (mem_fun(*this, &AutomationTimeAxisView::set_automation_state), (AutoState) Touch)));
207 automation_menu->popup (1, 0);
212 AutomationTimeAxisView::automation_state_changed ()
216 /* update button label */
221 state = lines.front()->the_list().automation_state ();
224 switch (state & (Off|Play|Touch|Write)) {
226 static_cast<Gtk::Label*>(auto_button.get_child())->set_text (_("off"));
228 ignore_state_request = true;
229 auto_off_item->set_active (true);
230 auto_play_item->set_active (false);
231 auto_touch_item->set_active (false);
232 auto_write_item->set_active (false);
233 ignore_state_request = false;
237 static_cast<Gtk::Label*>(auto_button.get_child())->set_text (_("play"));
238 if (auto_play_item) {
239 ignore_state_request = true;
240 auto_play_item->set_active (true);
241 auto_off_item->set_active (false);
242 auto_touch_item->set_active (false);
243 auto_write_item->set_active (false);
244 ignore_state_request = false;
248 static_cast<Gtk::Label*>(auto_button.get_child())->set_text (_("write"));
249 if (auto_write_item) {
250 ignore_state_request = true;
251 auto_write_item->set_active (true);
252 auto_off_item->set_active (false);
253 auto_play_item->set_active (false);
254 auto_touch_item->set_active (false);
255 ignore_state_request = false;
259 static_cast<Gtk::Label*>(auto_button.get_child())->set_text (_("touch"));
260 if (auto_touch_item) {
261 ignore_state_request = true;
262 auto_touch_item->set_active (true);
263 auto_off_item->set_active (false);
264 auto_play_item->set_active (false);
265 auto_write_item->set_active (false);
266 ignore_state_request = false;
270 static_cast<Gtk::Label*>(auto_button.get_child())->set_text (_("???"));
276 AutomationTimeAxisView::height_clicked ()
282 AutomationTimeAxisView::clear_clicked ()
284 _session.begin_reversible_command (_("clear automation"));
285 for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
288 _session.commit_reversible_command ();
292 AutomationTimeAxisView::set_height (TrackHeight h)
294 bool changed = (height != (uint32_t) h);
296 TimeAxisView* state_parent = get_parent_with_state ();
297 XMLNode* xml_node = state_parent->get_child_xml_node (_state_name);
299 controls_table.show_all ();
301 TimeAxisView::set_height (h);
302 gtk_object_set (GTK_OBJECT(base_rect), "y2", (double) h, NULL);
304 for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
305 (*i)->set_height (h);
308 for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
314 xml_node->add_property ("track_height", "largest");
315 controls_table.remove (name_hbox);
317 if (plugname_packed) {
318 controls_table.remove (*plugname);
319 plugname_packed = false;
321 controls_table.attach (*plugname, 1, 6, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
322 plugname_packed = true;
323 controls_table.attach (name_hbox, 1, 6, 1, 2, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
325 controls_table.attach (name_hbox, 1, 6, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
327 controls_table.show_all ();
332 xml_node->add_property ("track_height", "large");
333 controls_table.remove (name_hbox);
335 if (plugname_packed) {
336 controls_table.remove (*plugname);
337 plugname_packed = false;
339 controls_table.attach (*plugname, 1, 6, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
340 plugname_packed = true;
342 controls_table.attach (name_hbox, 1, 6, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
344 controls_table.show_all ();
349 xml_node->add_property ("track_height", "larger");
350 controls_table.remove (name_hbox);
352 if (plugname_packed) {
353 controls_table.remove (*plugname);
354 plugname_packed = false;
356 controls_table.attach (*plugname, 1, 6, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
357 plugname_packed = true;
359 controls_table.attach (name_hbox, 1, 6, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
361 controls_table.show_all ();
366 xml_node->add_property ("track_height", "normal");
367 controls_table.remove (name_hbox);
369 if (plugname_packed) {
370 controls_table.remove (*plugname);
371 plugname_packed = false;
373 controls_table.attach (*plugname, 1, 6, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
374 plugname_packed = true;
375 controls_table.attach (name_hbox, 1, 6, 1, 2, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
377 controls_table.attach (name_hbox, 1, 6, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
379 controls_table.show_all ();
384 xml_node->add_property ("track_height", "smaller");
385 controls_table.remove (name_hbox);
387 if (plugname_packed) {
388 controls_table.remove (*plugname);
389 plugname_packed = false;
392 controls_table.attach (name_hbox, 1, 6, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
393 controls_table.hide_all ();
394 name_hbox.show_all ();
395 controls_table.show ();
399 xml_node->add_property ("track_height", "small");
400 controls_table.remove (name_hbox);
402 if (plugname_packed) {
403 controls_table.remove (*plugname);
404 plugname_packed = false;
407 controls_table.attach (name_hbox, 1, 6, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
408 controls_table.hide_all ();
409 name_hbox.show_all ();
410 controls_table.show ();
415 /* only emit the signal if the height really changed */
416 route.gui_changed ("track_height", (void *) 0); /* EMIT_SIGNAL */
421 AutomationTimeAxisView::set_samples_per_unit (double spu)
423 TimeAxisView::set_samples_per_unit (editor.get_current_zoom());
425 for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
431 AutomationTimeAxisView::hide_clicked ()
433 set_marked_for_display (false);
439 AutomationTimeAxisView::build_display_menu ()
441 using namespace Menu_Helpers;
443 /* get the size menu ready */
449 TimeAxisView::build_display_menu ();
451 /* now fill it with our stuff */
453 MenuList& items = display_menu->items();
455 items.push_back (MenuElem (_("Height"), *size_menu));
456 items.push_back (SeparatorElem());
457 items.push_back (MenuElem (_("Hide"), mem_fun(*this, &AutomationTimeAxisView::hide_clicked)));
458 items.push_back (SeparatorElem());
459 items.push_back (MenuElem (_("Clear"), mem_fun(*this, &AutomationTimeAxisView::clear_clicked)));
460 items.push_back (SeparatorElem());
462 Menu* auto_state_menu = manage (new Menu);
463 auto_state_menu->set_name ("ArdourContextMenu");
464 MenuList& as_items = auto_state_menu->items();
466 as_items.push_back (CheckMenuElem (_("off"),
467 bind (mem_fun(*this, &AutomationTimeAxisView::set_automation_state), (AutoState) Off)));
468 auto_off_item = dynamic_cast<CheckMenuItem*>(&as_items.back());
470 as_items.push_back (CheckMenuElem (_("play"),
471 bind (mem_fun(*this, &AutomationTimeAxisView::set_automation_state), (AutoState) Play)));
472 auto_play_item = dynamic_cast<CheckMenuItem*>(&as_items.back());
474 as_items.push_back (CheckMenuElem (_("write"),
475 bind (mem_fun(*this, &AutomationTimeAxisView::set_automation_state), (AutoState) Write)));
476 auto_write_item = dynamic_cast<CheckMenuItem*>(&as_items.back());
478 as_items.push_back (CheckMenuElem (_("touch"),
479 bind (mem_fun(*this, &AutomationTimeAxisView::set_automation_state), (AutoState) Touch)));
480 auto_touch_item = dynamic_cast<CheckMenuItem*>(&as_items.back());
482 items.push_back (MenuElem (_("State"), *auto_state_menu));
484 /* make sure the automation menu state is correct */
486 automation_state_changed ();
490 AutomationTimeAxisView::cut_copy_clear (Selection& selection, CutCopyOp op)
494 for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
495 ret = cut_copy_clear_one ((**i), selection, op);
502 AutomationTimeAxisView::cut_copy_clear_one (AutomationLine& line, Selection& selection, CutCopyOp op)
504 AutomationList* what_we_got = 0;
505 AutomationList& alist (line.the_list());
508 _session.add_undo (alist.get_memento());
512 if ((what_we_got = alist.cut (selection.time.front().start, selection.time.front().end)) != 0) {
513 editor.get_cut_buffer().add (what_we_got);
514 _session.add_redo_no_execute (alist.get_memento());
519 if ((what_we_got = alist.copy (selection.time.front().start, selection.time.front().end)) != 0) {
520 editor.get_cut_buffer().add (what_we_got);
525 if ((what_we_got = alist.cut (selection.time.front().start, selection.time.front().end)) != 0) {
526 _session.add_redo_no_execute (alist.get_memento());
535 for (AutomationList::iterator x = what_we_got->begin(); x != what_we_got->end(); ++x) {
536 double foo = (*x)->value;
537 line.model_to_view_y (foo);
546 AutomationTimeAxisView::cut_copy_clear_objects (PointSelection& selection, CutCopyOp op)
550 for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
551 ret = cut_copy_clear_objects_one ((**i), selection, op);
558 AutomationTimeAxisView::cut_copy_clear_objects_one (AutomationLine& line, PointSelection& selection, CutCopyOp op)
560 AutomationList* what_we_got = 0;
561 AutomationList& alist (line.the_list());
564 _session.add_undo (alist.get_memento());
566 for (PointSelection::iterator i = selection.begin(); i != selection.end(); ++i) {
568 if (&(*i).track != this) {
574 if ((what_we_got = alist.cut ((*i).start, (*i).end)) != 0) {
575 editor.get_cut_buffer().add (what_we_got);
576 _session.add_redo_no_execute (alist.get_memento());
581 if ((what_we_got = alist.copy ((*i).start, (*i).end)) != 0) {
582 editor.get_cut_buffer().add (what_we_got);
587 if ((what_we_got = alist.cut ((*i).start, (*i).end)) != 0) {
588 _session.add_redo_no_execute (alist.get_memento());
598 for (AutomationList::iterator x = what_we_got->begin(); x != what_we_got->end(); ++x) {
599 double foo = (*x)->value;
600 line.model_to_view_y (foo);
609 AutomationTimeAxisView::paste (jack_nframes_t pos, float times, Selection& selection, size_t nth)
613 for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
614 ret = paste_one (**i, pos, times, selection, nth);
621 AutomationTimeAxisView::paste_one (AutomationLine& line, jack_nframes_t pos, float times, Selection& selection, size_t nth)
623 AutomationSelection::iterator p;
624 AutomationList& alist (line.the_list());
626 for (p = selection.lines.begin(); p != selection.lines.end() && nth; ++p, --nth);
628 if (p == selection.lines.end()) {
632 /* Make a copy of the list because we have to scale the
633 values from view coordinates to model coordinates, and we're
634 not supposed to modify the points in the selection.
637 AutomationList copy (**p);
639 for (AutomationList::iterator x = copy.begin(); x != copy.end(); ++x) {
640 double foo = (*x)->value;
641 line.view_to_model_y (foo);
645 _session.add_undo (alist.get_memento());
646 alist.paste (copy, pos, times);
647 _session.add_redo_no_execute (alist.get_memento());
653 AutomationTimeAxisView::add_ghost (GhostRegion* gr)
655 ghosts.push_back (gr);
656 gr->GoingAway.connect (mem_fun(*this, &AutomationTimeAxisView::remove_ghost));
660 AutomationTimeAxisView::remove_ghost (GhostRegion* gr)
666 list<GhostRegion*>::iterator i;
668 for (i = ghosts.begin(); i != ghosts.end(); ++i) {
677 AutomationTimeAxisView::get_selectables (jack_nframes_t start, jack_nframes_t end, double top, double bot, list<Selectable*>& results)
679 if (!lines.empty() && touched (top, bot)) {
683 /* remember: this is X Window - coordinate space starts in upper left and moves down.
684 y_position is the "origin" or "top" of the track.
687 double mybot = y_position + height; // XXX need to include Editor::track_spacing;
689 if (y_position >= top && mybot <= bot) {
691 /* y_position is below top, mybot is above bot, so we're fully
700 /* top and bot are within y_position .. mybot */
702 topfrac = 1.0 - ((top - y_position) / height);
703 botfrac = 1.0 - ((bot - y_position) / height);
706 for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
707 (*i)->get_selectables (start, end, botfrac, topfrac, results);
713 AutomationTimeAxisView::get_inverted_selectables (Selection& sel, list<Selectable*>& result)
715 for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
716 (*i)->get_inverted_selectables (sel, result);
721 AutomationTimeAxisView::set_selected_points (PointSelection& points)
723 for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
724 (*i)->set_selected_points (points);
729 AutomationTimeAxisView::clear_lines ()
731 for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
736 automation_connection.disconnect ();
740 AutomationTimeAxisView::add_line (AutomationLine& line)
745 /* first line is the Model for automation state */
746 automation_connection = line.the_list().automation_state_changed.connect
747 (mem_fun(*this, &AutomationTimeAxisView::automation_state_changed));
751 lines.push_back (&line);
752 line.set_height (height);
755 /* pick up the current state */
756 automation_state_changed ();
761 AutomationTimeAxisView::show_all_control_points ()
763 for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
764 (*i)->show_all_control_points ();
769 AutomationTimeAxisView::hide_all_but_selected_control_points ()
771 for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
772 (*i)->hide_all_but_selected_control_points ();
777 AutomationTimeAxisView::entered()
779 show_all_control_points ();
783 AutomationTimeAxisView::exited ()
785 hide_all_but_selected_control_points ();
789 AutomationTimeAxisView::set_state (const XMLNode& node)
791 TimeAxisView::set_state (node);
795 AutomationTimeAxisView::get_state_node ()
797 TimeAxisView* state_parent = get_parent_with_state ();
800 return state_parent->get_child_xml_node (_state_name);