2 Copyright (C) 2006 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.
29 #include <sigc++/bind.h>
31 #include "pbd/error.h"
32 #include "pbd/stl_delete.h"
33 #include "pbd/whitespace.h"
34 #include "pbd/memento_command.h"
35 #include "pbd/enumwriter.h"
36 #include "pbd/stateful_diff_command.h"
38 #include <gtkmm/menu.h>
39 #include <gtkmm/menuitem.h>
40 #include <gtkmm2ext/gtk_ui.h>
41 #include <gtkmm2ext/selector.h>
42 #include <gtkmm2ext/bindable_button.h>
43 #include <gtkmm2ext/utils.h>
45 #include "ardour/amp.h"
46 #include "ardour/audioplaylist.h"
47 #include "ardour/diskstream.h"
48 #include "ardour/event_type_map.h"
49 #include "ardour/ladspa_plugin.h"
50 #include "ardour/location.h"
51 #include "ardour/panner.h"
52 #include "ardour/playlist.h"
53 #include "ardour/playlist.h"
54 #include "ardour/processor.h"
55 #include "ardour/profile.h"
56 #include "ardour/region_factory.h"
57 #include "ardour/route_group.h"
58 #include "ardour/session.h"
59 #include "ardour/session_playlist.h"
60 #include "ardour/debug.h"
61 #include "ardour/utils.h"
62 #include "evoral/Parameter.hpp"
64 #include "ardour_ui.h"
66 #include "global_signals.h"
67 #include "route_time_axis.h"
68 #include "automation_time_axis.h"
69 #include "canvas_impl.h"
70 #include "crossfade_view.h"
72 #include "gui_thread.h"
74 #include "playlist_selector.h"
75 #include "point_selection.h"
77 #include "public_editor.h"
78 #include "region_view.h"
79 #include "rgb_macros.h"
80 #include "selection.h"
81 #include "simplerect.h"
82 #include "streamview.h"
84 #include "route_group_menu.h"
86 #include "ardour/track.h"
90 using namespace ARDOUR
;
92 using namespace Gtkmm2ext
;
94 using namespace Editing
;
97 Glib::RefPtr
<Gdk::Pixbuf
> RouteTimeAxisView::slider
;
100 RouteTimeAxisView::setup_slider_pix ()
102 if ((slider
= ::get_icon ("fader_belt_h")) == 0) {
103 throw failed_constructor ();
107 RouteTimeAxisView::RouteTimeAxisView (PublicEditor
& ed
, Session
* sess
, boost::shared_ptr
<Route
> rt
, Canvas
& canvas
)
110 , TimeAxisView(sess
,ed
,(TimeAxisView
*) 0, canvas
)
111 , parent_canvas (canvas
)
112 , button_table (3, 3)
113 , route_group_button (_("g"))
114 , playlist_button (_("p"))
115 , automation_button (_("a"))
116 , gm (sess
, slider
, true, 115)
118 gm
.set_controls (_route
, _route
->shared_peak_meter(), _route
->amp());
119 gm
.get_level_meter().set_no_show_all();
120 gm
.get_level_meter().setup_meters(50);
123 playlist_action_menu
= 0;
124 automation_action_menu
= 0;
125 plugins_submenu_item
= 0;
129 if (!_route
->is_hidden()) {
130 _marked_for_display
= true;
134 update_solo_display ();
136 timestretch_rect
= 0;
139 ignore_toggle
= false;
141 route_group_button
.set_name ("TrackGroupButton");
142 playlist_button
.set_name ("TrackPlaylistButton");
143 automation_button
.set_name ("TrackAutomationButton");
145 route_group_button
.unset_flags (Gtk::CAN_FOCUS
);
146 playlist_button
.unset_flags (Gtk::CAN_FOCUS
);
147 automation_button
.unset_flags (Gtk::CAN_FOCUS
);
149 route_group_button
.signal_button_release_event().connect (sigc::mem_fun(*this, &RouteTimeAxisView::route_group_click
), false);
150 playlist_button
.signal_clicked().connect (sigc::mem_fun(*this, &RouteTimeAxisView::playlist_click
));
151 automation_button
.signal_clicked().connect (sigc::mem_fun(*this, &RouteTimeAxisView::automation_click
));
157 rec_enable_button
->remove ();
159 switch (track()->mode()) {
161 case ARDOUR::NonLayered
:
162 rec_enable_button
->add (*(manage (new Image (::get_icon (X_("record_normal_red"))))));
164 case ARDOUR::Destructive
:
165 rec_enable_button
->add (*(manage (new Image (::get_icon (X_("record_tape_red"))))));
168 rec_enable_button
->show_all ();
170 controls_table
.attach (*rec_enable_button
, 5, 6, 0, 1, Gtk::FILL
|Gtk::EXPAND
, Gtk::FILL
|Gtk::EXPAND
, 0, 0);
172 if (is_midi_track()) {
173 ARDOUR_UI::instance()->set_tip(*rec_enable_button
, _("Record (Right-click for Step Edit)"));
175 ARDOUR_UI::instance()->set_tip(*rec_enable_button
, _("Record"));
178 rec_enable_button
->set_sensitive (_session
->writable());
181 controls_hbox
.pack_start(gm
.get_level_meter(), false, false);
182 _route
->meter_change
.connect (*this, invalidator (*this), bind (&RouteTimeAxisView::meter_changed
, this), gui_context());
183 _route
->input()->changed
.connect (*this, invalidator (*this), ui_bind (&RouteTimeAxisView::io_changed
, this, _1
, _2
), gui_context());
184 _route
->output()->changed
.connect (*this, invalidator (*this), ui_bind (&RouteTimeAxisView::io_changed
, this, _1
, _2
), gui_context());
186 controls_table
.attach (*mute_button
, 6, 7, 0, 1, Gtk::FILL
|Gtk::EXPAND
, Gtk::FILL
|Gtk::EXPAND
, 0, 0);
188 if (!_route
->is_master()) {
189 controls_table
.attach (*solo_button
, 7, 8, 0, 1, Gtk::FILL
|Gtk::EXPAND
, Gtk::FILL
|Gtk::EXPAND
, 0, 0);
192 controls_table
.attach (route_group_button
, 7, 8, 1, 2, Gtk::FILL
|Gtk::EXPAND
, Gtk::FILL
|Gtk::EXPAND
, 0, 0);
193 controls_table
.attach (gm
.get_gain_slider(), 0, 5, 1, 2, Gtk::SHRINK
, Gtk::SHRINK
, 0, 0);
195 ARDOUR_UI::instance()->set_tip(*solo_button
,_("Solo"));
196 ARDOUR_UI::instance()->set_tip(*mute_button
,_("Mute"));
197 ARDOUR_UI::instance()->set_tip(route_group_button
, _("Route Group"));
198 ARDOUR_UI::instance()->set_tip(playlist_button
,_("Playlist"));
199 ARDOUR_UI::instance()->set_tip(automation_button
, _("Automation"));
203 controls_table
.attach (automation_button
, 6, 7, 1, 2, Gtk::FILL
|Gtk::EXPAND
, Gtk::FILL
|Gtk::EXPAND
);
205 if (is_track() && track()->mode() == ARDOUR::Normal
) {
206 controls_table
.attach (playlist_button
, 5, 6, 1, 2, Gtk::FILL
|Gtk::EXPAND
, Gtk::FILL
|Gtk::EXPAND
);
211 _route
->processors_changed
.connect (*this, invalidator (*this), ui_bind (&RouteTimeAxisView::processors_changed
, this, _1
), gui_context());
212 _route
->PropertyChanged
.connect (*this, invalidator (*this), ui_bind (&RouteTimeAxisView::route_property_changed
, this, _1
), gui_context());
216 track()->FreezeChange
.connect (*this, invalidator (*this), boost::bind (&RouteTimeAxisView::map_frozen
, this), gui_context());
217 track()->SpeedChanged
.connect (*this, invalidator (*this), boost::bind (&RouteTimeAxisView::speed_changed
, this), gui_context());
219 /* pick up the correct freeze state */
224 _editor
.ZoomChanged
.connect (sigc::mem_fun(*this, &RouteTimeAxisView::reset_samples_per_unit
));
225 _editor
.HorizontalPositionChanged
.connect (sigc::mem_fun (*this, &RouteTimeAxisView::horizontal_position_changed
));
226 ColorsChanged
.connect (sigc::mem_fun (*this, &RouteTimeAxisView::color_handler
));
228 PropertyList
* plist
= new PropertyList();
230 plist
->add (ARDOUR::Properties::edit
, true);
231 plist
->add (ARDOUR::Properties::mute
, true);
232 plist
->add (ARDOUR::Properties::solo
, true);
234 route_group_menu
= new RouteGroupMenu (_session
, plist
);
236 gm
.get_gain_slider().signal_scroll_event().connect(sigc::mem_fun(*this, &RouteTimeAxisView::controls_ebox_scroll
), false);
237 gm
.get_gain_slider().set_name ("TrackGainFader");
243 RouteTimeAxisView::~RouteTimeAxisView ()
245 CatchDeletion (this);
247 for (list
<ProcessorAutomationInfo
*>::iterator i
= processor_automation
.begin(); i
!= processor_automation
.end(); ++i
) {
251 delete playlist_action_menu
;
252 playlist_action_menu
= 0;
257 _automation_tracks
.clear ();
259 delete route_group_menu
;
263 RouteTimeAxisView::post_construct ()
265 /* map current state of the route */
267 update_diskstream_display ();
269 _subplugin_menu_map
.clear ();
270 subplugin_menu
.items().clear ();
271 _route
->foreach_processor (sigc::mem_fun (*this, &RouteTimeAxisView::add_processor_to_subplugin_menu
));
272 _route
->foreach_processor (sigc::mem_fun (*this, &RouteTimeAxisView::add_existing_processor_automation_curves
));
273 reset_processor_automation_curves ();
277 RouteTimeAxisView::route_group_click (GdkEventButton
*ev
)
279 if (Keyboard::modifier_state_equals (ev
->state
, Keyboard::PrimaryModifier
)) {
280 if (_route
->route_group()) {
281 _route
->route_group()->remove (_route
);
287 r
.push_back (route ());
289 route_group_menu
->build (r
);
290 route_group_menu
->menu()->popup (ev
->button
, ev
->time
);
296 RouteTimeAxisView::playlist_changed ()
302 RouteTimeAxisView::label_view ()
304 string x
= _route
->name();
306 if (x
!= name_entry
.get_text()) {
307 name_entry
.set_text (x
);
310 if (x
!= name_label
.get_text()) {
311 name_label
.set_text (x
);
314 ARDOUR_UI::instance()->set_tip (name_entry
, x
);
318 RouteTimeAxisView::route_property_changed (const PropertyChange
& what_changed
)
320 if (what_changed
.contains (ARDOUR::Properties::name
)) {
326 RouteTimeAxisView::take_name_changed (void *src
)
334 RouteTimeAxisView::playlist_click ()
336 build_playlist_menu ();
337 conditionally_add_to_selection ();
338 playlist_action_menu
->popup (1, gtk_get_current_event_time());
342 RouteTimeAxisView::automation_click ()
344 conditionally_add_to_selection ();
345 build_automation_action_menu (false);
346 automation_action_menu
->popup (1, gtk_get_current_event_time());
350 RouteTimeAxisView::set_state (const XMLNode
& node
, int version
)
352 TimeAxisView::set_state (node
, version
);
354 XMLNodeList kids
= node
.children();
355 XMLNodeConstIterator iter
;
356 const XMLProperty
* prop
;
358 if (_view
&& (prop
= node
.property ("layer-display"))) {
359 set_layer_display (LayerDisplay (string_2_enum (prop
->value(), _view
->layer_display ())));
362 for (iter
= kids
.begin(); iter
!= kids
.end(); ++iter
) {
363 if ((*iter
)->name() == AutomationTimeAxisView::state_node_name
) {
364 if ((prop
= (*iter
)->property ("automation-id")) != 0) {
366 Evoral::Parameter param
= ARDOUR::EventTypeMap::instance().new_parameter(prop
->value());
367 bool show
= ((prop
= (*iter
)->property ("shown")) != 0) && string_is_affirmative (prop
->value());
368 create_automation_child(param
, show
);
370 warning
<< "Automation child has no ID" << endmsg
;
379 RouteTimeAxisView::build_automation_action_menu (bool for_selection
)
381 using namespace Menu_Helpers
;
383 /* detach subplugin_menu from automation_action_menu before we delete automation_action_menu,
384 otherwise bad things happen (see comment for similar case in MidiTimeAxisView::build_automation_action_menu)
387 detach_menu (subplugin_menu
);
389 _main_automation_menu_map
.clear ();
390 delete automation_action_menu
;
391 automation_action_menu
= new Menu
;
393 MenuList
& items
= automation_action_menu
->items();
395 automation_action_menu
->set_name ("ArdourContextMenu");
397 items
.push_back (MenuElem (_("Show All Automation"),
398 sigc::bind (sigc::mem_fun (*this, &RouteTimeAxisView::show_all_automation
), for_selection
)));
400 items
.push_back (MenuElem (_("Show Existing Automation"),
401 sigc::bind (sigc::mem_fun (*this, &RouteTimeAxisView::show_existing_automation
), for_selection
)));
403 items
.push_back (MenuElem (_("Hide All Automation"),
404 sigc::bind (sigc::mem_fun (*this, &RouteTimeAxisView::hide_all_automation
), for_selection
)));
406 items
.push_back (SeparatorElem ());
408 /* Attach the plugin submenu. It may have previously been used elsewhere,
409 so it was detached above */
411 items
.push_back (MenuElem (_("Plugins"), subplugin_menu
));
412 items
.back().set_sensitive (!subplugin_menu
.items().empty() && (!for_selection
|| _editor
.get_selection().tracks
.size() == 1));;
416 RouteTimeAxisView::build_display_menu ()
418 using namespace Menu_Helpers
;
422 TimeAxisView::build_display_menu ();
424 /* now fill it with our stuff */
426 MenuList
& items
= display_menu
->items();
427 display_menu
->set_name ("ArdourContextMenu");
429 items
.push_back (MenuElem (_("Color..."), sigc::mem_fun (*this, &RouteUI::choose_color
)));
432 detach_menu (*_size_menu
);
435 items
.push_back (MenuElem (_("Height"), *_size_menu
));
437 items
.push_back (SeparatorElem());
439 if (!Profile
->get_sae()) {
440 items
.push_back (MenuElem (_("Remote Control ID..."), sigc::mem_fun (*this, &RouteUI::open_remote_control_id_dialog
)));
441 items
.back().set_sensitive (_editor
.get_selection().tracks
.size() <= 1);
442 items
.push_back (SeparatorElem());
445 // Hook for derived classes to add type specific stuff
446 append_extra_display_menu_items ();
450 Menu
* layers_menu
= manage (new Menu
);
451 MenuList
&layers_items
= layers_menu
->items();
452 layers_menu
->set_name("ArdourContextMenu");
454 RadioMenuItem::Group layers_group
;
456 /* Find out how many overlaid/stacked tracks we have in the selection */
460 TrackSelection
const & s
= _editor
.get_selection().tracks
;
461 for (TrackSelection::const_iterator i
= s
.begin(); i
!= s
.end(); ++i
) {
462 StreamView
* v
= (*i
)->view ();
467 switch (v
->layer_display ()) {
477 /* We're not connecting to signal_toggled() here; in the case where these two items are
478 set to be in the `inconsistent' state, it seems that one or other will end up active
479 as well as inconsistent (presumably due to the RadioMenuItem::Group). Then when you
480 select the active one, no toggled signal is emitted so nothing happens.
483 layers_items
.push_back (RadioMenuElem (layers_group
, _("Overlaid")));
484 RadioMenuItem
* i
= dynamic_cast<RadioMenuItem
*> (&layers_items
.back ());
485 i
->set_active (overlaid
!= 0 && stacked
== 0);
486 i
->set_inconsistent (overlaid
!= 0 && stacked
!= 0);
487 i
->signal_activate().connect (sigc::bind (sigc::mem_fun (*this, &RouteTimeAxisView::set_layer_display
), Overlaid
, true));
489 layers_items
.push_back (
490 RadioMenuElem (layers_group
, _("Stacked"),
491 sigc::bind (sigc::mem_fun (*this, &RouteTimeAxisView::set_layer_display
), Stacked
, true))
494 i
= dynamic_cast<RadioMenuItem
*> (&layers_items
.back ());
495 i
->signal_activate().connect (sigc::bind (sigc::mem_fun (*this, &RouteTimeAxisView::set_layer_display
), Stacked
, true));
496 i
->set_active (overlaid
== 0 && stacked
!= 0);
497 i
->set_inconsistent (overlaid
!= 0 && stacked
!= 0);
499 items
.push_back (MenuElem (_("Layers"), *layers_menu
));
501 if (!Profile
->get_sae()) {
503 Menu
* alignment_menu
= manage (new Menu
);
504 MenuList
& alignment_items
= alignment_menu
->items();
505 alignment_menu
->set_name ("ArdourContextMenu");
507 RadioMenuItem::Group align_group
;
509 /* Same verbose hacks as for the layering options above */
515 boost::shared_ptr
<Track
> first_track
;
517 TrackSelection
const & s
= _editor
.get_selection().tracks
;
518 for (TrackSelection::const_iterator i
= s
.begin(); i
!= s
.end(); ++i
) {
519 RouteTimeAxisView
* r
= dynamic_cast<RouteTimeAxisView
*> (*i
);
520 if (!r
|| !r
->is_track ()) {
525 first_track
= r
->track();
528 switch (r
->track()->alignment_choice()) {
532 switch (r
->track()->alignment_style()) {
533 case ExistingMaterial
:
541 case UseExistingMaterial
:
557 inconsistent
= false;
566 if (!inconsistent
&& first_track
) {
568 alignment_items
.push_back (RadioMenuElem (align_group
, _("Automatic (based on I/O connections)")));
569 i
= dynamic_cast<RadioMenuItem
*> (&alignment_items
.back());
570 i
->set_active (automatic
!= 0 && existing
== 0 && capture
== 0);
571 i
->signal_activate().connect (sigc::bind (sigc::mem_fun(*this, &RouteTimeAxisView::set_align_choice
), i
, Automatic
, true));
573 switch (first_track
->alignment_choice()) {
575 switch (first_track
->alignment_style()) {
576 case ExistingMaterial
:
577 alignment_items
.push_back (MenuElem (_("(Currently: Existing Material)")));
580 alignment_items
.push_back (MenuElem (_("(Currently: Capture Time)")));
588 alignment_items
.push_back (RadioMenuElem (align_group
, _("Align With Existing Material")));
589 i
= dynamic_cast<RadioMenuItem
*> (&alignment_items
.back());
590 i
->set_active (existing
!= 0 && capture
== 0 && automatic
== 0);
591 i
->signal_activate().connect (sigc::bind (sigc::mem_fun(*this, &RouteTimeAxisView::set_align_choice
), i
, UseExistingMaterial
, true));
593 alignment_items
.push_back (RadioMenuElem (align_group
, _("Align With Capture Time")));
594 i
= dynamic_cast<RadioMenuItem
*> (&alignment_items
.back());
595 i
->set_active (existing
== 0 && capture
!= 0 && automatic
== 0);
596 i
->signal_activate().connect (sigc::bind (sigc::mem_fun(*this, &RouteTimeAxisView::set_align_choice
), i
, UseCaptureTime
, true));
598 items
.push_back (MenuElem (_("Alignment"), *alignment_menu
));
604 Menu
* mode_menu
= manage (new Menu
);
605 MenuList
& mode_items
= mode_menu
->items ();
606 mode_menu
->set_name ("ArdourContextMenu");
608 RadioMenuItem::Group mode_group
;
614 for (TrackSelection::const_iterator i
= s
.begin(); i
!= s
.end(); ++i
) {
615 RouteTimeAxisView
* r
= dynamic_cast<RouteTimeAxisView
*> (*i
);
616 if (!r
|| !r
->is_track ()) {
620 switch (r
->track()->mode()) {
633 mode_items
.push_back (RadioMenuElem (mode_group
, _("Normal Mode")));
634 i
= dynamic_cast<RadioMenuItem
*> (&mode_items
.back ());
635 i
->signal_activate().connect (sigc::bind (sigc::mem_fun (*this, &RouteTimeAxisView::set_track_mode
), ARDOUR::Normal
, true));
636 i
->set_active (normal
!= 0 && tape
== 0 && non_layered
== 0);
637 i
->set_inconsistent (normal
!= 0 && (tape
!= 0 || non_layered
!= 0));
639 mode_items
.push_back (RadioMenuElem (mode_group
, _("Tape Mode")));
640 i
= dynamic_cast<RadioMenuItem
*> (&mode_items
.back ());
641 i
->signal_activate().connect (sigc::bind (sigc::mem_fun (*this, &RouteTimeAxisView::set_track_mode
), ARDOUR::Destructive
, true));
642 i
->set_active (normal
== 0 && tape
!= 0 && non_layered
== 0);
643 i
->set_inconsistent (tape
!= 0 && (normal
!= 0 || non_layered
!= 0));
645 mode_items
.push_back (RadioMenuElem (mode_group
, _("Non-Layered Mode")));
646 i
= dynamic_cast<RadioMenuItem
*> (&mode_items
.back ());
647 i
->signal_activate().connect (sigc::bind (sigc::mem_fun (*this, &RouteTimeAxisView::set_track_mode
), ARDOUR::NonLayered
, true));
648 i
->set_active (normal
== 0 && tape
== 0 && non_layered
!= 0);
649 i
->set_inconsistent (non_layered
!= 0 && (normal
!= 0 || tape
!= 0));
651 items
.push_back (MenuElem (_("Mode"), *mode_menu
));
654 color_mode_menu
= build_color_mode_menu();
655 if (color_mode_menu
) {
656 items
.push_back (MenuElem (_("Color Mode"), *color_mode_menu
));
659 items
.push_back (SeparatorElem());
661 build_playlist_menu ();
662 items
.push_back (MenuElem (_("Playlist"), *playlist_action_menu
));
663 items
.back().set_sensitive (_editor
.get_selection().tracks
.size() <= 1);
665 route_group_menu
->detach ();
668 for (TrackSelection::iterator i
= _editor
.get_selection().tracks
.begin(); i
!= _editor
.get_selection().tracks
.end(); ++i
) {
669 RouteTimeAxisView
* rtv
= dynamic_cast<RouteTimeAxisView
*> (*i
);
671 r
.push_back (rtv
->route ());
676 r
.push_back (route ());
679 route_group_menu
->build (r
);
680 items
.push_back (MenuElem (_("Route Group"), *route_group_menu
->menu ()));
682 build_automation_action_menu (true);
683 items
.push_back (MenuElem (_("Automation"), *automation_action_menu
));
685 items
.push_back (SeparatorElem());
690 TrackSelection
const & s
= _editor
.get_selection().tracks
;
691 for (TrackSelection::const_iterator i
= s
.begin(); i
!= s
.end(); ++i
) {
692 RouteTimeAxisView
* r
= dynamic_cast<RouteTimeAxisView
*> (*i
);
697 if (r
->route()->active()) {
704 items
.push_back (CheckMenuElem (_("Active")));
705 CheckMenuItem
* i
= dynamic_cast<CheckMenuItem
*> (&items
.back());
706 bool click_sets_active
= true;
707 if (active
> 0 && inactive
== 0) {
708 i
->set_active (true);
709 click_sets_active
= false;
710 } else if (active
> 0 && inactive
> 0) {
711 i
->set_inconsistent (true);
713 i
->signal_activate().connect (sigc::bind (sigc::mem_fun (*this, &RouteUI::set_route_active
), click_sets_active
, true));
715 items
.push_back (SeparatorElem());
716 items
.push_back (MenuElem (_("Hide"), sigc::bind (sigc::mem_fun(_editor
, &PublicEditor::hide_track_in_display
), this, true)));
717 if (!Profile
->get_sae()) {
718 items
.push_back (MenuElem (_("Remove"), sigc::bind (sigc::mem_fun(*this, &RouteUI::remove_this_route
), true)));
720 items
.push_front (SeparatorElem());
721 items
.push_front (MenuElem (_("Delete"), sigc::bind (sigc::mem_fun(*this, &RouteUI::remove_this_route
), true)));
726 RouteTimeAxisView::set_track_mode (TrackMode mode
, bool apply_to_selection
)
728 if (apply_to_selection
) {
729 _editor
.get_selection().tracks
.foreach_route_time_axis (boost::bind (&RouteTimeAxisView::set_track_mode
, _1
, mode
, false));
734 if (!track()->can_use_mode (mode
, needs_bounce
)) {
740 cerr
<< "would bounce this one\n";
745 track()->set_mode (mode
);
747 rec_enable_button
->remove ();
750 case ARDOUR::NonLayered
:
752 rec_enable_button
->add (*(manage (new Image (::get_icon (X_("record_normal_red"))))));
754 case ARDOUR::Destructive
:
755 rec_enable_button
->add (*(manage (new Image (::get_icon (X_("record_tape_red"))))));
759 rec_enable_button
->show_all ();
764 RouteTimeAxisView::show_timestretch (framepos_t start
, framepos_t end
)
770 TimeAxisView::show_timestretch (start
, end
);
780 /* check that the time selection was made in our route, or our route group.
781 remember that route_group() == 0 implies the route is *not* in a edit group.
784 if (!(ts
.track
== this || (ts
.group
!= 0 && ts
.group
== _route
->route_group()))) {
785 /* this doesn't apply to us */
789 /* ignore it if our edit group is not active */
791 if ((ts
.track
!= this) && _route
->route_group() && !_route
->route_group()->is_active()) {
796 if (timestretch_rect
== 0) {
797 timestretch_rect
= new SimpleRect (*canvas_display ());
798 timestretch_rect
->property_x1() = 0.0;
799 timestretch_rect
->property_y1() = 0.0;
800 timestretch_rect
->property_x2() = 0.0;
801 timestretch_rect
->property_y2() = 0.0;
802 timestretch_rect
->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_TimeStretchFill
.get();
803 timestretch_rect
->property_outline_color_rgba() = ARDOUR_UI::config()->canvasvar_TimeStretchOutline
.get();
806 timestretch_rect
->show ();
807 timestretch_rect
->raise_to_top ();
809 x1
= start
/ _editor
.get_current_zoom();
810 x2
= (end
- 1) / _editor
.get_current_zoom();
811 y2
= current_height() - 2;
813 timestretch_rect
->property_x1() = x1
;
814 timestretch_rect
->property_y1() = 1.0;
815 timestretch_rect
->property_x2() = x2
;
816 timestretch_rect
->property_y2() = y2
;
820 RouteTimeAxisView::hide_timestretch ()
822 TimeAxisView::hide_timestretch ();
824 if (timestretch_rect
) {
825 timestretch_rect
->hide ();
830 RouteTimeAxisView::show_selection (TimeSelection
& ts
)
834 /* ignore it if our edit group is not active or if the selection was started
835 in some other track or route group (remember that route_group() == 0 means
836 that the track is not in an route group).
839 if (((ts
.track
!= this && !is_child (ts
.track
)) && _route
->route_group() && !_route
->route_group()->is_active()) ||
840 (!(ts
.track
== this || is_child (ts
.track
) || (ts
.group
!= 0 && ts
.group
== _route
->route_group())))) {
846 TimeAxisView::show_selection (ts
);
850 RouteTimeAxisView::set_height (uint32_t h
)
853 bool height_changed
= (height
== 0) || (h
!= height
);
854 gm
.get_level_meter().setup_meters (gmlen
);
856 TimeAxisView::set_height (h
);
861 _view
->set_height ((double) current_height());
865 snprintf (buf
, sizeof (buf
), "%u", height
);
866 xml_node
->add_property ("height", buf
);
868 if (height
>= preset_height (HeightNormal
)) {
870 _controls_padding_table
.set_row_spacings (2);
874 gm
.get_gain_slider().show();
876 if (!_route
|| _route
->is_monitor()) {
881 if (rec_enable_button
)
882 rec_enable_button
->show();
884 route_group_button
.show();
885 automation_button
.show();
887 if (is_track() && track()->mode() == ARDOUR::Normal
) {
888 playlist_button
.show();
891 } else if (height
>= preset_height (HeightSmaller
)) {
893 _controls_padding_table
.set_row_spacings (2);
897 gm
.get_gain_slider().hide();
899 if (!_route
|| _route
->is_monitor()) {
904 if (rec_enable_button
)
905 rec_enable_button
->show();
907 route_group_button
.hide ();
908 automation_button
.hide ();
910 if (is_track() && track()->mode() == ARDOUR::Normal
) {
911 playlist_button
.hide ();
916 _controls_padding_table
.set_row_spacings (0);
920 if (height_changed
&& !no_redraw
) {
921 /* only emit the signal if the height really changed */
922 _route
->gui_changed ("track_height", (void *) 0); /* EMIT_SIGNAL */
927 RouteTimeAxisView::set_color (Gdk::Color
const & c
)
929 RouteUI::set_color (c
);
932 _view
->apply_color (_color
, StreamView::RegionColor
);
937 RouteTimeAxisView::reset_samples_per_unit ()
939 set_samples_per_unit (_editor
.get_current_zoom());
943 RouteTimeAxisView::horizontal_position_changed ()
946 _view
->horizontal_position_changed ();
951 RouteTimeAxisView::set_samples_per_unit (double spu
)
956 speed
= track()->speed();
960 _view
->set_samples_per_unit (spu
* speed
);
963 TimeAxisView::set_samples_per_unit (spu
* speed
);
967 RouteTimeAxisView::set_align_choice (RadioMenuItem
* mitem
, AlignChoice choice
, bool apply_to_selection
)
969 if (!mitem
->get_active()) {
970 /* this is one of the two calls made when these radio menu items change status. this one
971 is for the item that became inactive, and we want to ignore it.
976 if (apply_to_selection
) {
977 _editor
.get_selection().tracks
.foreach_route_time_axis (boost::bind (&RouteTimeAxisView::set_align_choice
, _1
, mitem
, choice
, false));
980 track()->set_align_choice (choice
);
986 RouteTimeAxisView::rename_current_playlist ()
988 ArdourPrompter
prompter (true);
991 boost::shared_ptr
<Track
> tr
= track();
992 if (!tr
|| tr
->destructive()) {
996 boost::shared_ptr
<Playlist
> pl
= tr
->playlist();
1001 prompter
.set_title (_("Rename Playlist"));
1002 prompter
.set_prompt (_("New name for playlist:"));
1003 prompter
.set_initial_text (pl
->name());
1004 prompter
.add_button (_("Rename"), Gtk::RESPONSE_ACCEPT
);
1005 prompter
.set_response_sensitive (Gtk::RESPONSE_ACCEPT
, false);
1007 switch (prompter
.run ()) {
1008 case Gtk::RESPONSE_ACCEPT
:
1009 prompter
.get_result (name
);
1010 if (name
.length()) {
1011 pl
->set_name (name
);
1021 RouteTimeAxisView::resolve_new_group_playlist_name(std::string
&basename
, vector
<boost::shared_ptr
<Playlist
> > const & playlists
)
1023 std::string
ret (basename
);
1025 std::string
const group_string
= "." + route_group()->name() + ".";
1027 // iterate through all playlists
1029 for (vector
<boost::shared_ptr
<Playlist
> >::const_iterator i
= playlists
.begin(); i
!= playlists
.end(); ++i
) {
1030 std::string tmp
= (*i
)->name();
1032 std::string::size_type idx
= tmp
.find(group_string
);
1033 // find those which belong to this group
1034 if (idx
!= string::npos
) {
1035 tmp
= tmp
.substr(idx
+ group_string
.length());
1037 // and find the largest current number
1038 int x
= atoi(tmp
.c_str());
1039 if (x
> maxnumber
) {
1048 snprintf (buf
, sizeof(buf
), "%d", maxnumber
);
1050 ret
= this->name() + "." + route_group()->name () + "." + buf
;
1056 RouteTimeAxisView::use_copy_playlist (bool prompt
, vector
<boost::shared_ptr
<Playlist
> > const & playlists_before_op
)
1060 boost::shared_ptr
<Track
> tr
= track ();
1061 if (!tr
|| tr
->destructive()) {
1065 boost::shared_ptr
<const Playlist
> pl
= tr
->playlist();
1072 if (route_group() && route_group()->is_active() && route_group()->enabled_property (ARDOUR::Properties::edit
.property_id
)) {
1073 name
= resolve_new_group_playlist_name(name
, playlists_before_op
);
1076 while (_session
->playlists
->by_name(name
)) {
1077 name
= Playlist::bump_name (name
, *_session
);
1080 // TODO: The prompter "new" button should be de-activated if the user
1081 // specifies a playlist name which already exists in the session.
1085 ArdourPrompter
prompter (true);
1087 prompter
.set_title (_("New Copy Playlist"));
1088 prompter
.set_prompt (_("Name for new playlist:"));
1089 prompter
.set_initial_text (name
);
1090 prompter
.add_button (Gtk::Stock::NEW
, Gtk::RESPONSE_ACCEPT
);
1091 prompter
.set_response_sensitive (Gtk::RESPONSE_ACCEPT
, true);
1092 prompter
.show_all ();
1094 switch (prompter
.run ()) {
1095 case Gtk::RESPONSE_ACCEPT
:
1096 prompter
.get_result (name
);
1104 if (name
.length()) {
1105 tr
->use_copy_playlist ();
1106 tr
->playlist()->set_name (name
);
1111 RouteTimeAxisView::use_new_playlist (bool prompt
, vector
<boost::shared_ptr
<Playlist
> > const & playlists_before_op
)
1115 boost::shared_ptr
<Track
> tr
= track ();
1116 if (!tr
|| tr
->destructive()) {
1120 boost::shared_ptr
<const Playlist
> pl
= tr
->playlist();
1127 if (route_group() && route_group()->is_active() && route_group()->enabled_property (ARDOUR::Properties::edit
.property_id
)) {
1128 name
= resolve_new_group_playlist_name(name
,playlists_before_op
);
1131 while (_session
->playlists
->by_name(name
)) {
1132 name
= Playlist::bump_name (name
, *_session
);
1138 ArdourPrompter
prompter (true);
1140 prompter
.set_title (_("New Playlist"));
1141 prompter
.set_prompt (_("Name for new playlist:"));
1142 prompter
.set_initial_text (name
);
1143 prompter
.add_button (Gtk::Stock::NEW
, Gtk::RESPONSE_ACCEPT
);
1144 prompter
.set_response_sensitive (Gtk::RESPONSE_ACCEPT
, true);
1146 switch (prompter
.run ()) {
1147 case Gtk::RESPONSE_ACCEPT
:
1148 prompter
.get_result (name
);
1156 if (name
.length()) {
1157 tr
->use_new_playlist ();
1158 tr
->playlist()->set_name (name
);
1163 RouteTimeAxisView::clear_playlist ()
1165 boost::shared_ptr
<Track
> tr
= track ();
1166 if (!tr
|| tr
->destructive()) {
1170 boost::shared_ptr
<Playlist
> pl
= tr
->playlist();
1175 _editor
.clear_playlist (pl
);
1179 RouteTimeAxisView::speed_changed ()
1181 Gtkmm2ext::UI::instance()->call_slot (invalidator (*this), boost::bind (&RouteTimeAxisView::reset_samples_per_unit
, this));
1185 RouteTimeAxisView::update_diskstream_display ()
1195 RouteTimeAxisView::selection_click (GdkEventButton
* ev
)
1197 if (Keyboard::modifier_state_equals (ev
->state
, (Keyboard::TertiaryModifier
|Keyboard::PrimaryModifier
))) {
1199 /* special case: select/deselect all tracks */
1200 if (_editor
.get_selection().selected (this)) {
1201 _editor
.get_selection().clear_tracks ();
1203 _editor
.select_all_tracks ();
1209 switch (ArdourKeyboard::selection_type (ev
->state
)) {
1210 case Selection::Toggle
:
1211 _editor
.get_selection().toggle (this);
1214 case Selection::Set
:
1215 _editor
.get_selection().set (this);
1218 case Selection::Extend
:
1219 _editor
.extend_selection_to_track (*this);
1222 case Selection::Add
:
1223 _editor
.get_selection().add (this);
1229 RouteTimeAxisView::set_selected_points (PointSelection
& points
)
1231 for (Children::iterator i
= children
.begin(); i
!= children
.end(); ++i
) {
1232 (*i
)->set_selected_points (points
);
1237 RouteTimeAxisView::set_selected_regionviews (RegionSelection
& regions
)
1240 _view
->set_selected_regionviews (regions
);
1244 /** Add the selectable things that we have to a list.
1245 * @param results List to add things to.
1248 RouteTimeAxisView::get_selectables (framepos_t start
, framepos_t end
, double top
, double bot
, list
<Selectable
*>& results
)
1253 speed
= track()->speed();
1256 framepos_t
const start_adjusted
= session_frame_to_track_frame(start
, speed
);
1257 framepos_t
const end_adjusted
= session_frame_to_track_frame(end
, speed
);
1259 if ((_view
&& ((top
< 0.0 && bot
< 0.0))) || touched (top
, bot
)) {
1260 _view
->get_selectables (start_adjusted
, end_adjusted
, top
, bot
, results
);
1263 /* pick up visible automation tracks */
1265 for (Children::iterator i
= children
.begin(); i
!= children
.end(); ++i
) {
1266 if (!(*i
)->hidden()) {
1267 (*i
)->get_selectables (start_adjusted
, end_adjusted
, top
, bot
, results
);
1273 RouteTimeAxisView::get_inverted_selectables (Selection
& sel
, list
<Selectable
*>& results
)
1276 _view
->get_inverted_selectables (sel
, results
);
1279 for (Children::iterator i
= children
.begin(); i
!= children
.end(); ++i
) {
1280 if (!(*i
)->hidden()) {
1281 (*i
)->get_inverted_selectables (sel
, results
);
1289 RouteTimeAxisView::route_group () const
1291 return _route
->route_group();
1295 RouteTimeAxisView::name() const
1297 return _route
->name();
1300 boost::shared_ptr
<Playlist
>
1301 RouteTimeAxisView::playlist () const
1303 boost::shared_ptr
<Track
> tr
;
1305 if ((tr
= track()) != 0) {
1306 return tr
->playlist();
1308 return boost::shared_ptr
<Playlist
> ();
1313 RouteTimeAxisView::name_entry_changed ()
1317 x
= name_entry
.get_text ();
1319 if (x
== _route
->name()) {
1323 strip_whitespace_edges(x
);
1325 if (x
.length() == 0) {
1326 name_entry
.set_text (_route
->name());
1330 if (!_session
->route_name_unique (x
)) {
1331 ARDOUR_UI::instance()->popup_error (_("A track already exists with that name"));
1332 name_entry
.set_text (_route
->name());
1333 } else if (_session
->route_name_internal (x
)) {
1334 ARDOUR_UI::instance()->popup_error (string_compose (_("You cannot create a track with that name as it is reserved for %1"),
1336 name_entry
.set_text (_route
->name());
1338 _route
->set_name (x
);
1342 boost::shared_ptr
<Region
>
1343 RouteTimeAxisView::find_next_region (framepos_t pos
, RegionPoint point
, int32_t dir
)
1345 boost::shared_ptr
<Playlist
> pl
= playlist ();
1348 return pl
->find_next_region (pos
, point
, dir
);
1351 return boost::shared_ptr
<Region
> ();
1355 RouteTimeAxisView::find_next_region_boundary (framepos_t pos
, int32_t dir
)
1357 boost::shared_ptr
<Playlist
> pl
= playlist ();
1360 return pl
->find_next_region_boundary (pos
, dir
);
1367 RouteTimeAxisView::cut_copy_clear (Selection
& selection
, CutCopyOp op
)
1369 boost::shared_ptr
<Playlist
> what_we_got
;
1370 boost::shared_ptr
<Track
> tr
= track ();
1371 boost::shared_ptr
<Playlist
> playlist
;
1374 /* route is a bus, not a track */
1378 playlist
= tr
->playlist();
1380 TimeSelection
time (selection
.time
);
1381 float const speed
= tr
->speed();
1382 if (speed
!= 1.0f
) {
1383 for (TimeSelection::iterator i
= time
.begin(); i
!= time
.end(); ++i
) {
1384 (*i
).start
= session_frame_to_track_frame((*i
).start
, speed
);
1385 (*i
).end
= session_frame_to_track_frame((*i
).end
, speed
);
1389 playlist
->clear_changes ();
1390 playlist
->clear_owned_changes ();
1394 if ((what_we_got
= playlist
->cut (time
)) != 0) {
1395 _editor
.get_cut_buffer().add (what_we_got
);
1397 vector
<Command
*> cmds
;
1398 playlist
->rdiff (cmds
);
1399 _session
->add_commands (cmds
);
1401 _session
->add_command (new StatefulDiffCommand (playlist
));
1405 if ((what_we_got
= playlist
->copy (time
)) != 0) {
1406 _editor
.get_cut_buffer().add (what_we_got
);
1411 if ((what_we_got
= playlist
->cut (time
)) != 0) {
1413 vector
<Command
*> cmds
;
1414 playlist
->rdiff (cmds
);
1415 _session
->add_commands (cmds
);
1416 _session
->add_command (new StatefulDiffCommand (playlist
));
1417 what_we_got
->release ();
1424 RouteTimeAxisView::paste (framepos_t pos
, float times
, Selection
& selection
, size_t nth
)
1430 boost::shared_ptr
<Playlist
> pl
= playlist ();
1431 PlaylistSelection::iterator p
;
1433 for (p
= selection
.playlists
.begin(); p
!= selection
.playlists
.end() && nth
; ++p
, --nth
) {}
1435 if (p
== selection
.playlists
.end()) {
1439 DEBUG_TRACE (DEBUG::CutNPaste
, string_compose ("paste to %1\n", pos
));
1441 if (track()->speed() != 1.0f
) {
1442 pos
= session_frame_to_track_frame (pos
, track()->speed());
1443 DEBUG_TRACE (DEBUG::CutNPaste
, string_compose ("modified paste to %1\n", pos
));
1446 pl
->clear_changes ();
1447 pl
->paste (*p
, pos
, times
);
1448 _session
->add_command (new StatefulDiffCommand (pl
));
1454 struct PlaylistSorter
{
1455 bool operator() (boost::shared_ptr
<Playlist
> a
, boost::shared_ptr
<Playlist
> b
) const {
1456 return a
->sort_id() < b
->sort_id();
1461 RouteTimeAxisView::build_playlist_menu ()
1463 using namespace Menu_Helpers
;
1469 delete playlist_action_menu
;
1470 playlist_action_menu
= new Menu
;
1471 playlist_action_menu
->set_name ("ArdourContextMenu");
1473 MenuList
& playlist_items
= playlist_action_menu
->items();
1474 playlist_action_menu
->set_name ("ArdourContextMenu");
1475 playlist_items
.clear();
1477 vector
<boost::shared_ptr
<Playlist
> > playlists
, playlists_tr
;
1478 boost::shared_ptr
<Track
> tr
= track();
1479 RadioMenuItem::Group playlist_group
;
1481 _session
->playlists
->get (playlists
);
1483 /* find the playlists for this diskstream */
1484 for (vector
<boost::shared_ptr
<Playlist
> >::iterator i
= playlists
.begin(); i
!= playlists
.end(); ++i
) {
1485 if (((*i
)->get_orig_diskstream_id() == tr
->diskstream_id()) || (tr
->playlist()->id() == (*i
)->id())) {
1486 playlists_tr
.push_back(*i
);
1490 /* sort the playlists */
1492 sort (playlists_tr
.begin(), playlists_tr
.end(), cmp
);
1494 /* add the playlists to the menu */
1495 for (vector
<boost::shared_ptr
<Playlist
> >::iterator i
= playlists_tr
.begin(); i
!= playlists_tr
.end(); ++i
) {
1496 playlist_items
.push_back (RadioMenuElem (playlist_group
, (*i
)->name()));
1497 RadioMenuItem
*item
= static_cast<RadioMenuItem
*>(&playlist_items
.back());
1498 item
->signal_toggled().connect(sigc::bind (sigc::mem_fun (*this, &RouteTimeAxisView::use_playlist
), item
, boost::weak_ptr
<Playlist
> (*i
)));
1500 if (tr
->playlist()->id() == (*i
)->id()) {
1506 playlist_items
.push_back (SeparatorElem());
1507 playlist_items
.push_back (MenuElem (_("Rename..."), sigc::mem_fun(*this, &RouteTimeAxisView::rename_current_playlist
)));
1508 playlist_items
.push_back (SeparatorElem());
1510 if (!route_group() || !route_group()->is_active() || !route_group()->enabled_property (ARDOUR::Properties::edit
.property_id
)) {
1511 playlist_items
.push_back (MenuElem (_("New..."), sigc::bind(sigc::mem_fun(_editor
, &PublicEditor::new_playlists
), this)));
1512 playlist_items
.push_back (MenuElem (_("New Copy..."), sigc::bind(sigc::mem_fun(_editor
, &PublicEditor::copy_playlists
), this)));
1515 // Use a label which tells the user what is happening
1516 playlist_items
.push_back (MenuElem (_("New Take"), sigc::bind(sigc::mem_fun(_editor
, &PublicEditor::new_playlists
), this)));
1517 playlist_items
.push_back (MenuElem (_("Copy Take"), sigc::bind(sigc::mem_fun(_editor
, &PublicEditor::copy_playlists
), this)));
1521 playlist_items
.push_back (SeparatorElem());
1522 playlist_items
.push_back (MenuElem (_("Clear Current"), sigc::bind(sigc::mem_fun(_editor
, &PublicEditor::clear_playlists
), this)));
1523 playlist_items
.push_back (SeparatorElem());
1525 playlist_items
.push_back (MenuElem(_("Select From All..."), sigc::mem_fun(*this, &RouteTimeAxisView::show_playlist_selector
)));
1529 RouteTimeAxisView::use_playlist (RadioMenuItem
*item
, boost::weak_ptr
<Playlist
> wpl
)
1531 assert (is_track());
1533 // exit if we were triggered by deactivating the old playlist
1534 if (!item
->get_active()) {
1538 boost::shared_ptr
<Playlist
> pl (wpl
.lock());
1544 boost::shared_ptr
<AudioPlaylist
> apl
= boost::dynamic_pointer_cast
<AudioPlaylist
> (pl
);
1547 if (track()->playlist() == apl
) {
1548 // exit when use_playlist is called by the creation of the playlist menu
1549 // or the playlist choice is unchanged
1552 track()->use_playlist (apl
);
1554 RouteGroup
* rg
= route_group();
1556 if (rg
&& rg
->is_active() && rg
->enabled_property (ARDOUR::Properties::edit
.property_id
)) {
1557 std::string group_string
= "." + rg
->name() + ".";
1559 std::string take_name
= apl
->name();
1560 std::string::size_type idx
= take_name
.find(group_string
);
1562 if (idx
== std::string::npos
)
1565 take_name
= take_name
.substr(idx
+ group_string
.length()); // find the bit containing the take number / name
1567 boost::shared_ptr
<RouteList
> rl (rg
->route_list());
1569 for (RouteList::const_iterator i
= rl
->begin(); i
!= rl
->end(); ++i
) {
1570 if ( (*i
) == this->route()) {
1574 std::string playlist_name
= (*i
)->name()+group_string
+take_name
;
1576 boost::shared_ptr
<Track
> track
= boost::dynamic_pointer_cast
<Track
>(*i
);
1581 boost::shared_ptr
<Playlist
> ipl
= session()->playlists
->by_name(playlist_name
);
1583 // No playlist for this track for this take yet, make it
1584 track
->use_new_playlist();
1585 track
->playlist()->set_name(playlist_name
);
1587 track
->use_playlist(ipl
);
1595 RouteTimeAxisView::show_playlist_selector ()
1597 _editor
.playlist_selector().show_for (this);
1601 RouteTimeAxisView::map_frozen ()
1607 ENSURE_GUI_THREAD (*this, &RouteTimeAxisView::map_frozen
)
1609 switch (track()->freeze_state()) {
1611 playlist_button
.set_sensitive (false);
1612 rec_enable_button
->set_sensitive (false);
1615 playlist_button
.set_sensitive (true);
1616 rec_enable_button
->set_sensitive (true);
1622 RouteTimeAxisView::color_handler ()
1624 //case cTimeStretchOutline:
1625 if (timestretch_rect
) {
1626 timestretch_rect
->property_outline_color_rgba() = ARDOUR_UI::config()->canvasvar_TimeStretchOutline
.get();
1628 //case cTimeStretchFill:
1629 if (timestretch_rect
) {
1630 timestretch_rect
->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_TimeStretchFill
.get();
1636 /** Toggle an automation track for a fully-specified Parameter (type,channel,id)
1637 * Will add track if necessary.
1640 RouteTimeAxisView::toggle_automation_track (const Evoral::Parameter
& param
)
1642 boost::shared_ptr
<AutomationTimeAxisView
> track
= automation_child (param
);
1643 Gtk::CheckMenuItem
* menu
= automation_child_menu_item (param
);
1646 /* it doesn't exist yet, so we don't care about the button state: just add it */
1647 create_automation_child (param
, true);
1650 bool yn
= menu
->get_active();
1651 if (track
->set_visibility (menu
->get_active()) && yn
) {
1653 /* we made it visible, now trigger a redisplay. if it was hidden, then automation_track_hidden()
1654 will have done that for us.
1658 _route
->gui_changed (X_("track_height"), (void *) 0); /* EMIT_SIGNAL */
1665 RouteTimeAxisView::automation_track_hidden (Evoral::Parameter param
)
1667 boost::shared_ptr
<AutomationTimeAxisView
> track
= automation_child (param
);
1673 Gtk::CheckMenuItem
* menu
= automation_child_menu_item (param
);
1675 // if Evoral::Parameter::operator< doesn't obey strict weak ordering, we may crash here....
1676 track
->get_state_node()->add_property (X_("shown"), X_("no"));
1678 if (menu
&& !_hidden
) {
1679 ignore_toggle
= true;
1680 menu
->set_active (false);
1681 ignore_toggle
= false;
1684 if (_route
&& !no_redraw
) {
1685 _route
->gui_changed ("track_height", (void *) 0); /* EMIT_SIGNAL */
1691 RouteTimeAxisView::show_all_automation (bool apply_to_selection
)
1693 if (apply_to_selection
) {
1694 _editor
.get_selection().tracks
.foreach_route_time_axis (boost::bind (&RouteTimeAxisView::show_all_automation
, _1
, false));
1698 /* Show our automation */
1700 for (AutomationTracks::iterator i
= _automation_tracks
.begin(); i
!= _automation_tracks
.end(); ++i
) {
1701 i
->second
->set_marked_for_display (true);
1702 i
->second
->canvas_display()->show();
1703 i
->second
->get_state_node()->add_property ("shown", X_("yes"));
1705 Gtk::CheckMenuItem
* menu
= automation_child_menu_item (i
->first
);
1708 menu
->set_active(true);
1713 /* Show processor automation */
1715 for (list
<ProcessorAutomationInfo
*>::iterator i
= processor_automation
.begin(); i
!= processor_automation
.end(); ++i
) {
1716 for (vector
<ProcessorAutomationNode
*>::iterator ii
= (*i
)->lines
.begin(); ii
!= (*i
)->lines
.end(); ++ii
) {
1717 if ((*ii
)->view
== 0) {
1718 add_processor_automation_curve ((*i
)->processor
, (*ii
)->what
);
1721 (*ii
)->menu_item
->set_active (true);
1729 _route
->gui_changed ("track_height", (void *) 0); /* EMIT_SIGNAL */
1734 RouteTimeAxisView::show_existing_automation (bool apply_to_selection
)
1736 if (apply_to_selection
) {
1737 _editor
.get_selection().tracks
.foreach_route_time_axis (boost::bind (&RouteTimeAxisView::show_existing_automation
, _1
, false));
1741 /* Show our automation */
1743 for (AutomationTracks::iterator i
= _automation_tracks
.begin(); i
!= _automation_tracks
.end(); ++i
) {
1744 if (i
->second
->has_automation()) {
1745 i
->second
->set_marked_for_display (true);
1746 i
->second
->canvas_display()->show();
1747 i
->second
->get_state_node()->add_property ("shown", X_("yes"));
1749 Gtk::CheckMenuItem
* menu
= automation_child_menu_item (i
->first
);
1751 menu
->set_active(true);
1757 /* Show processor automation */
1759 for (list
<ProcessorAutomationInfo
*>::iterator i
= processor_automation
.begin(); i
!= processor_automation
.end(); ++i
) {
1760 for (vector
<ProcessorAutomationNode
*>::iterator ii
= (*i
)->lines
.begin(); ii
!= (*i
)->lines
.end(); ++ii
) {
1761 if ((*ii
)->view
!= 0 && (*i
)->processor
->control((*ii
)->what
)->list()->size() > 0) {
1762 (*ii
)->menu_item
->set_active (true);
1769 _route
->gui_changed ("track_height", (void *) 0); /* EMIT_SIGNAL */
1774 RouteTimeAxisView::hide_all_automation (bool apply_to_selection
)
1776 if (apply_to_selection
) {
1777 _editor
.get_selection().tracks
.foreach_route_time_axis (boost::bind (&RouteTimeAxisView::hide_all_automation
, _1
, false));
1781 /* Hide our automation */
1783 for (AutomationTracks::iterator i
= _automation_tracks
.begin(); i
!= _automation_tracks
.end(); ++i
) {
1784 i
->second
->set_marked_for_display (false);
1786 i
->second
->get_state_node()->add_property ("shown", X_("no"));
1788 Gtk::CheckMenuItem
* menu
= automation_child_menu_item (i
->first
);
1791 menu
->set_active (false);
1795 /* Hide processor automation */
1797 for (list
<ProcessorAutomationInfo
*>::iterator i
= processor_automation
.begin(); i
!= processor_automation
.end(); ++i
) {
1798 for (vector
<ProcessorAutomationNode
*>::iterator ii
= (*i
)->lines
.begin(); ii
!= (*i
)->lines
.end(); ++ii
) {
1799 (*ii
)->menu_item
->set_active (false);
1804 _route
->gui_changed ("track_height", (void *) 0); /* EMIT_SIGNAL */
1810 RouteTimeAxisView::region_view_added (RegionView
* rv
)
1812 /* XXX need to find out if automation children have automationstreamviews. If yes, no ghosts */
1813 for (Children::iterator i
= children
.begin(); i
!= children
.end(); ++i
) {
1814 boost::shared_ptr
<AutomationTimeAxisView
> atv
;
1816 if ((atv
= boost::dynamic_pointer_cast
<AutomationTimeAxisView
> (*i
)) != 0) {
1821 for (UnderlayMirrorList::iterator i
= _underlay_mirrors
.begin(); i
!= _underlay_mirrors
.end(); ++i
) {
1822 (*i
)->add_ghost(rv
);
1826 RouteTimeAxisView::ProcessorAutomationInfo::~ProcessorAutomationInfo ()
1828 for (vector
<ProcessorAutomationNode
*>::iterator i
= lines
.begin(); i
!= lines
.end(); ++i
) {
1834 RouteTimeAxisView::ProcessorAutomationNode::~ProcessorAutomationNode ()
1836 parent
.remove_processor_automation_node (this);
1840 RouteTimeAxisView::remove_processor_automation_node (ProcessorAutomationNode
* pan
)
1843 remove_child (pan
->view
);
1847 RouteTimeAxisView::ProcessorAutomationNode
*
1848 RouteTimeAxisView::find_processor_automation_node (boost::shared_ptr
<Processor
> processor
, Evoral::Parameter what
)
1850 for (list
<ProcessorAutomationInfo
*>::iterator i
= processor_automation
.begin(); i
!= processor_automation
.end(); ++i
) {
1852 if ((*i
)->processor
== processor
) {
1854 for (vector
<ProcessorAutomationNode
*>::iterator ii
= (*i
)->lines
.begin(); ii
!= (*i
)->lines
.end(); ++ii
) {
1855 if ((*ii
)->what
== what
) {
1866 legalize_for_xml_node (string str
)
1868 string::size_type pos
;
1869 string legal_chars
= "abcdefghijklmnopqrtsuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_=:";
1875 while ((pos
= legal
.find_first_not_of (legal_chars
, pos
)) != string::npos
) {
1876 legal
.replace (pos
, 1, "_");
1885 RouteTimeAxisView::add_processor_automation_curve (boost::shared_ptr
<Processor
> processor
, Evoral::Parameter what
)
1888 ProcessorAutomationNode
* pan
;
1890 if ((pan
= find_processor_automation_node (processor
, what
)) == 0) {
1891 /* session state may never have been saved with new plugin */
1892 error
<< _("programming error: ")
1893 << string_compose (X_("processor automation curve for %1:%2/%3/%4 not registered with track!"),
1894 processor
->name(), what
.type(), (int) what
.channel(), what
.id() )
1904 name
= processor
->describe_parameter (what
);
1906 /* create a string that is a legal XML node name that can be used to refer to this redirect+port combination */
1910 char state_name
[256];
1911 snprintf (state_name
, sizeof (state_name
), "%s-%" PRIu32
, legalize_for_xml_node (processor
->name()).c_str(), what
.id());
1913 boost::shared_ptr
<AutomationControl
> control
1914 = boost::dynamic_pointer_cast
<AutomationControl
>(processor
->control(what
, true));
1916 pan
->view
= boost::shared_ptr
<AutomationTimeAxisView
>(
1917 new AutomationTimeAxisView (_session
, _route
, processor
, control
, control
->parameter (),
1918 _editor
, *this, false, parent_canvas
, name
, state_name
));
1920 pan
->view
->Hiding
.connect (sigc::bind (sigc::mem_fun(*this, &RouteTimeAxisView::processor_automation_track_hidden
), pan
, processor
));
1922 if (!pan
->view
->marked_for_display()) {
1925 pan
->menu_item
->set_active (true);
1928 add_child (pan
->view
);
1931 _view
->foreach_regionview (sigc::mem_fun(*pan
->view
.get(), &TimeAxisView::add_ghost
));
1934 processor
->mark_automation_visible (what
, true);
1938 RouteTimeAxisView::processor_automation_track_hidden (RouteTimeAxisView::ProcessorAutomationNode
* pan
, boost::shared_ptr
<Processor
> i
)
1941 pan
->menu_item
->set_active (false);
1944 i
->mark_automation_visible (pan
->what
, false);
1947 _route
->gui_changed ("track_height", (void *) 0); /* EMIT_SIGNAL */
1952 RouteTimeAxisView::add_existing_processor_automation_curves (boost::weak_ptr
<Processor
> p
)
1954 boost::shared_ptr
<Processor
> processor (p
.lock ());
1959 set
<Evoral::Parameter
> s
;
1960 boost::shared_ptr
<AutomationLine
> al
;
1962 processor
->what_has_visible_data (s
);
1964 for (set
<Evoral::Parameter
>::iterator i
= s
.begin(); i
!= s
.end(); ++i
) {
1966 if ((al
= find_processor_automation_curve (processor
, *i
)) != 0) {
1969 add_processor_automation_curve (processor
, (*i
));
1975 RouteTimeAxisView::add_automation_child (Evoral::Parameter param
, boost::shared_ptr
<AutomationTimeAxisView
> track
, bool show
)
1977 using namespace Menu_Helpers
;
1984 track
->Hiding
.connect (sigc::bind (sigc::mem_fun (*this, &RouteTimeAxisView::automation_track_hidden
), param
));
1986 bool hideit
= (!show
);
1988 if ((node
= track
->get_state_node()) != 0) {
1989 if ((prop
= node
->property ("shown")) != 0) {
1990 if (string_is_affirmative (prop
->value())) {
1996 _automation_tracks
[param
] = track
;
1998 track
->set_visibility (!hideit
);
2001 _route
->gui_changed ("track_height", (void *) 0); /* EMIT_SIGNAL */
2004 if (!EventTypeMap::instance().is_midi_parameter(param
)) {
2005 /* MIDI-related parameters are always in the menu, there's no
2006 reason to rebuild the menu just because we added a automation
2007 lane for one of them. But if we add a non-MIDI automation
2008 lane, then we need to invalidate the display menu.
2010 delete display_menu
;
2016 RouteTimeAxisView::add_processor_to_subplugin_menu (boost::weak_ptr
<Processor
> p
)
2018 boost::shared_ptr
<Processor
> processor (p
.lock ());
2020 if (!processor
|| !processor
->display_to_user ()) {
2024 using namespace Menu_Helpers
;
2025 ProcessorAutomationInfo
*rai
;
2026 list
<ProcessorAutomationInfo
*>::iterator x
;
2028 const std::set
<Evoral::Parameter
>& automatable
= processor
->what_can_be_automated ();
2029 std::set
<Evoral::Parameter
> has_visible_automation
;
2031 processor
->what_has_visible_data(has_visible_automation
);
2033 if (automatable
.empty()) {
2037 for (x
= processor_automation
.begin(); x
!= processor_automation
.end(); ++x
) {
2038 if ((*x
)->processor
== processor
) {
2043 if (x
== processor_automation
.end()) {
2045 rai
= new ProcessorAutomationInfo (processor
);
2046 processor_automation
.push_back (rai
);
2054 /* any older menu was deleted at the top of processors_changed()
2055 when we cleared the subplugin menu.
2058 rai
->menu
= manage (new Menu
);
2059 MenuList
& items
= rai
->menu
->items();
2060 rai
->menu
->set_name ("ArdourContextMenu");
2064 for (std::set
<Evoral::Parameter
>::const_iterator i
= automatable
.begin(); i
!= automatable
.end(); ++i
) {
2066 ProcessorAutomationNode
* pan
;
2067 CheckMenuItem
* mitem
;
2069 string name
= processor
->describe_parameter (*i
);
2071 items
.push_back (CheckMenuElem (name
));
2072 mitem
= dynamic_cast<CheckMenuItem
*> (&items
.back());
2074 _subplugin_menu_map
[*i
] = mitem
;
2076 if (has_visible_automation
.find((*i
)) != has_visible_automation
.end()) {
2077 mitem
->set_active(true);
2080 if ((pan
= find_processor_automation_node (processor
, *i
)) == 0) {
2084 pan
= new ProcessorAutomationNode (*i
, mitem
, *this);
2086 rai
->lines
.push_back (pan
);
2090 pan
->menu_item
= mitem
;
2094 mitem
->signal_toggled().connect (sigc::bind (sigc::mem_fun(*this, &RouteTimeAxisView::processor_menu_item_toggled
), rai
, pan
));
2097 /* add the menu for this processor, because the subplugin
2098 menu is always cleared at the top of processors_changed().
2099 this is the result of some poor design in gtkmm and/or
2103 subplugin_menu
.items().push_back (MenuElem (processor
->name(), *rai
->menu
));
2108 RouteTimeAxisView::processor_menu_item_toggled (RouteTimeAxisView::ProcessorAutomationInfo
* rai
,
2109 RouteTimeAxisView::ProcessorAutomationNode
* pan
)
2111 bool showit
= pan
->menu_item
->get_active();
2112 bool redraw
= false;
2114 if (pan
->view
== 0 && showit
) {
2115 add_processor_automation_curve (rai
->processor
, pan
->what
);
2119 if (pan
->view
&& showit
!= pan
->view
->marked_for_display()) {
2122 pan
->view
->set_marked_for_display (true);
2123 pan
->view
->canvas_display()->show();
2124 pan
->view
->canvas_background()->show();
2126 rai
->processor
->mark_automation_visible (pan
->what
, true);
2127 pan
->view
->set_marked_for_display (false);
2135 if (redraw
&& !no_redraw
) {
2136 _route
->gui_changed ("track_height", (void *) 0); /* EMIT_SIGNAL */
2142 RouteTimeAxisView::processors_changed (RouteProcessorChange c
)
2144 if (c
.type
== RouteProcessorChange::MeterPointChange
) {
2145 /* nothing to do if only the meter point has changed */
2149 using namespace Menu_Helpers
;
2151 for (list
<ProcessorAutomationInfo
*>::iterator i
= processor_automation
.begin(); i
!= processor_automation
.end(); ++i
) {
2152 (*i
)->valid
= false;
2155 _subplugin_menu_map
.clear ();
2156 subplugin_menu
.items().clear ();
2158 _route
->foreach_processor (sigc::mem_fun (*this, &RouteTimeAxisView::add_processor_to_subplugin_menu
));
2159 _route
->foreach_processor (sigc::mem_fun (*this, &RouteTimeAxisView::add_existing_processor_automation_curves
));
2161 bool deleted_processor_automation
= false;
2163 for (list
<ProcessorAutomationInfo
*>::iterator i
= processor_automation
.begin(); i
!= processor_automation
.end(); ) {
2165 list
<ProcessorAutomationInfo
*>::iterator tmp
;
2173 processor_automation
.erase (i
);
2174 deleted_processor_automation
= true;
2181 if (deleted_processor_automation
&& !no_redraw
) {
2182 _route
->gui_changed ("track_height", this);
2186 boost::shared_ptr
<AutomationLine
>
2187 RouteTimeAxisView::find_processor_automation_curve (boost::shared_ptr
<Processor
> processor
, Evoral::Parameter what
)
2189 ProcessorAutomationNode
* pan
;
2191 if ((pan
= find_processor_automation_node (processor
, what
)) != 0) {
2197 return boost::shared_ptr
<AutomationLine
>();
2201 RouteTimeAxisView::reset_processor_automation_curves ()
2203 for (ProcessorAutomationCurves::iterator i
= processor_automation_curves
.begin(); i
!= processor_automation_curves
.end(); ++i
) {
2209 RouteTimeAxisView::update_rec_display ()
2211 RouteUI::update_rec_display ();
2212 name_entry
.set_sensitive (!_route
->record_enabled());
2216 RouteTimeAxisView::set_layer_display (LayerDisplay d
, bool apply_to_selection
)
2218 if (apply_to_selection
) {
2219 _editor
.get_selection().tracks
.foreach_route_time_axis (boost::bind (&RouteTimeAxisView::set_layer_display
, _1
, d
, false));
2223 _view
->set_layer_display (d
);
2227 xml_node
->add_property (N_("layer-display"), enum_2_string (d
));
2232 RouteTimeAxisView::layer_display () const
2235 return _view
->layer_display ();
2238 /* we don't know, since we don't have a _view, so just return something */
2244 boost::shared_ptr
<AutomationTimeAxisView
>
2245 RouteTimeAxisView::automation_child(Evoral::Parameter param
)
2247 AutomationTracks::iterator i
= _automation_tracks
.find(param
);
2248 if (i
!= _automation_tracks
.end()) {
2251 return boost::shared_ptr
<AutomationTimeAxisView
>();
2256 RouteTimeAxisView::fast_update ()
2258 gm
.get_level_meter().update_meters ();
2262 RouteTimeAxisView::hide_meter ()
2265 gm
.get_level_meter().hide_meters ();
2269 RouteTimeAxisView::show_meter ()
2275 RouteTimeAxisView::reset_meter ()
2277 if (Config
->get_show_track_meters()) {
2278 gm
.get_level_meter().setup_meters (height
-5);
2285 RouteTimeAxisView::clear_meter ()
2287 gm
.get_level_meter().clear_meters ();
2291 RouteTimeAxisView::meter_changed ()
2293 ENSURE_GUI_THREAD (*this, &RouteTimeAxisView::meter_changed
)
2298 RouteTimeAxisView::io_changed (IOChange
/*change*/, void */
*src*/
)
2304 RouteTimeAxisView::build_underlay_menu(Gtk::Menu
* parent_menu
)
2306 using namespace Menu_Helpers
;
2308 if (!_underlay_streams
.empty()) {
2309 MenuList
& parent_items
= parent_menu
->items();
2310 Menu
* gs_menu
= manage (new Menu
);
2311 gs_menu
->set_name ("ArdourContextMenu");
2312 MenuList
& gs_items
= gs_menu
->items();
2314 parent_items
.push_back (MenuElem (_("Underlays"), *gs_menu
));
2316 for(UnderlayList::iterator it
= _underlay_streams
.begin(); it
!= _underlay_streams
.end(); ++it
) {
2317 gs_items
.push_back(MenuElem(string_compose(_("Remove \"%1\""), (*it
)->trackview().name()),
2318 sigc::bind(sigc::mem_fun(*this, &RouteTimeAxisView::remove_underlay
), *it
)));
2324 RouteTimeAxisView::set_underlay_state()
2326 if (!underlay_xml_node
) {
2330 XMLNodeList nlist
= underlay_xml_node
->children();
2331 XMLNodeConstIterator niter
;
2332 XMLNode
*child_node
;
2334 for (niter
= nlist
.begin(); niter
!= nlist
.end(); ++niter
) {
2335 child_node
= *niter
;
2337 if (child_node
->name() != "Underlay") {
2341 XMLProperty
* prop
= child_node
->property ("id");
2343 PBD::ID
id (prop
->value());
2345 RouteTimeAxisView
* v
= _editor
.get_route_view_by_route_id (id
);
2348 add_underlay(v
->view(), false);
2357 RouteTimeAxisView::add_underlay (StreamView
* v
, bool update_xml
)
2363 RouteTimeAxisView
& other
= v
->trackview();
2365 if (find(_underlay_streams
.begin(), _underlay_streams
.end(), v
) == _underlay_streams
.end()) {
2366 if (find(other
._underlay_mirrors
.begin(), other
._underlay_mirrors
.end(), this) != other
._underlay_mirrors
.end()) {
2367 fatal
<< _("programming error: underlay reference pointer pairs are inconsistent!") << endmsg
;
2371 _underlay_streams
.push_back(v
);
2372 other
._underlay_mirrors
.push_back(this);
2374 v
->foreach_regionview(sigc::mem_fun(*this, &RouteTimeAxisView::add_ghost
));
2377 if (!underlay_xml_node
) {
2379 underlay_xml_node
= xml_node
->add_child("Underlays");
2382 XMLNode
* node
= underlay_xml_node
->add_child("Underlay");
2383 XMLProperty
* prop
= node
->add_property("id");
2384 prop
->set_value(v
->trackview().route()->id().to_s());
2390 RouteTimeAxisView::remove_underlay (StreamView
* v
)
2396 UnderlayList::iterator it
= find(_underlay_streams
.begin(), _underlay_streams
.end(), v
);
2397 RouteTimeAxisView
& other
= v
->trackview();
2399 if (it
!= _underlay_streams
.end()) {
2400 UnderlayMirrorList::iterator gm
= find(other
._underlay_mirrors
.begin(), other
._underlay_mirrors
.end(), this);
2402 if (gm
== other
._underlay_mirrors
.end()) {
2403 fatal
<< _("programming error: underlay reference pointer pairs are inconsistent!") << endmsg
;
2407 v
->foreach_regionview(sigc::mem_fun(*this, &RouteTimeAxisView::remove_ghost
));
2409 _underlay_streams
.erase(it
);
2410 other
._underlay_mirrors
.erase(gm
);
2412 if (underlay_xml_node
) {
2413 underlay_xml_node
->remove_nodes_and_delete("id", v
->trackview().route()->id().to_s());
2419 RouteTimeAxisView::set_button_names ()
2421 rec_enable_button_label
.set_text (_("r"));
2423 if (_route
&& _route
->solo_safe()) {
2424 solo_button_label
.set_text (X_("!"));
2426 if (Config
->get_solo_control_is_listen_control()) {
2427 switch (Config
->get_listen_position()) {
2428 case AfterFaderListen
:
2429 solo_button_label
.set_text (_("A"));
2431 case PreFaderListen
:
2432 solo_button_label
.set_text (_("P"));
2436 solo_button_label
.set_text (_("s"));
2439 mute_button_label
.set_text (_("m"));
2443 RouteTimeAxisView::automation_child_menu_item (Evoral::Parameter param
)
2445 ParameterMenuMap::iterator i
= _main_automation_menu_map
.find (param
);
2446 if (i
!= _main_automation_menu_map
.end()) {
2450 i
= _subplugin_menu_map
.find (param
);
2451 if (i
!= _subplugin_menu_map
.end()) {
2459 RouteTimeAxisView::create_gain_automation_child (const Evoral::Parameter
& param
, bool show
)
2461 boost::shared_ptr
<AutomationControl
> c
= _route
->gain_control();
2463 error
<< "Route has no gain automation, unable to add automation track view." << endmsg
;
2467 gain_track
.reset (new AutomationTimeAxisView (_session
,
2468 _route
, _route
->amp(), c
, param
,
2473 _route
->amp()->describe_parameter(param
)));
2476 _view
->foreach_regionview (sigc::mem_fun (*gain_track
.get(), &TimeAxisView::add_ghost
));
2479 add_automation_child (Evoral::Parameter(GainAutomation
), gain_track
, show
);
2483 void add_region_to_list (RegionView
* rv
, Playlist::RegionList
* l
)
2485 l
->push_back (rv
->region());
2489 RouteTimeAxisView::combine_regions ()
2491 assert (is_track());
2497 Playlist::RegionList selected_regions
;
2498 boost::shared_ptr
<Playlist
> playlist
= track()->playlist();
2500 _view
->foreach_selected_regionview (sigc::bind (sigc::ptr_fun (add_region_to_list
), &selected_regions
));
2502 if (selected_regions
.size() < 2) {
2506 playlist
->clear_changes ();
2507 boost::shared_ptr
<Region
> compound_region
= playlist
->combine (selected_regions
);
2509 _session
->add_command (new StatefulDiffCommand (playlist
));
2510 /* make the new region be selected */
2512 return _view
->find_view (compound_region
);
2516 RouteTimeAxisView::uncombine_regions ()
2518 assert (is_track());
2524 Playlist::RegionList selected_regions
;
2525 boost::shared_ptr
<Playlist
> playlist
= track()->playlist();
2527 /* have to grab selected regions first because the uncombine is going
2528 * to change that in the middle of the list traverse
2531 _view
->foreach_selected_regionview (sigc::bind (sigc::ptr_fun (add_region_to_list
), &selected_regions
));
2533 playlist
->clear_changes ();
2535 for (Playlist::RegionList::iterator i
= selected_regions
.begin(); i
!= selected_regions
.end(); ++i
) {
2536 playlist
->uncombine (*i
);
2539 _session
->add_command (new StatefulDiffCommand (playlist
));