#include <algorithm>
#include <string>
#include <vector>
-#include <map>
+#include <map>
#include <utility>
#include <sigc++/bind.h>
#include "evoral/Parameter.hpp"
#include "ardour_ui.h"
+#include "debug.h"
+#include "global_signals.h"
#include "route_time_axis.h"
#include "automation_time_axis.h"
#include "canvas_impl.h"
, playlist_button (_("p"))
, automation_button (_("a"))
, gm (sess, slider, true, 115)
- , _ignore_track_mode_change (false)
{
gm.set_controls (_route, _route->shared_peak_meter(), _route->amp());
gm.get_level_meter().set_no_show_all();
gm.get_level_meter().setup_meters(50);
_has_state = true;
- playlist_menu = 0;
playlist_action_menu = 0;
automation_action_menu = 0;
plugins_submenu_item = 0;
timestretch_rect = 0;
no_redraw = false;
- destructive_track_mode_item = 0;
- normal_track_mode_item = 0;
- non_layered_track_mode_item = 0;
ignore_toggle = false;
playlist_button.unset_flags (Gtk::CAN_FOCUS);
automation_button.unset_flags (Gtk::CAN_FOCUS);
- route_group_button.signal_button_release_event().connect (sigc::mem_fun(*this, &RouteTimeAxisView::edit_click), false);
+ route_group_button.signal_button_release_event().connect (sigc::mem_fun(*this, &RouteTimeAxisView::route_group_click), false);
playlist_button.signal_clicked().connect (sigc::mem_fun(*this, &RouteTimeAxisView::playlist_click));
automation_button.signal_clicked().connect (sigc::mem_fun(*this, &RouteTimeAxisView::automation_click));
if (is_track()) {
- track()->TrackModeChanged.connect (*this, invalidator (*this), boost::bind (&RouteTimeAxisView::track_mode_changed, this), gui_context());
track()->FreezeChange.connect (*this, invalidator (*this), boost::bind (&RouteTimeAxisView::map_frozen, this), gui_context());
track()->SpeedChanged.connect (*this, invalidator (*this), boost::bind (&RouteTimeAxisView::speed_changed, this), gui_context());
}
_editor.ZoomChanged.connect (sigc::mem_fun(*this, &RouteTimeAxisView::reset_samples_per_unit));
+ _editor.HorizontalPositionChanged.connect (sigc::mem_fun (*this, &RouteTimeAxisView::horizontal_position_changed));
ColorsChanged.connect (sigc::mem_fun (*this, &RouteTimeAxisView::color_handler));
PropertyList* plist = new PropertyList();
plist->add (ARDOUR::Properties::solo, true);
route_group_menu = new RouteGroupMenu (_session, plist);
- route_group_menu->GroupSelected.connect (sigc::mem_fun (*this, &RouteTimeAxisView::set_route_group_from_menu));
gm.get_gain_slider().signal_scroll_event().connect(sigc::mem_fun(*this, &RouteTimeAxisView::controls_ebox_scroll), false);
gm.get_gain_slider().set_name ("TrackGainFader");
+
+ show_name_entry ();
+ hide_name_label ();
}
RouteTimeAxisView::~RouteTimeAxisView ()
delete *i;
}
- delete playlist_menu;
- playlist_menu = 0;
-
delete playlist_action_menu;
playlist_action_menu = 0;
}
gint
-RouteTimeAxisView::edit_click (GdkEventButton *ev)
+RouteTimeAxisView::route_group_click (GdkEventButton *ev)
{
if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
if (_route->route_group()) {
return false;
}
- route_group_menu->rebuild (_route->route_group ());
- route_group_menu->popup (ev->button, ev->time);
+ WeakRouteList r;
+ r.push_back (route ());
+
+ route_group_menu->build (r);
+ route_group_menu->menu()->popup (ev->button, ev->time);
return false;
}
-void
-RouteTimeAxisView::set_route_group_from_menu (RouteGroup *eg)
-{
- if (eg) {
- eg->add (_route);
- } else {
- if (_route->route_group()) {
- _route->route_group()->remove (_route);
- }
- }
-}
-
void
RouteTimeAxisView::playlist_changed ()
{
name_entry.set_text (x);
}
+ if (x != name_label.get_text()) {
+ name_label.set_text (x);
+ }
+
ARDOUR_UI::instance()->set_tip (name_entry, x);
}
void
RouteTimeAxisView::take_name_changed (void *src)
-
{
if (src != this) {
label_view ();
void
RouteTimeAxisView::playlist_click ()
{
- // always build a new action menu
-
- delete playlist_action_menu;
-
- playlist_action_menu = new Menu;
- playlist_action_menu->set_name ("ArdourContextMenu");
-
- build_playlist_menu (playlist_action_menu);
-
+ build_playlist_menu ();
conditionally_add_to_selection ();
playlist_action_menu->popup (1, gtk_get_current_event_time());
}
RouteTimeAxisView::automation_click ()
{
conditionally_add_to_selection ();
- build_automation_action_menu ();
+ build_automation_action_menu (false);
automation_action_menu->popup (1, gtk_get_current_event_time());
}
}
void
-RouteTimeAxisView::build_automation_action_menu ()
+RouteTimeAxisView::build_automation_action_menu (bool for_selection)
{
using namespace Menu_Helpers;
automation_action_menu->set_name ("ArdourContextMenu");
items.push_back (MenuElem (_("Show All Automation"),
- sigc::mem_fun(*this, &RouteTimeAxisView::show_all_automation)));
+ sigc::bind (sigc::mem_fun (*this, &RouteTimeAxisView::show_all_automation), for_selection)));
items.push_back (MenuElem (_("Show Existing Automation"),
- sigc::mem_fun(*this, &RouteTimeAxisView::show_existing_automation)));
+ sigc::bind (sigc::mem_fun (*this, &RouteTimeAxisView::show_existing_automation), for_selection)));
items.push_back (MenuElem (_("Hide All Automation"),
- sigc::mem_fun(*this, &RouteTimeAxisView::hide_all_automation)));
+ sigc::bind (sigc::mem_fun (*this, &RouteTimeAxisView::hide_all_automation), for_selection)));
items.push_back (SeparatorElem ());
so it was detached above */
items.push_back (MenuElem (_("Plugins"), subplugin_menu));
- items.back().set_sensitive (!subplugin_menu.items().empty());
+ items.back().set_sensitive (!subplugin_menu.items().empty() && (!for_selection || _editor.get_selection().tracks.size() == 1));;
}
void
MenuList& items = display_menu->items();
display_menu->set_name ("ArdourContextMenu");
- items.push_back (MenuElem (_("Color..."), sigc::mem_fun(*this, &RouteTimeAxisView::select_track_color)));
+ items.push_back (MenuElem (_("Color..."), sigc::mem_fun (*this, &RouteUI::choose_color)));
+
+ if (_size_menu) {
+ detach_menu (*_size_menu);
+ }
+ build_size_menu ();
+ items.push_back (MenuElem (_("Height"), *_size_menu));
items.push_back (SeparatorElem());
if (!Profile->get_sae()) {
items.push_back (MenuElem (_("Remote Control ID..."), sigc::mem_fun (*this, &RouteUI::open_remote_control_id_dialog)));
- /* rebuild this every time */
- build_automation_action_menu ();
- detach_menu (*automation_action_menu);
- items.push_back (MenuElem (_("Automation"), *automation_action_menu));
+ items.back().set_sensitive (_editor.get_selection().tracks.size() <= 1);
items.push_back (SeparatorElem());
}
// Hook for derived classes to add type specific stuff
append_extra_display_menu_items ();
- items.push_back (SeparatorElem());
if (is_track()) {
- Menu *layers_menu = manage(new Menu);
+ Menu* layers_menu = manage (new Menu);
MenuList &layers_items = layers_menu->items();
layers_menu->set_name("ArdourContextMenu");
RadioMenuItem::Group layers_group;
- layers_items.push_back(RadioMenuElem (layers_group, _("Overlaid"),
- sigc::bind (sigc::mem_fun (*this, &RouteTimeAxisView::set_layer_display), Overlaid)));
- layers_items.push_back(RadioMenuElem (layers_group, _("Stacked"),
- sigc::bind (sigc::mem_fun (*this, &RouteTimeAxisView::set_layer_display), Stacked)));
+ /* Find out how many overlaid/stacked tracks we have in the selection */
+
+ int overlaid = 0;
+ int stacked = 0;
+ TrackSelection const & s = _editor.get_selection().tracks;
+ for (TrackSelection::const_iterator i = s.begin(); i != s.end(); ++i) {
+ StreamView* v = (*i)->view ();
+ if (!v) {
+ continue;
+ }
+
+ switch (v->layer_display ()) {
+ case Overlaid:
+ ++overlaid;
+ break;
+ case Stacked:
+ ++stacked;
+ break;
+ }
+ }
+ /* We're not connecting to signal_toggled() here; in the case where these two items are
+ set to be in the `inconsistent' state, it seems that one or other will end up active
+ as well as inconsistent (presumably due to the RadioMenuItem::Group). Then when you
+ select the active one, no toggled signal is emitted so nothing happens.
+ */
+
+ layers_items.push_back (RadioMenuElem (layers_group, _("Overlaid")));
+ RadioMenuItem* i = dynamic_cast<RadioMenuItem*> (&layers_items.back ());
+ i->set_active (overlaid != 0 && stacked == 0);
+ i->set_inconsistent (overlaid != 0 && stacked != 0);
+ i->signal_activate().connect (sigc::bind (sigc::mem_fun (*this, &RouteTimeAxisView::set_layer_display), Overlaid, true));
+
+ layers_items.push_back (
+ RadioMenuElem (layers_group, _("Stacked"),
+ sigc::bind (sigc::mem_fun (*this, &RouteTimeAxisView::set_layer_display), Stacked, true))
+ );
+
+ i = dynamic_cast<RadioMenuItem*> (&layers_items.back ());
+ i->signal_activate().connect (sigc::bind (sigc::mem_fun (*this, &RouteTimeAxisView::set_layer_display), Stacked, true));
+ i->set_active (overlaid == 0 && stacked != 0);
+ i->set_inconsistent (overlaid != 0 && stacked != 0);
+
items.push_back (MenuElem (_("Layers"), *layers_menu));
- Menu* alignment_menu = manage (new Menu);
- MenuList& alignment_items = alignment_menu->items();
- alignment_menu->set_name ("ArdourContextMenu");
+ if (!Profile->get_sae()) {
- RadioMenuItem::Group align_group;
+ Menu* alignment_menu = manage (new Menu);
+ MenuList& alignment_items = alignment_menu->items();
+ alignment_menu->set_name ("ArdourContextMenu");
+
+ RadioMenuItem::Group align_group;
- alignment_items.push_back (RadioMenuElem (align_group, _("Align With Existing Material"),
- sigc::bind (sigc::mem_fun(*this, &RouteTimeAxisView::set_align_style), ExistingMaterial)));
- align_existing_item = dynamic_cast<RadioMenuItem*>(&alignment_items.back());
- if (track()->alignment_style() == ExistingMaterial) {
- align_existing_item->set_active();
- }
+ /* Same verbose hacks as for the layering options above */
- alignment_items.push_back (RadioMenuElem (align_group, _("Align With Capture Time"),
- sigc::bind (sigc::mem_fun(*this, &RouteTimeAxisView::set_align_style), CaptureTime)));
- align_capture_item = dynamic_cast<RadioMenuItem*>(&alignment_items.back());
- if (track()->alignment_style() == CaptureTime) {
- align_capture_item->set_active();
- }
+ int existing = 0;
+ int capture = 0;
+ int automatic = 0;
+ int styles = 0;
+ boost::shared_ptr<Track> first_track;
- if (!Profile->get_sae()) {
+ TrackSelection const & s = _editor.get_selection().tracks;
+ for (TrackSelection::const_iterator i = s.begin(); i != s.end(); ++i) {
+ RouteTimeAxisView* r = dynamic_cast<RouteTimeAxisView*> (*i);
+ if (!r || !r->is_track ()) {
+ continue;
+ }
- items.push_back (MenuElem (_("Alignment"), *alignment_menu));
- track()->AlignmentStyleChanged.connect (route_connections, invalidator (*this), boost::bind (&RouteTimeAxisView::align_style_changed, this), gui_context());
+ if (!first_track) {
+ first_track = r->track();
+ }
+
+ switch (r->track()->alignment_choice()) {
+ case Automatic:
+ ++automatic;
+ styles |= 0x1;
+ switch (r->track()->alignment_style()) {
+ case ExistingMaterial:
+ ++existing;
+ break;
+ case CaptureTime:
+ ++capture;
+ break;
+ }
+ break;
+ case UseExistingMaterial:
+ ++existing;
+ styles |= 0x2;
+ break;
+ case UseCaptureTime:
+ ++capture;
+ styles |= 0x4;
+ break;
+ }
+ }
- RadioMenuItem::Group mode_group;
- items.push_back (RadioMenuElem (mode_group, _("Normal Mode"), sigc::bind (
- sigc::mem_fun (*this, &RouteTimeAxisView::set_track_mode),
- ARDOUR::Normal)));
- normal_track_mode_item = dynamic_cast<RadioMenuItem*>(&items.back());
+ bool inconsistent;
+ switch (styles) {
+ case 1:
+ case 2:
+ case 4:
+ inconsistent = false;
+ break;
+ default:
+ inconsistent = true;
+ break;
+ }
- items.push_back (RadioMenuElem (mode_group, _("Tape Mode"), sigc::bind (
- sigc::mem_fun (*this, &RouteTimeAxisView::set_track_mode),
- ARDOUR::Destructive)));
- destructive_track_mode_item = dynamic_cast<RadioMenuItem*>(&items.back());
+ RadioMenuItem* i;
+
+ if (!inconsistent && first_track) {
+
+ alignment_items.push_back (RadioMenuElem (align_group, _("Automatic (based on I/O connections)")));
+ i = dynamic_cast<RadioMenuItem*> (&alignment_items.back());
+ i->set_active (automatic != 0 && existing == 0 && capture == 0);
+ i->signal_activate().connect (sigc::bind (sigc::mem_fun(*this, &RouteTimeAxisView::set_align_choice), i, Automatic, true));
+
+ switch (first_track->alignment_choice()) {
+ case Automatic:
+ switch (first_track->alignment_style()) {
+ case ExistingMaterial:
+ alignment_items.push_back (MenuElem (_("(Currently: Existing Material)")));
+ break;
+ case CaptureTime:
+ alignment_items.push_back (MenuElem (_("(Currently: Capture Time)")));
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+
+ alignment_items.push_back (RadioMenuElem (align_group, _("Align With Existing Material")));
+ i = dynamic_cast<RadioMenuItem*> (&alignment_items.back());
+ i->set_active (existing != 0 && capture == 0 && automatic == 0);
+ i->signal_activate().connect (sigc::bind (sigc::mem_fun(*this, &RouteTimeAxisView::set_align_choice), i, UseExistingMaterial, true));
+
+ alignment_items.push_back (RadioMenuElem (align_group, _("Align With Capture Time")));
+ i = dynamic_cast<RadioMenuItem*> (&alignment_items.back());
+ i->set_active (existing == 0 && capture != 0 && automatic == 0);
+ i->signal_activate().connect (sigc::bind (sigc::mem_fun(*this, &RouteTimeAxisView::set_align_choice), i, UseCaptureTime, true));
+
+ items.push_back (MenuElem (_("Alignment"), *alignment_menu));
+
+ } else {
+ /* show nothing */
+ }
- items.push_back (RadioMenuElem (mode_group, _("Non-Layered Mode"),
- sigc::bind (sigc::mem_fun (*this, &RouteTimeAxisView::set_track_mode), ARDOUR::NonLayered)));
- non_layered_track_mode_item = dynamic_cast<RadioMenuItem*>(&items.back());
+ Menu* mode_menu = manage (new Menu);
+ MenuList& mode_items = mode_menu->items ();
+ mode_menu->set_name ("ArdourContextMenu");
+ RadioMenuItem::Group mode_group;
- _ignore_track_mode_change = true;
-
- switch (track()->mode()) {
- case ARDOUR::Destructive:
- destructive_track_mode_item->set_active ();
- break;
- case ARDOUR::Normal:
- normal_track_mode_item->set_active ();
- break;
- case ARDOUR::NonLayered:
- non_layered_track_mode_item->set_active ();
- break;
+ int normal = 0;
+ int tape = 0;
+ int non_layered = 0;
+
+ for (TrackSelection::const_iterator i = s.begin(); i != s.end(); ++i) {
+ RouteTimeAxisView* r = dynamic_cast<RouteTimeAxisView*> (*i);
+ if (!r || !r->is_track ()) {
+ continue;
+ }
+
+ switch (r->track()->mode()) {
+ case Normal:
+ ++normal;
+ break;
+ case Destructive:
+ ++tape;
+ break;
+ case NonLayered:
+ ++non_layered;
+ break;
+ }
}
-
- _ignore_track_mode_change = false;
- }
- track()->AlignmentStyleChanged.connect (route_connections, invalidator (*this), boost::bind (&RouteTimeAxisView::align_style_changed, this), gui_context());
+ mode_items.push_back (RadioMenuElem (mode_group, _("Normal Mode")));
+ i = dynamic_cast<RadioMenuItem*> (&mode_items.back ());
+ i->signal_activate().connect (sigc::bind (sigc::mem_fun (*this, &RouteTimeAxisView::set_track_mode), ARDOUR::Normal, true));
+ i->set_active (normal != 0 && tape == 0 && non_layered == 0);
+ i->set_inconsistent (normal != 0 && (tape != 0 || non_layered != 0));
+
+ mode_items.push_back (RadioMenuElem (mode_group, _("Tape Mode")));
+ i = dynamic_cast<RadioMenuItem*> (&mode_items.back ());
+ i->signal_activate().connect (sigc::bind (sigc::mem_fun (*this, &RouteTimeAxisView::set_track_mode), ARDOUR::Destructive, true));
+ i->set_active (normal == 0 && tape != 0 && non_layered == 0);
+ i->set_inconsistent (tape != 0 && (normal != 0 || non_layered != 0));
+
+ mode_items.push_back (RadioMenuElem (mode_group, _("Non-Layered Mode")));
+ i = dynamic_cast<RadioMenuItem*> (&mode_items.back ());
+ i->signal_activate().connect (sigc::bind (sigc::mem_fun (*this, &RouteTimeAxisView::set_track_mode), ARDOUR::NonLayered, true));
+ i->set_active (normal == 0 && tape == 0 && non_layered != 0);
+ i->set_inconsistent (non_layered != 0 && (normal != 0 || tape != 0));
+
+ items.push_back (MenuElem (_("Mode"), *mode_menu));
+ }
color_mode_menu = build_color_mode_menu();
if (color_mode_menu) {
}
items.push_back (SeparatorElem());
- }
- items.push_back (CheckMenuElem (_("Active"), sigc::mem_fun(*this, &RouteUI::toggle_route_active)));
- route_active_menu_item = dynamic_cast<CheckMenuItem *> (&items.back());
- route_active_menu_item->set_active (_route->active());
+ build_playlist_menu ();
+ items.push_back (MenuElem (_("Playlist"), *playlist_action_menu));
+ items.back().set_sensitive (_editor.get_selection().tracks.size() <= 1);
- items.push_back (SeparatorElem());
- items.push_back (MenuElem (_("Hide"), sigc::bind (sigc::mem_fun(_editor, &PublicEditor::hide_track_in_display), this, false)));
- if (!Profile->get_sae()) {
- items.push_back (MenuElem (_("Remove"), sigc::mem_fun(*this, &RouteUI::remove_this_route)));
- } else {
- items.push_front (SeparatorElem());
- items.push_front (MenuElem (_("Delete"), sigc::mem_fun(*this, &RouteUI::remove_this_route)));
- }
-}
+ route_group_menu->detach ();
-static bool __reset_item (RadioMenuItem* item, RadioMenuItem* item_2)
-{
- item->set_active ();
- item_2->set_active ();
- return false;
-}
+ WeakRouteList r;
+ for (TrackSelection::iterator i = _editor.get_selection().tracks.begin(); i != _editor.get_selection().tracks.end(); ++i) {
+ RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (*i);
+ if (rtv) {
+ r.push_back (rtv->route ());
+ }
+ }
-void
-RouteTimeAxisView::set_track_mode (TrackMode mode)
-{
- if (_ignore_track_mode_change) {
- return;
- }
-
- RadioMenuItem* item;
- RadioMenuItem* other_item;
- RadioMenuItem* other_item_2;
-
- switch (mode) {
- case ARDOUR::Normal:
- item = normal_track_mode_item;
- other_item = non_layered_track_mode_item;
- other_item_2 = destructive_track_mode_item;
- break;
- case ARDOUR::NonLayered:
- item = non_layered_track_mode_item;
- other_item = normal_track_mode_item;
- other_item_2 = destructive_track_mode_item;
- break;
- case ARDOUR::Destructive:
- item = destructive_track_mode_item;
- other_item = normal_track_mode_item;
- other_item_2 = non_layered_track_mode_item;
- break;
- default:
- fatal << string_compose (_("programming error: %1 %2"), "illegal track mode in RouteTimeAxisView::set_track_mode", mode) << endmsg;
- /*NOTREACHED*/
- return;
- }
+ if (r.empty ()) {
+ r.push_back (route ());
+ }
+
+ route_group_menu->build (r);
+ items.push_back (MenuElem (_("Route Group"), *route_group_menu->menu ()));
- if (item && other_item && other_item_2 && track()->mode() != mode) {
- _set_track_mode (track().get(), mode, other_item, other_item_2);
- }
-}
+ build_automation_action_menu (true);
+ items.push_back (MenuElem (_("Automation"), *automation_action_menu));
-void
-RouteTimeAxisView::_set_track_mode (Track* track, TrackMode mode, RadioMenuItem* reset_item, RadioMenuItem* reset_item_2)
-{
- bool needs_bounce;
+ items.push_back (SeparatorElem());
+ }
- if (!track->can_use_mode (mode, needs_bounce)) {
+ int active = 0;
+ int inactive = 0;
+ TrackSelection const & s = _editor.get_selection().tracks;
+ for (TrackSelection::const_iterator i = s.begin(); i != s.end(); ++i) {
+ RouteTimeAxisView* r = dynamic_cast<RouteTimeAxisView*> (*i);
+ if (!r) {
+ continue;
+ }
- if (!needs_bounce) {
- /* cannot be done */
- Glib::signal_idle().connect (sigc::bind (sigc::ptr_fun (__reset_item), reset_item, reset_item_2));
- return;
+ if (r->route()->active()) {
+ ++active;
} else {
- cerr << "would bounce this one\n";
- /* XXX: radio menu item becomes inconsistent with track state in this case */
- return;
+ ++inactive;
}
}
- track->set_mode (mode);
-
- rec_enable_button->remove ();
-
- switch (mode) {
- case ARDOUR::NonLayered:
- case ARDOUR::Normal:
- rec_enable_button->add (*(manage (new Image (::get_icon (X_("record_normal_red"))))));
- break;
- case ARDOUR::Destructive:
- rec_enable_button->add (*(manage (new Image (::get_icon (X_("record_tape_red"))))));
- break;
+ items.push_back (CheckMenuElem (_("Active")));
+ CheckMenuItem* i = dynamic_cast<CheckMenuItem *> (&items.back());
+ bool click_sets_active = true;
+ if (active > 0 && inactive == 0) {
+ i->set_active (true);
+ click_sets_active = false;
+ } else if (active > 0 && inactive > 0) {
+ i->set_inconsistent (true);
}
+ i->signal_activate().connect (sigc::bind (sigc::mem_fun (*this, &RouteUI::set_route_active), click_sets_active, true));
- rec_enable_button->show_all ();
+ items.push_back (SeparatorElem());
+ items.push_back (MenuElem (_("Hide"), sigc::bind (sigc::mem_fun(_editor, &PublicEditor::hide_track_in_display), this, true)));
+ if (!Profile->get_sae()) {
+ items.push_back (MenuElem (_("Remove"), sigc::bind (sigc::mem_fun(*this, &RouteUI::remove_this_route), true)));
+ } else {
+ items.push_front (SeparatorElem());
+ items.push_front (MenuElem (_("Delete"), sigc::bind (sigc::mem_fun(*this, &RouteUI::remove_this_route), true)));
+ }
}
void
-RouteTimeAxisView::track_mode_changed ()
+RouteTimeAxisView::set_track_mode (TrackMode mode, bool apply_to_selection)
{
- RadioMenuItem* item;
+ if (apply_to_selection) {
+ _editor.get_selection().tracks.foreach_route_time_axis (boost::bind (&RouteTimeAxisView::set_track_mode, _1, mode, false));
+ } else {
- switch (track()->mode()) {
- case ARDOUR::Normal:
- item = normal_track_mode_item;
- break;
- case ARDOUR::NonLayered:
- item = non_layered_track_mode_item;
- break;
- case ARDOUR::Destructive:
- item = destructive_track_mode_item;
- break;
- default:
- fatal << string_compose (_("programming error: %1 %2"), "illegal track mode in RouteTimeAxisView::set_track_mode", track()->mode()) << endmsg;
- /*NOTREACHED*/
- return;
- }
+ bool needs_bounce;
- item->set_active ();
+ if (!track()->can_use_mode (mode, needs_bounce)) {
+
+ if (!needs_bounce) {
+ /* cannot be done */
+ return;
+ } else {
+ cerr << "would bounce this one\n";
+ return;
+ }
+ }
+
+ track()->set_mode (mode);
+
+ rec_enable_button->remove ();
+
+ switch (mode) {
+ case ARDOUR::NonLayered:
+ case ARDOUR::Normal:
+ rec_enable_button->add (*(manage (new Image (::get_icon (X_("record_normal_red"))))));
+ break;
+ case ARDOUR::Destructive:
+ rec_enable_button->add (*(manage (new Image (::get_icon (X_("record_tape_red"))))));
+ break;
+ }
+
+ rec_enable_button->show_all ();
+ }
}
void
-RouteTimeAxisView::show_timestretch (nframes_t start, nframes_t end)
+RouteTimeAxisView::show_timestretch (framepos_t start, framepos_t end)
{
double x1;
double x2;
xml_node->add_property ("height", buf);
if (height >= preset_height (HeightNormal)) {
+
+ _controls_padding_table.set_row_spacings (2);
+
reset_meter();
- show_name_entry ();
- hide_name_label ();
gm.get_gain_slider().show();
mute_button->show();
} else if (height >= preset_height (HeightSmaller)) {
+ _controls_padding_table.set_row_spacings (2);
+
reset_meter();
- show_name_entry ();
- hide_name_label ();
gm.get_gain_slider().hide();
mute_button->show();
} else {
-
- /* don't allow name_entry to be hidden while
- it has focus, otherwise the GUI becomes unusable.
- */
-
- if (name_entry.has_focus()) {
- if (name_entry.get_text() != _route->name()) {
- name_entry_changed ();
- }
- controls_ebox.grab_focus ();
- }
-
- hide_name_entry ();
- show_name_label ();
-
- gm.get_gain_slider().hide();
- mute_button->hide();
- solo_button->hide();
- if (rec_enable_button)
- rec_enable_button->hide();
-
- route_group_button.hide ();
- automation_button.hide ();
- playlist_button.hide ();
- name_label.set_text (_route->name());
+ _controls_padding_table.set_row_spacings (0);
+
}
if (height_changed && !no_redraw) {
}
void
-RouteTimeAxisView::select_track_color ()
+RouteTimeAxisView::set_color (Gdk::Color const & c)
{
- if (RouteUI::choose_color ()) {
-
- if (_view) {
- _view->apply_color (_color, StreamView::RegionColor);
- }
+ RouteUI::set_color (c);
+
+ if (_view) {
+ _view->apply_color (_color, StreamView::RegionColor);
}
}
set_samples_per_unit (_editor.get_current_zoom());
}
+void
+RouteTimeAxisView::horizontal_position_changed ()
+{
+ if (_view) {
+ _view->horizontal_position_changed ();
+ }
+}
+
void
RouteTimeAxisView::set_samples_per_unit (double spu)
{
}
void
-RouteTimeAxisView::align_style_changed ()
+RouteTimeAxisView::set_align_choice (RadioMenuItem* mitem, AlignChoice choice, bool apply_to_selection)
{
- switch (track()->alignment_style()) {
- case ExistingMaterial:
- if (!align_existing_item->get_active()) {
- align_existing_item->set_active();
- }
- break;
- case CaptureTime:
- if (!align_capture_item->get_active()) {
- align_capture_item->set_active();
- }
- break;
- }
-}
-
-void
-RouteTimeAxisView::set_align_style (AlignStyle style)
-{
- RadioMenuItem* item;
-
- switch (style) {
- case ExistingMaterial:
- item = align_existing_item;
- break;
- case CaptureTime:
- item = align_capture_item;
- break;
- default:
- fatal << string_compose (_("programming error: %1 %2"), "illegal align style in RouteTimeAxisView::set_align_style", style) << endmsg;
- /*NOTREACHED*/
- return;
- }
+ if (!mitem->get_active()) {
+ /* this is one of the two calls made when these radio menu items change status. this one
+ is for the item that became inactive, and we want to ignore it.
+ */
+ return;
+ }
- if (item->get_active()) {
- track()->set_align_style (style);
+ if (apply_to_selection) {
+ _editor.get_selection().tracks.foreach_route_time_axis (boost::bind (&RouteTimeAxisView::set_align_choice, _1, mitem, choice, false));
+ } else {
+ if (track ()) {
+ track()->set_align_choice (choice);
+ }
}
}
name = pl->name();
- if (route_group() && route_group()->is_active()) {
+ if (route_group() && route_group()->is_active() && route_group()->enabled_property (ARDOUR::Properties::edit.property_id)) {
name = resolve_new_group_playlist_name(name, playlists_before_op);
}
name = pl->name();
- if (route_group() && route_group()->is_active()) {
+ if (route_group() && route_group()->is_active() && route_group()->enabled_property (ARDOUR::Properties::edit.property_id)) {
name = resolve_new_group_playlist_name(name,playlists_before_op);
}
* @param results List to add things to.
*/
void
-RouteTimeAxisView::get_selectables (nframes_t start, nframes_t end, double top, double bot, list<Selectable*>& results)
+RouteTimeAxisView::get_selectables (framepos_t start, framepos_t end, double top, double bot, list<Selectable*>& results)
{
double speed = 1.0;
speed = track()->speed();
}
- nframes_t start_adjusted = session_frame_to_track_frame(start, speed);
- nframes_t end_adjusted = session_frame_to_track_frame(end, speed);
+ framepos_t const start_adjusted = session_frame_to_track_frame(start, speed);
+ framepos_t const end_adjusted = session_frame_to_track_frame(end, speed);
if ((_view && ((top < 0.0 && bot < 0.0))) || touched (top, bot)) {
_view->get_selectables (start_adjusted, end_adjusted, top, bot, results);
ARDOUR_UI::instance()->popup_error (_("A track already exists with that name"));
name_entry.set_text (_route->name());
} else if (_session->route_name_internal (x)) {
- ARDOUR_UI::instance()->popup_error (_("You cannot create a track with that name as it is reserved for Ardour"));
+ ARDOUR_UI::instance()->popup_error (string_compose (_("You cannot create a track with that name as it is reserved for %1"),
+ PROGRAM_NAME));
name_entry.set_text (_route->name());
} else {
_route->set_name (x);
}
boost::shared_ptr<Region>
-RouteTimeAxisView::find_next_region (nframes_t pos, RegionPoint point, int32_t dir)
+RouteTimeAxisView::find_next_region (framepos_t pos, RegionPoint point, int32_t dir)
{
boost::shared_ptr<Playlist> pl = playlist ();
return boost::shared_ptr<Region> ();
}
-nframes64_t
-RouteTimeAxisView::find_next_region_boundary (nframes64_t pos, int32_t dir)
+framepos_t
+RouteTimeAxisView::find_next_region_boundary (framepos_t pos, int32_t dir)
{
boost::shared_ptr<Playlist> pl = playlist ();
return -1;
}
-bool
+void
RouteTimeAxisView::cut_copy_clear (Selection& selection, CutCopyOp op)
{
boost::shared_ptr<Playlist> what_we_got;
boost::shared_ptr<Track> tr = track ();
boost::shared_ptr<Playlist> playlist;
- bool ret = false;
if (tr == 0) {
/* route is a bus, not a track */
- return false;
+ return;
}
playlist = tr->playlist();
}
}
- playlist->clear_history ();
- playlist->clear_owned_history ();
+ playlist->clear_changes ();
+ playlist->clear_owned_changes ();
switch (op) {
case Cut:
if ((what_we_got = playlist->cut (time)) != 0) {
_editor.get_cut_buffer().add (what_we_got);
- vector<StatefulDiffCommand*> cmds;
-
+ vector<Command*> cmds;
playlist->rdiff (cmds);
-
- for (vector<StatefulDiffCommand*>::iterator c = cmds.begin(); c != cmds.end(); ++c) {
- _session->add_command (*c);
- }
+ _session->add_commands (cmds);
+
_session->add_command (new StatefulDiffCommand (playlist));
- ret = true;
}
break;
case Copy:
case Clear:
if ((what_we_got = playlist->cut (time)) != 0) {
- vector<StatefulDiffCommand*> cmds;
-
+
+ vector<Command*> cmds;
playlist->rdiff (cmds);
-
- for (vector<StatefulDiffCommand*>::iterator c = cmds.begin(); c != cmds.end(); ++c) {
- _session->add_command (*c);
- }
+ _session->add_commands (cmds);
_session->add_command (new StatefulDiffCommand (playlist));
what_we_got->release ();
- ret = true;
}
break;
}
-
- return ret;
}
bool
-RouteTimeAxisView::paste (nframes_t pos, float times, Selection& selection, size_t nth)
+RouteTimeAxisView::paste (framepos_t pos, float times, Selection& selection, size_t nth)
{
if (!is_track()) {
return false;
return false;
}
+ DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("paste to %1\n", pos));
+
if (track()->speed() != 1.0f) {
pos = session_frame_to_track_frame (pos, track()->speed());
+ DEBUG_TRACE (DEBUG::CutNPaste, string_compose ("modified paste to %1\n", pos));
}
- pl->clear_history ();
+ pl->clear_changes ();
pl->paste (*p, pos, times);
_session->add_command (new StatefulDiffCommand (pl));
}
-TimeAxisView::Children
-RouteTimeAxisView::get_child_list()
-{
- TimeAxisView::Children redirect_children;
-
- for (Children::iterator i = children.begin(); i != children.end(); ++i) {
- if (!(*i)->hidden()) {
- redirect_children.push_back(*i);
- }
- }
- return redirect_children;
-}
-
-
struct PlaylistSorter {
bool operator() (boost::shared_ptr<Playlist> a, boost::shared_ptr<Playlist> b) const {
return a->sort_id() < b->sort_id();
};
void
-RouteTimeAxisView::build_playlist_menu (Gtk::Menu * menu)
+RouteTimeAxisView::build_playlist_menu ()
{
using namespace Menu_Helpers;
- if (!menu || !is_track()) {
+ if (!is_track()) {
return;
}
- MenuList& playlist_items = menu->items();
- menu->set_name ("ArdourContextMenu");
- playlist_items.clear();
-
- delete playlist_menu;
+ delete playlist_action_menu;
+ playlist_action_menu = new Menu;
+ playlist_action_menu->set_name ("ArdourContextMenu");
+ MenuList& playlist_items = playlist_action_menu->items();
+ playlist_action_menu->set_name ("ArdourContextMenu");
+ playlist_items.clear();
vector<boost::shared_ptr<Playlist> > playlists, playlists_tr;
boost::shared_ptr<Track> tr = track();
playlist_items.push_back (MenuElem (_("Rename..."), sigc::mem_fun(*this, &RouteTimeAxisView::rename_current_playlist)));
playlist_items.push_back (SeparatorElem());
- if (!route_group() || !route_group()->is_active()) {
+ if (!route_group() || !route_group()->is_active() || !route_group()->enabled_property (ARDOUR::Properties::edit.property_id)) {
playlist_items.push_back (MenuElem (_("New..."), sigc::bind(sigc::mem_fun(_editor, &PublicEditor::new_playlists), this)));
playlist_items.push_back (MenuElem (_("New Copy..."), sigc::bind(sigc::mem_fun(_editor, &PublicEditor::copy_playlists), this)));
}
track()->use_playlist (apl);
- if (route_group() && route_group()->is_active()) {
- std::string group_string = "."+route_group()->name()+".";
+ RouteGroup* rg = route_group();
+
+ if (rg && rg->is_active() && rg->enabled_property (ARDOUR::Properties::edit.property_id)) {
+ std::string group_string = "." + rg->name() + ".";
std::string take_name = apl->name();
std::string::size_type idx = take_name.find(group_string);
take_name = take_name.substr(idx + group_string.length()); // find the bit containing the take number / name
- boost::shared_ptr<RouteList> rl (route_group()->route_list());
+ boost::shared_ptr<RouteList> rl (rg->route_list());
for (RouteList::const_iterator i = rl->begin(); i != rl->end(); ++i) {
if ( (*i) == this->route()) {
boost::shared_ptr<Track> track = boost::dynamic_pointer_cast<Track>(*i);
if (!track) {
- std::cerr << "route " << (*i)->name() << " is not a Track" << std::endl;
continue;
}
void
-RouteTimeAxisView::show_all_automation ()
+RouteTimeAxisView::show_all_automation (bool apply_to_selection)
{
- no_redraw = true;
-
- /* Show our automation */
-
- for (AutomationTracks::iterator i = _automation_tracks.begin(); i != _automation_tracks.end(); ++i) {
- i->second->set_marked_for_display (true);
- i->second->canvas_display()->show();
- i->second->get_state_node()->add_property ("shown", X_("yes"));
-
- Gtk::CheckMenuItem* menu = automation_child_menu_item (i->first);
+ if (apply_to_selection) {
+ _editor.get_selection().tracks.foreach_route_time_axis (boost::bind (&RouteTimeAxisView::show_all_automation, _1, false));
+ } else {
+ no_redraw = true;
- if (menu) {
- menu->set_active(true);
- }
- }
-
-
- /* Show processor automation */
-
- for (list<ProcessorAutomationInfo*>::iterator i = processor_automation.begin(); i != processor_automation.end(); ++i) {
- for (vector<ProcessorAutomationNode*>::iterator ii = (*i)->lines.begin(); ii != (*i)->lines.end(); ++ii) {
- if ((*ii)->view == 0) {
- add_processor_automation_curve ((*i)->processor, (*ii)->what);
- }
-
- (*ii)->menu_item->set_active (true);
- }
- }
-
- no_redraw = false;
-
- /* Redraw */
-
- _route->gui_changed ("track_height", (void *) 0); /* EMIT_SIGNAL */
-}
-
-void
-RouteTimeAxisView::show_existing_automation ()
-{
- no_redraw = true;
-
- /* Show our automation */
-
- for (AutomationTracks::iterator i = _automation_tracks.begin(); i != _automation_tracks.end(); ++i) {
- if (i->second->has_automation()) {
+ /* Show our automation */
+
+ for (AutomationTracks::iterator i = _automation_tracks.begin(); i != _automation_tracks.end(); ++i) {
i->second->set_marked_for_display (true);
i->second->canvas_display()->show();
i->second->get_state_node()->add_property ("shown", X_("yes"));
-
+
Gtk::CheckMenuItem* menu = automation_child_menu_item (i->first);
+
if (menu) {
menu->set_active(true);
}
}
- }
-
-
- /* Show processor automation */
-
- for (list<ProcessorAutomationInfo*>::iterator i = processor_automation.begin(); i != processor_automation.end(); ++i) {
- for (vector<ProcessorAutomationNode*>::iterator ii = (*i)->lines.begin(); ii != (*i)->lines.end(); ++ii) {
- if ((*ii)->view != 0 && (*i)->processor->control((*ii)->what)->list()->size() > 0) {
+
+
+ /* Show processor automation */
+
+ for (list<ProcessorAutomationInfo*>::iterator i = processor_automation.begin(); i != processor_automation.end(); ++i) {
+ for (vector<ProcessorAutomationNode*>::iterator ii = (*i)->lines.begin(); ii != (*i)->lines.end(); ++ii) {
+ if ((*ii)->view == 0) {
+ add_processor_automation_curve ((*i)->processor, (*ii)->what);
+ }
+
(*ii)->menu_item->set_active (true);
}
}
+
+ no_redraw = false;
+
+ /* Redraw */
+
+ _route->gui_changed ("track_height", (void *) 0); /* EMIT_SIGNAL */
}
-
- no_redraw = false;
-
- _route->gui_changed ("track_height", (void *) 0); /* EMIT_SIGNAL */
}
-
+
void
-RouteTimeAxisView::hide_all_automation ()
+RouteTimeAxisView::show_existing_automation (bool apply_to_selection)
{
- no_redraw = true;
-
- /* Hide our automation */
-
- for (AutomationTracks::iterator i = _automation_tracks.begin(); i != _automation_tracks.end(); ++i) {
- i->second->set_marked_for_display (false);
- i->second->hide ();
- i->second->get_state_node()->add_property ("shown", X_("no"));
-
- Gtk::CheckMenuItem* menu = automation_child_menu_item (i->first);
+ if (apply_to_selection) {
+ _editor.get_selection().tracks.foreach_route_time_axis (boost::bind (&RouteTimeAxisView::show_existing_automation, _1, false));
+ } else {
+ no_redraw = true;
+
+ /* Show our automation */
+
+ for (AutomationTracks::iterator i = _automation_tracks.begin(); i != _automation_tracks.end(); ++i) {
+ if (i->second->has_automation()) {
+ i->second->set_marked_for_display (true);
+ i->second->canvas_display()->show();
+ i->second->get_state_node()->add_property ("shown", X_("yes"));
+
+ Gtk::CheckMenuItem* menu = automation_child_menu_item (i->first);
+ if (menu) {
+ menu->set_active(true);
+ }
+ }
+ }
+
+
+ /* Show processor automation */
- if (menu) {
- menu->set_active (false);
+ for (list<ProcessorAutomationInfo*>::iterator i = processor_automation.begin(); i != processor_automation.end(); ++i) {
+ for (vector<ProcessorAutomationNode*>::iterator ii = (*i)->lines.begin(); ii != (*i)->lines.end(); ++ii) {
+ if ((*ii)->view != 0 && (*i)->processor->control((*ii)->what)->list()->size() > 0) {
+ (*ii)->menu_item->set_active (true);
+ }
+ }
}
+
+ no_redraw = false;
+
+ _route->gui_changed ("track_height", (void *) 0); /* EMIT_SIGNAL */
}
+}
- /* Hide processor automation */
+void
+RouteTimeAxisView::hide_all_automation (bool apply_to_selection)
+{
+ if (apply_to_selection) {
+ _editor.get_selection().tracks.foreach_route_time_axis (boost::bind (&RouteTimeAxisView::hide_all_automation, _1, false));
+ } else {
+ no_redraw = true;
- for (list<ProcessorAutomationInfo*>::iterator i = processor_automation.begin(); i != processor_automation.end(); ++i) {
- for (vector<ProcessorAutomationNode*>::iterator ii = (*i)->lines.begin(); ii != (*i)->lines.end(); ++ii) {
- (*ii)->menu_item->set_active (false);
+ /* Hide our automation */
+
+ for (AutomationTracks::iterator i = _automation_tracks.begin(); i != _automation_tracks.end(); ++i) {
+ i->second->set_marked_for_display (false);
+ i->second->hide ();
+ i->second->get_state_node()->add_property ("shown", X_("no"));
+
+ Gtk::CheckMenuItem* menu = automation_child_menu_item (i->first);
+
+ if (menu) {
+ menu->set_active (false);
+ }
+ }
+
+ /* Hide processor automation */
+
+ for (list<ProcessorAutomationInfo*>::iterator i = processor_automation.begin(); i != processor_automation.end(); ++i) {
+ for (vector<ProcessorAutomationNode*>::iterator ii = (*i)->lines.begin(); ii != (*i)->lines.end(); ++ii) {
+ (*ii)->menu_item->set_active (false);
+ }
}
+
+ no_redraw = false;
+ _route->gui_changed ("track_height", (void *) 0); /* EMIT_SIGNAL */
}
-
- no_redraw = false;
- _route->gui_changed ("track_height", (void *) 0); /* EMIT_SIGNAL */
}
RouteTimeAxisView::region_view_added (RegionView* rv)
{
/* XXX need to find out if automation children have automationstreamviews. If yes, no ghosts */
- if (is_audio_track()) {
- for (Children::iterator i = children.begin(); i != children.end(); ++i) {
- boost::shared_ptr<AutomationTimeAxisView> atv;
-
- if ((atv = boost::dynamic_pointer_cast<AutomationTimeAxisView> (*i)) != 0) {
- atv->add_ghost(rv);
- }
+ for (Children::iterator i = children.begin(); i != children.end(); ++i) {
+ boost::shared_ptr<AutomationTimeAxisView> atv;
+
+ if ((atv = boost::dynamic_pointer_cast<AutomationTimeAxisView> (*i)) != 0) {
+ atv->add_ghost(rv);
}
}
ProcessorAutomationNode* pan;
if ((pan = find_processor_automation_node (processor, what)) == 0) {
- error << _("programming error: ")
- << string_compose (X_("processor automation curve for %1:%2 not registered with track!"),
- processor->name(), what)
+ /* session state may never have been saved with new plugin */
+ error << _("programming error: ")
+ << string_compose (X_("processor automation curve for %1:%2/%3/%4 not registered with track!"),
+ processor->name(), what.type(), (int) what.channel(), what.id() )
<< endmsg;
/*NOTREACHED*/
return;
/* FIXME: ew */
char state_name[256];
- snprintf (state_name, sizeof (state_name), "Redirect-%s-%" PRIu32, legalize_for_xml_node (processor->name()).c_str(), what.id());
+ snprintf (state_name, sizeof (state_name), "%s-%" PRIu32, legalize_for_xml_node (processor->name()).c_str(), what.id());
boost::shared_ptr<AutomationControl> control
= boost::dynamic_pointer_cast<AutomationControl>(processor->control(what, true));
pan->view = boost::shared_ptr<AutomationTimeAxisView>(
- new AutomationTimeAxisView (_session, _route, processor, control,
- _editor, *this, false, parent_canvas, name, state_name));
+ new AutomationTimeAxisView (_session, _route, processor, control, control->parameter (),
+ _editor, *this, false, parent_canvas, name, state_name));
pan->view->Hiding.connect (sigc::bind (sigc::mem_fun(*this, &RouteTimeAxisView::processor_automation_track_hidden), pan, processor));
}
void
-RouteTimeAxisView::set_layer_display (LayerDisplay d)
+RouteTimeAxisView::set_layer_display (LayerDisplay d, bool apply_to_selection)
{
- if (_view) {
- _view->set_layer_display (d);
+ if (apply_to_selection) {
+ _editor.get_selection().tracks.foreach_route_time_axis (boost::bind (&RouteTimeAxisView::set_layer_display, _1, d, false));
+ } else {
+
+ if (_view) {
+ _view->set_layer_display (d);
+ }
+
+ ensure_xml_node ();
+ xml_node->add_property (N_("layer-display"), enum_2_string (d));
}
-
- ensure_xml_node ();
- xml_node->add_property (N_("layer-display"), enum_2_string (d));
}
LayerDisplay
if (prop) {
PBD::ID id (prop->value());
- RouteTimeAxisView* v = _editor.get_route_view_by_id (id);
+ RouteTimeAxisView* v = _editor.get_route_view_by_route_id (id);
if (v) {
add_underlay(v->view(), false);
return 0;
}
+
+void
+RouteTimeAxisView::create_gain_automation_child (const Evoral::Parameter& param, bool show)
+{
+ boost::shared_ptr<AutomationControl> c = _route->gain_control();
+ if (!c) {
+ error << "Route has no gain automation, unable to add automation track view." << endmsg;
+ return;
+ }
+
+ gain_track.reset (new AutomationTimeAxisView (_session,
+ _route, _route->amp(), c, param,
+ _editor,
+ *this,
+ false,
+ parent_canvas,
+ _route->amp()->describe_parameter(param)));
+
+ if (_view) {
+ _view->foreach_regionview (sigc::mem_fun (*gain_track.get(), &TimeAxisView::add_ghost));
+ }
+
+ add_automation_child (Evoral::Parameter(GainAutomation), gain_track, show);
+}
+
+static
+void add_region_to_list (RegionView* rv, Playlist::RegionList* l, uint32_t* max_level)
+{
+ l->push_back (rv->region());
+ *max_level = max (*max_level, rv->region()->max_source_level());
+}
+
+void
+RouteTimeAxisView::combine_regions ()
+{
+ assert (is_track());
+
+ if (!_view) {
+ return;
+ }
+
+ Playlist::RegionList selected_regions;
+ boost::shared_ptr<Playlist> playlist = track()->playlist();
+ uint32_t max_level = 0;
+
+ _view->foreach_selected_regionview (sigc::bind (sigc::ptr_fun (add_region_to_list), &selected_regions, &max_level));
+
+ uint32_t num_joined_regions = playlist->count_joined_regions();
+ string name = string_compose (_("%1 compound-%2 (%3)"), playlist->name(), num_joined_regions+1, max_level+1);
+
+
+ playlist->clear_changes ();
+ playlist->join (selected_regions, name);
+ _session->add_command (new StatefulDiffCommand (playlist));
+}