2 Copyright (C) 2000-2004 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.
21 #include "gtk2ardour-config.h"
26 #include <sigc++/bind.h>
28 #include <gtkmm/accelmap.h>
30 #include "pbd/convert.h"
31 #include "pbd/stacktrace.h"
32 #include "pbd/unwind.h"
34 #include <glibmm/threads.h>
36 #include <gtkmm2ext/gtk_ui.h>
37 #include <gtkmm2ext/keyboard.h>
38 #include <gtkmm2ext/utils.h>
39 #include <gtkmm2ext/tearoff.h>
40 #include <gtkmm2ext/window_title.h>
41 #include <gtkmm2ext/doi.h>
43 #include "ardour/amp.h"
44 #include "ardour/debug.h"
45 #include "ardour/midi_track.h"
46 #include "ardour/plugin_manager.h"
47 #include "ardour/route_group.h"
48 #include "ardour/route_sorters.h"
49 #include "ardour/session.h"
50 #include "ardour/vca.h"
51 #include "ardour/vca_manager.h"
55 #include "mixer_strip.h"
56 #include "monitor_section.h"
57 #include "plugin_selector.h"
58 #include "public_editor.h"
59 #include "ardour_ui.h"
62 #include "route_sorter.h"
64 #include "gui_thread.h"
65 #include "mixer_group_tabs.h"
67 #include "ui_config.h"
68 #include "vca_master_strip.h"
72 using namespace ARDOUR;
73 using namespace ARDOUR_UI_UTILS;
77 using namespace Gtkmm2ext;
83 Mixer_UI* Mixer_UI::_instance = 0;
89 _instance = new Mixer_UI;
96 : Tabbable (_content, _("Mixer"))
97 , no_track_list_redisplay (false)
98 , in_group_row_change (false)
100 , _monitor_section (0)
101 , _plugin_selector (0)
102 , _strip_width (UIConfiguration::instance().get_default_narrow_ms() ? Narrow : Wide)
103 , ignore_reorder (false)
104 , _in_group_rebuild_or_clear (false)
105 , _route_deletion_in_progress (false)
106 , _following_editor_selection (false)
108 , _show_mixer_list (true)
110 Route::SyncOrderKeys.connect (*this, invalidator (*this), boost::bind (&Mixer_UI::sync_treeview_from_order_keys, this), gui_context());
112 /* bindings was already set in MixerActor constructor */
114 _content.set_data ("ardour-bindings", bindings);
116 scroller.set_can_default (true);
117 // set_default (scroller);
119 scroller_base.set_flags (Gtk::CAN_FOCUS);
120 scroller_base.add_events (Gdk::BUTTON_PRESS_MASK|Gdk::BUTTON_RELEASE_MASK);
121 scroller_base.set_name ("MixerWindow");
122 scroller_base.signal_button_release_event().connect (sigc::mem_fun(*this, &Mixer_UI::strip_scroller_button_release));
124 /* set up drag-n-drop */
125 vector<TargetEntry> target_table;
126 target_table.push_back (TargetEntry ("PluginFavoritePtr"));
127 scroller_base.drag_dest_set (target_table);
128 scroller_base.signal_drag_data_received().connect (sigc::mem_fun(*this, &Mixer_UI::scroller_drag_data_received));
130 // add as last item of strip packer
131 strip_packer.pack_end (scroller_base, true, true);
133 _group_tabs = new MixerGroupTabs (this);
134 VBox* b = manage (new VBox);
135 b->pack_start (*_group_tabs, PACK_SHRINK);
136 b->pack_start (strip_packer);
140 scroller.set_policy (Gtk::POLICY_ALWAYS, Gtk::POLICY_AUTOMATIC);
142 setup_track_display ();
144 group_model = ListStore::create (group_columns);
145 group_display.set_model (group_model);
146 group_display.append_column (_("Group"), group_columns.text);
147 group_display.append_column (_("Show"), group_columns.visible);
148 group_display.get_column (0)->set_data (X_("colnum"), GUINT_TO_POINTER(0));
149 group_display.get_column (1)->set_data (X_("colnum"), GUINT_TO_POINTER(1));
150 group_display.get_column (0)->set_expand(true);
151 group_display.get_column (1)->set_expand(false);
152 group_display.set_name ("EditGroupList");
153 group_display.get_selection()->set_mode (Gtk::SELECTION_SINGLE);
154 group_display.set_reorderable (true);
155 group_display.set_headers_visible (true);
156 group_display.set_rules_hint (true);
157 group_display.set_can_focus(false);
159 /* name is directly editable */
161 CellRendererText* name_cell = dynamic_cast<CellRendererText*>(group_display.get_column_cell_renderer (0));
162 name_cell->property_editable() = true;
163 name_cell->signal_edited().connect (sigc::mem_fun (*this, &Mixer_UI::route_group_name_edit));
165 /* use checkbox for the active column */
167 CellRendererToggle* active_cell = dynamic_cast<CellRendererToggle*>(group_display.get_column_cell_renderer (1));
168 active_cell->property_activatable() = true;
169 active_cell->property_radio() = false;
171 group_model->signal_row_changed().connect (sigc::mem_fun (*this, &Mixer_UI::route_group_row_change));
172 /* We use this to notice drag-and-drop reorders of the group list */
173 group_model->signal_row_deleted().connect (sigc::mem_fun (*this, &Mixer_UI::route_group_row_deleted));
174 group_display.signal_button_press_event().connect (sigc::mem_fun (*this, &Mixer_UI::group_display_button_press), false);
176 group_display_scroller.add (group_display);
177 group_display_scroller.set_policy (Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
179 HBox* route_group_display_button_box = manage (new HBox());
181 Button* route_group_add_button = manage (new Button ());
182 Button* route_group_remove_button = manage (new Button ());
186 w = manage (new Image (Stock::ADD, ICON_SIZE_BUTTON));
188 route_group_add_button->add (*w);
190 w = manage (new Image (Stock::REMOVE, ICON_SIZE_BUTTON));
192 route_group_remove_button->add (*w);
194 route_group_display_button_box->set_homogeneous (true);
196 route_group_add_button->signal_clicked().connect (sigc::mem_fun (*this, &Mixer_UI::new_route_group));
197 route_group_remove_button->signal_clicked().connect (sigc::mem_fun (*this, &Mixer_UI::remove_selected_route_group));
199 route_group_display_button_box->add (*route_group_add_button);
200 route_group_display_button_box->add (*route_group_remove_button);
202 group_display_vbox.pack_start (group_display_scroller, true, true);
203 group_display_vbox.pack_start (*route_group_display_button_box, false, false);
205 group_display_frame.set_name ("BaseFrame");
206 group_display_frame.set_shadow_type (Gtk::SHADOW_IN);
207 group_display_frame.add (group_display_vbox);
210 list<TargetEntry> target_list;
211 target_list.push_back (TargetEntry ("PluginPresetPtr"));
213 favorite_plugins_model = PluginTreeStore::create (favorite_plugins_columns);
214 favorite_plugins_display.set_model (favorite_plugins_model);
215 favorite_plugins_display.append_column (_("Favorite Plugins"), favorite_plugins_columns.name);
216 favorite_plugins_display.set_name ("EditGroupList");
217 favorite_plugins_display.get_selection()->set_mode (Gtk::SELECTION_SINGLE);
218 favorite_plugins_display.set_reorderable (false);
219 favorite_plugins_display.set_headers_visible (true);
220 favorite_plugins_display.set_rules_hint (true);
221 favorite_plugins_display.set_can_focus (false);
222 favorite_plugins_display.add_object_drag (favorite_plugins_columns.plugin.index(), "PluginFavoritePtr");
223 favorite_plugins_display.set_drag_column (favorite_plugins_columns.name.index());
224 favorite_plugins_display.add_drop_targets (target_list);
225 favorite_plugins_display.signal_row_activated().connect (sigc::mem_fun (*this, &Mixer_UI::plugin_row_activated));
226 favorite_plugins_display.signal_button_press_event().connect (sigc::mem_fun (*this, &Mixer_UI::plugin_row_button_press), false);
227 favorite_plugins_display.signal_drop.connect (sigc::mem_fun (*this, &Mixer_UI::plugin_drop));
228 favorite_plugins_display.signal_row_expanded().connect (sigc::mem_fun (*this, &Mixer_UI::save_favorite_ui_state));
229 favorite_plugins_display.signal_row_collapsed().connect (sigc::mem_fun (*this, &Mixer_UI::save_favorite_ui_state));
230 favorite_plugins_model->signal_row_has_child_toggled().connect (sigc::mem_fun (*this, &Mixer_UI::sync_treeview_favorite_ui_state));
232 favorite_plugins_scroller.add (favorite_plugins_display);
233 favorite_plugins_scroller.set_policy (Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
235 favorite_plugins_frame.set_name ("BaseFrame");
236 favorite_plugins_frame.set_shadow_type (Gtk::SHADOW_IN);
237 favorite_plugins_frame.add (favorite_plugins_scroller);
239 rhs_pane1.pack1 (favorite_plugins_frame, false, true);
240 rhs_pane1.pack2 (track_display_frame);
241 rhs_pane2.pack1 (rhs_pane1);
242 rhs_pane2.pack2 (group_display_frame);
244 list_vpacker.pack_start (rhs_pane2, true, true);
246 vca_scroller.add (vca_packer);
247 vca_scroller.set_policy (Gtk::POLICY_ALWAYS, Gtk::POLICY_AUTOMATIC);
249 inner_pane.pack1 (scroller);
250 inner_pane.pack2 (vca_scroller);
252 global_hpacker.pack_start (inner_pane, true, true);
253 global_hpacker.pack_start (out_packer, false, false);
255 list_hpane.pack1(list_vpacker, false, true);
256 list_hpane.pack2(global_hpacker, true, false);
258 rhs_pane1.signal_size_allocate().connect (sigc::bind (sigc::mem_fun(*this, &Mixer_UI::pane_allocation_handler),
259 static_cast<Gtk::Paned*> (&rhs_pane1)));
260 rhs_pane2.signal_size_allocate().connect (sigc::bind (sigc::mem_fun(*this, &Mixer_UI::pane_allocation_handler),
261 static_cast<Gtk::Paned*> (&rhs_pane2)));
262 list_hpane.signal_size_allocate().connect (sigc::bind (sigc::mem_fun(*this, &Mixer_UI::pane_allocation_handler),
263 static_cast<Gtk::Paned*> (&list_hpane)));
264 inner_pane.signal_size_allocate().connect (sigc::bind (sigc::mem_fun(*this, &Mixer_UI::pane_allocation_handler),
265 static_cast<Gtk::Paned*> (&inner_pane)));
267 _content.pack_start (list_hpane, true, true);
271 route_group_display_button_box->show();
272 route_group_add_button->show();
273 route_group_remove_button->show();
276 _content.set_name ("MixerWindow");
278 global_hpacker.show();
280 scroller_base.show();
281 scroller_hpacker.show();
282 mixer_scroller_vpacker.show();
284 group_display_button_label.show();
285 group_display_button.show();
286 group_display_scroller.show();
287 favorite_plugins_scroller.show();
288 group_display_vbox.show();
289 group_display_frame.show();
290 favorite_plugins_frame.show();
299 group_display.show();
300 favorite_plugins_display.show();
302 MixerStrip::CatchDeletion.connect (*this, invalidator (*this), boost::bind (&Mixer_UI::remove_strip, this, _1), gui_context());
304 #ifndef DEFER_PLUGIN_SELECTOR_LOAD
305 _plugin_selector = new PluginSelector (PluginManager::instance ());
307 #error implement deferred Plugin-Favorite list
309 PluginManager::instance ().PluginListChanged.connect (*this, invalidator (*this), boost::bind (&Mixer_UI::refill_favorite_plugins, this), gui_context());
310 PluginManager::instance ().PluginStatusesChanged.connect (*this, invalidator (*this), boost::bind (&Mixer_UI::refill_favorite_plugins, this), gui_context());
311 ARDOUR::Plugin::PresetsChanged.connect (*this, invalidator (*this), boost::bind (&Mixer_UI::refill_favorite_plugins, this), gui_context());
314 Mixer_UI::~Mixer_UI ()
316 if (_monitor_section) {
317 monitor_section_detached ();
318 delete _monitor_section;
320 delete _plugin_selector;
324 Mixer_UI::track_editor_selection ()
326 PublicEditor::instance().get_selection().TracksChanged.connect (sigc::mem_fun (*this, &Mixer_UI::follow_editor_selection));
330 Mixer_UI::use_own_window (bool and_fill_it)
332 bool new_window = !own_window();
334 Gtk::Window* win = Tabbable::use_own_window (and_fill_it);
337 if (win && new_window) {
338 win->set_name ("MixerWindow");
339 ARDOUR_UI::instance()->setup_toplevel_window (*win, _("Mixer"), this);
340 win->signal_scroll_event().connect (sigc::mem_fun (*this, &Mixer_UI::on_scroll_event), false);
341 win->signal_event().connect (sigc::bind (sigc::ptr_fun (&Keyboard::catch_user_event_for_pre_dialog_focus), win));
342 win->set_data ("ardour-bindings", bindings);
350 Mixer_UI::show_window ()
352 Tabbable::show_window ();
354 /* show/hide group tabs as required */
355 parameter_changed ("show-group-tabs");
357 /* now reset each strips width so the right widgets are shown */
360 TreeModel::Children rows = track_model->children();
361 TreeModel::Children::iterator ri;
363 for (ri = rows.begin(); ri != rows.end(); ++ri) {
364 ms = (*ri)[track_columns.strip];
368 ms->set_width_enum (ms->get_width_enum (), ms->width_owner());
369 /* Fix visibility of mixer strip stuff */
370 ms->parameter_changed (X_("mixer-element-visibility"));
373 /* force focus into main area */
374 scroller_base.grab_focus ();
378 Mixer_UI::add_masters (VCAList& vcas)
380 for (VCAList::iterator v = vcas.begin(); v != vcas.end(); ++v) {
382 VCAMasterStrip* vms = new VCAMasterStrip (_session, *v);
384 TreeModel::Row row = *(track_model->append());
385 row[track_columns.text] = (*v)->name();
386 row[track_columns.visible] = true;
387 row[track_columns.vca] = vms;
390 redisplay_track_list ();
394 Mixer_UI::remove_master (VCAMasterStrip* vms)
399 Mixer_UI::add_strips (RouteList& routes)
401 bool from_scratch = track_model->children().size() == 0;
402 Gtk::TreeModel::Children::iterator insert_iter = track_model->children().end();
404 for (Gtk::TreeModel::Children::iterator it = track_model->children().begin(); it != track_model->children().end(); ++it) {
405 boost::shared_ptr<Route> r = (*it)[track_columns.route];
411 if (r->order_key() == (routes.front()->order_key() + routes.size())) {
418 _selection.clear_routes ();
424 no_track_list_redisplay = true;
425 track_display.set_model (Glib::RefPtr<ListStore>());
427 for (RouteList::iterator x = routes.begin(); x != routes.end(); ++x) {
428 boost::shared_ptr<Route> route = (*x);
430 if (route->is_auditioner()) {
434 if (route->is_monitor()) {
436 if (!_monitor_section) {
437 _monitor_section = new MonitorSection (_session);
439 XMLNode* mnode = ARDOUR_UI::instance()->tearoff_settings (X_("monitor-section"));
441 _monitor_section->tearoff().set_state (*mnode);
445 out_packer.pack_end (_monitor_section->tearoff(), false, false);
446 _monitor_section->set_session (_session);
447 _monitor_section->tearoff().show_all ();
449 _monitor_section->tearoff().Detach.connect (sigc::mem_fun(*this, &Mixer_UI::monitor_section_detached));
450 _monitor_section->tearoff().Attach.connect (sigc::mem_fun(*this, &Mixer_UI::monitor_section_attached));
452 monitor_section_attached ();
454 route->DropReferences.connect (*this, invalidator(*this), boost::bind (&Mixer_UI::monitor_section_going_away, this), gui_context());
456 /* no regular strip shown for control out */
461 strip = new MixerStrip (*this, _session, route);
462 strips.push_back (strip);
464 UIConfiguration::instance().get_default_narrow_ms() ? _strip_width = Narrow : _strip_width = Wide;
466 if (strip->width_owner() != strip) {
467 strip->set_width_enum (_strip_width, this);
472 TreeModel::Row row = *(track_model->insert(insert_iter));
473 row[track_columns.text] = route->name();
474 row[track_columns.visible] = strip->route()->is_master() ? true : strip->marked_for_display();
475 row[track_columns.route] = route;
476 row[track_columns.strip] = strip;
477 row[track_columns.vca] = 0;
480 _selection.add (strip);
483 route->PropertyChanged.connect (*this, invalidator (*this), boost::bind (&Mixer_UI::strip_property_changed, this, _1, strip), gui_context());
485 strip->WidthChanged.connect (sigc::mem_fun(*this, &Mixer_UI::strip_width_changed));
486 strip->signal_button_release_event().connect (sigc::bind (sigc::mem_fun(*this, &Mixer_UI::strip_button_release_event), strip));
489 } catch (const std::exception& e) {
490 error << string_compose (_("Error adding GUI elements for new tracks/busses %1"), e.what()) << endmsg;
493 no_track_list_redisplay = false;
494 track_display.set_model (track_model);
496 sync_order_keys_from_treeview ();
497 redisplay_track_list ();
501 Mixer_UI::deselect_all_strip_processors ()
503 for (list<MixerStrip *>::iterator i = strips.begin(); i != strips.end(); ++i) {
504 (*i)->deselect_all_processors();
509 Mixer_UI::select_strip (MixerStrip& ms, bool add)
512 _selection.add (&ms);
514 _selection.set (&ms);
519 Mixer_UI::select_none ()
521 _selection.clear_routes();
522 deselect_all_strip_processors();
526 Mixer_UI::delete_processors ()
528 for (list<MixerStrip *>::iterator i = strips.begin(); i != strips.end(); ++i) {
529 (*i)->delete_processors();
535 Mixer_UI::remove_strip (MixerStrip* strip)
537 if (_session && _session->deletion_in_progress()) {
538 /* its all being taken care of */
542 TreeModel::Children rows = track_model->children();
543 TreeModel::Children::iterator ri;
544 list<MixerStrip *>::iterator i;
546 if ((i = find (strips.begin(), strips.end(), strip)) != strips.end()) {
550 for (ri = rows.begin(); ri != rows.end(); ++ri) {
551 if ((*ri)[track_columns.strip] == strip) {
552 PBD::Unwinder<bool> uw (_route_deletion_in_progress, true);
553 track_model->erase (ri);
560 Mixer_UI::reset_remote_control_ids ()
562 if (Config->get_remote_model() == UserOrdered || !_session || _session->deletion_in_progress()) {
566 TreeModel::Children rows = track_model->children();
572 DEBUG_TRACE (DEBUG::OrderKeys, "mixer resets remote control ids after remote model change\n");
574 TreeModel::Children::iterator ri;
575 bool rid_change = false;
577 uint32_t invisible_key = UINT32_MAX;
579 for (ri = rows.begin(); ri != rows.end(); ++ri) {
581 /* skip two special values */
583 if (rid == Route::MasterBusRemoteControlID) {
587 if (rid == Route::MonitorBusRemoteControlID) {
591 boost::shared_ptr<Route> route = (*ri)[track_columns.route];
592 bool visible = (*ri)[track_columns.visible];
598 if (!route->is_master() && !route->is_monitor()) {
600 uint32_t new_rid = (visible ? rid : invisible_key--);
602 if (new_rid != route->remote_control_id()) {
603 route->set_remote_control_id_explicit (new_rid);
614 /* tell the world that we changed the remote control IDs */
615 _session->notify_remote_id_change ();
620 Mixer_UI::sync_order_keys_from_treeview ()
622 if (ignore_reorder || !_session || _session->deletion_in_progress()) {
626 TreeModel::Children rows = track_model->children();
632 DEBUG_TRACE (DEBUG::OrderKeys, "mixer sync order keys from model\n");
634 TreeModel::Children::iterator ri;
635 bool changed = false;
636 bool rid_change = false;
639 uint32_t invisible_key = UINT32_MAX;
641 for (ri = rows.begin(); ri != rows.end(); ++ri) {
642 boost::shared_ptr<Route> route = (*ri)[track_columns.route];
643 bool visible = (*ri)[track_columns.visible];
649 uint32_t old_key = route->order_key ();
651 if (order != old_key) {
652 route->set_order_key (order);
656 if ((Config->get_remote_model() == MixerOrdered) && !route->is_master() && !route->is_monitor()) {
658 uint32_t new_rid = (visible ? rid : invisible_key--);
660 if (new_rid != route->remote_control_id()) {
661 route->set_remote_control_id_explicit (new_rid);
675 /* tell everyone that we changed the mixer sort keys */
676 _session->sync_order_keys ();
680 /* tell the world that we changed the remote control IDs */
681 _session->notify_remote_id_change ();
686 Mixer_UI::sync_treeview_from_order_keys ()
688 if (!_session || _session->deletion_in_progress()) {
692 DEBUG_TRACE (DEBUG::OrderKeys, "mixer sync model from order keys.\n");
694 /* we could get here after either a change in the Mixer or Editor sort
695 * order, but either way, the mixer order keys reflect the intended
696 * order for the GUI, so reorder the treeview model to match it.
699 vector<int> neworder;
700 TreeModel::Children rows = track_model->children();
701 uint32_t old_order = 0;
702 bool changed = false;
709 uint32_t vca_cnt = 0;
710 uint32_t max_route_order_key = 0;
712 /* count number of Routes in track_model (there may be some odd reason
713 why this is not the same as the number in the session, but here we
714 care about the track model.
717 for (TreeModel::Children::iterator ri = rows.begin(); ri != rows.end(); ++ri) {
718 boost::shared_ptr<Route> route = (*ri)[track_columns.route];
720 max_route_order_key = max (route->order_key(), max_route_order_key);
724 for (TreeModel::Children::iterator ri = rows.begin(); ri != rows.end(); ++ri, ++old_order) {
725 boost::shared_ptr<Route> route = (*ri)[track_columns.route];
727 /* VCAs need to sort after all routes. We don't display
728 * them in the same place (March 2016), but we don't
729 * want them intermixed in the track_model
731 sorted.push_back (OrderKeys (old_order, max_route_order_key + ++vca_cnt));
733 sorted.push_back (OrderKeys (old_order, route->order_key ()));
737 SortByNewDisplayOrder cmp;
739 sort (sorted.begin(), sorted.end(), cmp);
740 neworder.assign (sorted.size(), 0);
744 for (OrderingKeys::iterator sr = sorted.begin(); sr != sorted.end(); ++sr, ++n) {
746 neworder[n] = sr->old_display_order;
748 if (sr->old_display_order != n) {
752 DEBUG_TRACE (DEBUG::OrderKeys, string_compose ("MIXER change order from %1 to %2\n",
753 sr->old_display_order, n));
757 Unwinder<bool> uw (ignore_reorder, true);
758 track_model->reorder (neworder);
761 redisplay_track_list ();
765 Mixer_UI::follow_editor_selection ()
767 if (_following_editor_selection) {
771 _following_editor_selection = true;
772 _selection.block_routes_changed (true);
774 TrackSelection& s (PublicEditor::instance().get_selection().tracks);
776 _selection.clear_routes ();
778 for (TrackViewList::iterator i = s.begin(); i != s.end(); ++i) {
779 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*> (*i);
781 MixerStrip* ms = strip_by_route (rtav->route());
788 _following_editor_selection = false;
789 _selection.block_routes_changed (false);
794 Mixer_UI::strip_by_route (boost::shared_ptr<Route> r)
796 for (list<MixerStrip *>::iterator i = strips.begin(); i != strips.end(); ++i) {
797 if ((*i)->route() == r) {
806 Mixer_UI::strip_button_release_event (GdkEventButton *ev, MixerStrip *strip)
808 if (ev->button == 1) {
809 if (_selection.selected (strip)) {
810 /* primary-click: toggle selection state of strip */
811 if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
812 _selection.remove (strip);
813 } else if (_selection.routes.size() > 1) {
814 /* de-select others */
815 _selection.set (strip);
818 if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
819 _selection.add (strip);
820 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::RangeSelectModifier)) {
822 if (!_selection.selected(strip)) {
824 /* extend selection */
826 vector<MixerStrip*> tmp;
827 bool accumulate = false;
828 bool found_another = false;
830 tmp.push_back (strip);
832 for (list<MixerStrip*>::iterator i = strips.begin(); i != strips.end(); ++i) {
834 /* hit clicked strip, start accumulating till we hit the first
843 } else if (_selection.selected (*i)) {
844 /* hit selected strip. if currently accumulating others,
845 we're done. if not accumulating others, start doing so.
847 found_another = true;
862 for (vector<MixerStrip*>::iterator i = tmp.begin(); i != tmp.end(); ++i) {
866 _selection.set (strip); //user wants to start a range selection, but there aren't any others selected yet
870 _selection.set (strip);
879 Mixer_UI::set_session (Session* sess)
881 SessionHandlePtr::set_session (sess);
883 if (_plugin_selector) {
884 _plugin_selector->set_session (_session);
887 _group_tabs->set_session (sess);
893 refill_favorite_plugins();
895 XMLNode* node = ARDOUR_UI::instance()->mixer_settings();
896 set_state (*node, 0);
900 initial_track_display ();
902 _session->RouteAdded.connect (_session_connections, invalidator (*this), boost::bind (&Mixer_UI::add_strips, this, _1), gui_context());
903 _session->route_group_added.connect (_session_connections, invalidator (*this), boost::bind (&Mixer_UI::add_route_group, this, _1), gui_context());
904 _session->route_group_removed.connect (_session_connections, invalidator (*this), boost::bind (&Mixer_UI::route_groups_changed, this), gui_context());
905 _session->route_groups_reordered.connect (_session_connections, invalidator (*this), boost::bind (&Mixer_UI::route_groups_changed, this), gui_context());
906 _session->config.ParameterChanged.connect (_session_connections, invalidator (*this), boost::bind (&Mixer_UI::parameter_changed, this, _1), gui_context());
907 _session->DirtyChanged.connect (_session_connections, invalidator (*this), boost::bind (&Mixer_UI::update_title, this), gui_context());
908 _session->StateSaved.connect (_session_connections, invalidator (*this), boost::bind (&Mixer_UI::update_title, this), gui_context());
910 _session->vca_manager().VCAAdded.connect (_session_connections, invalidator (*this), boost::bind (&Mixer_UI::add_masters, this, _1), gui_context());
912 Config->ParameterChanged.connect (*this, invalidator (*this), boost::bind (&Mixer_UI::parameter_changed, this, _1), gui_context ());
914 route_groups_changed ();
923 Mixer_UI::session_going_away ()
925 ENSURE_GUI_THREAD (*this, &Mixer_UI::session_going_away);
927 _in_group_rebuild_or_clear = true;
928 group_model->clear ();
929 _in_group_rebuild_or_clear = false;
932 track_model->clear ();
934 for (list<MixerStrip *>::iterator i = strips.begin(); i != strips.end(); ++i) {
938 if (_monitor_section) {
939 _monitor_section->tearoff().hide_visible ();
942 monitor_section_detached ();
948 SessionHandlePtr::session_going_away ();
955 Mixer_UI::track_visibility_changed (std::string const & path)
957 if (_session && _session->deletion_in_progress()) {
963 if ((iter = track_model->get_iter (path))) {
964 MixerStrip* strip = (*iter)[track_columns.strip];
966 bool visible = (*iter)[track_columns.visible];
968 if (strip->set_marked_for_display (!visible)) {
969 update_track_visibility ();
976 Mixer_UI::update_track_visibility ()
978 TreeModel::Children rows = track_model->children();
979 TreeModel::Children::iterator i;
982 Unwinder<bool> uw (no_track_list_redisplay, true);
984 for (i = rows.begin(); i != rows.end(); ++i) {
985 MixerStrip *strip = (*i)[track_columns.strip];
987 (*i)[track_columns.visible] = strip->marked_for_display ();
991 /* force route order keys catch up with visibility changes
994 sync_order_keys_from_treeview ();
997 redisplay_track_list ();
1001 Mixer_UI::show_strip (MixerStrip* ms)
1003 TreeModel::Children rows = track_model->children();
1004 TreeModel::Children::iterator i;
1006 for (i = rows.begin(); i != rows.end(); ++i) {
1008 MixerStrip* strip = (*i)[track_columns.strip];
1010 (*i)[track_columns.visible] = true;
1011 redisplay_track_list ();
1018 Mixer_UI::hide_strip (MixerStrip* ms)
1020 TreeModel::Children rows = track_model->children();
1021 TreeModel::Children::iterator i;
1023 for (i = rows.begin(); i != rows.end(); ++i) {
1025 MixerStrip* strip = (*i)[track_columns.strip];
1027 (*i)[track_columns.visible] = false;
1028 redisplay_track_list ();
1035 Mixer_UI::start_updating ()
1037 fast_screen_update_connection = Timers::super_rapid_connect (sigc::mem_fun(*this, &Mixer_UI::fast_update_strips));
1042 Mixer_UI::stop_updating ()
1044 fast_screen_update_connection.disconnect();
1049 Mixer_UI::fast_update_strips ()
1051 if (_content.is_mapped () && _session) {
1052 for (list<MixerStrip *>::iterator i = strips.begin(); i != strips.end(); ++i) {
1053 (*i)->fast_update ();
1059 Mixer_UI::set_all_strips_visibility (bool yn)
1061 TreeModel::Children rows = track_model->children();
1062 TreeModel::Children::iterator i;
1065 Unwinder<bool> uw (no_track_list_redisplay, true);
1067 for (i = rows.begin(); i != rows.end(); ++i) {
1069 TreeModel::Row row = (*i);
1070 MixerStrip* strip = row[track_columns.strip];
1076 if (strip->route()->is_master() || strip->route()->is_monitor()) {
1080 (*i)[track_columns.visible] = yn;
1084 redisplay_track_list ();
1089 Mixer_UI::set_all_audio_midi_visibility (int tracks, bool yn)
1091 TreeModel::Children rows = track_model->children();
1092 TreeModel::Children::iterator i;
1095 Unwinder<bool> uw (no_track_list_redisplay, true);
1097 for (i = rows.begin(); i != rows.end(); ++i) {
1098 TreeModel::Row row = (*i);
1099 MixerStrip* strip = row[track_columns.strip];
1105 if (strip->route()->is_master() || strip->route()->is_monitor()) {
1109 boost::shared_ptr<AudioTrack> at = strip->audio_track();
1110 boost::shared_ptr<MidiTrack> mt = strip->midi_track();
1114 (*i)[track_columns.visible] = yn;
1118 if (at) { /* track */
1119 (*i)[track_columns.visible] = yn;
1124 if (!at && !mt) { /* bus */
1125 (*i)[track_columns.visible] = yn;
1130 if (mt) { /* midi-track */
1131 (*i)[track_columns.visible] = yn;
1138 redisplay_track_list ();
1142 Mixer_UI::hide_all_routes ()
1144 set_all_strips_visibility (false);
1148 Mixer_UI::show_all_routes ()
1150 set_all_strips_visibility (true);
1154 Mixer_UI::show_all_audiobus ()
1156 set_all_audio_midi_visibility (2, true);
1159 Mixer_UI::hide_all_audiobus ()
1161 set_all_audio_midi_visibility (2, false);
1165 Mixer_UI::show_all_audiotracks()
1167 set_all_audio_midi_visibility (1, true);
1170 Mixer_UI::hide_all_audiotracks ()
1172 set_all_audio_midi_visibility (1, false);
1176 Mixer_UI::show_all_miditracks()
1178 set_all_audio_midi_visibility (3, true);
1181 Mixer_UI::hide_all_miditracks ()
1183 set_all_audio_midi_visibility (3, false);
1188 Mixer_UI::track_list_reorder (const TreeModel::Path&, const TreeModel::iterator&, int* /*new_order*/)
1190 DEBUG_TRACE (DEBUG::OrderKeys, "mixer UI treeview reordered\n");
1191 sync_order_keys_from_treeview ();
1195 Mixer_UI::track_list_delete (const Gtk::TreeModel::Path&)
1197 /* this happens as the second step of a DnD within the treeview as well
1198 as when a row/route is actually deleted.
1200 if it was a deletion then we have to force a redisplay because
1201 order keys may not have changed.
1204 DEBUG_TRACE (DEBUG::OrderKeys, "mixer UI treeview row deleted\n");
1205 sync_order_keys_from_treeview ();
1207 if (_route_deletion_in_progress) {
1208 redisplay_track_list ();
1213 Mixer_UI::redisplay_track_list ()
1215 TreeModel::Children rows = track_model->children();
1216 TreeModel::Children::iterator i;
1218 if (no_track_list_redisplay) {
1222 container_clear (vca_packer);
1224 for (i = rows.begin(); i != rows.end(); ++i) {
1226 VCAMasterStrip* vms = (*i)[track_columns.vca];
1229 vca_packer.pack_start (*vms, false, false);
1234 MixerStrip* strip = (*i)[track_columns.strip];
1237 /* we're in the middle of changing a row, don't worry */
1241 bool const visible = (*i)[track_columns.visible];
1244 strip->set_gui_property ("visible", true);
1246 if (strip->packed()) {
1248 if (strip->route()->is_master() || strip->route()->is_monitor()) {
1249 out_packer.reorder_child (*strip, -1);
1252 strip_packer.reorder_child (*strip, -1); /* put at end */
1257 if (strip->route()->is_master() || strip->route()->is_monitor()) {
1258 out_packer.pack_start (*strip, false, false);
1260 strip_packer.pack_start (*strip, false, false);
1262 strip->set_packed (true);
1267 strip->set_gui_property ("visible", false);
1269 if (strip->route()->is_master() || strip->route()->is_monitor()) {
1270 /* do nothing, these cannot be hidden */
1272 if (strip->packed()) {
1273 strip_packer.remove (*strip);
1274 strip->set_packed (false);
1280 _group_tabs->set_dirty ();
1284 Mixer_UI::strip_width_changed ()
1286 _group_tabs->set_dirty ();
1289 TreeModel::Children rows = track_model->children();
1290 TreeModel::Children::iterator i;
1293 for (order = 0, i = rows.begin(); i != rows.end(); ++i, ++order) {
1294 MixerStrip* strip = (*i)[track_columns.strip];
1300 bool visible = (*i)[track_columns.visible];
1303 strip->queue_draw();
1311 Mixer_UI::initial_track_display ()
1313 boost::shared_ptr<RouteList> routes = _session->get_routes();
1314 RouteList copy (*routes);
1315 ARDOUR::SignalOrderRouteSorter sorter;
1320 Unwinder<bool> uw1 (no_track_list_redisplay, true);
1321 Unwinder<bool> uw2 (ignore_reorder, true);
1323 track_model->clear ();
1324 VCAList vcas = _session->vca_manager().vcas();
1329 _session->sync_order_keys ();
1331 redisplay_track_list ();
1335 Mixer_UI::show_track_list_menu ()
1337 if (track_menu == 0) {
1338 build_track_menu ();
1341 track_menu->popup (1, gtk_get_current_event_time());
1345 Mixer_UI::track_display_button_press (GdkEventButton* ev)
1347 if (Keyboard::is_context_menu_event (ev)) {
1348 show_track_list_menu ();
1356 Mixer_UI::build_track_menu ()
1358 using namespace Menu_Helpers;
1359 using namespace Gtk;
1361 track_menu = new Menu;
1362 track_menu->set_name ("ArdourContextMenu");
1363 MenuList& items = track_menu->items();
1365 items.push_back (MenuElem (_("Show All"), sigc::mem_fun(*this, &Mixer_UI::show_all_routes)));
1366 items.push_back (MenuElem (_("Hide All"), sigc::mem_fun(*this, &Mixer_UI::hide_all_routes)));
1367 items.push_back (MenuElem (_("Show All Audio Tracks"), sigc::mem_fun(*this, &Mixer_UI::show_all_audiotracks)));
1368 items.push_back (MenuElem (_("Hide All Audio Tracks"), sigc::mem_fun(*this, &Mixer_UI::hide_all_audiotracks)));
1369 items.push_back (MenuElem (_("Show All Audio Busses"), sigc::mem_fun(*this, &Mixer_UI::show_all_audiobus)));
1370 items.push_back (MenuElem (_("Hide All Audio Busses"), sigc::mem_fun(*this, &Mixer_UI::hide_all_audiobus)));
1371 items.push_back (MenuElem (_("Show All Midi Tracks"), sigc::mem_fun (*this, &Mixer_UI::show_all_miditracks)));
1372 items.push_back (MenuElem (_("Hide All Midi Tracks"), sigc::mem_fun (*this, &Mixer_UI::hide_all_miditracks)));
1377 Mixer_UI::strip_property_changed (const PropertyChange& what_changed, MixerStrip* mx)
1379 if (!what_changed.contains (ARDOUR::Properties::name)) {
1383 ENSURE_GUI_THREAD (*this, &Mixer_UI::strip_name_changed, what_changed, mx)
1385 TreeModel::Children rows = track_model->children();
1386 TreeModel::Children::iterator i;
1388 for (i = rows.begin(); i != rows.end(); ++i) {
1389 if ((*i)[track_columns.strip] == mx) {
1390 (*i)[track_columns.text] = mx->route()->name();
1395 error << _("track display list item for renamed strip not found!") << endmsg;
1399 Mixer_UI::group_display_button_press (GdkEventButton* ev)
1401 TreeModel::Path path;
1402 TreeViewColumn* column;
1406 if (!group_display.get_path_at_pos ((int)ev->x, (int)ev->y, path, column, cellx, celly)) {
1407 _group_tabs->get_menu(0)->popup (1, ev->time);
1411 TreeIter iter = group_model->get_iter (path);
1413 _group_tabs->get_menu(0)->popup (1, ev->time);
1417 RouteGroup* group = (*iter)[group_columns.group];
1419 if (Keyboard::is_context_menu_event (ev)) {
1420 _group_tabs->get_menu(group)->popup (1, ev->time);
1424 switch (GPOINTER_TO_UINT (column->get_data (X_("colnum")))) {
1426 if (Keyboard::is_edit_event (ev)) {
1428 // edit_route_group (group);
1430 group_display.queue_draw();
1439 bool visible = (*iter)[group_columns.visible];
1440 (*iter)[group_columns.visible] = !visible;
1442 group_display.queue_draw();
1455 Mixer_UI::activate_all_route_groups ()
1457 _session->foreach_route_group (sigc::bind (sigc::mem_fun (*this, &Mixer_UI::set_route_group_activation), true));
1461 Mixer_UI::disable_all_route_groups ()
1463 _session->foreach_route_group (sigc::bind (sigc::mem_fun (*this, &Mixer_UI::set_route_group_activation), false));
1467 Mixer_UI::route_groups_changed ()
1469 ENSURE_GUI_THREAD (*this, &Mixer_UI::route_groups_changed);
1471 _in_group_rebuild_or_clear = true;
1473 /* just rebuild the while thing */
1475 group_model->clear ();
1478 /* this is currently not used,
1479 * Mixer_UI::group_display_button_press() has a case for it,
1480 * and a commented edit_route_group() but that's n/a since 2011.
1482 * This code is left as reminder that
1483 * row[group_columns.group] = 0 has special meaning.
1487 row = *(group_model->append());
1488 row[group_columns.visible] = true;
1489 row[group_columns.text] = (_("-all-"));
1490 row[group_columns.group] = 0;
1494 _session->foreach_route_group (sigc::mem_fun (*this, &Mixer_UI::add_route_group));
1496 _group_tabs->set_dirty ();
1497 _in_group_rebuild_or_clear = false;
1501 Mixer_UI::new_route_group ()
1505 _group_tabs->run_new_group_dialog (rl);
1509 Mixer_UI::remove_selected_route_group ()
1511 Glib::RefPtr<TreeSelection> selection = group_display.get_selection();
1512 TreeView::Selection::ListHandle_Path rows = selection->get_selected_rows ();
1518 TreeView::Selection::ListHandle_Path::iterator i = rows.begin();
1521 /* selection mode is single, so rows.begin() is it */
1523 if ((iter = group_model->get_iter (*i))) {
1525 RouteGroup* rg = (*iter)[group_columns.group];
1528 _session->remove_route_group (*rg);
1534 Mixer_UI::route_group_property_changed (RouteGroup* group, const PropertyChange& change)
1536 if (in_group_row_change) {
1540 /* force an update of any mixer strips that are using this group,
1541 otherwise mix group names don't change in mixer strips
1544 for (list<MixerStrip *>::iterator i = strips.begin(); i != strips.end(); ++i) {
1545 if ((*i)->route_group() == group) {
1546 (*i)->route_group_changed();
1550 TreeModel::iterator i;
1551 TreeModel::Children rows = group_model->children();
1552 Glib::RefPtr<TreeSelection> selection = group_display.get_selection();
1554 in_group_row_change = true;
1556 for (i = rows.begin(); i != rows.end(); ++i) {
1557 if ((*i)[group_columns.group] == group) {
1558 (*i)[group_columns.visible] = !group->is_hidden ();
1559 (*i)[group_columns.text] = group->name ();
1564 in_group_row_change = false;
1566 if (change.contains (Properties::name)) {
1567 _group_tabs->set_dirty ();
1570 for (list<MixerStrip*>::iterator j = strips.begin(); j != strips.end(); ++j) {
1571 if ((*j)->route_group() == group) {
1572 if (group->is_hidden ()) {
1582 Mixer_UI::show_mixer_list (bool yn)
1585 list_vpacker.show ();
1587 //if user wants to show the pane, we should make sure that it is wide enough to be visible
1588 int width = list_hpane.get_position();
1590 list_hpane.set_position(40);
1592 list_vpacker.hide ();
1595 _show_mixer_list = yn;
1599 Mixer_UI::show_monitor_section (bool yn)
1601 if (!monitor_section()) {
1604 if (monitor_section()->tearoff().torn_off()) {
1609 monitor_section()->tearoff().show();
1611 monitor_section()->tearoff().hide();
1616 Mixer_UI::route_group_name_edit (const std::string& path, const std::string& new_text)
1621 if ((iter = group_model->get_iter (path))) {
1623 if ((group = (*iter)[group_columns.group]) == 0) {
1627 if (new_text != group->name()) {
1628 group->set_name (new_text);
1634 Mixer_UI::route_group_row_change (const Gtk::TreeModel::Path&, const Gtk::TreeModel::iterator& iter)
1638 if (in_group_row_change) {
1642 if ((group = (*iter)[group_columns.group]) == 0) {
1646 std::string name = (*iter)[group_columns.text];
1648 if (name != group->name()) {
1649 group->set_name (name);
1652 bool hidden = !(*iter)[group_columns.visible];
1654 if (hidden != group->is_hidden ()) {
1655 group->set_hidden (hidden, this);
1659 /** Called when a group model row is deleted, but also when the model is
1660 * reordered by a user drag-and-drop; the latter is what we are
1661 * interested in here.
1664 Mixer_UI::route_group_row_deleted (Gtk::TreeModel::Path const &)
1666 if (_in_group_rebuild_or_clear) {
1670 /* Re-write the session's route group list so that the new order is preserved */
1672 list<RouteGroup*> new_list;
1674 Gtk::TreeModel::Children children = group_model->children();
1675 for (Gtk::TreeModel::Children::iterator i = children.begin(); i != children.end(); ++i) {
1676 RouteGroup* g = (*i)[group_columns.group];
1678 new_list.push_back (g);
1682 _session->reorder_route_groups (new_list);
1687 Mixer_UI::add_route_group (RouteGroup* group)
1689 ENSURE_GUI_THREAD (*this, &Mixer_UI::add_route_group, group)
1692 in_group_row_change = true;
1694 TreeModel::Row row = *(group_model->append());
1695 row[group_columns.visible] = !group->is_hidden ();
1696 row[group_columns.group] = group;
1697 if (!group->name().empty()) {
1698 row[group_columns.text] = group->name();
1700 row[group_columns.text] = _("unnamed");
1704 group->PropertyChanged.connect (*this, invalidator (*this), boost::bind (&Mixer_UI::route_group_property_changed, this, group, _1), gui_context());
1707 TreeViewColumn* col = group_display.get_column (0);
1708 CellRendererText* name_cell = dynamic_cast<CellRendererText*>(group_display.get_column_cell_renderer (0));
1709 group_display.set_cursor (group_model->get_path (row), *col, *name_cell, true);
1712 _group_tabs->set_dirty ();
1714 in_group_row_change = false;
1718 Mixer_UI::strip_scroller_button_release (GdkEventButton* ev)
1720 using namespace Menu_Helpers;
1722 if (Keyboard::is_context_menu_event (ev)) {
1723 ARDOUR_UI::instance()->add_route ();
1731 Mixer_UI::scroller_drag_data_received (const Glib::RefPtr<Gdk::DragContext>& context, int x, int y, const Gtk::SelectionData& data, guint info, guint time)
1733 printf ("Mixer_UI::scroller_drag_data_received\n");
1734 if (data.get_target() != "PluginFavoritePtr") {
1735 context->drag_finish (false, false, time);
1739 const void * d = data.get_data();
1740 const Gtkmm2ext::DnDTreeView<ARDOUR::PluginPresetPtr>* tv = reinterpret_cast<const Gtkmm2ext::DnDTreeView<ARDOUR::PluginPresetPtr>*>(d);
1742 PluginPresetList nfos;
1744 tv->get_object_drag_data (nfos, &source);
1746 Route::ProcessorList pl;
1749 for (list<PluginPresetPtr>::const_iterator i = nfos.begin(); i != nfos.end(); ++i) {
1750 PluginPresetPtr ppp = (*i);
1751 PluginInfoPtr pip = ppp->_pip;
1752 if (!pip->is_instrument ()) {
1755 ARDOUR_UI::instance()->session_add_midi_track (NULL, 1, _("MIDI"), Config->get_strict_io (), pip, ppp->_preset.valid ? &ppp->_preset : 0);
1759 context->drag_finish (ok, false, time);
1763 Mixer_UI::set_strip_width (Width w, bool save)
1767 for (list<MixerStrip*>::iterator i = strips.begin(); i != strips.end(); ++i) {
1768 (*i)->set_width_enum (w, save ? (*i)->width_owner() : this);
1773 struct PluginStateSorter {
1775 bool operator() (PluginInfoPtr a, PluginInfoPtr b) const {
1776 std::list<std::string>::const_iterator aiter = std::find(_user.begin(), _user.end(), (*a).unique_id);
1777 std::list<std::string>::const_iterator biter = std::find(_user.begin(), _user.end(), (*b).unique_id);
1778 if (aiter != _user.end() && biter != _user.end()) {
1779 return std::distance (_user.begin(), aiter) < std::distance (_user.begin(), biter);
1781 if (aiter != _user.end()) {
1784 if (biter != _user.end()) {
1787 return ARDOUR::cmp_nocase((*a).name, (*b).name) == -1;
1790 PluginStateSorter(std::list<std::string> user) : _user (user) {}
1792 std::list<std::string> _user;
1796 Mixer_UI::set_state (const XMLNode& node, int version)
1798 XMLProperty const * prop;
1800 Tabbable::set_state (node, version);
1802 if ((prop = node.property ("narrow-strips"))) {
1803 if (string_is_affirmative (prop->value())) {
1804 set_strip_width (Narrow);
1806 set_strip_width (Wide);
1810 if ((prop = node.property ("show-mixer"))) {
1811 if (string_is_affirmative (prop->value())) {
1816 if ((prop = node.property ("maximised"))) {
1817 bool yn = string_is_affirmative (prop->value());
1818 Glib::RefPtr<Action> act = ActionManager::get_action (X_("Common"), X_("ToggleMaximalMixer"));
1820 Glib::RefPtr<ToggleAction> tact = Glib::RefPtr<ToggleAction>::cast_dynamic(act);
1821 bool fs = tact && tact->get_active();
1823 ActionManager::do_action ("Common", "ToggleMaximalMixer");
1827 if ((prop = node.property ("show-mixer-list"))) {
1828 bool yn = string_is_affirmative (prop->value());
1829 Glib::RefPtr<Action> act = ActionManager::get_action (X_("Common"), X_("ToggleMixerList"));
1831 Glib::RefPtr<ToggleAction> tact = Glib::RefPtr<ToggleAction>::cast_dynamic(act);
1833 /* do it twice to force the change */
1834 tact->set_active (!yn);
1835 tact->set_active (yn);
1839 XMLNode* plugin_order;
1840 if ((plugin_order = find_named_node (node, "PluginOrder")) != 0) {
1841 store_current_favorite_order ();
1842 std::list<string> order;
1843 const XMLNodeList& kids = plugin_order->children("PluginInfo");
1844 XMLNodeConstIterator i;
1845 for (i = kids.begin(); i != kids.end(); ++i) {
1846 if ((prop = (*i)->property ("unique-id"))) {
1847 std::string unique_id = prop->value();
1848 order.push_back (unique_id);
1849 if ((prop = (*i)->property ("expanded"))) {
1850 favorite_ui_state[unique_id] = string_is_affirmative (prop->value());
1854 PluginStateSorter cmp (order);
1855 favorite_order.sort (cmp);
1856 sync_treeview_from_favorite_order ();
1862 Mixer_UI::get_state ()
1864 XMLNode* node = new XMLNode (X_("Mixer"));
1867 node->add_child_nocopy (Tabbable::get_state());
1869 snprintf(buf,sizeof(buf), "%d",gtk_paned_get_position (const_cast<GtkPaned*>(static_cast<const Paned*>(&rhs_pane1)->gobj())));
1870 node->add_property(X_("mixer-rhs-pane1-pos"), string(buf));
1871 snprintf(buf,sizeof(buf), "%d",gtk_paned_get_position (const_cast<GtkPaned*>(static_cast<const Paned*>(&rhs_pane2)->gobj())));
1872 node->add_property(X_("mixer-rhs_pane2-pos"), string(buf));
1873 snprintf(buf,sizeof(buf), "%d",gtk_paned_get_position (const_cast<GtkPaned*>(static_cast<const Paned*>(&list_hpane)->gobj())));
1874 node->add_property(X_("mixer-list-hpane-pos"), string(buf));
1875 snprintf(buf,sizeof(buf), "%d",gtk_paned_get_position (const_cast<GtkPaned*>(static_cast<const Paned*>(&inner_pane)->gobj())));
1876 node->add_property(X_("mixer-inner-pane-pos"), string(buf));
1878 node->add_property ("narrow-strips", _strip_width == Narrow ? "yes" : "no");
1879 node->add_property ("show-mixer", _visible ? "yes" : "no");
1880 node->add_property ("show-mixer-list", _show_mixer_list ? "yes" : "no");
1881 node->add_property ("maximised", _maximised ? "yes" : "no");
1883 store_current_favorite_order ();
1884 XMLNode* plugin_order = new XMLNode ("PluginOrder");
1886 for (PluginInfoList::const_iterator i = favorite_order.begin(); i != favorite_order.end(); ++i, ++cnt) {
1887 XMLNode* p = new XMLNode ("PluginInfo");
1888 p->add_property ("sort", cnt);
1889 p->add_property ("unique-id", (*i)->unique_id);
1890 if (favorite_ui_state.find ((*i)->unique_id) != favorite_ui_state.end ()) {
1891 p->add_property ("expanded", favorite_ui_state[(*i)->unique_id]);
1893 plugin_order->add_child_nocopy (*p);
1895 node->add_child_nocopy (*plugin_order);
1901 Mixer_UI::pane_allocation_handler (Allocation&, Gtk::Paned* which)
1904 XMLProperty* prop = 0;
1905 XMLNode* geometry = ARDOUR_UI::instance()->mixer_settings();
1906 int height = default_height;
1907 static bool done[4] = { false, false, false, false };
1909 if (which == static_cast<Gtk::Paned*> (&rhs_pane1)) {
1915 if (!geometry || (prop = geometry->property("mixer-rhs-pane1-pos")) == 0) {
1918 pos = atoi (prop->value());
1921 if ((done[0] = (GTK_WIDGET(rhs_pane1.gobj())->allocation.height > pos))) {
1922 rhs_pane1.set_position (pos);
1925 } else if (which == static_cast<Gtk::Paned*> (&rhs_pane2)) {
1930 if (!geometry || (prop = geometry->property("mixer-rhs-pane2-pos")) == 0) {
1931 pos = 2 * height / 3;
1933 pos = atoi (prop->value());
1936 if ((done[1] = (GTK_WIDGET(rhs_pane2.gobj())->allocation.height > pos))) {
1937 rhs_pane2.set_position (pos);
1939 } else if (which == static_cast<Gtk::Paned*> (&list_hpane)) {
1945 if (!geometry || (prop = geometry->property("mixer-list-hpane-pos")) == 0) {
1946 pos = std::max ((float)100, rintf ((float) 125 * UIConfiguration::instance().get_ui_scale()));
1948 pos = max (36, atoi (prop->value ()));
1951 if ((done[2] = (GTK_WIDGET(list_hpane.gobj())->allocation.width > pos))) {
1952 list_hpane.set_position (pos);
1954 } else if (which == static_cast<Gtk::Paned*> (&inner_pane)) {
1960 if (!geometry || (prop = geometry->property("mixer-inner-pane-pos")) == 0) {
1961 cerr << "using default value\n";
1962 pos = std::max ((float)100, rintf ((float) 125 * UIConfiguration::instance().get_ui_scale()));
1964 cerr << "using " << prop->value() << endl;
1965 pos = max (36, atoi (prop->value ()));
1968 cerr << "Setting inner pane pos to " << pos << endl;
1970 if ((done[3] = (GTK_WIDGET(inner_pane.gobj())->allocation.width > pos))) {
1971 inner_pane.set_position (pos);
1977 Mixer_UI::scroll_left ()
1979 if (!scroller.get_hscrollbar()) return;
1980 Adjustment* adj = scroller.get_hscrollbar()->get_adjustment();
1981 /* stupid GTK: can't rely on clamping across versions */
1982 scroller.get_hscrollbar()->set_value (max (adj->get_lower(), adj->get_value() - adj->get_step_increment()));
1986 Mixer_UI::scroll_right ()
1988 if (!scroller.get_hscrollbar()) return;
1989 Adjustment* adj = scroller.get_hscrollbar()->get_adjustment();
1990 /* stupid GTK: can't rely on clamping across versions */
1991 scroller.get_hscrollbar()->set_value (min (adj->get_upper(), adj->get_value() + adj->get_step_increment()));
1995 Mixer_UI::on_scroll_event (GdkEventScroll* ev)
1997 switch (ev->direction) {
1998 case GDK_SCROLL_LEFT:
2002 if (ev->state & Keyboard::TertiaryModifier) {
2008 case GDK_SCROLL_RIGHT:
2012 case GDK_SCROLL_DOWN:
2013 if (ev->state & Keyboard::TertiaryModifier) {
2025 Mixer_UI::parameter_changed (string const & p)
2027 if (p == "show-group-tabs") {
2028 bool const s = _session->config.get_show_group_tabs ();
2030 _group_tabs->show ();
2032 _group_tabs->hide ();
2034 } else if (p == "default-narrow_ms") {
2035 bool const s = UIConfiguration::instance().get_default_narrow_ms ();
2036 for (list<MixerStrip*>::iterator i = strips.begin(); i != strips.end(); ++i) {
2037 (*i)->set_width_enum (s ? Narrow : Wide, this);
2039 } else if (p == "remote-model") {
2040 reset_remote_control_ids ();
2041 } else if (p == "use-monitor-bus") {
2042 if (_session && !_session->monitor_out()) {
2043 monitor_section_detached ();
2049 Mixer_UI::set_route_group_activation (RouteGroup* g, bool a)
2051 g->set_active (a, this);
2055 Mixer_UI::plugin_selector()
2057 #ifdef DEFER_PLUGIN_SELECTOR_LOAD
2058 if (!_plugin_selector)
2059 _plugin_selector = new PluginSelector (PluginManager::instance());
2062 return _plugin_selector;
2066 Mixer_UI::setup_track_display ()
2068 track_model = ListStore::create (track_columns);
2069 track_display.set_model (track_model);
2070 track_display.append_column (_("Strips"), track_columns.text);
2071 track_display.append_column (_("Show"), track_columns.visible);
2072 track_display.get_column (0)->set_data (X_("colnum"), GUINT_TO_POINTER(0));
2073 track_display.get_column (1)->set_data (X_("colnum"), GUINT_TO_POINTER(1));
2074 track_display.get_column (0)->set_expand(true);
2075 track_display.get_column (1)->set_expand(false);
2076 track_display.get_column (0)->set_sizing (Gtk::TREE_VIEW_COLUMN_FIXED);
2077 track_display.set_name (X_("EditGroupList"));
2078 track_display.get_selection()->set_mode (Gtk::SELECTION_NONE);
2079 track_display.set_reorderable (true);
2080 track_display.set_headers_visible (true);
2081 track_display.set_can_focus(false);
2083 track_model->signal_row_deleted().connect (sigc::mem_fun (*this, &Mixer_UI::track_list_delete));
2084 track_model->signal_rows_reordered().connect (sigc::mem_fun (*this, &Mixer_UI::track_list_reorder));
2086 CellRendererToggle* track_list_visible_cell = dynamic_cast<CellRendererToggle*>(track_display.get_column_cell_renderer (1));
2087 track_list_visible_cell->property_activatable() = true;
2088 track_list_visible_cell->property_radio() = false;
2089 track_list_visible_cell->signal_toggled().connect (sigc::mem_fun (*this, &Mixer_UI::track_visibility_changed));
2091 track_display.signal_button_press_event().connect (sigc::mem_fun (*this, &Mixer_UI::track_display_button_press), false);
2093 track_display_scroller.add (track_display);
2094 track_display_scroller.set_policy (Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
2096 VBox* v = manage (new VBox);
2098 v->pack_start (track_display_scroller, true, true);
2100 Button* b = manage (new Button);
2102 Widget* w = manage (new Image (Stock::ADD, ICON_SIZE_BUTTON));
2106 b->signal_clicked().connect (sigc::mem_fun (*this, &Mixer_UI::new_track_or_bus));
2108 v->pack_start (*b, false, false);
2110 track_display_frame.set_name("BaseFrame");
2111 track_display_frame.set_shadow_type (Gtk::SHADOW_IN);
2112 track_display_frame.add (*v);
2114 track_display_scroller.show();
2115 track_display_frame.show();
2116 track_display.show();
2120 Mixer_UI::new_track_or_bus ()
2122 ARDOUR_UI::instance()->add_route ();
2127 Mixer_UI::update_title ()
2129 if (!own_window()) {
2136 if (_session->snap_name() != _session->name()) {
2137 n = _session->snap_name ();
2139 n = _session->name ();
2142 if (_session->dirty ()) {
2146 WindowTitle title (n);
2147 title += S_("Window|Mixer");
2148 title += Glib::get_application_name ();
2149 own_window()->set_title (title.get_string());
2153 WindowTitle title (S_("Window|Mixer"));
2154 title += Glib::get_application_name ();
2155 own_window()->set_title (title.get_string());
2160 Mixer_UI::strip_by_x (int x)
2162 for (list<MixerStrip*>::iterator i = strips.begin(); i != strips.end(); ++i) {
2165 (*i)->translate_coordinates (_content, 0, 0, x1, y);
2166 x2 = x1 + (*i)->get_width();
2168 if (x >= x1 && x <= x2) {
2177 Mixer_UI::set_route_targets_for_operation ()
2179 _route_targets.clear ();
2181 if (!_selection.empty()) {
2182 _route_targets = _selection.routes;
2186 // removed "implicit" selections of strips, after discussion on IRC
2191 Mixer_UI::monitor_section_going_away ()
2193 if (_monitor_section) {
2194 monitor_section_detached ();
2195 out_packer.remove (_monitor_section->tearoff());
2196 _monitor_section->set_session (0);
2197 delete _monitor_section;
2198 _monitor_section = 0;
2203 Mixer_UI::toggle_midi_input_active (bool flip_others)
2205 boost::shared_ptr<RouteList> rl (new RouteList);
2208 set_route_targets_for_operation ();
2210 for (RouteUISelection::iterator r = _route_targets.begin(); r != _route_targets.end(); ++r) {
2211 boost::shared_ptr<MidiTrack> mt = (*r)->midi_track();
2214 rl->push_back ((*r)->route());
2215 onoff = !mt->input_active();
2219 _session->set_exclusive_input_active (rl, onoff, flip_others);
2223 Mixer_UI::maximise_mixer_space ()
2225 if (!own_window()) {
2233 _window->fullscreen ();
2238 Mixer_UI::restore_mixer_space ()
2240 if (!own_window()) {
2248 own_window()->unfullscreen();
2253 Mixer_UI::monitor_section_attached ()
2255 Glib::RefPtr<Action> act = ActionManager::get_action ("Common", "ToggleMonitorSection");
2256 Glib::RefPtr<ToggleAction> tact = Glib::RefPtr<ToggleAction>::cast_dynamic(act);
2257 act->set_sensitive (true);
2258 tact->set_active ();
2262 Mixer_UI::monitor_section_detached ()
2264 Glib::RefPtr<Action> act = ActionManager::get_action ("Common", "ToggleMonitorSection");
2265 act->set_sensitive (false);
2269 Mixer_UI::store_current_favorite_order ()
2271 typedef Gtk::TreeModel::Children type_children;
2272 type_children children = favorite_plugins_model->children();
2273 favorite_order.clear();
2274 for(type_children::iterator iter = children.begin(); iter != children.end(); ++iter)
2276 Gtk::TreeModel::Row row = *iter;
2277 ARDOUR::PluginPresetPtr ppp = row[favorite_plugins_columns.plugin];
2278 favorite_order.push_back (ppp->_pip);
2279 std::string name = row[favorite_plugins_columns.name];
2280 favorite_ui_state[(*ppp->_pip).unique_id] = favorite_plugins_display.row_expanded (favorite_plugins_model->get_path(iter));
2285 Mixer_UI::save_favorite_ui_state (const TreeModel::iterator& iter, const TreeModel::Path& path)
2287 Gtk::TreeModel::Row row = *iter;
2288 ARDOUR::PluginPresetPtr ppp = row[favorite_plugins_columns.plugin];
2290 favorite_ui_state[(*ppp->_pip).unique_id] = favorite_plugins_display.row_expanded (favorite_plugins_model->get_path(iter));
2294 Mixer_UI::refiller (PluginInfoList& result, const PluginInfoList& plugs)
2296 PluginManager& manager (PluginManager::instance());
2297 for (PluginInfoList::const_iterator i = plugs.begin(); i != plugs.end(); ++i) {
2298 if (manager.get_status (*i) != PluginManager::Favorite) {
2301 result.push_back (*i);
2305 struct PluginCustomSorter {
2307 bool operator() (PluginInfoPtr a, PluginInfoPtr b) const {
2308 PluginInfoList::const_iterator aiter = _user.begin();
2309 PluginInfoList::const_iterator biter = _user.begin();
2310 while (aiter != _user.end()) { if ((*aiter)->unique_id == a->unique_id) { break; } ++aiter; }
2311 while (biter != _user.end()) { if ((*biter)->unique_id == b->unique_id) { break; } ++biter; }
2313 if (aiter != _user.end() && biter != _user.end()) {
2314 return std::distance (_user.begin(), aiter) < std::distance (_user.begin(), biter);
2316 if (aiter != _user.end()) {
2319 if (biter != _user.end()) {
2322 return ARDOUR::cmp_nocase((*a).name, (*b).name) == -1;
2324 PluginCustomSorter(PluginInfoList user) : _user (user) {}
2326 PluginInfoList _user;
2330 Mixer_UI::refill_favorite_plugins ()
2332 PluginInfoList plugs;
2333 PluginManager& mgr (PluginManager::instance());
2336 refiller (plugs, mgr.lv2_plugin_info ());
2338 #ifdef WINDOWS_VST_SUPPORT
2339 refiller (plugs, mgr.windows_vst_plugin_info ());
2341 #ifdef LXVST_SUPPORT
2342 refiller (plugs, mgr.lxvst_plugin_info ());
2344 #ifdef AUDIOUNIT_SUPPORT
2345 refiller (plugs, mgr.au_plugin_info ());
2347 refiller (plugs, mgr.ladspa_plugin_info ());
2348 refiller (plugs, mgr.lua_plugin_info ());
2350 store_current_favorite_order ();
2352 PluginCustomSorter cmp (favorite_order);
2355 favorite_order = plugs;
2357 sync_treeview_from_favorite_order ();
2361 Mixer_UI::sync_treeview_favorite_ui_state (const TreeModel::Path& path, const TreeModel::iterator&)
2364 if (!(iter = favorite_plugins_model->get_iter (path))) {
2367 ARDOUR::PluginPresetPtr ppp = (*iter)[favorite_plugins_columns.plugin];
2371 PluginInfoPtr pip = ppp->_pip;
2372 if (favorite_ui_state.find (pip->unique_id) != favorite_ui_state.end ()) {
2373 if (favorite_ui_state[pip->unique_id]) {
2374 favorite_plugins_display.expand_row (path, true);
2380 Mixer_UI::sync_treeview_from_favorite_order ()
2382 favorite_plugins_model->clear ();
2383 for (PluginInfoList::const_iterator i = favorite_order.begin(); i != favorite_order.end(); ++i) {
2384 PluginInfoPtr pip = (*i);
2386 TreeModel::Row newrow = *(favorite_plugins_model->append());
2387 newrow[favorite_plugins_columns.name] = (*i)->name;
2388 newrow[favorite_plugins_columns.plugin] = PluginPresetPtr (new PluginPreset(pip));
2393 vector<ARDOUR::Plugin::PresetRecord> presets = (*i)->get_presets (true);
2394 for (vector<ARDOUR::Plugin::PresetRecord>::const_iterator j = presets.begin(); j != presets.end(); ++j) {
2395 Gtk::TreeModel::Row child_row = *(favorite_plugins_model->append (newrow.children()));
2396 child_row[favorite_plugins_columns.name] = (*j).label;
2397 child_row[favorite_plugins_columns.plugin] = PluginPresetPtr (new PluginPreset(pip, &(*j)));
2399 if (favorite_ui_state.find (pip->unique_id) != favorite_ui_state.end ()) {
2400 if (favorite_ui_state[pip->unique_id]) {
2401 favorite_plugins_display.expand_row (favorite_plugins_model->get_path(newrow), true);
2408 Mixer_UI::popup_note_context_menu (GdkEventButton *ev)
2410 using namespace Gtk::Menu_Helpers;
2412 Gtk::Menu* m = manage (new Menu);
2413 MenuList& items = m->items ();
2415 if (_selection.routes.empty()) {
2416 items.push_back (MenuElem (_("No Track/Bus is selected.")));
2418 items.push_back (MenuElem (_("Add at the top"),
2419 sigc::bind (sigc::mem_fun (*this, &Mixer_UI::add_selected_processor), AddTop)));
2420 items.push_back (MenuElem (_("Add Pre-Fader"),
2421 sigc::bind (sigc::mem_fun (*this, &Mixer_UI::add_selected_processor), AddPreFader)));
2422 items.push_back (MenuElem (_("Add Post-Fader"),
2423 sigc::bind (sigc::mem_fun (*this, &Mixer_UI::add_selected_processor), AddPostFader)));
2424 items.push_back (MenuElem (_("Add at the end"),
2425 sigc::bind (sigc::mem_fun (*this, &Mixer_UI::add_selected_processor), AddBottom)));
2428 items.push_back (SeparatorElem());
2430 items.push_back (MenuElem (_("Remove from favorites"), sigc::mem_fun (*this, &Mixer_UI::remove_selected_from_favorites)));
2432 ARDOUR::PluginPresetPtr ppp = selected_plugin();
2433 if (ppp && ppp->_preset.valid && ppp->_preset.user) {
2434 // we cannot currently delete AU presets
2435 if (!ppp->_pip || ppp->_pip->type != AudioUnit) {
2436 items.push_back (MenuElem (_("Delete Preset"), sigc::mem_fun (*this, &Mixer_UI::delete_selected_preset)));
2440 m->popup (ev->button, ev->time);
2444 Mixer_UI::plugin_row_button_press (GdkEventButton *ev)
2446 if ((ev->type == GDK_BUTTON_PRESS) && (ev->button == 3) ) {
2447 TreeModel::Path path;
2448 TreeViewColumn* column;
2450 if (favorite_plugins_display.get_path_at_pos ((int)ev->x, (int)ev->y, path, column, cellx, celly)) {
2451 Glib::RefPtr<Gtk::TreeView::Selection> selection = favorite_plugins_display.get_selection();
2453 selection->unselect_all();
2454 selection->select(path);
2457 ARDOUR::PluginPresetPtr ppp = selected_plugin();
2459 popup_note_context_menu (ev);
2467 Mixer_UI::selected_plugin ()
2469 Glib::RefPtr<Gtk::TreeView::Selection> selection = favorite_plugins_display.get_selection();
2471 return PluginPresetPtr();
2473 Gtk::TreeModel::iterator iter = selection->get_selected();
2475 return PluginPresetPtr();
2477 return (*iter)[favorite_plugins_columns.plugin];
2481 Mixer_UI::add_selected_processor (ProcessorPosition pos)
2483 ARDOUR::PluginPresetPtr ppp = selected_plugin();
2485 add_favorite_processor (ppp, pos);
2490 Mixer_UI::delete_selected_preset ()
2495 ARDOUR::PluginPresetPtr ppp = selected_plugin();
2496 if (!ppp || !ppp->_preset.valid || !ppp->_preset.user) {
2499 PluginPtr plugin = ppp->_pip->load (*_session);
2500 plugin->get_presets();
2501 plugin->remove_preset (ppp->_preset.label);
2505 Mixer_UI::remove_selected_from_favorites ()
2507 ARDOUR::PluginPresetPtr ppp = selected_plugin();
2511 PluginManager::PluginStatusType status = PluginManager::Normal;
2512 PluginManager& manager (PluginManager::instance());
2514 manager.set_status (ppp->_pip->type, ppp->_pip->unique_id, status);
2515 manager.save_statuses ();
2519 Mixer_UI::plugin_row_activated (const TreeModel::Path& path, TreeViewColumn* column)
2522 if (!(iter = favorite_plugins_model->get_iter (path))) {
2525 ARDOUR::PluginPresetPtr ppp = (*iter)[favorite_plugins_columns.plugin];
2526 add_favorite_processor (ppp, AddPreFader); // TODO: preference?!
2530 Mixer_UI::add_favorite_processor (ARDOUR::PluginPresetPtr ppp, ProcessorPosition pos)
2532 if (!_session || _selection.routes.empty()) {
2536 PluginInfoPtr pip = ppp->_pip;
2537 for (RouteUISelection::iterator i = _selection.routes.begin(); i != _selection.routes.end(); ++i) {
2538 boost::shared_ptr<ARDOUR::Route> rt = (*i)->route();
2539 if (!rt) { continue; }
2541 PluginPtr p = pip->load (*_session);
2542 if (!p) { continue; }
2544 if (ppp->_preset.valid) {
2545 p->load_preset (ppp->_preset);
2548 Route::ProcessorStreams err;
2549 boost::shared_ptr<Processor> processor (new PluginInsert (*_session, p));
2553 rt->add_processor_by_index (processor, 0, &err, Config->get_new_plugins_active ());
2556 rt->add_processor (processor, PreFader, &err, Config->get_new_plugins_active ());
2563 boost::shared_ptr<Processor> np = rt->nth_processor (idx);
2567 if (!np->display_to_user()) {
2570 if (boost::dynamic_pointer_cast<Amp> (np) && // Fader, not Trim
2571 boost::dynamic_pointer_cast<Amp> (np)->gain_control()->parameter().type() == GainAutomation) {
2576 rt->add_processor_by_index (processor, ++pos, &err, Config->get_new_plugins_active ());
2580 rt->add_processor_by_index (processor, -1, &err, Config->get_new_plugins_active ());
2587 PluginTreeStore::row_drop_possible_vfunc(const Gtk::TreeModel::Path& dest, const Gtk::SelectionData& data) const
2589 if (data.get_target() != "GTK_TREE_MODEL_ROW") {
2593 // only allow to re-order top-level items
2595 if (TreePath::get_from_selection_data (data, src)) {
2596 if (src.up() && src.up()) {
2601 // don't allow to drop as child-rows.
2602 Gtk::TreeModel::Path _dest = dest; // un const
2603 const bool is_child = _dest.up (); // explicit bool for clang
2604 if (!is_child || _dest.empty ()) {
2611 Mixer_UI::plugin_drop (const Glib::RefPtr<Gdk::DragContext>&, const Gtk::SelectionData& data)
2613 if (data.get_target() != "PluginPresetPtr") {
2616 if (data.get_length() != sizeof (PluginPresetPtr)) {
2619 const void *d = data.get_data();
2620 const PluginPresetPtr ppp = *(static_cast<const PluginPresetPtr*> (d));
2622 PluginManager::PluginStatusType status = PluginManager::Favorite;
2623 PluginManager& manager (PluginManager::instance());
2625 manager.set_status (ppp->_pip->type, ppp->_pip->unique_id, status);
2626 manager.save_statuses ();
2630 Mixer_UI::do_vca_assign (boost::shared_ptr<VCA> vca)
2632 /* call protected MixerActor:: method */
2637 Mixer_UI::do_vca_unassign (boost::shared_ptr<VCA> vca)
2639 /* call protected MixerActor:: method */