2 Copyright (C) 2000-2007 Paul Davis
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20 #include <ardour/route.h>
21 #include <pbd/memento_command.h>
23 #include "ardour_ui.h"
24 #include "automation_time_axis.h"
25 #include "automation_line.h"
26 #include "public_editor.h"
27 #include "simplerect.h"
28 #include "selection.h"
29 #include "ghostregion.h"
30 #include "rgb_macros.h"
31 #include "automation_selectable.h"
32 #include "point_selection.h"
33 #include "canvas_impl.h"
38 using namespace ARDOUR;
41 using namespace Editing;
43 Pango::FontDescription AutomationTimeAxisView::name_font;
44 bool AutomationTimeAxisView::have_name_font = false;
46 AutomationTimeAxisView::AutomationTimeAxisView (Session& s, boost::shared_ptr<Route> r, PublicEditor& e, TimeAxisView& rent,
47 ArdourCanvas::Canvas& canvas, const string & nom,
48 const string & state_name, const string & nomparent)
51 TimeAxisView (s, e, &rent, canvas),
54 _state_name (state_name),
55 height_button (_("h")),
56 clear_button (_("clear")),
57 auto_button (X_("")) /* force addition of a label */
59 if (!have_name_font) {
60 name_font = get_font_for_style (X_("AutomationTrackName"));
61 have_name_font = true;
65 in_destructor = false;
70 ignore_state_request = false;
71 first_call_to_set_height = true;
73 base_rect = new SimpleRect(*canvas_display);
74 base_rect->property_x1() = 0.0;
75 base_rect->property_y1() = 0.0;
76 base_rect->property_x2() = max_frames;
77 base_rect->property_outline_color_rgba() = color_map[cAutomationTrackOutline];
78 /* outline ends and bottom */
79 base_rect->property_outline_what() = (guint32) (0x1|0x2|0x8);
80 base_rect->property_fill_color_rgba() = color_map[cAutomationTrackFill];
81 //base_rect->property_fill_color_rgba() = color_map[cEnteredControlPoint];
83 base_rect->set_data ("trackview", this);
85 base_rect->signal_event().connect (bind (mem_fun (editor, &PublicEditor::canvas_automation_track_event),
88 hide_button.add (*(manage (new Gtk::Image (::get_icon("hide")))));
90 height_button.set_name ("TrackSizeButton");
91 auto_button.set_name ("TrackVisualButton");
92 clear_button.set_name ("TrackVisualButton");
93 hide_button.set_name ("TrackRemoveButton");
95 controls_table.set_no_show_all();
97 ARDOUR_UI::instance()->tooltips().set_tip(height_button, _("track height"));
98 ARDOUR_UI::instance()->tooltips().set_tip(auto_button, _("automation state"));
99 ARDOUR_UI::instance()->tooltips().set_tip(clear_button, _("clear track"));
100 ARDOUR_UI::instance()->tooltips().set_tip(hide_button, _("hide track"));
102 /* rearrange the name display */
104 /* we never show these for automation tracks, so make
105 life easier and remove them.
110 /* move the name label over a bit */
112 string shortpname = _name;
113 bool shortened = false;
116 shortpname = fit_to_pixels (_name, 60, name_font, ignore_width, true);
118 if (shortpname != _name ){
122 name_label.set_text (shortpname);
123 name_label.set_alignment (Gtk::ALIGN_CENTER, Gtk::ALIGN_CENTER);
125 if (nomparent.length()) {
127 /* limit the plug name string */
129 string pname = fit_to_pixels (nomparent, 60, name_font, ignore_width, true);
130 if (pname != nomparent) {
134 plugname = new Label (pname);
135 plugname->set_name (X_("TrackPlugName"));
137 name_label.set_name (X_("TrackParameterName"));
138 controls_table.remove (name_hbox);
139 controls_table.attach (*plugname, 1, 5, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
140 plugname_packed = true;
141 controls_table.attach (name_hbox, 1, 5, 1, 2, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
144 plugname_packed = false;
148 string tipname = nomparent;
149 if (!tipname.empty()) {
153 ARDOUR_UI::instance()->tooltips().set_tip(controls_ebox, tipname);
156 /* add the buttons */
157 controls_table.attach (hide_button, 0, 1, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
158 controls_table.attach (height_button, 0, 1, 1, 2, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
160 controls_table.attach (auto_button, 5, 8, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
161 controls_table.attach (clear_button, 5, 8, 1, 2, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
163 controls_table.show_all ();
165 height_button.signal_clicked().connect (mem_fun(*this, &AutomationTimeAxisView::height_clicked));
166 clear_button.signal_clicked().connect (mem_fun(*this, &AutomationTimeAxisView::clear_clicked));
167 hide_button.signal_clicked().connect (mem_fun(*this, &AutomationTimeAxisView::hide_clicked));
168 auto_button.signal_clicked().connect (mem_fun(*this, &AutomationTimeAxisView::auto_clicked));
170 controls_base_selected_name = X_("AutomationTrackControlsBaseSelected");
171 controls_base_unselected_name = X_("AutomationTrackControlsBase");
172 controls_ebox.set_name (controls_base_unselected_name);
174 controls_frame.set_shadow_type (Gtk::SHADOW_ETCHED_OUT);
176 XMLNode* xml_node = get_parent_with_state()->get_child_xml_node (_state_name);
179 set_state (*xml_node);
182 /* make sure labels etc. are correct */
184 automation_state_changed ();
185 ColorChanged.connect (mem_fun (*this, &AutomationTimeAxisView::color_handler));
188 AutomationTimeAxisView::~AutomationTimeAxisView ()
190 in_destructor = true;
192 for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
198 AutomationTimeAxisView::auto_clicked ()
200 using namespace Menu_Helpers;
202 if (automation_menu == 0) {
203 automation_menu = manage (new Menu);
204 automation_menu->set_name ("ArdourContextMenu");
205 MenuList& items (automation_menu->items());
207 items.push_back (MenuElem (_("Manual"),
208 bind (mem_fun(*this, &AutomationTimeAxisView::set_automation_state), (AutoState) Off)));
209 items.push_back (MenuElem (_("Play"),
210 bind (mem_fun(*this, &AutomationTimeAxisView::set_automation_state), (AutoState) Play)));
211 items.push_back (MenuElem (_("Write"),
212 bind (mem_fun(*this, &AutomationTimeAxisView::set_automation_state), (AutoState) Write)));
213 items.push_back (MenuElem (_("Touch"),
214 bind (mem_fun(*this, &AutomationTimeAxisView::set_automation_state), (AutoState) Touch)));
217 automation_menu->popup (1, gtk_get_current_event_time());
222 AutomationTimeAxisView::automation_state_changed ()
226 /* update button label */
231 state = lines.front()->the_list().automation_state ();
234 switch (state & (Off|Play|Touch|Write)) {
236 auto_button.set_label (_("Manual"));
238 ignore_state_request = true;
239 auto_off_item->set_active (true);
240 auto_play_item->set_active (false);
241 auto_touch_item->set_active (false);
242 auto_write_item->set_active (false);
243 ignore_state_request = false;
247 auto_button.set_label (_("Play"));
248 if (auto_play_item) {
249 ignore_state_request = true;
250 auto_play_item->set_active (true);
251 auto_off_item->set_active (false);
252 auto_touch_item->set_active (false);
253 auto_write_item->set_active (false);
254 ignore_state_request = false;
258 auto_button.set_label (_("Write"));
259 if (auto_write_item) {
260 ignore_state_request = true;
261 auto_write_item->set_active (true);
262 auto_off_item->set_active (false);
263 auto_play_item->set_active (false);
264 auto_touch_item->set_active (false);
265 ignore_state_request = false;
269 auto_button.set_label (_("Touch"));
270 if (auto_touch_item) {
271 ignore_state_request = true;
272 auto_touch_item->set_active (true);
273 auto_off_item->set_active (false);
274 auto_play_item->set_active (false);
275 auto_write_item->set_active (false);
276 ignore_state_request = false;
280 auto_button.set_label (_("???"));
286 AutomationTimeAxisView::height_clicked ()
292 AutomationTimeAxisView::clear_clicked ()
294 _session.begin_reversible_command (_("clear automation"));
295 for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
298 _session.commit_reversible_command ();
302 AutomationTimeAxisView::set_height (TrackHeight ht)
304 uint32_t h = height_to_pixels (ht);
305 bool changed = (height != (uint32_t) h);
307 bool changed_between_small_and_normal = ( (ht == Small || ht == Smaller) ^ (height_style == Small || height_style == Smaller) );
309 TimeAxisView* state_parent = get_parent_with_state ();
310 XMLNode* xml_node = state_parent->get_child_xml_node (_state_name);
312 TimeAxisView::set_height (ht);
313 base_rect->property_y2() = h;
315 for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
316 (*i)->set_height (h);
319 for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
326 xml_node->add_property ("track_height", "largest");
330 xml_node->add_property ("track_height", "large");
334 xml_node->add_property ("track_height", "larger");
338 xml_node->add_property ("track_height", "normal");
342 xml_node->add_property ("track_height", "smaller");
346 xml_node->add_property ("track_height", "small");
350 if (changed_between_small_and_normal || first_call_to_set_height) {
351 first_call_to_set_height = false;
358 controls_table.remove (name_hbox);
361 if (plugname_packed) {
362 controls_table.remove (*plugname);
363 plugname_packed = false;
365 controls_table.attach (*plugname, 1, 5, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
366 plugname_packed = true;
367 controls_table.attach (name_hbox, 1, 5, 1, 2, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
369 controls_table.attach (name_hbox, 1, 5, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
373 name_hbox.show_all ();
376 height_button.show();
378 hide_button.show_all();
384 controls_table.remove (name_hbox);
386 if (plugname_packed) {
387 controls_table.remove (*plugname);
388 plugname_packed = false;
391 controls_table.attach (name_hbox, 1, 5, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
392 controls_table.hide_all ();
395 name_hbox.show_all ();
398 height_button.hide();
406 /* only emit the signal if the height really changed */
407 route->gui_changed ("track_height", (void *) 0); /* EMIT_SIGNAL */
412 AutomationTimeAxisView::set_samples_per_unit (double spu)
414 TimeAxisView::set_samples_per_unit (editor.get_current_zoom());
416 for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
422 AutomationTimeAxisView::hide_clicked ()
424 // LAME fix for refreshing the hide button
425 hide_button.set_sensitive(false);
427 set_marked_for_display (false);
430 hide_button.set_sensitive(true);
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 = alist.get_state();
507 if ((what_we_got = alist.cut (selection.time.front().start, selection.time.front().end)) != 0) {
508 editor.get_cut_buffer().add (what_we_got);
509 _session.add_command(new MementoCommand<AutomationList>(alist, &before, &alist.get_state()));
514 if ((what_we_got = alist.copy (selection.time.front().start, selection.time.front().end)) != 0) {
515 editor.get_cut_buffer().add (what_we_got);
520 if ((what_we_got = alist.cut (selection.time.front().start, selection.time.front().end)) != 0) {
521 _session.add_command(new MementoCommand<AutomationList>(alist, &before, &alist.get_state()));
530 for (AutomationList::iterator x = what_we_got->begin(); x != what_we_got->end(); ++x) {
531 double foo = (*x)->value;
532 line.model_to_view_y (foo);
541 AutomationTimeAxisView::reset_objects (PointSelection& selection)
543 for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
544 reset_objects_one ((**i), selection);
549 AutomationTimeAxisView::reset_objects_one (AutomationLine& line, PointSelection& selection)
551 AutomationList& alist (line.the_list());
553 _session.add_command (new MementoCommand<AutomationList>(alist, &alist.get_state(), 0));
555 for (PointSelection::iterator i = selection.begin(); i != selection.end(); ++i) {
557 if (&(*i).track != this) {
561 alist.reset_range ((*i).start, (*i).end);
566 AutomationTimeAxisView::cut_copy_clear_objects (PointSelection& selection, CutCopyOp op)
570 for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
571 ret = cut_copy_clear_objects_one ((**i), selection, op);
578 AutomationTimeAxisView::cut_copy_clear_objects_one (AutomationLine& line, PointSelection& selection, CutCopyOp op)
580 AutomationList* what_we_got = 0;
581 AutomationList& alist (line.the_list());
584 XMLNode &before = alist.get_state();
586 for (PointSelection::iterator i = selection.begin(); i != selection.end(); ++i) {
588 if (&(*i).track != this) {
594 if ((what_we_got = alist.cut ((*i).start, (*i).end)) != 0) {
595 editor.get_cut_buffer().add (what_we_got);
596 _session.add_command (new MementoCommand<AutomationList>(alist, new XMLNode (before), &alist.get_state()));
601 if ((what_we_got = alist.copy ((*i).start, (*i).end)) != 0) {
602 editor.get_cut_buffer().add (what_we_got);
607 if ((what_we_got = alist.cut ((*i).start, (*i).end)) != 0) {
608 _session.add_command (new MementoCommand<AutomationList>(alist, new XMLNode (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 (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, 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 (new 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 (nframes_t start, 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;
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_colors () {
813 for( list<GhostRegion *>::iterator i=ghosts.begin(); i != ghosts.end(); i++ ) {
817 for( vector<AutomationLine *>::iterator i=lines.begin(); i != lines.end(); i++ ) {
824 AutomationTimeAxisView::color_handler (ColorID id, uint32_t val) {
827 case cGhostTrackWave:
828 case cGhostTrackWaveClip:
829 case cGhostTrackZeroLine:
832 case cControlPointFill:
833 case cControlPointOutline:
834 case cAutomationLine:
847 AutomationTimeAxisView::set_state (const XMLNode& node)
849 TimeAxisView::set_state (node);
853 AutomationTimeAxisView::get_state_node ()
855 TimeAxisView* state_parent = get_parent_with_state ();
858 return state_parent->get_child_xml_node (_state_name);