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 Pango::FontDescription AutomationTimeAxisView::name_font;
25 bool AutomationTimeAxisView::have_name_font = false;
27 AutomationTimeAxisView::AutomationTimeAxisView (Session& s, boost::shared_ptr<Route> r, PublicEditor& e, TimeAxisView& rent,
28 ArdourCanvas::Canvas& canvas, const string & nom,
29 const string & state_name, const string & nomparent)
32 TimeAxisView (s, e, &rent, canvas),
35 _state_name (state_name),
36 height_button (_("h")),
37 clear_button (_("clear")),
38 auto_button (X_("")) /* force addition of a label */
40 if (!have_name_font) {
41 name_font = get_font_for_style (X_("AutomationTrackName"));
42 have_name_font = true;
46 in_destructor = false;
51 ignore_state_request = false;
52 first_call_to_set_height = true;
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() = max_frames;
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];
62 //base_rect->property_fill_color_rgba() = color_map[cEnteredControlPoint];
64 base_rect->set_data ("trackview", this);
66 base_rect->signal_event().connect (bind (mem_fun (editor, &PublicEditor::canvas_automation_track_event),
69 hide_button.add (*(manage (new Gtk::Image (::get_icon("hide")))));
71 height_button.set_name ("TrackSizeButton");
72 auto_button.set_name ("TrackVisualButton");
73 clear_button.set_name ("TrackVisualButton");
74 hide_button.set_name ("TrackRemoveButton");
76 controls_table.set_no_show_all();
78 ARDOUR_UI::instance()->tooltips().set_tip(height_button, _("track height"));
79 ARDOUR_UI::instance()->tooltips().set_tip(auto_button, _("automation state"));
80 ARDOUR_UI::instance()->tooltips().set_tip(clear_button, _("clear track"));
81 ARDOUR_UI::instance()->tooltips().set_tip(hide_button, _("hide track"));
83 /* rearrange the name display */
85 /* we never show these for automation tracks, so make
86 life easier and remove them.
91 /* move the name label over a bit */
93 string shortpname = _name;
94 bool shortened = false;
97 shortpname = fit_to_pixels (_name, 60, name_font, ignore_width, true);
99 if (shortpname != _name ){
103 name_label.set_text (shortpname);
104 name_label.set_alignment (Gtk::ALIGN_CENTER, Gtk::ALIGN_CENTER);
106 if (nomparent.length()) {
108 /* limit the plug name string */
110 string pname = fit_to_pixels (nomparent, 60, name_font, ignore_width, true);
111 if (pname != nomparent) {
115 plugname = new Label (pname);
116 plugname->set_name (X_("TrackPlugName"));
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, 5, 8, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
142 controls_table.attach (clear_button, 5, 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);
160 set_state (*xml_node);
163 /* make sure labels etc. are correct */
165 automation_state_changed ();
166 ColorChanged.connect (mem_fun (*this, &AutomationTimeAxisView::color_handler));
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, gtk_get_current_event_time());
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 // LAME fix for refreshing the hide button
406 hide_button.set_sensitive(false);
408 set_marked_for_display (false);
411 hide_button.set_sensitive(true);
415 AutomationTimeAxisView::build_display_menu ()
417 using namespace Menu_Helpers;
419 /* get the size menu ready */
425 TimeAxisView::build_display_menu ();
427 /* now fill it with our stuff */
429 MenuList& items = display_menu->items();
431 items.push_back (MenuElem (_("Height"), *size_menu));
432 items.push_back (SeparatorElem());
433 items.push_back (MenuElem (_("Hide"), mem_fun(*this, &AutomationTimeAxisView::hide_clicked)));
434 items.push_back (SeparatorElem());
435 items.push_back (MenuElem (_("Clear"), mem_fun(*this, &AutomationTimeAxisView::clear_clicked)));
436 items.push_back (SeparatorElem());
438 Menu* auto_state_menu = manage (new Menu);
439 auto_state_menu->set_name ("ArdourContextMenu");
440 MenuList& as_items = auto_state_menu->items();
442 as_items.push_back (CheckMenuElem (_("Manual"),
443 bind (mem_fun(*this, &AutomationTimeAxisView::set_automation_state), (AutoState) Off)));
444 auto_off_item = dynamic_cast<CheckMenuItem*>(&as_items.back());
446 as_items.push_back (CheckMenuElem (_("Play"),
447 bind (mem_fun(*this, &AutomationTimeAxisView::set_automation_state), (AutoState) Play)));
448 auto_play_item = dynamic_cast<CheckMenuItem*>(&as_items.back());
450 as_items.push_back (CheckMenuElem (_("Write"),
451 bind (mem_fun(*this, &AutomationTimeAxisView::set_automation_state), (AutoState) Write)));
452 auto_write_item = dynamic_cast<CheckMenuItem*>(&as_items.back());
454 as_items.push_back (CheckMenuElem (_("Touch"),
455 bind (mem_fun(*this, &AutomationTimeAxisView::set_automation_state), (AutoState) Touch)));
456 auto_touch_item = dynamic_cast<CheckMenuItem*>(&as_items.back());
458 items.push_back (MenuElem (_("State"), *auto_state_menu));
460 /* make sure the automation menu state is correct */
462 automation_state_changed ();
466 AutomationTimeAxisView::cut_copy_clear (Selection& selection, CutCopyOp op)
470 for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
471 ret = cut_copy_clear_one ((**i), selection, op);
478 AutomationTimeAxisView::cut_copy_clear_one (AutomationLine& line, Selection& selection, CutCopyOp op)
480 AutomationList* what_we_got = 0;
481 AutomationList& alist (line.the_list());
484 XMLNode &before = alist.get_state();
488 if ((what_we_got = alist.cut (selection.time.front().start, selection.time.front().end)) != 0) {
489 editor.get_cut_buffer().add (what_we_got);
490 _session.add_command(new MementoCommand<AutomationList>(alist, &before, &alist.get_state()));
495 if ((what_we_got = alist.copy (selection.time.front().start, selection.time.front().end)) != 0) {
496 editor.get_cut_buffer().add (what_we_got);
501 if ((what_we_got = alist.cut (selection.time.front().start, selection.time.front().end)) != 0) {
502 _session.add_command(new MementoCommand<AutomationList>(alist, &before, &alist.get_state()));
511 for (AutomationList::iterator x = what_we_got->begin(); x != what_we_got->end(); ++x) {
512 double foo = (*x)->value;
513 line.model_to_view_y (foo);
522 AutomationTimeAxisView::reset_objects (PointSelection& selection)
524 for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
525 reset_objects_one ((**i), selection);
530 AutomationTimeAxisView::reset_objects_one (AutomationLine& line, PointSelection& selection)
532 AutomationList& alist (line.the_list());
534 _session.add_command (new MementoCommand<AutomationList>(alist, &alist.get_state(), 0));
536 for (PointSelection::iterator i = selection.begin(); i != selection.end(); ++i) {
538 if (&(*i).track != this) {
542 alist.reset_range ((*i).start, (*i).end);
547 AutomationTimeAxisView::cut_copy_clear_objects (PointSelection& selection, CutCopyOp op)
551 for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
552 ret = cut_copy_clear_objects_one ((**i), selection, op);
559 AutomationTimeAxisView::cut_copy_clear_objects_one (AutomationLine& line, PointSelection& selection, CutCopyOp op)
561 AutomationList* what_we_got = 0;
562 AutomationList& alist (line.the_list());
565 XMLNode &before = alist.get_state();
567 for (PointSelection::iterator i = selection.begin(); i != selection.end(); ++i) {
569 if (&(*i).track != this) {
575 if ((what_we_got = alist.cut ((*i).start, (*i).end)) != 0) {
576 editor.get_cut_buffer().add (what_we_got);
577 _session.add_command (new MementoCommand<AutomationList>(alist, new XMLNode (before), &alist.get_state()));
582 if ((what_we_got = alist.copy ((*i).start, (*i).end)) != 0) {
583 editor.get_cut_buffer().add (what_we_got);
588 if ((what_we_got = alist.cut ((*i).start, (*i).end)) != 0) {
589 _session.add_command (new MementoCommand<AutomationList>(alist, new XMLNode (before), &alist.get_state()));
601 for (AutomationList::iterator x = what_we_got->begin(); x != what_we_got->end(); ++x) {
602 double foo = (*x)->value;
603 line.model_to_view_y (foo);
612 AutomationTimeAxisView::paste (nframes_t pos, float times, Selection& selection, size_t nth)
616 for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
617 ret = paste_one (**i, pos, times, selection, nth);
624 AutomationTimeAxisView::paste_one (AutomationLine& line, nframes_t pos, float times, Selection& selection, size_t nth)
626 AutomationSelection::iterator p;
627 AutomationList& alist (line.the_list());
629 for (p = selection.lines.begin(); p != selection.lines.end() && nth; ++p, --nth);
631 if (p == selection.lines.end()) {
635 /* Make a copy of the list because we have to scale the
636 values from view coordinates to model coordinates, and we're
637 not supposed to modify the points in the selection.
640 AutomationList copy (**p);
642 for (AutomationList::iterator x = copy.begin(); x != copy.end(); ++x) {
643 double foo = (*x)->value;
644 line.view_to_model_y (foo);
648 XMLNode &before = alist.get_state();
649 alist.paste (copy, pos, times);
650 _session.add_command (new MementoCommand<AutomationList>(alist, &before, &alist.get_state()));
656 AutomationTimeAxisView::add_ghost (GhostRegion* gr)
658 ghosts.push_back (gr);
659 gr->GoingAway.connect (mem_fun(*this, &AutomationTimeAxisView::remove_ghost));
663 AutomationTimeAxisView::remove_ghost (GhostRegion* gr)
669 list<GhostRegion*>::iterator i;
671 for (i = ghosts.begin(); i != ghosts.end(); ++i) {
680 AutomationTimeAxisView::get_selectables (nframes_t start, nframes_t end, double top, double bot, list<Selectable*>& results)
682 if (!lines.empty() && touched (top, bot)) {
686 /* remember: this is X Window - coordinate space starts in upper left and moves down.
687 y_position is the "origin" or "top" of the track.
690 double mybot = y_position + height;
692 if (y_position >= top && mybot <= bot) {
694 /* y_position is below top, mybot is above bot, so we're fully
703 /* top and bot are within y_position .. mybot */
705 topfrac = 1.0 - ((top - y_position) / height);
706 botfrac = 1.0 - ((bot - y_position) / height);
709 for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
710 (*i)->get_selectables (start, end, botfrac, topfrac, results);
716 AutomationTimeAxisView::get_inverted_selectables (Selection& sel, list<Selectable*>& result)
718 for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
719 (*i)->get_inverted_selectables (sel, result);
724 AutomationTimeAxisView::set_selected_points (PointSelection& points)
726 for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
727 (*i)->set_selected_points (points);
732 AutomationTimeAxisView::clear_lines ()
734 for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
739 automation_connection.disconnect ();
743 AutomationTimeAxisView::add_line (AutomationLine& line)
748 /* first line is the Model for automation state */
749 automation_connection = line.the_list().automation_state_changed.connect
750 (mem_fun(*this, &AutomationTimeAxisView::automation_state_changed));
754 lines.push_back (&line);
755 line.set_height (height);
758 /* pick up the current state */
759 automation_state_changed ();
764 AutomationTimeAxisView::show_all_control_points ()
766 for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
767 (*i)->show_all_control_points ();
772 AutomationTimeAxisView::hide_all_but_selected_control_points ()
774 for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
775 (*i)->hide_all_but_selected_control_points ();
780 AutomationTimeAxisView::entered()
782 show_all_control_points ();
786 AutomationTimeAxisView::exited ()
788 hide_all_but_selected_control_points ();
792 AutomationTimeAxisView::set_colors () {
794 for( list<GhostRegion *>::iterator i=ghosts.begin(); i != ghosts.end(); i++ ) {
798 for( vector<AutomationLine *>::iterator i=lines.begin(); i != lines.end(); i++ ) {
805 AutomationTimeAxisView::color_handler (ColorID id, uint32_t val) {
808 case cGhostTrackWave:
809 case cGhostTrackWaveClip:
810 case cGhostTrackZeroLine:
813 case cControlPointFill:
814 case cControlPointOutline:
815 case cAutomationLine:
828 AutomationTimeAxisView::set_state (const XMLNode& node)
830 TimeAxisView::set_state (node);
834 AutomationTimeAxisView::get_state_node ()
836 TimeAxisView* state_parent = get_parent_with_state ();
839 return state_parent->get_child_xml_node (_state_name);