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 = 0;
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() = LONG_MAX - 2;
77 base_rect->property_outline_color_rgba() = ARDOUR_UI::config()->canvasvar_AutomationTrackOutline.get();
78 /* outline ends and bottom */
79 base_rect->property_outline_what() = (guint32) (0x1|0x2|0x8);
80 base_rect->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_AutomationTrackFill.get();
81 //base_rect->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_EnteredControlPoint.get();
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 height_button.unset_flags (Gtk::CAN_FOCUS);
96 auto_button.unset_flags (Gtk::CAN_FOCUS);
97 clear_button.unset_flags (Gtk::CAN_FOCUS);
98 hide_button.unset_flags (Gtk::CAN_FOCUS);
100 controls_table.set_no_show_all();
102 ARDOUR_UI::instance()->tooltips().set_tip(height_button, _("track height"));
103 ARDOUR_UI::instance()->tooltips().set_tip(auto_button, _("automation state"));
104 ARDOUR_UI::instance()->tooltips().set_tip(clear_button, _("clear track"));
105 ARDOUR_UI::instance()->tooltips().set_tip(hide_button, _("hide track"));
107 /* rearrange the name display */
109 /* we never show these for automation tracks, so make
110 life easier and remove them.
115 /* move the name label over a bit */
117 string shortpname = _name;
118 bool shortened = false;
121 shortpname = fit_to_pixels (_name, 60, *name_font, ignore_width, true);
123 if (shortpname != _name ){
127 name_label.set_text (shortpname);
128 name_label.set_alignment (Gtk::ALIGN_CENTER, Gtk::ALIGN_CENTER);
130 if (nomparent.length()) {
132 /* limit the plug name string */
134 string pname = fit_to_pixels (nomparent, 60, *name_font, ignore_width, true);
135 if (pname != nomparent) {
139 plugname = new Label (pname);
140 plugname->set_name (X_("TrackPlugName"));
142 name_label.set_name (X_("TrackParameterName"));
143 controls_table.remove (name_hbox);
144 controls_table.attach (*plugname, 1, 5, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
145 plugname_packed = true;
146 controls_table.attach (name_hbox, 1, 5, 1, 2, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
149 plugname_packed = false;
153 string tipname = nomparent;
154 if (!tipname.empty()) {
158 ARDOUR_UI::instance()->tooltips().set_tip(controls_ebox, tipname);
161 /* add the buttons */
162 controls_table.attach (hide_button, 0, 1, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
163 controls_table.attach (height_button, 0, 1, 1, 2, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
165 controls_table.attach (auto_button, 5, 8, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
166 controls_table.attach (clear_button, 5, 8, 1, 2, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
168 controls_table.show_all ();
170 height_button.signal_clicked().connect (mem_fun(*this, &AutomationTimeAxisView::height_clicked));
171 clear_button.signal_clicked().connect (mem_fun(*this, &AutomationTimeAxisView::clear_clicked));
172 hide_button.signal_clicked().connect (mem_fun(*this, &AutomationTimeAxisView::hide_clicked));
173 auto_button.signal_clicked().connect (mem_fun(*this, &AutomationTimeAxisView::auto_clicked));
175 controls_base_selected_name = X_("AutomationTrackControlsBaseSelected");
176 controls_base_unselected_name = X_("AutomationTrackControlsBase");
177 controls_ebox.set_name (controls_base_unselected_name);
179 controls_frame.set_shadow_type (Gtk::SHADOW_ETCHED_OUT);
181 XMLNode* xml_node = get_parent_with_state()->get_child_xml_node (_state_name);
184 set_state (*xml_node);
187 /* make sure labels etc. are correct */
189 automation_state_changed ();
190 ColorsChanged.connect (mem_fun (*this, &AutomationTimeAxisView::color_handler));
193 AutomationTimeAxisView::~AutomationTimeAxisView ()
195 in_destructor = true;
197 for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
203 AutomationTimeAxisView::auto_clicked ()
205 using namespace Menu_Helpers;
207 if (automation_menu == 0) {
208 automation_menu = manage (new Menu);
209 automation_menu->set_name ("ArdourContextMenu");
210 MenuList& items (automation_menu->items());
212 items.push_back (MenuElem (_("Manual"),
213 bind (mem_fun(*this, &AutomationTimeAxisView::set_automation_state), (AutoState) Off)));
214 items.push_back (MenuElem (_("Play"),
215 bind (mem_fun(*this, &AutomationTimeAxisView::set_automation_state), (AutoState) Play)));
216 items.push_back (MenuElem (_("Write"),
217 bind (mem_fun(*this, &AutomationTimeAxisView::set_automation_state), (AutoState) Write)));
218 items.push_back (MenuElem (_("Touch"),
219 bind (mem_fun(*this, &AutomationTimeAxisView::set_automation_state), (AutoState) Touch)));
222 automation_menu->popup (1, gtk_get_current_event_time());
227 AutomationTimeAxisView::automation_state_changed ()
231 /* update button label */
236 state = lines.front()->the_list().automation_state ();
239 switch (state & (Off|Play|Touch|Write)) {
241 auto_button.set_label (_("Manual"));
243 ignore_state_request = true;
244 auto_off_item->set_active (true);
245 auto_play_item->set_active (false);
246 auto_touch_item->set_active (false);
247 auto_write_item->set_active (false);
248 ignore_state_request = false;
252 auto_button.set_label (_("Play"));
253 if (auto_play_item) {
254 ignore_state_request = true;
255 auto_play_item->set_active (true);
256 auto_off_item->set_active (false);
257 auto_touch_item->set_active (false);
258 auto_write_item->set_active (false);
259 ignore_state_request = false;
263 auto_button.set_label (_("Write"));
264 if (auto_write_item) {
265 ignore_state_request = true;
266 auto_write_item->set_active (true);
267 auto_off_item->set_active (false);
268 auto_play_item->set_active (false);
269 auto_touch_item->set_active (false);
270 ignore_state_request = false;
274 auto_button.set_label (_("Touch"));
275 if (auto_touch_item) {
276 ignore_state_request = true;
277 auto_touch_item->set_active (true);
278 auto_off_item->set_active (false);
279 auto_play_item->set_active (false);
280 auto_write_item->set_active (false);
281 ignore_state_request = false;
285 auto_button.set_label (_("???"));
291 AutomationTimeAxisView::height_clicked ()
297 AutomationTimeAxisView::clear_clicked ()
299 _session.begin_reversible_command (_("clear automation"));
300 for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
303 _session.commit_reversible_command ();
307 AutomationTimeAxisView::set_height (uint32_t h)
309 bool changed = (height != (uint32_t) h);
310 bool changed_between_small_and_normal = ( (h == hSmall || h == hSmaller) ^ (height == hSmall || height == hSmaller) );
312 TimeAxisView* state_parent = get_parent_with_state ();
313 XMLNode* xml_node = (state_parent ? state_parent->get_child_xml_node (_state_name) : NULL);
315 TimeAxisView::set_height (h);
316 base_rect->property_y2() = h;
318 for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
319 (*i)->set_height (h);
322 for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
327 snprintf (buf, sizeof (buf), "%u", height);
329 xml_node->add_property ("height", buf);
332 if (changed_between_small_and_normal || first_call_to_set_height) {
333 first_call_to_set_height = false;
336 controls_table.remove (name_hbox);
339 if (plugname_packed) {
340 controls_table.remove (*plugname);
341 plugname_packed = false;
343 controls_table.attach (*plugname, 1, 5, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
344 plugname_packed = true;
345 controls_table.attach (name_hbox, 1, 5, 1, 2, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
347 controls_table.attach (name_hbox, 1, 5, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
351 name_hbox.show_all ();
354 height_button.show();
356 hide_button.show_all();
358 } else if (h >= hSmall) {
359 controls_table.remove (name_hbox);
361 if (plugname_packed) {
362 controls_table.remove (*plugname);
363 plugname_packed = false;
366 controls_table.attach (name_hbox, 1, 5, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
367 controls_table.hide_all ();
370 name_hbox.show_all ();
373 height_button.hide();
377 } else if (h >= hNormal){
379 height_button.show();
381 hide_button.show_all();
385 /* only emit the signal if the height really changed */
386 route->gui_changed ("visible_tracks", (void *) 0); /* EMIT_SIGNAL */
391 AutomationTimeAxisView::set_samples_per_unit (double spu)
393 TimeAxisView::set_samples_per_unit (editor.get_current_zoom());
395 for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
401 AutomationTimeAxisView::hide_clicked ()
403 // LAME fix for refreshing the hide button
404 hide_button.set_sensitive(false);
406 set_marked_for_display (false);
409 hide_button.set_sensitive(true);
413 AutomationTimeAxisView::build_display_menu ()
415 using namespace Menu_Helpers;
417 /* get the size menu ready */
423 TimeAxisView::build_display_menu ();
425 /* now fill it with our stuff */
427 MenuList& items = display_menu->items();
429 items.push_back (MenuElem (_("Height"), *size_menu));
430 items.push_back (SeparatorElem());
431 items.push_back (MenuElem (_("Hide"), mem_fun(*this, &AutomationTimeAxisView::hide_clicked)));
432 items.push_back (SeparatorElem());
433 items.push_back (MenuElem (_("Clear"), mem_fun(*this, &AutomationTimeAxisView::clear_clicked)));
434 items.push_back (SeparatorElem());
436 Menu* auto_state_menu = manage (new Menu);
437 auto_state_menu->set_name ("ArdourContextMenu");
438 MenuList& as_items = auto_state_menu->items();
440 as_items.push_back (CheckMenuElem (_("Manual"),
441 bind (mem_fun(*this, &AutomationTimeAxisView::set_automation_state), (AutoState) Off)));
442 auto_off_item = dynamic_cast<CheckMenuItem*>(&as_items.back());
444 as_items.push_back (CheckMenuElem (_("Play"),
445 bind (mem_fun(*this, &AutomationTimeAxisView::set_automation_state), (AutoState) Play)));
446 auto_play_item = dynamic_cast<CheckMenuItem*>(&as_items.back());
448 as_items.push_back (CheckMenuElem (_("Write"),
449 bind (mem_fun(*this, &AutomationTimeAxisView::set_automation_state), (AutoState) Write)));
450 auto_write_item = dynamic_cast<CheckMenuItem*>(&as_items.back());
452 as_items.push_back (CheckMenuElem (_("Touch"),
453 bind (mem_fun(*this, &AutomationTimeAxisView::set_automation_state), (AutoState) Touch)));
454 auto_touch_item = dynamic_cast<CheckMenuItem*>(&as_items.back());
456 items.push_back (MenuElem (_("State"), *auto_state_menu));
458 /* make sure the automation menu state is correct */
460 automation_state_changed ();
464 AutomationTimeAxisView::cut_copy_clear (Selection& selection, CutCopyOp op)
468 for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
469 ret = cut_copy_clear_one ((**i), selection, op);
476 AutomationTimeAxisView::cut_copy_clear_one (AutomationLine& line, Selection& selection, CutCopyOp op)
478 AutomationList* what_we_got = 0;
479 AutomationList& alist (line.the_list());
482 XMLNode &before = alist.get_state();
486 if ((what_we_got = alist.cut (selection.time.front().start, selection.time.front().end)) != 0) {
487 editor.get_cut_buffer().add (what_we_got);
488 _session.add_command(new MementoCommand<AutomationList>(alist, &before, &alist.get_state()));
493 if ((what_we_got = alist.copy (selection.time.front().start, selection.time.front().end)) != 0) {
494 editor.get_cut_buffer().add (what_we_got);
499 if ((what_we_got = alist.cut (selection.time.front().start, selection.time.front().end)) != 0) {
500 _session.add_command(new MementoCommand<AutomationList>(alist, &before, &alist.get_state()));
509 for (AutomationList::iterator x = what_we_got->begin(); x != what_we_got->end(); ++x) {
510 double foo = (*x)->value;
511 line.model_to_view_y (foo);
520 AutomationTimeAxisView::reset_objects (PointSelection& selection)
522 for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
523 reset_objects_one ((**i), selection);
528 AutomationTimeAxisView::reset_objects_one (AutomationLine& line, PointSelection& selection)
530 AutomationList& alist (line.the_list());
532 _session.add_command (new MementoCommand<AutomationList>(alist, &alist.get_state(), 0));
534 for (PointSelection::iterator i = selection.begin(); i != selection.end(); ++i) {
536 if (&(*i).track != this) {
540 alist.reset_range ((*i).start, (*i).end);
545 AutomationTimeAxisView::cut_copy_clear_objects (PointSelection& selection, CutCopyOp op)
549 for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
550 ret = cut_copy_clear_objects_one ((**i), selection, op);
557 AutomationTimeAxisView::cut_copy_clear_objects_one (AutomationLine& line, PointSelection& selection, CutCopyOp op)
559 AutomationList* what_we_got = 0;
560 AutomationList& alist (line.the_list());
563 XMLNode &before = alist.get_state();
565 for (PointSelection::iterator i = selection.begin(); i != selection.end(); ++i) {
567 if (&(*i).track != this) {
573 if ((what_we_got = alist.cut ((*i).start, (*i).end)) != 0) {
574 editor.get_cut_buffer().add (what_we_got);
575 _session.add_command (new MementoCommand<AutomationList>(alist, new XMLNode (before), &alist.get_state()));
580 if ((what_we_got = alist.copy ((*i).start, (*i).end)) != 0) {
581 editor.get_cut_buffer().add (what_we_got);
586 if ((what_we_got = alist.cut ((*i).start, (*i).end)) != 0) {
587 _session.add_command (new MementoCommand<AutomationList>(alist, new XMLNode (before), &alist.get_state()));
599 for (AutomationList::iterator x = what_we_got->begin(); x != what_we_got->end(); ++x) {
600 double foo = (*x)->value;
601 line.model_to_view_y (foo);
610 AutomationTimeAxisView::paste (nframes_t pos, float times, Selection& selection, size_t nth)
614 for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
615 ret = paste_one (**i, pos, times, selection, nth);
622 AutomationTimeAxisView::paste_one (AutomationLine& line, nframes_t pos, float times, Selection& selection, size_t nth)
624 AutomationSelection::iterator p;
625 AutomationList& alist (line.the_list());
627 for (p = selection.lines.begin(); p != selection.lines.end() && nth; ++p, --nth);
629 if (p == selection.lines.end()) {
633 /* Make a copy of the list because we have to scale the
634 values from view coordinates to model coordinates, and we're
635 not supposed to modify the points in the selection.
638 AutomationList copy (**p);
640 for (AutomationList::iterator x = copy.begin(); x != copy.end(); ++x) {
641 double foo = (*x)->value;
642 line.view_to_model_y (foo);
646 XMLNode &before = alist.get_state();
647 alist.paste (copy, pos, times);
648 _session.add_command (new MementoCommand<AutomationList>(alist, &before, &alist.get_state()));
654 AutomationTimeAxisView::add_ghost (GhostRegion* gr)
656 ghosts.push_back (gr);
657 gr->GoingAway.connect (mem_fun(*this, &AutomationTimeAxisView::remove_ghost));
661 AutomationTimeAxisView::remove_ghost (GhostRegion* gr)
667 list<GhostRegion*>::iterator i;
669 for (i = ghosts.begin(); i != ghosts.end(); ++i) {
678 AutomationTimeAxisView::get_selectables (nframes_t start, nframes_t end, double top, double bot, list<Selectable*>& results)
680 if (!lines.empty() && touched (top, bot)) {
684 /* remember: this is X Window - coordinate space starts in upper left and moves down.
685 y_position is the "origin" or "top" of the track.
688 double mybot = y_position + height;
690 if (y_position >= top && mybot <= bot) {
692 /* y_position is below top, mybot is above bot, so we're fully
701 /* top and bot are within y_position .. mybot */
703 topfrac = 1.0 - ((top - y_position) / height);
704 botfrac = 1.0 - ((bot - y_position) / height);
707 for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
708 (*i)->get_selectables (start, end, botfrac, topfrac, results);
714 AutomationTimeAxisView::get_inverted_selectables (Selection& sel, list<Selectable*>& result)
716 for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
717 (*i)->get_inverted_selectables (sel, result);
722 AutomationTimeAxisView::set_selected_points (PointSelection& points)
724 for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
725 (*i)->set_selected_points (points);
730 AutomationTimeAxisView::clear_lines ()
732 for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
737 automation_connection.disconnect ();
741 AutomationTimeAxisView::add_line (AutomationLine& line)
746 /* first line is the Model for automation state */
747 automation_connection = line.the_list().automation_state_changed.connect
748 (mem_fun(*this, &AutomationTimeAxisView::automation_state_changed));
752 lines.push_back (&line);
753 line.set_height (height);
756 /* pick up the current state */
757 automation_state_changed ();
762 AutomationTimeAxisView::show_all_control_points ()
764 for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
765 (*i)->show_all_control_points ();
770 AutomationTimeAxisView::hide_all_but_selected_control_points ()
772 for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
773 (*i)->hide_all_but_selected_control_points ();
778 AutomationTimeAxisView::entered()
780 show_all_control_points ();
784 AutomationTimeAxisView::exited ()
786 hide_all_but_selected_control_points ();
790 AutomationTimeAxisView::set_colors () {
792 for( list<GhostRegion *>::iterator i=ghosts.begin(); i != ghosts.end(); i++ ) {
796 for( vector<AutomationLine *>::iterator i=lines.begin(); i != lines.end(); i++ ) {
803 AutomationTimeAxisView::color_handler ()
811 AutomationTimeAxisView::set_state (const XMLNode& node)
813 return TimeAxisView::set_state (node);
817 AutomationTimeAxisView::get_state_node ()
819 TimeAxisView* state_parent = get_parent_with_state ();
822 return state_parent->get_child_xml_node (_state_name);