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() = editor.frame_to_pixel (max_frames);
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 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 ColorsChanged.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 (uint32_t h)
304 bool changed = (height != (uint32_t) h);
305 bool changed_between_small_and_normal = ( (h == hSmall || h == hSmaller) ^ (height == hSmall || height == hSmaller) );
307 TimeAxisView* state_parent = get_parent_with_state ();
308 XMLNode* xml_node = state_parent->get_child_xml_node (_state_name);
310 TimeAxisView::set_height (h);
311 base_rect->property_y2() = h;
313 for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
314 (*i)->set_height (h);
317 for (list<GhostRegion*>::iterator i = ghosts.begin(); i != ghosts.end(); ++i) {
322 snprintf (buf, sizeof (buf), "%u", height);
323 xml_node->add_property ("height", buf);
325 if (changed_between_small_and_normal || first_call_to_set_height) {
326 first_call_to_set_height = false;
329 controls_table.remove (name_hbox);
332 if (plugname_packed) {
333 controls_table.remove (*plugname);
334 plugname_packed = false;
336 controls_table.attach (*plugname, 1, 5, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
337 plugname_packed = true;
338 controls_table.attach (name_hbox, 1, 5, 1, 2, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
340 controls_table.attach (name_hbox, 1, 5, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
344 name_hbox.show_all ();
347 height_button.show();
349 hide_button.show_all();
351 } else if (h >= hSmall) {
352 controls_table.remove (name_hbox);
354 if (plugname_packed) {
355 controls_table.remove (*plugname);
356 plugname_packed = false;
359 controls_table.attach (name_hbox, 1, 5, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
360 controls_table.hide_all ();
363 name_hbox.show_all ();
366 height_button.hide();
373 /* only emit the signal if the height really changed */
374 route->gui_changed ("visible_tracks", (void *) 0); /* EMIT_SIGNAL */
379 AutomationTimeAxisView::set_samples_per_unit (double spu)
381 TimeAxisView::set_samples_per_unit (editor.get_current_zoom());
383 for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
389 AutomationTimeAxisView::hide_clicked ()
391 // LAME fix for refreshing the hide button
392 hide_button.set_sensitive(false);
394 set_marked_for_display (false);
397 hide_button.set_sensitive(true);
401 AutomationTimeAxisView::build_display_menu ()
403 using namespace Menu_Helpers;
405 /* get the size menu ready */
411 TimeAxisView::build_display_menu ();
413 /* now fill it with our stuff */
415 MenuList& items = display_menu->items();
417 items.push_back (MenuElem (_("Height"), *size_menu));
418 items.push_back (SeparatorElem());
419 items.push_back (MenuElem (_("Hide"), mem_fun(*this, &AutomationTimeAxisView::hide_clicked)));
420 items.push_back (SeparatorElem());
421 items.push_back (MenuElem (_("Clear"), mem_fun(*this, &AutomationTimeAxisView::clear_clicked)));
422 items.push_back (SeparatorElem());
424 Menu* auto_state_menu = manage (new Menu);
425 auto_state_menu->set_name ("ArdourContextMenu");
426 MenuList& as_items = auto_state_menu->items();
428 as_items.push_back (CheckMenuElem (_("Manual"),
429 bind (mem_fun(*this, &AutomationTimeAxisView::set_automation_state), (AutoState) Off)));
430 auto_off_item = dynamic_cast<CheckMenuItem*>(&as_items.back());
432 as_items.push_back (CheckMenuElem (_("Play"),
433 bind (mem_fun(*this, &AutomationTimeAxisView::set_automation_state), (AutoState) Play)));
434 auto_play_item = dynamic_cast<CheckMenuItem*>(&as_items.back());
436 as_items.push_back (CheckMenuElem (_("Write"),
437 bind (mem_fun(*this, &AutomationTimeAxisView::set_automation_state), (AutoState) Write)));
438 auto_write_item = dynamic_cast<CheckMenuItem*>(&as_items.back());
440 as_items.push_back (CheckMenuElem (_("Touch"),
441 bind (mem_fun(*this, &AutomationTimeAxisView::set_automation_state), (AutoState) Touch)));
442 auto_touch_item = dynamic_cast<CheckMenuItem*>(&as_items.back());
444 items.push_back (MenuElem (_("State"), *auto_state_menu));
446 /* make sure the automation menu state is correct */
448 automation_state_changed ();
452 AutomationTimeAxisView::cut_copy_clear (Selection& selection, CutCopyOp op)
456 for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
457 ret = cut_copy_clear_one ((**i), selection, op);
464 AutomationTimeAxisView::cut_copy_clear_one (AutomationLine& line, Selection& selection, CutCopyOp op)
466 AutomationList* what_we_got = 0;
467 AutomationList& alist (line.the_list());
470 XMLNode &before = alist.get_state();
474 if ((what_we_got = alist.cut (selection.time.front().start, selection.time.front().end)) != 0) {
475 editor.get_cut_buffer().add (what_we_got);
476 _session.add_command(new MementoCommand<AutomationList>(alist, &before, &alist.get_state()));
481 if ((what_we_got = alist.copy (selection.time.front().start, selection.time.front().end)) != 0) {
482 editor.get_cut_buffer().add (what_we_got);
487 if ((what_we_got = alist.cut (selection.time.front().start, selection.time.front().end)) != 0) {
488 _session.add_command(new MementoCommand<AutomationList>(alist, &before, &alist.get_state()));
497 for (AutomationList::iterator x = what_we_got->begin(); x != what_we_got->end(); ++x) {
498 double foo = (*x)->value;
499 line.model_to_view_y (foo);
508 AutomationTimeAxisView::reset_objects (PointSelection& selection)
510 for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
511 reset_objects_one ((**i), selection);
516 AutomationTimeAxisView::reset_objects_one (AutomationLine& line, PointSelection& selection)
518 AutomationList& alist (line.the_list());
520 _session.add_command (new MementoCommand<AutomationList>(alist, &alist.get_state(), 0));
522 for (PointSelection::iterator i = selection.begin(); i != selection.end(); ++i) {
524 if (&(*i).track != this) {
528 alist.reset_range ((*i).start, (*i).end);
533 AutomationTimeAxisView::cut_copy_clear_objects (PointSelection& selection, CutCopyOp op)
537 for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
538 ret = cut_copy_clear_objects_one ((**i), selection, op);
545 AutomationTimeAxisView::cut_copy_clear_objects_one (AutomationLine& line, PointSelection& selection, CutCopyOp op)
547 AutomationList* what_we_got = 0;
548 AutomationList& alist (line.the_list());
551 XMLNode &before = alist.get_state();
553 for (PointSelection::iterator i = selection.begin(); i != selection.end(); ++i) {
555 if (&(*i).track != this) {
561 if ((what_we_got = alist.cut ((*i).start, (*i).end)) != 0) {
562 editor.get_cut_buffer().add (what_we_got);
563 _session.add_command (new MementoCommand<AutomationList>(alist, new XMLNode (before), &alist.get_state()));
568 if ((what_we_got = alist.copy ((*i).start, (*i).end)) != 0) {
569 editor.get_cut_buffer().add (what_we_got);
574 if ((what_we_got = alist.cut ((*i).start, (*i).end)) != 0) {
575 _session.add_command (new MementoCommand<AutomationList>(alist, new XMLNode (before), &alist.get_state()));
587 for (AutomationList::iterator x = what_we_got->begin(); x != what_we_got->end(); ++x) {
588 double foo = (*x)->value;
589 line.model_to_view_y (foo);
598 AutomationTimeAxisView::paste (nframes_t pos, float times, Selection& selection, size_t nth)
602 for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
603 ret = paste_one (**i, pos, times, selection, nth);
610 AutomationTimeAxisView::paste_one (AutomationLine& line, nframes_t pos, float times, Selection& selection, size_t nth)
612 AutomationSelection::iterator p;
613 AutomationList& alist (line.the_list());
615 for (p = selection.lines.begin(); p != selection.lines.end() && nth; ++p, --nth);
617 if (p == selection.lines.end()) {
621 /* Make a copy of the list because we have to scale the
622 values from view coordinates to model coordinates, and we're
623 not supposed to modify the points in the selection.
626 AutomationList copy (**p);
628 for (AutomationList::iterator x = copy.begin(); x != copy.end(); ++x) {
629 double foo = (*x)->value;
630 line.view_to_model_y (foo);
634 XMLNode &before = alist.get_state();
635 alist.paste (copy, pos, times);
636 _session.add_command (new MementoCommand<AutomationList>(alist, &before, &alist.get_state()));
642 AutomationTimeAxisView::add_ghost (GhostRegion* gr)
644 ghosts.push_back (gr);
645 gr->GoingAway.connect (mem_fun(*this, &AutomationTimeAxisView::remove_ghost));
649 AutomationTimeAxisView::remove_ghost (GhostRegion* gr)
655 list<GhostRegion*>::iterator i;
657 for (i = ghosts.begin(); i != ghosts.end(); ++i) {
666 AutomationTimeAxisView::get_selectables (nframes_t start, nframes_t end, double top, double bot, list<Selectable*>& results)
668 if (!lines.empty() && touched (top, bot)) {
672 /* remember: this is X Window - coordinate space starts in upper left and moves down.
673 y_position is the "origin" or "top" of the track.
676 double mybot = y_position + height;
678 if (y_position >= top && mybot <= bot) {
680 /* y_position is below top, mybot is above bot, so we're fully
689 /* top and bot are within y_position .. mybot */
691 topfrac = 1.0 - ((top - y_position) / height);
692 botfrac = 1.0 - ((bot - y_position) / height);
695 for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
696 (*i)->get_selectables (start, end, botfrac, topfrac, results);
702 AutomationTimeAxisView::get_inverted_selectables (Selection& sel, list<Selectable*>& result)
704 for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
705 (*i)->get_inverted_selectables (sel, result);
710 AutomationTimeAxisView::set_selected_points (PointSelection& points)
712 for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
713 (*i)->set_selected_points (points);
718 AutomationTimeAxisView::clear_lines ()
720 for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
725 automation_connection.disconnect ();
729 AutomationTimeAxisView::add_line (AutomationLine& line)
734 /* first line is the Model for automation state */
735 automation_connection = line.the_list().automation_state_changed.connect
736 (mem_fun(*this, &AutomationTimeAxisView::automation_state_changed));
740 lines.push_back (&line);
741 line.set_height (height);
744 /* pick up the current state */
745 automation_state_changed ();
750 AutomationTimeAxisView::show_all_control_points ()
752 for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
753 (*i)->show_all_control_points ();
758 AutomationTimeAxisView::hide_all_but_selected_control_points ()
760 for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
761 (*i)->hide_all_but_selected_control_points ();
766 AutomationTimeAxisView::entered()
768 show_all_control_points ();
772 AutomationTimeAxisView::exited ()
774 hide_all_but_selected_control_points ();
778 AutomationTimeAxisView::set_colors () {
780 for( list<GhostRegion *>::iterator i=ghosts.begin(); i != ghosts.end(); i++ ) {
784 for( vector<AutomationLine *>::iterator i=lines.begin(); i != lines.end(); i++ ) {
791 AutomationTimeAxisView::color_handler ()
799 AutomationTimeAxisView::set_state (const XMLNode& node)
801 TimeAxisView::set_state (node);
805 AutomationTimeAxisView::get_state_node ()
807 TimeAxisView* state_parent = get_parent_with_state ();
810 return state_parent->get_child_xml_node (_state_name);