1 #include <ardour/route.h>
2 #include <pbd/memento_command.h>
5 #include "automation_time_axis.h"
6 #include "automation_line.h"
7 #include "public_editor.h"
8 #include "simplerect.h"
10 #include "ghostregion.h"
11 #include "rgb_macros.h"
12 #include "automation_selectable.h"
13 #include "point_selection.h"
14 #include "canvas_impl.h"
19 using namespace ARDOUR;
22 using namespace Editing;
24 AutomationTimeAxisView::AutomationTimeAxisView (Session& s, boost::shared_ptr<Route> r, PublicEditor& e, TimeAxisView& rent,
25 ArdourCanvas::Canvas& canvas, const string & nom,
26 const string & state_name, const string & nomparent)
29 TimeAxisView (s, e, &rent, canvas),
32 _state_name (state_name),
33 height_button (_("h")),
34 clear_button (_("clear")),
35 auto_button (X_("")) /* force addition of a label */
38 in_destructor = false;
43 ignore_state_request = false;
44 first_call_to_set_height = true;
46 // base_rect = gnome_canvas_item_new (GNOME_CANVAS_GROUP(canvas_display),
47 // gnome_canvas_simplerect_get_type(),
51 // "outline_color_rgba", color_map[cAutomationTrackOutline],
52 // /* outline ends and bottom */
53 // "outline_what", (guint32) (0x1|0x2|0x8),
54 // "fill_color_rgba", color_map[cAutomationTrackFill],
56 base_rect = new SimpleRect(*canvas_display);
57 base_rect->property_x1() = 0.0;
58 base_rect->property_y1() = 0.0;
59 base_rect->property_x2() = 1000000.0;
60 base_rect->property_outline_color_rgba() = color_map[cAutomationTrackOutline];
61 /* outline ends and bottom */
62 base_rect->property_outline_what() = (guint32) (0x1|0x2|0x8);
63 base_rect->property_fill_color_rgba() = color_map[cAutomationTrackFill];
65 base_rect->set_data ("trackview", this);
67 base_rect->signal_event().connect (bind (mem_fun (editor, &PublicEditor::canvas_automation_track_event),
70 hide_button.add (*(manage (new Gtk::Image (get_xpm("small_x.xpm")))));
72 height_button.set_name ("TrackSizeButton");
73 auto_button.set_name ("TrackVisualButton");
74 clear_button.set_name ("TrackVisualButton");
75 hide_button.set_name ("TrackRemoveButton");
77 controls_table.set_no_show_all();
79 ARDOUR_UI::instance()->tooltips().set_tip(height_button, _("track height"));
80 ARDOUR_UI::instance()->tooltips().set_tip(auto_button, _("automation state"));
81 ARDOUR_UI::instance()->tooltips().set_tip(clear_button, _("clear track"));
82 ARDOUR_UI::instance()->tooltips().set_tip(hide_button, _("hide track"));
84 /* rearrange the name display */
86 /* we never show these for automation tracks, so make
87 life easier and remove them.
92 /* move the name label over a bit */
94 string shortpname = _name;
95 bool shortened = false;
98 if (shortpname.length() > 18) {
99 shortpname = shortpname.substr (0, 16);
104 name_label.set_text (shortpname);
105 name_label.set_alignment (Gtk::ALIGN_CENTER, Gtk::ALIGN_CENTER);
107 if (nomparent.length()) {
109 /* limit the plug name string */
111 string pname = nomparent;
113 if (pname.length() > 14) {
114 pname = pname.substr (0, 11);
119 plugname = new Label (pname);
120 plugname->set_name (X_("TrackPlugName"));
122 name_label.set_name (X_("TrackParameterName"));
123 controls_table.remove (name_hbox);
124 controls_table.attach (*plugname, 1, 5, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
125 plugname_packed = true;
126 controls_table.attach (name_hbox, 1, 5, 1, 2, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
129 plugname_packed = false;
133 string tipname = nomparent;
134 if (!tipname.empty()) {
138 ARDOUR_UI::instance()->tooltips().set_tip(controls_ebox, tipname);
141 /* add the buttons */
142 controls_table.attach (hide_button, 0, 1, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
143 controls_table.attach (height_button, 0, 1, 1, 2, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
145 controls_table.attach (auto_button, 5, 8, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
146 controls_table.attach (clear_button, 5, 8, 1, 2, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
148 controls_table.show_all ();
150 height_button.signal_clicked().connect (mem_fun(*this, &AutomationTimeAxisView::height_clicked));
151 clear_button.signal_clicked().connect (mem_fun(*this, &AutomationTimeAxisView::clear_clicked));
152 hide_button.signal_clicked().connect (mem_fun(*this, &AutomationTimeAxisView::hide_clicked));
153 auto_button.signal_clicked().connect (mem_fun(*this, &AutomationTimeAxisView::auto_clicked));
155 controls_base_selected_name = X_("AutomationTrackControlsBaseSelected");
156 controls_base_unselected_name = X_("AutomationTrackControlsBase");
157 controls_ebox.set_name (controls_base_unselected_name);
159 controls_frame.set_shadow_type (Gtk::SHADOW_ETCHED_OUT);
161 XMLNode* xml_node = get_parent_with_state()->get_child_xml_node (_state_name);
162 set_state (*xml_node);
164 /* make sure labels etc. are correct */
166 automation_state_changed ();
169 AutomationTimeAxisView::~AutomationTimeAxisView ()
171 in_destructor = true;
173 for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
179 AutomationTimeAxisView::auto_clicked ()
181 using namespace Menu_Helpers;
183 if (automation_menu == 0) {
184 automation_menu = manage (new Menu);
185 automation_menu->set_name ("ArdourContextMenu");
186 MenuList& items (automation_menu->items());
188 items.push_back (MenuElem (_("Manual"),
189 bind (mem_fun(*this, &AutomationTimeAxisView::set_automation_state), (AutoState) Off)));
190 items.push_back (MenuElem (_("Play"),
191 bind (mem_fun(*this, &AutomationTimeAxisView::set_automation_state), (AutoState) Play)));
192 items.push_back (MenuElem (_("Write"),
193 bind (mem_fun(*this, &AutomationTimeAxisView::set_automation_state), (AutoState) Write)));
194 items.push_back (MenuElem (_("Touch"),
195 bind (mem_fun(*this, &AutomationTimeAxisView::set_automation_state), (AutoState) Touch)));
198 automation_menu->popup (1, 0);
203 AutomationTimeAxisView::automation_state_changed ()
207 /* update button label */
212 state = lines.front()->the_list().automation_state ();
215 switch (state & (Off|Play|Touch|Write)) {
217 auto_button.set_label (_("Manual"));
219 ignore_state_request = true;
220 auto_off_item->set_active (true);
221 auto_play_item->set_active (false);
222 auto_touch_item->set_active (false);
223 auto_write_item->set_active (false);
224 ignore_state_request = false;
228 auto_button.set_label (_("Play"));
229 if (auto_play_item) {
230 ignore_state_request = true;
231 auto_play_item->set_active (true);
232 auto_off_item->set_active (false);
233 auto_touch_item->set_active (false);
234 auto_write_item->set_active (false);
235 ignore_state_request = false;
239 auto_button.set_label (_("Write"));
240 if (auto_write_item) {
241 ignore_state_request = true;
242 auto_write_item->set_active (true);
243 auto_off_item->set_active (false);
244 auto_play_item->set_active (false);
245 auto_touch_item->set_active (false);
246 ignore_state_request = false;
250 auto_button.set_label (_("Touch"));
251 if (auto_touch_item) {
252 ignore_state_request = true;
253 auto_touch_item->set_active (true);
254 auto_off_item->set_active (false);
255 auto_play_item->set_active (false);
256 auto_write_item->set_active (false);
257 ignore_state_request = false;
261 auto_button.set_label (_("???"));
267 AutomationTimeAxisView::height_clicked ()
273 AutomationTimeAxisView::clear_clicked ()
275 _session.begin_reversible_command (_("clear automation"));
276 for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
279 _session.commit_reversible_command ();
283 AutomationTimeAxisView::set_height (TrackHeight ht)
285 uint32_t h = height_to_pixels (ht);
286 bool changed = (height != (uint32_t) h);
288 bool changed_between_small_and_normal = ( (ht == Small || ht == Smaller) ^ (height_style == Small || height_style == Smaller) );
290 TimeAxisView* state_parent = get_parent_with_state ();
291 XMLNode* xml_node = state_parent->get_child_xml_node (_state_name);
293 TimeAxisView::set_height (ht);
294 base_rect->property_y2() = h;
296 for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
297 (*i)->set_height (h);
300 for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
307 xml_node->add_property ("track_height", "largest");
311 xml_node->add_property ("track_height", "large");
315 xml_node->add_property ("track_height", "larger");
319 xml_node->add_property ("track_height", "normal");
323 xml_node->add_property ("track_height", "smaller");
327 xml_node->add_property ("track_height", "small");
331 if (changed_between_small_and_normal || first_call_to_set_height) {
332 first_call_to_set_height = false;
339 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;
348 controls_table.attach (name_hbox, 1, 5, 1, 2, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
350 controls_table.attach (name_hbox, 1, 5, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
354 name_hbox.show_all ();
357 height_button.show();
359 hide_button.show_all();
365 controls_table.remove (name_hbox);
367 if (plugname_packed) {
368 controls_table.remove (*plugname);
369 plugname_packed = false;
372 controls_table.attach (name_hbox, 1, 5, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
373 controls_table.hide_all ();
376 name_hbox.show_all ();
379 height_button.hide();
387 /* only emit the signal if the height really changed */
388 route->gui_changed ("track_height", (void *) 0); /* EMIT_SIGNAL */
393 AutomationTimeAxisView::set_samples_per_unit (double spu)
395 TimeAxisView::set_samples_per_unit (editor.get_current_zoom());
397 for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
403 AutomationTimeAxisView::hide_clicked ()
405 set_marked_for_display (false);
410 AutomationTimeAxisView::build_display_menu ()
412 using namespace Menu_Helpers;
414 /* get the size menu ready */
420 TimeAxisView::build_display_menu ();
422 /* now fill it with our stuff */
424 MenuList& items = display_menu->items();
426 items.push_back (MenuElem (_("Height"), *size_menu));
427 items.push_back (SeparatorElem());
428 items.push_back (MenuElem (_("Hide"), mem_fun(*this, &AutomationTimeAxisView::hide_clicked)));
429 items.push_back (SeparatorElem());
430 items.push_back (MenuElem (_("Clear"), mem_fun(*this, &AutomationTimeAxisView::clear_clicked)));
431 items.push_back (SeparatorElem());
433 Menu* auto_state_menu = manage (new Menu);
434 auto_state_menu->set_name ("ArdourContextMenu");
435 MenuList& as_items = auto_state_menu->items();
437 as_items.push_back (CheckMenuElem (_("Manual"),
438 bind (mem_fun(*this, &AutomationTimeAxisView::set_automation_state), (AutoState) Off)));
439 auto_off_item = dynamic_cast<CheckMenuItem*>(&as_items.back());
441 as_items.push_back (CheckMenuElem (_("Play"),
442 bind (mem_fun(*this, &AutomationTimeAxisView::set_automation_state), (AutoState) Play)));
443 auto_play_item = dynamic_cast<CheckMenuItem*>(&as_items.back());
445 as_items.push_back (CheckMenuElem (_("Write"),
446 bind (mem_fun(*this, &AutomationTimeAxisView::set_automation_state), (AutoState) Write)));
447 auto_write_item = dynamic_cast<CheckMenuItem*>(&as_items.back());
449 as_items.push_back (CheckMenuElem (_("Touch"),
450 bind (mem_fun(*this, &AutomationTimeAxisView::set_automation_state), (AutoState) Touch)));
451 auto_touch_item = dynamic_cast<CheckMenuItem*>(&as_items.back());
453 items.push_back (MenuElem (_("State"), *auto_state_menu));
455 /* make sure the automation menu state is correct */
457 automation_state_changed ();
461 AutomationTimeAxisView::cut_copy_clear (Selection& selection, CutCopyOp op)
465 for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
466 ret = cut_copy_clear_one ((**i), selection, op);
473 AutomationTimeAxisView::cut_copy_clear_one (AutomationLine& line, Selection& selection, CutCopyOp op)
475 AutomationList* what_we_got = 0;
476 AutomationList& alist (line.the_list());
479 XMLNode &before = alist.get_state();
483 if ((what_we_got = alist.cut (selection.time.front().start, selection.time.front().end)) != 0) {
484 editor.get_cut_buffer().add (what_we_got);
485 _session.add_command(new MementoCommand<AutomationList>(alist, &before, &alist.get_state()));
490 if ((what_we_got = alist.copy (selection.time.front().start, selection.time.front().end)) != 0) {
491 editor.get_cut_buffer().add (what_we_got);
496 if ((what_we_got = alist.cut (selection.time.front().start, selection.time.front().end)) != 0) {
497 _session.add_command(new MementoCommand<AutomationList>(alist, &before, &alist.get_state()));
506 for (AutomationList::iterator x = what_we_got->begin(); x != what_we_got->end(); ++x) {
507 double foo = (*x)->value;
508 line.model_to_view_y (foo);
517 AutomationTimeAxisView::reset_objects (PointSelection& selection)
519 for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
520 reset_objects_one ((**i), selection);
525 AutomationTimeAxisView::reset_objects_one (AutomationLine& line, PointSelection& selection)
527 AutomationList& alist (line.the_list());
529 _session.add_command (new MementoCommand<AutomationList>(alist, &alist.get_state(), 0));
531 for (PointSelection::iterator i = selection.begin(); i != selection.end(); ++i) {
533 if (&(*i).track != this) {
537 alist.reset_range ((*i).start, (*i).end);
542 AutomationTimeAxisView::cut_copy_clear_objects (PointSelection& selection, CutCopyOp op)
546 for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
547 ret = cut_copy_clear_objects_one ((**i), selection, op);
554 AutomationTimeAxisView::cut_copy_clear_objects_one (AutomationLine& line, PointSelection& selection, CutCopyOp op)
556 AutomationList* what_we_got = 0;
557 AutomationList& alist (line.the_list());
560 XMLNode &before = alist.get_state();
562 for (PointSelection::iterator i = selection.begin(); i != selection.end(); ++i) {
564 if (&(*i).track != this) {
570 if ((what_we_got = alist.cut ((*i).start, (*i).end)) != 0) {
571 editor.get_cut_buffer().add (what_we_got);
572 _session.add_command (new MementoCommand<AutomationList>(alist, &before, &alist.get_state()));
577 if ((what_we_got = alist.copy ((*i).start, (*i).end)) != 0) {
578 editor.get_cut_buffer().add (what_we_got);
583 if ((what_we_got = alist.cut ((*i).start, (*i).end)) != 0) {
584 _session.add_command (new MementoCommand<AutomationList>(alist, &before, &alist.get_state()));
594 for (AutomationList::iterator x = what_we_got->begin(); x != what_we_got->end(); ++x) {
595 double foo = (*x)->value;
596 line.model_to_view_y (foo);
605 AutomationTimeAxisView::paste (nframes_t pos, float times, Selection& selection, size_t nth)
609 for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
610 ret = paste_one (**i, pos, times, selection, nth);
617 AutomationTimeAxisView::paste_one (AutomationLine& line, nframes_t pos, float times, Selection& selection, size_t nth)
619 AutomationSelection::iterator p;
620 AutomationList& alist (line.the_list());
622 for (p = selection.lines.begin(); p != selection.lines.end() && nth; ++p, --nth);
624 if (p == selection.lines.end()) {
628 /* Make a copy of the list because we have to scale the
629 values from view coordinates to model coordinates, and we're
630 not supposed to modify the points in the selection.
633 AutomationList copy (**p);
635 for (AutomationList::iterator x = copy.begin(); x != copy.end(); ++x) {
636 double foo = (*x)->value;
637 line.view_to_model_y (foo);
641 XMLNode &before = alist.get_state();
642 alist.paste (copy, pos, times);
643 _session.add_command (new MementoCommand<AutomationList>(alist, &before, &alist.get_state()));
649 AutomationTimeAxisView::add_ghost (GhostRegion* gr)
651 ghosts.push_back (gr);
652 gr->GoingAway.connect (mem_fun(*this, &AutomationTimeAxisView::remove_ghost));
656 AutomationTimeAxisView::remove_ghost (GhostRegion* gr)
662 list<GhostRegion*>::iterator i;
664 for (i = ghosts.begin(); i != ghosts.end(); ++i) {
673 AutomationTimeAxisView::get_selectables (nframes_t start, nframes_t end, double top, double bot, list<Selectable*>& results)
675 if (!lines.empty() && touched (top, bot)) {
679 /* remember: this is X Window - coordinate space starts in upper left and moves down.
680 y_position is the "origin" or "top" of the track.
683 double mybot = y_position + height; // XXX need to include Editor::track_spacing;
685 if (y_position >= top && mybot <= bot) {
687 /* y_position is below top, mybot is above bot, so we're fully
696 /* top and bot are within y_position .. mybot */
698 topfrac = 1.0 - ((top - y_position) / height);
699 botfrac = 1.0 - ((bot - y_position) / height);
702 for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
703 (*i)->get_selectables (start, end, botfrac, topfrac, results);
709 AutomationTimeAxisView::get_inverted_selectables (Selection& sel, list<Selectable*>& result)
711 for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
712 (*i)->get_inverted_selectables (sel, result);
717 AutomationTimeAxisView::set_selected_points (PointSelection& points)
719 for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
720 (*i)->set_selected_points (points);
725 AutomationTimeAxisView::clear_lines ()
727 for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
732 automation_connection.disconnect ();
736 AutomationTimeAxisView::add_line (AutomationLine& line)
741 /* first line is the Model for automation state */
742 automation_connection = line.the_list().automation_state_changed.connect
743 (mem_fun(*this, &AutomationTimeAxisView::automation_state_changed));
747 lines.push_back (&line);
748 line.set_height (height);
751 /* pick up the current state */
752 automation_state_changed ();
757 AutomationTimeAxisView::show_all_control_points ()
759 for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
760 (*i)->show_all_control_points ();
765 AutomationTimeAxisView::hide_all_but_selected_control_points ()
767 for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
768 (*i)->hide_all_but_selected_control_points ();
773 AutomationTimeAxisView::entered()
775 show_all_control_points ();
779 AutomationTimeAxisView::exited ()
781 hide_all_but_selected_control_points ();
785 AutomationTimeAxisView::set_state (const XMLNode& node)
787 TimeAxisView::set_state (node);
791 AutomationTimeAxisView::get_state_node ()
793 TimeAxisView* state_parent = get_parent_with_state ();
796 return state_parent->get_child_xml_node (_state_name);