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 clear_button (_("clear")),
56 auto_button (X_("")) /* force addition of a label */
58 if (!have_name_font
) {
59 name_font
= get_font_for_style (X_("AutomationTrackName"));
60 have_name_font
= true;
64 in_destructor
= false;
69 ignore_state_request
= false;
70 first_call_to_set_height
= true;
72 base_rect
= new SimpleRect(*canvas_display
);
73 base_rect
->property_x1() = 0.0;
74 base_rect
->property_y1() = 0.0;
75 base_rect
->property_x2() = LONG_MAX
- 2;
76 base_rect
->property_outline_color_rgba() = ARDOUR_UI::config()->canvasvar_AutomationTrackOutline
.get();
77 /* outline ends and bottom */
78 base_rect
->property_outline_what() = (guint32
) (0x1|0x2|0x8);
79 base_rect
->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_AutomationTrackFill
.get();
80 //base_rect->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_EnteredControlPoint.get();
82 base_rect
->set_data ("trackview", this);
84 base_rect
->signal_event().connect (bind (mem_fun (editor
, &PublicEditor::canvas_automation_track_event
),
87 hide_button
.add (*(manage (new Gtk::Image (::get_icon("hide")))));
89 auto_button
.set_name ("TrackVisualButton");
90 clear_button
.set_name ("TrackVisualButton");
91 hide_button
.set_name ("TrackRemoveButton");
93 auto_button
.unset_flags (Gtk::CAN_FOCUS
);
94 clear_button
.unset_flags (Gtk::CAN_FOCUS
);
95 hide_button
.unset_flags (Gtk::CAN_FOCUS
);
97 controls_table
.set_no_show_all();
99 ARDOUR_UI::instance()->tooltips().set_tip(auto_button
, _("automation state"));
100 ARDOUR_UI::instance()->tooltips().set_tip(clear_button
, _("clear track"));
101 ARDOUR_UI::instance()->tooltips().set_tip(hide_button
, _("hide track"));
103 /* rearrange the name display */
105 /* we never show these for automation tracks, so make
106 life easier and remove them.
111 /* move the name label over a bit */
113 string shortpname
= _name
;
114 bool shortened
= false;
117 shortpname
= fit_to_pixels (_name
, 60, *name_font
, ignore_width
, true);
119 if (shortpname
!= _name
){
123 name_label
.set_text (shortpname
);
124 name_label
.set_alignment (Gtk::ALIGN_CENTER
, Gtk::ALIGN_CENTER
);
126 if (nomparent
.length()) {
128 /* limit the plug name string */
130 string pname
= fit_to_pixels (nomparent
, 60, *name_font
, ignore_width
, true);
131 if (pname
!= nomparent
) {
135 plugname
= new Label (pname
);
136 plugname
->set_name (X_("TrackPlugName"));
138 name_label
.set_name (X_("TrackParameterName"));
139 controls_table
.remove (name_hbox
);
140 controls_table
.attach (*plugname
, 1, 5, 0, 1, Gtk::FILL
|Gtk::EXPAND
, Gtk::FILL
|Gtk::EXPAND
);
141 plugname_packed
= true;
142 controls_table
.attach (name_hbox
, 1, 5, 1, 2, Gtk::FILL
|Gtk::EXPAND
, Gtk::FILL
|Gtk::EXPAND
);
145 plugname_packed
= false;
149 string tipname
= nomparent
;
150 if (!tipname
.empty()) {
154 ARDOUR_UI::instance()->tooltips().set_tip(controls_ebox
, tipname
);
157 /* add the buttons */
158 controls_table
.attach (hide_button
, 0, 1, 0, 1, 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 clear_button
.signal_clicked().connect (mem_fun(*this, &AutomationTimeAxisView::clear_clicked
));
166 hide_button
.signal_clicked().connect (mem_fun(*this, &AutomationTimeAxisView::hide_clicked
));
167 auto_button
.signal_clicked().connect (mem_fun(*this, &AutomationTimeAxisView::auto_clicked
));
169 controls_base_selected_name
= X_("AutomationTrackControlsBaseSelected");
170 controls_base_unselected_name
= X_("AutomationTrackControlsBase");
171 controls_ebox
.set_name (controls_base_unselected_name
);
173 controls_frame
.set_shadow_type (Gtk::SHADOW_ETCHED_OUT
);
175 XMLNode
* xml_node
= get_parent_with_state()->get_child_xml_node (_state_name
);
178 set_state (*xml_node
);
181 /* make sure labels etc. are correct */
183 automation_state_changed ();
184 ColorsChanged
.connect (mem_fun (*this, &AutomationTimeAxisView::color_handler
));
187 AutomationTimeAxisView::~AutomationTimeAxisView ()
189 in_destructor
= true;
191 for (list
<GhostRegion
*>::iterator i
= ghosts
.begin(); i
!= ghosts
.end(); ++i
) {
197 AutomationTimeAxisView::auto_clicked ()
199 using namespace Menu_Helpers
;
201 if (automation_menu
== 0) {
202 automation_menu
= manage (new Menu
);
203 automation_menu
->set_name ("ArdourContextMenu");
204 MenuList
& items (automation_menu
->items());
206 items
.push_back (MenuElem (_("Manual"),
207 bind (mem_fun(*this, &AutomationTimeAxisView::set_automation_state
), (AutoState
) Off
)));
208 items
.push_back (MenuElem (_("Play"),
209 bind (mem_fun(*this, &AutomationTimeAxisView::set_automation_state
), (AutoState
) Play
)));
210 items
.push_back (MenuElem (_("Write"),
211 bind (mem_fun(*this, &AutomationTimeAxisView::set_automation_state
), (AutoState
) Write
)));
212 items
.push_back (MenuElem (_("Touch"),
213 bind (mem_fun(*this, &AutomationTimeAxisView::set_automation_state
), (AutoState
) Touch
)));
216 automation_menu
->popup (1, gtk_get_current_event_time());
221 AutomationTimeAxisView::automation_state_changed ()
225 /* update button label */
230 state
= lines
.front()->the_list().automation_state ();
233 switch (state
& (Off
|Play
|Touch
|Write
)) {
235 auto_button
.set_label (_("Manual"));
237 ignore_state_request
= true;
238 auto_off_item
->set_active (true);
239 auto_play_item
->set_active (false);
240 auto_touch_item
->set_active (false);
241 auto_write_item
->set_active (false);
242 ignore_state_request
= false;
246 auto_button
.set_label (_("Play"));
247 if (auto_play_item
) {
248 ignore_state_request
= true;
249 auto_play_item
->set_active (true);
250 auto_off_item
->set_active (false);
251 auto_touch_item
->set_active (false);
252 auto_write_item
->set_active (false);
253 ignore_state_request
= false;
257 auto_button
.set_label (_("Write"));
258 if (auto_write_item
) {
259 ignore_state_request
= true;
260 auto_write_item
->set_active (true);
261 auto_off_item
->set_active (false);
262 auto_play_item
->set_active (false);
263 auto_touch_item
->set_active (false);
264 ignore_state_request
= false;
268 auto_button
.set_label (_("Touch"));
269 if (auto_touch_item
) {
270 ignore_state_request
= true;
271 auto_touch_item
->set_active (true);
272 auto_off_item
->set_active (false);
273 auto_play_item
->set_active (false);
274 auto_write_item
->set_active (false);
275 ignore_state_request
= false;
279 auto_button
.set_label (_("???"));
285 AutomationTimeAxisView::clear_clicked ()
287 _session
.begin_reversible_command (_("clear automation"));
288 for (vector
<AutomationLine
*>::iterator i
= lines
.begin(); i
!= lines
.end(); ++i
) {
291 _session
.commit_reversible_command ();
295 AutomationTimeAxisView::set_height (uint32_t h
)
297 bool changed
= (height
!= (uint32_t) h
);
298 bool changed_between_small_and_normal
= ( (height
< hNormal
&& h
>= hNormal
) || (height
>= hNormal
|| h
< hNormal
) );
300 TimeAxisView
* state_parent
= get_parent_with_state ();
301 XMLNode
* xml_node
= (state_parent
? state_parent
->get_child_xml_node (_state_name
) : NULL
);
303 TimeAxisView::set_height (h
);
304 base_rect
->property_y2() = h
;
306 for (vector
<AutomationLine
*>::iterator i
= lines
.begin(); i
!= lines
.end(); ++i
) {
307 (*i
)->set_height (h
);
310 for (list
<GhostRegion
*>::iterator i
= ghosts
.begin(); i
!= ghosts
.end(); ++i
) {
315 snprintf (buf
, sizeof (buf
), "%u", height
);
317 xml_node
->add_property ("height", buf
);
320 if (changed_between_small_and_normal
|| first_call_to_set_height
) {
321 first_call_to_set_height
= false;
324 controls_table
.remove (name_hbox
);
327 if (plugname_packed
) {
328 controls_table
.remove (*plugname
);
329 plugname_packed
= false;
331 controls_table
.attach (*plugname
, 1, 5, 0, 1, Gtk::FILL
|Gtk::EXPAND
, Gtk::FILL
|Gtk::EXPAND
);
332 plugname_packed
= true;
333 controls_table
.attach (name_hbox
, 1, 5, 1, 2, Gtk::FILL
|Gtk::EXPAND
, Gtk::FILL
|Gtk::EXPAND
);
335 controls_table
.attach (name_hbox
, 1, 5, 0, 1, Gtk::FILL
|Gtk::EXPAND
, Gtk::FILL
|Gtk::EXPAND
);
339 name_hbox
.show_all ();
343 hide_button
.show_all();
345 } else if (h
>= hSmall
) {
346 controls_table
.remove (name_hbox
);
348 if (plugname_packed
) {
349 controls_table
.remove (*plugname
);
350 plugname_packed
= false;
353 controls_table
.attach (name_hbox
, 1, 5, 0, 1, Gtk::FILL
|Gtk::EXPAND
, Gtk::FILL
|Gtk::EXPAND
);
354 controls_table
.hide_all ();
357 name_hbox
.show_all ();
363 } else if (h
>= hNormal
){
364 cerr
<< "track grown, but neither changed_between_small_and_normal nor first_call_to_set_height set!" << endl
;
368 /* only emit the signal if the height really changed */
369 route
->gui_changed ("visible_tracks", (void *) 0); /* EMIT_SIGNAL */
374 AutomationTimeAxisView::set_samples_per_unit (double spu
)
376 TimeAxisView::set_samples_per_unit (editor
.get_current_zoom());
378 for (vector
<AutomationLine
*>::iterator i
= lines
.begin(); i
!= lines
.end(); ++i
) {
384 AutomationTimeAxisView::hide_clicked ()
386 // LAME fix for refreshing the hide button
387 hide_button
.set_sensitive(false);
389 set_marked_for_display (false);
392 hide_button
.set_sensitive(true);
396 AutomationTimeAxisView::build_display_menu ()
398 using namespace Menu_Helpers
;
400 /* get the size menu ready */
406 TimeAxisView::build_display_menu ();
408 /* now fill it with our stuff */
410 MenuList
& items
= display_menu
->items();
412 items
.push_back (MenuElem (_("Height"), *size_menu
));
413 items
.push_back (SeparatorElem());
414 items
.push_back (MenuElem (_("Hide"), mem_fun(*this, &AutomationTimeAxisView::hide_clicked
)));
415 items
.push_back (SeparatorElem());
416 items
.push_back (MenuElem (_("Clear"), mem_fun(*this, &AutomationTimeAxisView::clear_clicked
)));
417 items
.push_back (SeparatorElem());
419 Menu
* auto_state_menu
= manage (new Menu
);
420 auto_state_menu
->set_name ("ArdourContextMenu");
421 MenuList
& as_items
= auto_state_menu
->items();
423 as_items
.push_back (CheckMenuElem (_("Manual"),
424 bind (mem_fun(*this, &AutomationTimeAxisView::set_automation_state
), (AutoState
) Off
)));
425 auto_off_item
= dynamic_cast<CheckMenuItem
*>(&as_items
.back());
427 as_items
.push_back (CheckMenuElem (_("Play"),
428 bind (mem_fun(*this, &AutomationTimeAxisView::set_automation_state
), (AutoState
) Play
)));
429 auto_play_item
= dynamic_cast<CheckMenuItem
*>(&as_items
.back());
431 as_items
.push_back (CheckMenuElem (_("Write"),
432 bind (mem_fun(*this, &AutomationTimeAxisView::set_automation_state
), (AutoState
) Write
)));
433 auto_write_item
= dynamic_cast<CheckMenuItem
*>(&as_items
.back());
435 as_items
.push_back (CheckMenuElem (_("Touch"),
436 bind (mem_fun(*this, &AutomationTimeAxisView::set_automation_state
), (AutoState
) Touch
)));
437 auto_touch_item
= dynamic_cast<CheckMenuItem
*>(&as_items
.back());
439 items
.push_back (MenuElem (_("State"), *auto_state_menu
));
441 /* make sure the automation menu state is correct */
443 automation_state_changed ();
447 AutomationTimeAxisView::cut_copy_clear (Selection
& selection
, CutCopyOp op
)
451 for (vector
<AutomationLine
*>::iterator i
= lines
.begin(); i
!= lines
.end(); ++i
) {
452 ret
= cut_copy_clear_one ((**i
), selection
, op
);
459 AutomationTimeAxisView::cut_copy_clear_one (AutomationLine
& line
, Selection
& selection
, CutCopyOp op
)
461 AutomationList
* what_we_got
= 0;
462 AutomationList
& alist (line
.the_list());
465 XMLNode
&before
= alist
.get_state();
469 if ((what_we_got
= alist
.cut (selection
.time
.front().start
, selection
.time
.front().end
)) != 0) {
470 editor
.get_cut_buffer().add (what_we_got
);
471 _session
.add_command(new MementoCommand
<AutomationList
>(alist
, &before
, &alist
.get_state()));
476 if ((what_we_got
= alist
.copy (selection
.time
.front().start
, selection
.time
.front().end
)) != 0) {
477 editor
.get_cut_buffer().add (what_we_got
);
482 if ((what_we_got
= alist
.cut (selection
.time
.front().start
, selection
.time
.front().end
)) != 0) {
483 _session
.add_command(new MementoCommand
<AutomationList
>(alist
, &before
, &alist
.get_state()));
492 for (AutomationList::iterator x
= what_we_got
->begin(); x
!= what_we_got
->end(); ++x
) {
493 double foo
= (*x
)->value
;
494 line
.model_to_view_y (foo
);
503 AutomationTimeAxisView::reset_objects (PointSelection
& selection
)
505 for (vector
<AutomationLine
*>::iterator i
= lines
.begin(); i
!= lines
.end(); ++i
) {
506 reset_objects_one ((**i
), selection
);
511 AutomationTimeAxisView::reset_objects_one (AutomationLine
& line
, PointSelection
& selection
)
513 AutomationList
& alist (line
.the_list());
515 _session
.add_command (new MementoCommand
<AutomationList
>(alist
, &alist
.get_state(), 0));
517 for (PointSelection::iterator i
= selection
.begin(); i
!= selection
.end(); ++i
) {
519 if (&(*i
).track
!= this) {
523 alist
.reset_range ((*i
).start
, (*i
).end
);
528 AutomationTimeAxisView::cut_copy_clear_objects (PointSelection
& selection
, CutCopyOp op
)
532 for (vector
<AutomationLine
*>::iterator i
= lines
.begin(); i
!= lines
.end(); ++i
) {
533 ret
= cut_copy_clear_objects_one ((**i
), selection
, op
);
540 AutomationTimeAxisView::cut_copy_clear_objects_one (AutomationLine
& line
, PointSelection
& selection
, CutCopyOp op
)
542 AutomationList
* what_we_got
= 0;
543 AutomationList
& alist (line
.the_list());
546 XMLNode
&before
= alist
.get_state();
548 for (PointSelection::iterator i
= selection
.begin(); i
!= selection
.end(); ++i
) {
550 if (&(*i
).track
!= this) {
556 if ((what_we_got
= alist
.cut ((*i
).start
, (*i
).end
)) != 0) {
557 editor
.get_cut_buffer().add (what_we_got
);
558 _session
.add_command (new MementoCommand
<AutomationList
>(alist
, new XMLNode (before
), &alist
.get_state()));
563 if ((what_we_got
= alist
.copy ((*i
).start
, (*i
).end
)) != 0) {
564 editor
.get_cut_buffer().add (what_we_got
);
569 if ((what_we_got
= alist
.cut ((*i
).start
, (*i
).end
)) != 0) {
570 _session
.add_command (new MementoCommand
<AutomationList
>(alist
, new XMLNode (before
), &alist
.get_state()));
582 for (AutomationList::iterator x
= what_we_got
->begin(); x
!= what_we_got
->end(); ++x
) {
583 double foo
= (*x
)->value
;
584 line
.model_to_view_y (foo
);
593 AutomationTimeAxisView::paste (nframes_t pos
, float times
, Selection
& selection
, size_t nth
)
597 for (vector
<AutomationLine
*>::iterator i
= lines
.begin(); i
!= lines
.end(); ++i
) {
598 ret
= paste_one (**i
, pos
, times
, selection
, nth
);
605 AutomationTimeAxisView::paste_one (AutomationLine
& line
, nframes_t pos
, float times
, Selection
& selection
, size_t nth
)
607 AutomationSelection::iterator p
;
608 AutomationList
& alist (line
.the_list());
610 for (p
= selection
.lines
.begin(); p
!= selection
.lines
.end() && nth
; ++p
, --nth
);
612 if (p
== selection
.lines
.end()) {
616 /* Make a copy of the list because we have to scale the
617 values from view coordinates to model coordinates, and we're
618 not supposed to modify the points in the selection.
621 AutomationList
copy (**p
);
623 for (AutomationList::iterator x
= copy
.begin(); x
!= copy
.end(); ++x
) {
624 double foo
= (*x
)->value
;
625 line
.view_to_model_y (foo
);
629 XMLNode
&before
= alist
.get_state();
630 alist
.paste (copy
, pos
, times
);
631 _session
.add_command (new MementoCommand
<AutomationList
>(alist
, &before
, &alist
.get_state()));
637 AutomationTimeAxisView::add_ghost (GhostRegion
* gr
)
639 ghosts
.push_back (gr
);
640 gr
->GoingAway
.connect (mem_fun(*this, &AutomationTimeAxisView::remove_ghost
));
644 AutomationTimeAxisView::remove_ghost (GhostRegion
* gr
)
650 list
<GhostRegion
*>::iterator i
;
652 for (i
= ghosts
.begin(); i
!= ghosts
.end(); ++i
) {
661 AutomationTimeAxisView::get_selectables (nframes_t start
, nframes_t end
, double top
, double bot
, list
<Selectable
*>& results
)
663 if (!lines
.empty() && touched (top
, bot
)) {
667 /* remember: this is X Window - coordinate space starts in upper left and moves down.
668 y_position is the "origin" or "top" of the track.
671 double mybot
= y_position
+ height
;
673 if (y_position
>= top
&& mybot
<= bot
) {
675 /* y_position is below top, mybot is above bot, so we're fully
684 /* top and bot are within y_position .. mybot */
686 topfrac
= 1.0 - ((top
- y_position
) / height
);
687 botfrac
= 1.0 - ((bot
- y_position
) / height
);
690 for (vector
<AutomationLine
*>::iterator i
= lines
.begin(); i
!= lines
.end(); ++i
) {
691 (*i
)->get_selectables (start
, end
, botfrac
, topfrac
, results
);
697 AutomationTimeAxisView::get_inverted_selectables (Selection
& sel
, list
<Selectable
*>& result
)
699 for (vector
<AutomationLine
*>::iterator i
= lines
.begin(); i
!= lines
.end(); ++i
) {
700 (*i
)->get_inverted_selectables (sel
, result
);
705 AutomationTimeAxisView::set_selected_points (PointSelection
& points
)
707 for (vector
<AutomationLine
*>::iterator i
= lines
.begin(); i
!= lines
.end(); ++i
) {
708 (*i
)->set_selected_points (points
);
713 AutomationTimeAxisView::clear_lines ()
715 for (vector
<AutomationLine
*>::iterator i
= lines
.begin(); i
!= lines
.end(); ++i
) {
720 automation_connection
.disconnect ();
724 AutomationTimeAxisView::add_line (AutomationLine
& line
)
729 /* first line is the Model for automation state */
730 automation_connection
= line
.the_list().automation_state_changed
.connect
731 (mem_fun(*this, &AutomationTimeAxisView::automation_state_changed
));
735 lines
.push_back (&line
);
736 line
.set_height (height
);
739 /* pick up the current state */
740 automation_state_changed ();
745 AutomationTimeAxisView::show_all_control_points ()
747 for (vector
<AutomationLine
*>::iterator i
= lines
.begin(); i
!= lines
.end(); ++i
) {
748 (*i
)->show_all_control_points ();
753 AutomationTimeAxisView::hide_all_but_selected_control_points ()
755 for (vector
<AutomationLine
*>::iterator i
= lines
.begin(); i
!= lines
.end(); ++i
) {
756 (*i
)->hide_all_but_selected_control_points ();
761 AutomationTimeAxisView::entered()
763 show_all_control_points ();
767 AutomationTimeAxisView::exited ()
769 hide_all_but_selected_control_points ();
773 AutomationTimeAxisView::set_colors () {
775 for( list
<GhostRegion
*>::iterator i
=ghosts
.begin(); i
!= ghosts
.end(); i
++ ) {
779 for( vector
<AutomationLine
*>::iterator i
=lines
.begin(); i
!= lines
.end(); i
++ ) {
786 AutomationTimeAxisView::color_handler ()
794 AutomationTimeAxisView::set_state (const XMLNode
& node
)
796 return TimeAxisView::set_state (node
);
800 AutomationTimeAxisView::get_state_node ()
802 TimeAxisView
* state_parent
= get_parent_with_state ();
805 return state_parent
->get_child_xml_node (_state_name
);