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();
370 } else if (h >= hNormal){
372 height_button.show();
374 hide_button.show_all();
378 /* only emit the signal if the height really changed */
379 route->gui_changed ("visible_tracks", (void *) 0); /* EMIT_SIGNAL */
384 AutomationTimeAxisView::set_samples_per_unit (double spu)
386 TimeAxisView::set_samples_per_unit (editor.get_current_zoom());
388 for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
394 AutomationTimeAxisView::hide_clicked ()
396 // LAME fix for refreshing the hide button
397 hide_button.set_sensitive(false);
399 set_marked_for_display (false);
402 hide_button.set_sensitive(true);
406 AutomationTimeAxisView::build_display_menu ()
408 using namespace Menu_Helpers;
410 /* get the size menu ready */
416 TimeAxisView::build_display_menu ();
418 /* now fill it with our stuff */
420 MenuList& items = display_menu->items();
422 items.push_back (MenuElem (_("Height"), *size_menu));
423 items.push_back (SeparatorElem());
424 items.push_back (MenuElem (_("Hide"), mem_fun(*this, &AutomationTimeAxisView::hide_clicked)));
425 items.push_back (SeparatorElem());
426 items.push_back (MenuElem (_("Clear"), mem_fun(*this, &AutomationTimeAxisView::clear_clicked)));
427 items.push_back (SeparatorElem());
429 Menu* auto_state_menu = manage (new Menu);
430 auto_state_menu->set_name ("ArdourContextMenu");
431 MenuList& as_items = auto_state_menu->items();
433 as_items.push_back (CheckMenuElem (_("Manual"),
434 bind (mem_fun(*this, &AutomationTimeAxisView::set_automation_state), (AutoState) Off)));
435 auto_off_item = dynamic_cast<CheckMenuItem*>(&as_items.back());
437 as_items.push_back (CheckMenuElem (_("Play"),
438 bind (mem_fun(*this, &AutomationTimeAxisView::set_automation_state), (AutoState) Play)));
439 auto_play_item = dynamic_cast<CheckMenuItem*>(&as_items.back());
441 as_items.push_back (CheckMenuElem (_("Write"),
442 bind (mem_fun(*this, &AutomationTimeAxisView::set_automation_state), (AutoState) Write)));
443 auto_write_item = dynamic_cast<CheckMenuItem*>(&as_items.back());
445 as_items.push_back (CheckMenuElem (_("Touch"),
446 bind (mem_fun(*this, &AutomationTimeAxisView::set_automation_state), (AutoState) Touch)));
447 auto_touch_item = dynamic_cast<CheckMenuItem*>(&as_items.back());
449 items.push_back (MenuElem (_("State"), *auto_state_menu));
451 /* make sure the automation menu state is correct */
453 automation_state_changed ();
457 AutomationTimeAxisView::cut_copy_clear (Selection& selection, CutCopyOp op)
461 for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
462 ret = cut_copy_clear_one ((**i), selection, op);
469 AutomationTimeAxisView::cut_copy_clear_one (AutomationLine& line, Selection& selection, CutCopyOp op)
471 AutomationList* what_we_got = 0;
472 AutomationList& alist (line.the_list());
475 XMLNode &before = alist.get_state();
479 if ((what_we_got = alist.cut (selection.time.front().start, selection.time.front().end)) != 0) {
480 editor.get_cut_buffer().add (what_we_got);
481 _session.add_command(new MementoCommand<AutomationList>(alist, &before, &alist.get_state()));
486 if ((what_we_got = alist.copy (selection.time.front().start, selection.time.front().end)) != 0) {
487 editor.get_cut_buffer().add (what_we_got);
492 if ((what_we_got = alist.cut (selection.time.front().start, selection.time.front().end)) != 0) {
493 _session.add_command(new MementoCommand<AutomationList>(alist, &before, &alist.get_state()));
502 for (AutomationList::iterator x = what_we_got->begin(); x != what_we_got->end(); ++x) {
503 double foo = (*x)->value;
504 line.model_to_view_y (foo);
513 AutomationTimeAxisView::reset_objects (PointSelection& selection)
515 for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
516 reset_objects_one ((**i), selection);
521 AutomationTimeAxisView::reset_objects_one (AutomationLine& line, PointSelection& selection)
523 AutomationList& alist (line.the_list());
525 _session.add_command (new MementoCommand<AutomationList>(alist, &alist.get_state(), 0));
527 for (PointSelection::iterator i = selection.begin(); i != selection.end(); ++i) {
529 if (&(*i).track != this) {
533 alist.reset_range ((*i).start, (*i).end);
538 AutomationTimeAxisView::cut_copy_clear_objects (PointSelection& selection, CutCopyOp op)
542 for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
543 ret = cut_copy_clear_objects_one ((**i), selection, op);
550 AutomationTimeAxisView::cut_copy_clear_objects_one (AutomationLine& line, PointSelection& selection, CutCopyOp op)
552 AutomationList* what_we_got = 0;
553 AutomationList& alist (line.the_list());
556 XMLNode &before = alist.get_state();
558 for (PointSelection::iterator i = selection.begin(); i != selection.end(); ++i) {
560 if (&(*i).track != this) {
566 if ((what_we_got = alist.cut ((*i).start, (*i).end)) != 0) {
567 editor.get_cut_buffer().add (what_we_got);
568 _session.add_command (new MementoCommand<AutomationList>(alist, new XMLNode (before), &alist.get_state()));
573 if ((what_we_got = alist.copy ((*i).start, (*i).end)) != 0) {
574 editor.get_cut_buffer().add (what_we_got);
579 if ((what_we_got = alist.cut ((*i).start, (*i).end)) != 0) {
580 _session.add_command (new MementoCommand<AutomationList>(alist, new XMLNode (before), &alist.get_state()));
592 for (AutomationList::iterator x = what_we_got->begin(); x != what_we_got->end(); ++x) {
593 double foo = (*x)->value;
594 line.model_to_view_y (foo);
603 AutomationTimeAxisView::paste (nframes_t pos, float times, Selection& selection, size_t nth)
607 for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
608 ret = paste_one (**i, pos, times, selection, nth);
615 AutomationTimeAxisView::paste_one (AutomationLine& line, nframes_t pos, float times, Selection& selection, size_t nth)
617 AutomationSelection::iterator p;
618 AutomationList& alist (line.the_list());
620 for (p = selection.lines.begin(); p != selection.lines.end() && nth; ++p, --nth);
622 if (p == selection.lines.end()) {
626 /* Make a copy of the list because we have to scale the
627 values from view coordinates to model coordinates, and we're
628 not supposed to modify the points in the selection.
631 AutomationList copy (**p);
633 for (AutomationList::iterator x = copy.begin(); x != copy.end(); ++x) {
634 double foo = (*x)->value;
635 line.view_to_model_y (foo);
639 XMLNode &before = alist.get_state();
640 alist.paste (copy, pos, times);
641 _session.add_command (new MementoCommand<AutomationList>(alist, &before, &alist.get_state()));
647 AutomationTimeAxisView::add_ghost (GhostRegion* gr)
649 ghosts.push_back (gr);
650 gr->GoingAway.connect (mem_fun(*this, &AutomationTimeAxisView::remove_ghost));
654 AutomationTimeAxisView::remove_ghost (GhostRegion* gr)
660 list<GhostRegion*>::iterator i;
662 for (i = ghosts.begin(); i != ghosts.end(); ++i) {
671 AutomationTimeAxisView::get_selectables (nframes_t start, nframes_t end, double top, double bot, list<Selectable*>& results)
673 if (!lines.empty() && touched (top, bot)) {
677 /* remember: this is X Window - coordinate space starts in upper left and moves down.
678 y_position is the "origin" or "top" of the track.
681 double mybot = y_position + height;
683 if (y_position >= top && mybot <= bot) {
685 /* y_position is below top, mybot is above bot, so we're fully
694 /* top and bot are within y_position .. mybot */
696 topfrac = 1.0 - ((top - y_position) / height);
697 botfrac = 1.0 - ((bot - y_position) / height);
700 for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
701 (*i)->get_selectables (start, end, botfrac, topfrac, results);
707 AutomationTimeAxisView::get_inverted_selectables (Selection& sel, list<Selectable*>& result)
709 for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
710 (*i)->get_inverted_selectables (sel, result);
715 AutomationTimeAxisView::set_selected_points (PointSelection& points)
717 for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
718 (*i)->set_selected_points (points);
723 AutomationTimeAxisView::clear_lines ()
725 for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
730 automation_connection.disconnect ();
734 AutomationTimeAxisView::add_line (AutomationLine& line)
739 /* first line is the Model for automation state */
740 automation_connection = line.the_list().automation_state_changed.connect
741 (mem_fun(*this, &AutomationTimeAxisView::automation_state_changed));
745 lines.push_back (&line);
746 line.set_height (height);
749 /* pick up the current state */
750 automation_state_changed ();
755 AutomationTimeAxisView::show_all_control_points ()
757 for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
758 (*i)->show_all_control_points ();
763 AutomationTimeAxisView::hide_all_but_selected_control_points ()
765 for (vector<AutomationLine*>::iterator i = lines.begin(); i != lines.end(); ++i) {
766 (*i)->hide_all_but_selected_control_points ();
771 AutomationTimeAxisView::entered()
773 show_all_control_points ();
777 AutomationTimeAxisView::exited ()
779 hide_all_but_selected_control_points ();
783 AutomationTimeAxisView::set_colors () {
785 for( list<GhostRegion *>::iterator i=ghosts.begin(); i != ghosts.end(); i++ ) {
789 for( vector<AutomationLine *>::iterator i=lines.begin(); i != lines.end(); i++ ) {
796 AutomationTimeAxisView::color_handler ()
804 AutomationTimeAxisView::set_state (const XMLNode& node)
806 return TimeAxisView::set_state (node);
810 AutomationTimeAxisView::get_state_node ()
812 TimeAxisView* state_parent = get_parent_with_state ();
815 return state_parent->get_child_xml_node (_state_name);