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 Gtk::TreeModel::Children::iterator insert_iter = track_model->children().end();
402 uint32_t nroutes = 0;
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];
413 if (r->order_key() == (routes.front()->order_key() + routes.size())) {
420 _selection.clear_routes ();
426 no_track_list_redisplay = true;
427 track_display.set_model (Glib::RefPtr<ListStore>());
429 for (RouteList::iterator x = routes.begin(); x != routes.end(); ++x) {
430 boost::shared_ptr<Route> route = (*x);
432 if (route->is_auditioner()) {
436 if (route->is_monitor()) {
438 if (!_monitor_section) {
439 _monitor_section = new MonitorSection (_session);
441 XMLNode* mnode = ARDOUR_UI::instance()->tearoff_settings (X_("monitor-section"));
443 _monitor_section->tearoff().set_state (*mnode);
447 out_packer.pack_end (_monitor_section->tearoff(), false, false);
448 _monitor_section->set_session (_session);
449 _monitor_section->tearoff().show_all ();
451 _monitor_section->tearoff().Detach.connect (sigc::mem_fun(*this, &Mixer_UI::monitor_section_detached));
452 _monitor_section->tearoff().Attach.connect (sigc::mem_fun(*this, &Mixer_UI::monitor_section_attached));
454 monitor_section_attached ();
456 route->DropReferences.connect (*this, invalidator(*this), boost::bind (&Mixer_UI::monitor_section_going_away, this), gui_context());
458 /* no regular strip shown for control out */
463 strip = new MixerStrip (*this, _session, route);
464 strips.push_back (strip);
466 UIConfiguration::instance().get_default_narrow_ms() ? _strip_width = Narrow : _strip_width = Wide;
468 if (strip->width_owner() != strip) {
469 strip->set_width_enum (_strip_width, this);
474 TreeModel::Row row = *(track_model->insert(insert_iter));
475 row[track_columns.text] = route->name();
476 row[track_columns.visible] = strip->route()->is_master() ? true : strip->marked_for_display();
477 row[track_columns.route] = route;
478 row[track_columns.strip] = strip;
479 row[track_columns.vca] = 0;
482 _selection.add (strip);
485 route->PropertyChanged.connect (*this, invalidator (*this), boost::bind (&Mixer_UI::strip_property_changed, this, _1, strip), gui_context());
487 strip->WidthChanged.connect (sigc::mem_fun(*this, &Mixer_UI::strip_width_changed));
488 strip->signal_button_release_event().connect (sigc::bind (sigc::mem_fun(*this, &Mixer_UI::strip_button_release_event), strip));
491 } catch (const std::exception& e) {
492 error << string_compose (_("Error adding GUI elements for new tracks/busses %1"), e.what()) << endmsg;
495 no_track_list_redisplay = false;
496 track_display.set_model (track_model);
498 sync_order_keys_from_treeview ();
499 redisplay_track_list ();
503 Mixer_UI::deselect_all_strip_processors ()
505 for (list<MixerStrip *>::iterator i = strips.begin(); i != strips.end(); ++i) {
506 (*i)->deselect_all_processors();
511 Mixer_UI::select_strip (MixerStrip& ms, bool add)
514 _selection.add (&ms);
516 _selection.set (&ms);
521 Mixer_UI::select_none ()
523 _selection.clear_routes();
524 deselect_all_strip_processors();
528 Mixer_UI::delete_processors ()
530 for (list<MixerStrip *>::iterator i = strips.begin(); i != strips.end(); ++i) {
531 (*i)->delete_processors();
537 Mixer_UI::remove_strip (MixerStrip* strip)
539 if (_session && _session->deletion_in_progress()) {
540 /* its all being taken care of */
544 TreeModel::Children rows = track_model->children();
545 TreeModel::Children::iterator ri;
546 list<MixerStrip *>::iterator i;
548 if ((i = find (strips.begin(), strips.end(), strip)) != strips.end()) {
552 for (ri = rows.begin(); ri != rows.end(); ++ri) {
553 if ((*ri)[track_columns.strip] == strip) {
554 PBD::Unwinder<bool> uw (_route_deletion_in_progress, true);
555 track_model->erase (ri);
562 Mixer_UI::reset_remote_control_ids ()
564 if (Config->get_remote_model() == UserOrdered || !_session || _session->deletion_in_progress()) {
568 TreeModel::Children rows = track_model->children();
574 DEBUG_TRACE (DEBUG::OrderKeys, "mixer resets remote control ids after remote model change\n");
576 TreeModel::Children::iterator ri;
577 bool rid_change = false;
579 uint32_t invisible_key = UINT32_MAX;
581 for (ri = rows.begin(); ri != rows.end(); ++ri) {
583 /* skip two special values */
585 if (rid == Route::MasterBusRemoteControlID) {
589 if (rid == Route::MonitorBusRemoteControlID) {
593 boost::shared_ptr<Route> route = (*ri)[track_columns.route];
594 bool visible = (*ri)[track_columns.visible];
600 if (!route->is_master() && !route->is_monitor()) {
602 uint32_t new_rid = (visible ? rid : invisible_key--);
604 if (new_rid != route->remote_control_id()) {
605 route->set_remote_control_id_explicit (new_rid);
616 /* tell the world that we changed the remote control IDs */
617 _session->notify_remote_id_change ();
622 Mixer_UI::sync_order_keys_from_treeview ()
624 if (ignore_reorder || !_session || _session->deletion_in_progress()) {
628 TreeModel::Children rows = track_model->children();
634 DEBUG_TRACE (DEBUG::OrderKeys, "mixer sync order keys from model\n");
636 TreeModel::Children::iterator ri;
637 bool changed = false;
638 bool rid_change = false;
641 uint32_t invisible_key = UINT32_MAX;
643 for (ri = rows.begin(); ri != rows.end(); ++ri) {
644 boost::shared_ptr<Route> route = (*ri)[track_columns.route];
645 bool visible = (*ri)[track_columns.visible];
651 uint32_t old_key = route->order_key ();
653 if (order != old_key) {
654 route->set_order_key (order);
658 if ((Config->get_remote_model() == MixerOrdered) && !route->is_master() && !route->is_monitor()) {
660 uint32_t new_rid = (visible ? rid : invisible_key--);
662 if (new_rid != route->remote_control_id()) {
663 route->set_remote_control_id_explicit (new_rid);
677 /* tell everyone that we changed the mixer sort keys */
678 _session->sync_order_keys ();
682 /* tell the world that we changed the remote control IDs */
683 _session->notify_remote_id_change ();
688 Mixer_UI::sync_treeview_from_order_keys ()
690 if (!_session || _session->deletion_in_progress()) {
694 DEBUG_TRACE (DEBUG::OrderKeys, "mixer sync model from order keys.\n");
696 /* we could get here after either a change in the Mixer or Editor sort
697 * order, but either way, the mixer order keys reflect the intended
698 * order for the GUI, so reorder the treeview model to match it.
701 vector<int> neworder;
702 TreeModel::Children rows = track_model->children();
703 uint32_t old_order = 0;
704 bool changed = false;
711 uint32_t vca_cnt = 0;
712 uint32_t max_route_order_key = 0;
714 /* count number of Routes in track_model (there may be some odd reason
715 why this is not the same as the number in the session, but here we
716 care about the track model.
719 for (TreeModel::Children::iterator ri = rows.begin(); ri != rows.end(); ++ri) {
720 boost::shared_ptr<Route> route = (*ri)[track_columns.route];
722 max_route_order_key = max (route->order_key(), max_route_order_key);
726 for (TreeModel::Children::iterator ri = rows.begin(); ri != rows.end(); ++ri, ++old_order) {
727 boost::shared_ptr<Route> route = (*ri)[track_columns.route];
729 /* VCAs need to sort after all routes. We don't display
730 * them in the same place (March 2016), but we don't
731 * want them intermixed in the track_model
733 sorted.push_back (OrderKeys (old_order, max_route_order_key + ++vca_cnt));
735 sorted.push_back (OrderKeys (old_order, route->order_key ()));
739 SortByNewDisplayOrder cmp;
741 sort (sorted.begin(), sorted.end(), cmp);
742 neworder.assign (sorted.size(), 0);
746 for (OrderingKeys::iterator sr = sorted.begin(); sr != sorted.end(); ++sr, ++n) {
748 neworder[n] = sr->old_display_order;
750 if (sr->old_display_order != n) {
754 DEBUG_TRACE (DEBUG::OrderKeys, string_compose ("MIXER change order from %1 to %2\n",
755 sr->old_display_order, n));
759 Unwinder<bool> uw (ignore_reorder, true);
760 track_model->reorder (neworder);
763 redisplay_track_list ();
767 Mixer_UI::follow_editor_selection ()
769 if (_following_editor_selection) {
773 _following_editor_selection = true;
774 _selection.block_routes_changed (true);
776 TrackSelection& s (PublicEditor::instance().get_selection().tracks);
778 _selection.clear_routes ();
780 for (TrackViewList::iterator i = s.begin(); i != s.end(); ++i) {
781 RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*> (*i);
783 MixerStrip* ms = strip_by_route (rtav->route());
790 _following_editor_selection = false;
791 _selection.block_routes_changed (false);
796 Mixer_UI::strip_by_route (boost::shared_ptr<Route> r)
798 for (list<MixerStrip *>::iterator i = strips.begin(); i != strips.end(); ++i) {
799 if ((*i)->route() == r) {
808 Mixer_UI::strip_button_release_event (GdkEventButton *ev, MixerStrip *strip)
810 if (ev->button == 1) {
811 if (_selection.selected (strip)) {
812 /* primary-click: toggle selection state of strip */
813 if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
814 _selection.remove (strip);
815 } else if (_selection.routes.size() > 1) {
816 /* de-select others */
817 _selection.set (strip);
820 if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
821 _selection.add (strip);
822 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::RangeSelectModifier)) {
824 if (!_selection.selected(strip)) {
826 /* extend selection */
828 vector<MixerStrip*> tmp;
829 bool accumulate = false;
830 bool found_another = false;
832 tmp.push_back (strip);
834 for (list<MixerStrip*>::iterator i = strips.begin(); i != strips.end(); ++i) {
836 /* hit clicked strip, start accumulating till we hit the first
845 } else if (_selection.selected (*i)) {
846 /* hit selected strip. if currently accumulating others,
847 we're done. if not accumulating others, start doing so.
849 found_another = true;
864 for (vector<MixerStrip*>::iterator i = tmp.begin(); i != tmp.end(); ++i) {
868 _selection.set (strip); //user wants to start a range selection, but there aren't any others selected yet
872 _selection.set (strip);
881 Mixer_UI::set_session (Session* sess)
883 SessionHandlePtr::set_session (sess);
885 if (_plugin_selector) {
886 _plugin_selector->set_session (_session);
889 _group_tabs->set_session (sess);
895 refill_favorite_plugins();
897 XMLNode* node = ARDOUR_UI::instance()->mixer_settings();
898 set_state (*node, 0);
902 initial_track_display ();
904 _session->RouteAdded.connect (_session_connections, invalidator (*this), boost::bind (&Mixer_UI::add_strips, this, _1), gui_context());
905 _session->route_group_added.connect (_session_connections, invalidator (*this), boost::bind (&Mixer_UI::add_route_group, this, _1), gui_context());
906 _session->route_group_removed.connect (_session_connections, invalidator (*this), boost::bind (&Mixer_UI::route_groups_changed, this), gui_context());
907 _session->route_groups_reordered.connect (_session_connections, invalidator (*this), boost::bind (&Mixer_UI::route_groups_changed, this), gui_context());
908 _session->config.ParameterChanged.connect (_session_connections, invalidator (*this), boost::bind (&Mixer_UI::parameter_changed, this, _1), gui_context());
909 _session->DirtyChanged.connect (_session_connections, invalidator (*this), boost::bind (&Mixer_UI::update_title, this), gui_context());
910 _session->StateSaved.connect (_session_connections, invalidator (*this), boost::bind (&Mixer_UI::update_title, this), gui_context());
912 _session->vca_manager().VCAAdded.connect (_session_connections, invalidator (*this), boost::bind (&Mixer_UI::add_masters, this, _1), gui_context());
914 Config->ParameterChanged.connect (*this, invalidator (*this), boost::bind (&Mixer_UI::parameter_changed, this, _1), gui_context ());
916 route_groups_changed ();
925 Mixer_UI::session_going_away ()
927 ENSURE_GUI_THREAD (*this, &Mixer_UI::session_going_away);
929 _in_group_rebuild_or_clear = true;
930 group_model->clear ();
931 _in_group_rebuild_or_clear = false;
934 track_model->clear ();
936 for (list<MixerStrip *>::iterator i = strips.begin(); i != strips.end(); ++i) {
940 if (_monitor_section) {
941 _monitor_section->tearoff().hide_visible ();
944 monitor_section_detached ();
950 SessionHandlePtr::session_going_away ();
957 Mixer_UI::track_visibility_changed (std::string const & path)
959 if (_session && _session->deletion_in_progress()) {
965 if ((iter = track_model->get_iter (path))) {
966 MixerStrip* strip = (*iter)[track_columns.strip];
968 bool visible = (*iter)[track_columns.visible];
970 if (strip->set_marked_for_display (!visible)) {
971 update_track_visibility ();
978 Mixer_UI::update_track_visibility ()
980 TreeModel::Children rows = track_model->children();
981 TreeModel::Children::iterator i;
984 Unwinder<bool> uw (no_track_list_redisplay, true);
986 for (i = rows.begin(); i != rows.end(); ++i) {
987 MixerStrip *strip = (*i)[track_columns.strip];
989 (*i)[track_columns.visible] = strip->marked_for_display ();
993 /* force route order keys catch up with visibility changes
996 sync_order_keys_from_treeview ();
999 redisplay_track_list ();
1003 Mixer_UI::show_strip (MixerStrip* ms)
1005 TreeModel::Children rows = track_model->children();
1006 TreeModel::Children::iterator i;
1008 for (i = rows.begin(); i != rows.end(); ++i) {
1010 MixerStrip* strip = (*i)[track_columns.strip];
1012 (*i)[track_columns.visible] = true;
1013 redisplay_track_list ();
1020 Mixer_UI::hide_strip (MixerStrip* ms)
1022 TreeModel::Children rows = track_model->children();
1023 TreeModel::Children::iterator i;
1025 for (i = rows.begin(); i != rows.end(); ++i) {
1027 MixerStrip* strip = (*i)[track_columns.strip];
1029 (*i)[track_columns.visible] = false;
1030 redisplay_track_list ();
1037 Mixer_UI::start_updating ()
1039 fast_screen_update_connection = Timers::super_rapid_connect (sigc::mem_fun(*this, &Mixer_UI::fast_update_strips));
1044 Mixer_UI::stop_updating ()
1046 fast_screen_update_connection.disconnect();
1051 Mixer_UI::fast_update_strips ()
1053 if (_content.is_mapped () && _session) {
1054 for (list<MixerStrip *>::iterator i = strips.begin(); i != strips.end(); ++i) {
1055 (*i)->fast_update ();
1061 Mixer_UI::set_all_strips_visibility (bool yn)
1063 TreeModel::Children rows = track_model->children();
1064 TreeModel::Children::iterator i;
1067 Unwinder<bool> uw (no_track_list_redisplay, true);
1069 for (i = rows.begin(); i != rows.end(); ++i) {
1071 TreeModel::Row row = (*i);
1072 MixerStrip* strip = row[track_columns.strip];
1078 if (strip->route()->is_master() || strip->route()->is_monitor()) {
1082 (*i)[track_columns.visible] = yn;
1086 redisplay_track_list ();
1091 Mixer_UI::set_all_audio_midi_visibility (int tracks, bool yn)
1093 TreeModel::Children rows = track_model->children();
1094 TreeModel::Children::iterator i;
1097 Unwinder<bool> uw (no_track_list_redisplay, true);
1099 for (i = rows.begin(); i != rows.end(); ++i) {
1100 TreeModel::Row row = (*i);
1101 MixerStrip* strip = row[track_columns.strip];
1107 if (strip->route()->is_master() || strip->route()->is_monitor()) {
1111 boost::shared_ptr<AudioTrack> at = strip->audio_track();
1112 boost::shared_ptr<MidiTrack> mt = strip->midi_track();
1116 (*i)[track_columns.visible] = yn;
1120 if (at) { /* track */
1121 (*i)[track_columns.visible] = yn;
1126 if (!at && !mt) { /* bus */
1127 (*i)[track_columns.visible] = yn;
1132 if (mt) { /* midi-track */
1133 (*i)[track_columns.visible] = yn;
1140 redisplay_track_list ();
1144 Mixer_UI::hide_all_routes ()
1146 set_all_strips_visibility (false);
1150 Mixer_UI::show_all_routes ()
1152 set_all_strips_visibility (true);
1156 Mixer_UI::show_all_audiobus ()
1158 set_all_audio_midi_visibility (2, true);
1161 Mixer_UI::hide_all_audiobus ()
1163 set_all_audio_midi_visibility (2, false);
1167 Mixer_UI::show_all_audiotracks()
1169 set_all_audio_midi_visibility (1, true);
1172 Mixer_UI::hide_all_audiotracks ()
1174 set_all_audio_midi_visibility (1, false);
1178 Mixer_UI::show_all_miditracks()
1180 set_all_audio_midi_visibility (3, true);
1183 Mixer_UI::hide_all_miditracks ()
1185 set_all_audio_midi_visibility (3, false);
1190 Mixer_UI::track_list_reorder (const TreeModel::Path&, const TreeModel::iterator&, int* /*new_order*/)
1192 DEBUG_TRACE (DEBUG::OrderKeys, "mixer UI treeview reordered\n");
1193 sync_order_keys_from_treeview ();
1197 Mixer_UI::track_list_delete (const Gtk::TreeModel::Path&)
1199 /* this happens as the second step of a DnD within the treeview as well
1200 as when a row/route is actually deleted.
1202 if it was a deletion then we have to force a redisplay because
1203 order keys may not have changed.
1206 DEBUG_TRACE (DEBUG::OrderKeys, "mixer UI treeview row deleted\n");
1207 sync_order_keys_from_treeview ();
1209 if (_route_deletion_in_progress) {
1210 redisplay_track_list ();
1215 Mixer_UI::redisplay_track_list ()
1217 TreeModel::Children rows = track_model->children();
1218 TreeModel::Children::iterator i;
1220 if (no_track_list_redisplay) {
1224 container_clear (vca_packer);
1226 for (i = rows.begin(); i != rows.end(); ++i) {
1228 VCAMasterStrip* vms = (*i)[track_columns.vca];
1231 vca_packer.pack_start (*vms, false, false);
1236 MixerStrip* strip = (*i)[track_columns.strip];
1239 /* we're in the middle of changing a row, don't worry */
1243 bool const visible = (*i)[track_columns.visible];
1246 strip->set_gui_property ("visible", true);
1248 if (strip->packed()) {
1250 if (strip->route()->is_master() || strip->route()->is_monitor()) {
1251 out_packer.reorder_child (*strip, -1);
1254 strip_packer.reorder_child (*strip, -1); /* put at end */
1259 if (strip->route()->is_master() || strip->route()->is_monitor()) {
1260 out_packer.pack_start (*strip, false, false);
1262 strip_packer.pack_start (*strip, false, false);
1264 strip->set_packed (true);
1269 strip->set_gui_property ("visible", false);
1271 if (strip->route()->is_master() || strip->route()->is_monitor()) {
1272 /* do nothing, these cannot be hidden */
1274 if (strip->packed()) {
1275 strip_packer.remove (*strip);
1276 strip->set_packed (false);
1282 _group_tabs->set_dirty ();
1286 Mixer_UI::strip_width_changed ()
1288 _group_tabs->set_dirty ();
1291 TreeModel::Children rows = track_model->children();
1292 TreeModel::Children::iterator i;
1295 for (order = 0, i = rows.begin(); i != rows.end(); ++i, ++order) {
1296 MixerStrip* strip = (*i)[track_columns.strip];
1302 bool visible = (*i)[track_columns.visible];
1305 strip->queue_draw();
1313 Mixer_UI::initial_track_display ()
1315 boost::shared_ptr<RouteList> routes = _session->get_routes();
1316 RouteList copy (*routes);
1317 ARDOUR::SignalOrderRouteSorter sorter;
1322 Unwinder<bool> uw1 (no_track_list_redisplay, true);
1323 Unwinder<bool> uw2 (ignore_reorder, true);
1325 track_model->clear ();
1326 VCAList vcas = _session->vca_manager().vcas();
1331 _session->sync_order_keys ();
1333 redisplay_track_list ();
1337 Mixer_UI::show_track_list_menu ()
1339 if (track_menu == 0) {
1340 build_track_menu ();
1343 track_menu->popup (1, gtk_get_current_event_time());
1347 Mixer_UI::track_display_button_press (GdkEventButton* ev)
1349 if (Keyboard::is_context_menu_event (ev)) {
1350 show_track_list_menu ();
1358 Mixer_UI::build_track_menu ()
1360 using namespace Menu_Helpers;
1361 using namespace Gtk;
1363 track_menu = new Menu;
1364 track_menu->set_name ("ArdourContextMenu");
1365 MenuList& items = track_menu->items();
1367 items.push_back (MenuElem (_("Show All"), sigc::mem_fun(*this, &Mixer_UI::show_all_routes)));
1368 items.push_back (MenuElem (_("Hide All"), sigc::mem_fun(*this, &Mixer_UI::hide_all_routes)));
1369 items.push_back (MenuElem (_("Show All Audio Tracks"), sigc::mem_fun(*this, &Mixer_UI::show_all_audiotracks)));
1370 items.push_back (MenuElem (_("Hide All Audio Tracks"), sigc::mem_fun(*this, &Mixer_UI::hide_all_audiotracks)));
1371 items.push_back (MenuElem (_("Show All Audio Busses"), sigc::mem_fun(*this, &Mixer_UI::show_all_audiobus)));
1372 items.push_back (MenuElem (_("Hide All Audio Busses"), sigc::mem_fun(*this, &Mixer_UI::hide_all_audiobus)));
1373 items.push_back (MenuElem (_("Show All Midi Tracks"), sigc::mem_fun (*this, &Mixer_UI::show_all_miditracks)));
1374 items.push_back (MenuElem (_("Hide All Midi Tracks"), sigc::mem_fun (*this, &Mixer_UI::hide_all_miditracks)));
1379 Mixer_UI::strip_property_changed (const PropertyChange& what_changed, MixerStrip* mx)
1381 if (!what_changed.contains (ARDOUR::Properties::name)) {
1385 ENSURE_GUI_THREAD (*this, &Mixer_UI::strip_name_changed, what_changed, mx)
1387 TreeModel::Children rows = track_model->children();
1388 TreeModel::Children::iterator i;
1390 for (i = rows.begin(); i != rows.end(); ++i) {
1391 if ((*i)[track_columns.strip] == mx) {
1392 (*i)[track_columns.text] = mx->route()->name();
1397 error << _("track display list item for renamed strip not found!") << endmsg;
1401 Mixer_UI::group_display_button_press (GdkEventButton* ev)
1403 TreeModel::Path path;
1404 TreeViewColumn* column;
1408 if (!group_display.get_path_at_pos ((int)ev->x, (int)ev->y, path, column, cellx, celly)) {
1409 _group_tabs->get_menu(0)->popup (1, ev->time);
1413 TreeIter iter = group_model->get_iter (path);
1415 _group_tabs->get_menu(0)->popup (1, ev->time);
1419 RouteGroup* group = (*iter)[group_columns.group];
1421 if (Keyboard::is_context_menu_event (ev)) {
1422 _group_tabs->get_menu(group)->popup (1, ev->time);
1426 switch (GPOINTER_TO_UINT (column->get_data (X_("colnum")))) {
1428 if (Keyboard::is_edit_event (ev)) {
1430 // edit_route_group (group);
1432 group_display.queue_draw();
1441 bool visible = (*iter)[group_columns.visible];
1442 (*iter)[group_columns.visible] = !visible;
1444 group_display.queue_draw();
1457 Mixer_UI::activate_all_route_groups ()
1459 _session->foreach_route_group (sigc::bind (sigc::mem_fun (*this, &Mixer_UI::set_route_group_activation), true));
1463 Mixer_UI::disable_all_route_groups ()
1465 _session->foreach_route_group (sigc::bind (sigc::mem_fun (*this, &Mixer_UI::set_route_group_activation), false));
1469 Mixer_UI::route_groups_changed ()
1471 ENSURE_GUI_THREAD (*this, &Mixer_UI::route_groups_changed);
1473 _in_group_rebuild_or_clear = true;
1475 /* just rebuild the while thing */
1477 group_model->clear ();
1480 /* this is currently not used,
1481 * Mixer_UI::group_display_button_press() has a case for it,
1482 * and a commented edit_route_group() but that's n/a since 2011.
1484 * This code is left as reminder that
1485 * row[group_columns.group] = 0 has special meaning.
1489 row = *(group_model->append());
1490 row[group_columns.visible] = true;
1491 row[group_columns.text] = (_("-all-"));
1492 row[group_columns.group] = 0;
1496 _session->foreach_route_group (sigc::mem_fun (*this, &Mixer_UI::add_route_group));
1498 _group_tabs->set_dirty ();
1499 _in_group_rebuild_or_clear = false;
1503 Mixer_UI::new_route_group ()
1507 _group_tabs->run_new_group_dialog (rl);
1511 Mixer_UI::remove_selected_route_group ()
1513 Glib::RefPtr<TreeSelection> selection = group_display.get_selection();
1514 TreeView::Selection::ListHandle_Path rows = selection->get_selected_rows ();
1520 TreeView::Selection::ListHandle_Path::iterator i = rows.begin();
1523 /* selection mode is single, so rows.begin() is it */
1525 if ((iter = group_model->get_iter (*i))) {
1527 RouteGroup* rg = (*iter)[group_columns.group];
1530 _session->remove_route_group (*rg);
1536 Mixer_UI::route_group_property_changed (RouteGroup* group, const PropertyChange& change)
1538 if (in_group_row_change) {
1542 /* force an update of any mixer strips that are using this group,
1543 otherwise mix group names don't change in mixer strips
1546 for (list<MixerStrip *>::iterator i = strips.begin(); i != strips.end(); ++i) {
1547 if ((*i)->route_group() == group) {
1548 (*i)->route_group_changed();
1552 TreeModel::iterator i;
1553 TreeModel::Children rows = group_model->children();
1554 Glib::RefPtr<TreeSelection> selection = group_display.get_selection();
1556 in_group_row_change = true;
1558 for (i = rows.begin(); i != rows.end(); ++i) {
1559 if ((*i)[group_columns.group] == group) {
1560 (*i)[group_columns.visible] = !group->is_hidden ();
1561 (*i)[group_columns.text] = group->name ();
1566 in_group_row_change = false;
1568 if (change.contains (Properties::name)) {
1569 _group_tabs->set_dirty ();
1572 for (list<MixerStrip*>::iterator j = strips.begin(); j != strips.end(); ++j) {
1573 if ((*j)->route_group() == group) {
1574 if (group->is_hidden ()) {
1584 Mixer_UI::show_mixer_list (bool yn)
1587 list_vpacker.show ();
1589 //if user wants to show the pane, we should make sure that it is wide enough to be visible
1590 int width = list_hpane.get_position();
1592 list_hpane.set_position(40);
1594 list_vpacker.hide ();
1597 _show_mixer_list = yn;
1601 Mixer_UI::show_monitor_section (bool yn)
1603 if (!monitor_section()) {
1606 if (monitor_section()->tearoff().torn_off()) {
1611 monitor_section()->tearoff().show();
1613 monitor_section()->tearoff().hide();
1618 Mixer_UI::route_group_name_edit (const std::string& path, const std::string& new_text)
1623 if ((iter = group_model->get_iter (path))) {
1625 if ((group = (*iter)[group_columns.group]) == 0) {
1629 if (new_text != group->name()) {
1630 group->set_name (new_text);
1636 Mixer_UI::route_group_row_change (const Gtk::TreeModel::Path&, const Gtk::TreeModel::iterator& iter)
1640 if (in_group_row_change) {
1644 if ((group = (*iter)[group_columns.group]) == 0) {
1648 std::string name = (*iter)[group_columns.text];
1650 if (name != group->name()) {
1651 group->set_name (name);
1654 bool hidden = !(*iter)[group_columns.visible];
1656 if (hidden != group->is_hidden ()) {
1657 group->set_hidden (hidden, this);
1661 /** Called when a group model row is deleted, but also when the model is
1662 * reordered by a user drag-and-drop; the latter is what we are
1663 * interested in here.
1666 Mixer_UI::route_group_row_deleted (Gtk::TreeModel::Path const &)
1668 if (_in_group_rebuild_or_clear) {
1672 /* Re-write the session's route group list so that the new order is preserved */
1674 list<RouteGroup*> new_list;
1676 Gtk::TreeModel::Children children = group_model->children();
1677 for (Gtk::TreeModel::Children::iterator i = children.begin(); i != children.end(); ++i) {
1678 RouteGroup* g = (*i)[group_columns.group];
1680 new_list.push_back (g);
1684 _session->reorder_route_groups (new_list);
1689 Mixer_UI::add_route_group (RouteGroup* group)
1691 ENSURE_GUI_THREAD (*this, &Mixer_UI::add_route_group, group)
1694 in_group_row_change = true;
1696 TreeModel::Row row = *(group_model->append());
1697 row[group_columns.visible] = !group->is_hidden ();
1698 row[group_columns.group] = group;
1699 if (!group->name().empty()) {
1700 row[group_columns.text] = group->name();
1702 row[group_columns.text] = _("unnamed");
1706 group->PropertyChanged.connect (*this, invalidator (*this), boost::bind (&Mixer_UI::route_group_property_changed, this, group, _1), gui_context());
1709 TreeViewColumn* col = group_display.get_column (0);
1710 CellRendererText* name_cell = dynamic_cast<CellRendererText*>(group_display.get_column_cell_renderer (0));
1711 group_display.set_cursor (group_model->get_path (row), *col, *name_cell, true);
1714 _group_tabs->set_dirty ();
1716 in_group_row_change = false;
1720 Mixer_UI::strip_scroller_button_release (GdkEventButton* ev)
1722 using namespace Menu_Helpers;
1724 if (Keyboard::is_context_menu_event (ev)) {
1725 ARDOUR_UI::instance()->add_route ();
1733 Mixer_UI::scroller_drag_data_received (const Glib::RefPtr<Gdk::DragContext>& context, int x, int y, const Gtk::SelectionData& data, guint info, guint time)
1735 printf ("Mixer_UI::scroller_drag_data_received\n");
1736 if (data.get_target() != "PluginFavoritePtr") {
1737 context->drag_finish (false, false, time);
1741 const void * d = data.get_data();
1742 const Gtkmm2ext::DnDTreeView<ARDOUR::PluginPresetPtr>* tv = reinterpret_cast<const Gtkmm2ext::DnDTreeView<ARDOUR::PluginPresetPtr>*>(d);
1744 PluginPresetList nfos;
1746 tv->get_object_drag_data (nfos, &source);
1748 Route::ProcessorList pl;
1751 for (list<PluginPresetPtr>::const_iterator i = nfos.begin(); i != nfos.end(); ++i) {
1752 PluginPresetPtr ppp = (*i);
1753 PluginInfoPtr pip = ppp->_pip;
1754 if (!pip->is_instrument ()) {
1757 ARDOUR_UI::instance()->session_add_midi_track (NULL, 1, _("MIDI"), Config->get_strict_io (), pip, ppp->_preset.valid ? &ppp->_preset : 0);
1761 context->drag_finish (ok, false, time);
1765 Mixer_UI::set_strip_width (Width w, bool save)
1769 for (list<MixerStrip*>::iterator i = strips.begin(); i != strips.end(); ++i) {
1770 (*i)->set_width_enum (w, save ? (*i)->width_owner() : this);
1775 struct PluginStateSorter {
1777 bool operator() (PluginInfoPtr a, PluginInfoPtr b) const {
1778 std::list<std::string>::const_iterator aiter = std::find(_user.begin(), _user.end(), (*a).unique_id);
1779 std::list<std::string>::const_iterator biter = std::find(_user.begin(), _user.end(), (*b).unique_id);
1780 if (aiter != _user.end() && biter != _user.end()) {
1781 return std::distance (_user.begin(), aiter) < std::distance (_user.begin(), biter);
1783 if (aiter != _user.end()) {
1786 if (biter != _user.end()) {
1789 return ARDOUR::cmp_nocase((*a).name, (*b).name) == -1;
1792 PluginStateSorter(std::list<std::string> user) : _user (user) {}
1794 std::list<std::string> _user;
1798 Mixer_UI::set_state (const XMLNode& node, int version)
1800 XMLProperty const * prop;
1802 Tabbable::set_state (node, version);
1804 if ((prop = node.property ("narrow-strips"))) {
1805 if (string_is_affirmative (prop->value())) {
1806 set_strip_width (Narrow);
1808 set_strip_width (Wide);
1812 if ((prop = node.property ("show-mixer"))) {
1813 if (string_is_affirmative (prop->value())) {
1818 if ((prop = node.property ("maximised"))) {
1819 bool yn = string_is_affirmative (prop->value());
1820 Glib::RefPtr<Action> act = ActionManager::get_action (X_("Common"), X_("ToggleMaximalMixer"));
1822 Glib::RefPtr<ToggleAction> tact = Glib::RefPtr<ToggleAction>::cast_dynamic(act);
1823 bool fs = tact && tact->get_active();
1825 ActionManager::do_action ("Common", "ToggleMaximalMixer");
1829 if ((prop = node.property ("show-mixer-list"))) {
1830 bool yn = string_is_affirmative (prop->value());
1831 Glib::RefPtr<Action> act = ActionManager::get_action (X_("Common"), X_("ToggleMixerList"));
1833 Glib::RefPtr<ToggleAction> tact = Glib::RefPtr<ToggleAction>::cast_dynamic(act);
1835 /* do it twice to force the change */
1836 tact->set_active (!yn);
1837 tact->set_active (yn);
1841 XMLNode* plugin_order;
1842 if ((plugin_order = find_named_node (node, "PluginOrder")) != 0) {
1843 store_current_favorite_order ();
1844 std::list<string> order;
1845 const XMLNodeList& kids = plugin_order->children("PluginInfo");
1846 XMLNodeConstIterator i;
1847 for (i = kids.begin(); i != kids.end(); ++i) {
1848 if ((prop = (*i)->property ("unique-id"))) {
1849 std::string unique_id = prop->value();
1850 order.push_back (unique_id);
1851 if ((prop = (*i)->property ("expanded"))) {
1852 favorite_ui_state[unique_id] = string_is_affirmative (prop->value());
1856 PluginStateSorter cmp (order);
1857 favorite_order.sort (cmp);
1858 sync_treeview_from_favorite_order ();
1864 Mixer_UI::get_state ()
1866 XMLNode* node = new XMLNode (X_("Mixer"));
1869 node->add_child_nocopy (Tabbable::get_state());
1871 snprintf(buf,sizeof(buf), "%d",gtk_paned_get_position (const_cast<GtkPaned*>(static_cast<const Paned*>(&rhs_pane1)->gobj())));
1872 node->add_property(X_("mixer-rhs-pane1-pos"), string(buf));
1873 snprintf(buf,sizeof(buf), "%d",gtk_paned_get_position (const_cast<GtkPaned*>(static_cast<const Paned*>(&rhs_pane2)->gobj())));
1874 node->add_property(X_("mixer-rhs_pane2-pos"), string(buf));
1875 snprintf(buf,sizeof(buf), "%d",gtk_paned_get_position (const_cast<GtkPaned*>(static_cast<const Paned*>(&list_hpane)->gobj())));
1876 node->add_property(X_("mixer-list-hpane-pos"), string(buf));
1877 snprintf(buf,sizeof(buf), "%d",gtk_paned_get_position (const_cast<GtkPaned*>(static_cast<const Paned*>(&inner_pane)->gobj())));
1878 node->add_property(X_("mixer-inner-pane-pos"), string(buf));
1880 node->add_property ("narrow-strips", _strip_width == Narrow ? "yes" : "no");
1881 node->add_property ("show-mixer", _visible ? "yes" : "no");
1882 node->add_property ("show-mixer-list", _show_mixer_list ? "yes" : "no");
1883 node->add_property ("maximised", _maximised ? "yes" : "no");
1885 store_current_favorite_order ();
1886 XMLNode* plugin_order = new XMLNode ("PluginOrder");
1888 for (PluginInfoList::const_iterator i = favorite_order.begin(); i != favorite_order.end(); ++i, ++cnt) {
1889 XMLNode* p = new XMLNode ("PluginInfo");
1890 p->add_property ("sort", cnt);
1891 p->add_property ("unique-id", (*i)->unique_id);
1892 if (favorite_ui_state.find ((*i)->unique_id) != favorite_ui_state.end ()) {
1893 p->add_property ("expanded", favorite_ui_state[(*i)->unique_id]);
1895 plugin_order->add_child_nocopy (*p);
1897 node->add_child_nocopy (*plugin_order);
1903 Mixer_UI::pane_allocation_handler (Allocation&, Gtk::Paned* which)
1906 XMLProperty* prop = 0;
1907 XMLNode* geometry = ARDOUR_UI::instance()->mixer_settings();
1908 int height = default_height;
1909 static bool done[4] = { false, false, false, false };
1911 if (which == static_cast<Gtk::Paned*> (&rhs_pane1)) {
1917 if (!geometry || (prop = geometry->property("mixer-rhs-pane1-pos")) == 0) {
1920 pos = atoi (prop->value());
1923 if ((done[0] = (GTK_WIDGET(rhs_pane1.gobj())->allocation.height > pos))) {
1924 rhs_pane1.set_position (pos);
1927 } else if (which == static_cast<Gtk::Paned*> (&rhs_pane2)) {
1932 if (!geometry || (prop = geometry->property("mixer-rhs-pane2-pos")) == 0) {
1933 pos = 2 * height / 3;
1935 pos = atoi (prop->value());
1938 if ((done[1] = (GTK_WIDGET(rhs_pane2.gobj())->allocation.height > pos))) {
1939 rhs_pane2.set_position (pos);
1941 } else if (which == static_cast<Gtk::Paned*> (&list_hpane)) {
1947 if (!geometry || (prop = geometry->property("mixer-list-hpane-pos")) == 0) {
1948 pos = std::max ((float)100, rintf ((float) 125 * UIConfiguration::instance().get_ui_scale()));
1950 pos = max (36, atoi (prop->value ()));
1953 if ((done[2] = (GTK_WIDGET(list_hpane.gobj())->allocation.width > pos))) {
1954 list_hpane.set_position (pos);
1956 } else if (which == static_cast<Gtk::Paned*> (&inner_pane)) {
1962 if (!geometry || (prop = geometry->property("mixer-inner-pane-pos")) == 0) {
1963 cerr << "using default value\n";
1964 pos = std::max ((float)100, rintf ((float) 125 * UIConfiguration::instance().get_ui_scale()));
1966 cerr << "using " << prop->value() << endl;
1967 pos = max (36, atoi (prop->value ()));
1970 cerr << "Setting inner pane pos to " << pos << endl;
1972 if ((done[3] = (GTK_WIDGET(inner_pane.gobj())->allocation.width > pos))) {
1973 inner_pane.set_position (pos);
1979 Mixer_UI::scroll_left ()
1981 if (!scroller.get_hscrollbar()) return;
1982 Adjustment* adj = scroller.get_hscrollbar()->get_adjustment();
1983 /* stupid GTK: can't rely on clamping across versions */
1984 scroller.get_hscrollbar()->set_value (max (adj->get_lower(), adj->get_value() - adj->get_step_increment()));
1988 Mixer_UI::scroll_right ()
1990 if (!scroller.get_hscrollbar()) return;
1991 Adjustment* adj = scroller.get_hscrollbar()->get_adjustment();
1992 /* stupid GTK: can't rely on clamping across versions */
1993 scroller.get_hscrollbar()->set_value (min (adj->get_upper(), adj->get_value() + adj->get_step_increment()));
1997 Mixer_UI::on_scroll_event (GdkEventScroll* ev)
1999 switch (ev->direction) {
2000 case GDK_SCROLL_LEFT:
2004 if (ev->state & Keyboard::TertiaryModifier) {
2010 case GDK_SCROLL_RIGHT:
2014 case GDK_SCROLL_DOWN:
2015 if (ev->state & Keyboard::TertiaryModifier) {
2027 Mixer_UI::parameter_changed (string const & p)
2029 if (p == "show-group-tabs") {
2030 bool const s = _session->config.get_show_group_tabs ();
2032 _group_tabs->show ();
2034 _group_tabs->hide ();
2036 } else if (p == "default-narrow_ms") {
2037 bool const s = UIConfiguration::instance().get_default_narrow_ms ();
2038 for (list<MixerStrip*>::iterator i = strips.begin(); i != strips.end(); ++i) {
2039 (*i)->set_width_enum (s ? Narrow : Wide, this);
2041 } else if (p == "remote-model") {
2042 reset_remote_control_ids ();
2043 } else if (p == "use-monitor-bus") {
2044 if (_session && !_session->monitor_out()) {
2045 monitor_section_detached ();
2051 Mixer_UI::set_route_group_activation (RouteGroup* g, bool a)
2053 g->set_active (a, this);
2057 Mixer_UI::plugin_selector()
2059 #ifdef DEFER_PLUGIN_SELECTOR_LOAD
2060 if (!_plugin_selector)
2061 _plugin_selector = new PluginSelector (PluginManager::instance());
2064 return _plugin_selector;
2068 Mixer_UI::setup_track_display ()
2070 track_model = ListStore::create (track_columns);
2071 track_display.set_model (track_model);
2072 track_display.append_column (_("Strips"), track_columns.text);
2073 track_display.append_column (_("Show"), track_columns.visible);
2074 track_display.get_column (0)->set_data (X_("colnum"), GUINT_TO_POINTER(0));
2075 track_display.get_column (1)->set_data (X_("colnum"), GUINT_TO_POINTER(1));
2076 track_display.get_column (0)->set_expand(true);
2077 track_display.get_column (1)->set_expand(false);
2078 track_display.get_column (0)->set_sizing (Gtk::TREE_VIEW_COLUMN_FIXED);
2079 track_display.set_name (X_("EditGroupList"));
2080 track_display.get_selection()->set_mode (Gtk::SELECTION_NONE);
2081 track_display.set_reorderable (true);
2082 track_display.set_headers_visible (true);
2083 track_display.set_can_focus(false);
2085 track_model->signal_row_deleted().connect (sigc::mem_fun (*this, &Mixer_UI::track_list_delete));
2086 track_model->signal_rows_reordered().connect (sigc::mem_fun (*this, &Mixer_UI::track_list_reorder));
2088 CellRendererToggle* track_list_visible_cell = dynamic_cast<CellRendererToggle*>(track_display.get_column_cell_renderer (1));
2089 track_list_visible_cell->property_activatable() = true;
2090 track_list_visible_cell->property_radio() = false;
2091 track_list_visible_cell->signal_toggled().connect (sigc::mem_fun (*this, &Mixer_UI::track_visibility_changed));
2093 track_display.signal_button_press_event().connect (sigc::mem_fun (*this, &Mixer_UI::track_display_button_press), false);
2095 track_display_scroller.add (track_display);
2096 track_display_scroller.set_policy (Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
2098 VBox* v = manage (new VBox);
2100 v->pack_start (track_display_scroller, true, true);
2102 Button* b = manage (new Button);
2104 Widget* w = manage (new Image (Stock::ADD, ICON_SIZE_BUTTON));
2108 b->signal_clicked().connect (sigc::mem_fun (*this, &Mixer_UI::new_track_or_bus));
2110 v->pack_start (*b, false, false);
2112 track_display_frame.set_name("BaseFrame");
2113 track_display_frame.set_shadow_type (Gtk::SHADOW_IN);
2114 track_display_frame.add (*v);
2116 track_display_scroller.show();
2117 track_display_frame.show();
2118 track_display.show();
2122 Mixer_UI::new_track_or_bus ()
2124 ARDOUR_UI::instance()->add_route ();
2129 Mixer_UI::update_title ()
2131 if (!own_window()) {
2138 if (_session->snap_name() != _session->name()) {
2139 n = _session->snap_name ();
2141 n = _session->name ();
2144 if (_session->dirty ()) {
2148 WindowTitle title (n);
2149 title += S_("Window|Mixer");
2150 title += Glib::get_application_name ();
2151 own_window()->set_title (title.get_string());
2155 WindowTitle title (S_("Window|Mixer"));
2156 title += Glib::get_application_name ();
2157 own_window()->set_title (title.get_string());
2162 Mixer_UI::strip_by_x (int x)
2164 for (list<MixerStrip*>::iterator i = strips.begin(); i != strips.end(); ++i) {
2167 (*i)->translate_coordinates (_content, 0, 0, x1, y);
2168 x2 = x1 + (*i)->get_width();
2170 if (x >= x1 && x <= x2) {
2179 Mixer_UI::set_route_targets_for_operation ()
2181 _route_targets.clear ();
2183 if (!_selection.empty()) {
2184 _route_targets = _selection.routes;
2188 // removed "implicit" selections of strips, after discussion on IRC
2193 Mixer_UI::monitor_section_going_away ()
2195 if (_monitor_section) {
2196 monitor_section_detached ();
2197 out_packer.remove (_monitor_section->tearoff());
2198 _monitor_section->set_session (0);
2199 delete _monitor_section;
2200 _monitor_section = 0;
2205 Mixer_UI::toggle_midi_input_active (bool flip_others)
2207 boost::shared_ptr<RouteList> rl (new RouteList);
2210 set_route_targets_for_operation ();
2212 for (RouteUISelection::iterator r = _route_targets.begin(); r != _route_targets.end(); ++r) {
2213 boost::shared_ptr<MidiTrack> mt = (*r)->midi_track();
2216 rl->push_back ((*r)->route());
2217 onoff = !mt->input_active();
2221 _session->set_exclusive_input_active (rl, onoff, flip_others);
2225 Mixer_UI::maximise_mixer_space ()
2227 if (!own_window()) {
2235 _window->fullscreen ();
2240 Mixer_UI::restore_mixer_space ()
2242 if (!own_window()) {
2250 own_window()->unfullscreen();
2255 Mixer_UI::monitor_section_attached ()
2257 Glib::RefPtr<Action> act = ActionManager::get_action ("Common", "ToggleMonitorSection");
2258 Glib::RefPtr<ToggleAction> tact = Glib::RefPtr<ToggleAction>::cast_dynamic(act);
2259 act->set_sensitive (true);
2260 tact->set_active ();
2264 Mixer_UI::monitor_section_detached ()
2266 Glib::RefPtr<Action> act = ActionManager::get_action ("Common", "ToggleMonitorSection");
2267 act->set_sensitive (false);
2271 Mixer_UI::store_current_favorite_order ()
2273 typedef Gtk::TreeModel::Children type_children;
2274 type_children children = favorite_plugins_model->children();
2275 favorite_order.clear();
2276 for(type_children::iterator iter = children.begin(); iter != children.end(); ++iter)
2278 Gtk::TreeModel::Row row = *iter;
2279 ARDOUR::PluginPresetPtr ppp = row[favorite_plugins_columns.plugin];
2280 favorite_order.push_back (ppp->_pip);
2281 std::string name = row[favorite_plugins_columns.name];
2282 favorite_ui_state[(*ppp->_pip).unique_id] = favorite_plugins_display.row_expanded (favorite_plugins_model->get_path(iter));
2287 Mixer_UI::save_favorite_ui_state (const TreeModel::iterator& iter, const TreeModel::Path& path)
2289 Gtk::TreeModel::Row row = *iter;
2290 ARDOUR::PluginPresetPtr ppp = row[favorite_plugins_columns.plugin];
2292 favorite_ui_state[(*ppp->_pip).unique_id] = favorite_plugins_display.row_expanded (favorite_plugins_model->get_path(iter));
2296 Mixer_UI::refiller (PluginInfoList& result, const PluginInfoList& plugs)
2298 PluginManager& manager (PluginManager::instance());
2299 for (PluginInfoList::const_iterator i = plugs.begin(); i != plugs.end(); ++i) {
2300 if (manager.get_status (*i) != PluginManager::Favorite) {
2303 result.push_back (*i);
2307 struct PluginCustomSorter {
2309 bool operator() (PluginInfoPtr a, PluginInfoPtr b) const {
2310 PluginInfoList::const_iterator aiter = _user.begin();
2311 PluginInfoList::const_iterator biter = _user.begin();
2312 while (aiter != _user.end()) { if ((*aiter)->unique_id == a->unique_id) { break; } ++aiter; }
2313 while (biter != _user.end()) { if ((*biter)->unique_id == b->unique_id) { break; } ++biter; }
2315 if (aiter != _user.end() && biter != _user.end()) {
2316 return std::distance (_user.begin(), aiter) < std::distance (_user.begin(), biter);
2318 if (aiter != _user.end()) {
2321 if (biter != _user.end()) {
2324 return ARDOUR::cmp_nocase((*a).name, (*b).name) == -1;
2326 PluginCustomSorter(PluginInfoList user) : _user (user) {}
2328 PluginInfoList _user;
2332 Mixer_UI::refill_favorite_plugins ()
2334 PluginInfoList plugs;
2335 PluginManager& mgr (PluginManager::instance());
2338 refiller (plugs, mgr.lv2_plugin_info ());
2340 #ifdef WINDOWS_VST_SUPPORT
2341 refiller (plugs, mgr.windows_vst_plugin_info ());
2343 #ifdef LXVST_SUPPORT
2344 refiller (plugs, mgr.lxvst_plugin_info ());
2346 #ifdef AUDIOUNIT_SUPPORT
2347 refiller (plugs, mgr.au_plugin_info ());
2349 refiller (plugs, mgr.ladspa_plugin_info ());
2350 refiller (plugs, mgr.lua_plugin_info ());
2352 store_current_favorite_order ();
2354 PluginCustomSorter cmp (favorite_order);
2357 favorite_order = plugs;
2359 sync_treeview_from_favorite_order ();
2363 Mixer_UI::sync_treeview_favorite_ui_state (const TreeModel::Path& path, const TreeModel::iterator&)
2366 if (!(iter = favorite_plugins_model->get_iter (path))) {
2369 ARDOUR::PluginPresetPtr ppp = (*iter)[favorite_plugins_columns.plugin];
2373 PluginInfoPtr pip = ppp->_pip;
2374 if (favorite_ui_state.find (pip->unique_id) != favorite_ui_state.end ()) {
2375 if (favorite_ui_state[pip->unique_id]) {
2376 favorite_plugins_display.expand_row (path, true);
2382 Mixer_UI::sync_treeview_from_favorite_order ()
2384 favorite_plugins_model->clear ();
2385 for (PluginInfoList::const_iterator i = favorite_order.begin(); i != favorite_order.end(); ++i) {
2386 PluginInfoPtr pip = (*i);
2388 TreeModel::Row newrow = *(favorite_plugins_model->append());
2389 newrow[favorite_plugins_columns.name] = (*i)->name;
2390 newrow[favorite_plugins_columns.plugin] = PluginPresetPtr (new PluginPreset(pip));
2395 vector<ARDOUR::Plugin::PresetRecord> presets = (*i)->get_presets (true);
2396 for (vector<ARDOUR::Plugin::PresetRecord>::const_iterator j = presets.begin(); j != presets.end(); ++j) {
2397 Gtk::TreeModel::Row child_row = *(favorite_plugins_model->append (newrow.children()));
2398 child_row[favorite_plugins_columns.name] = (*j).label;
2399 child_row[favorite_plugins_columns.plugin] = PluginPresetPtr (new PluginPreset(pip, &(*j)));
2401 if (favorite_ui_state.find (pip->unique_id) != favorite_ui_state.end ()) {
2402 if (favorite_ui_state[pip->unique_id]) {
2403 favorite_plugins_display.expand_row (favorite_plugins_model->get_path(newrow), true);
2410 Mixer_UI::popup_note_context_menu (GdkEventButton *ev)
2412 using namespace Gtk::Menu_Helpers;
2414 Gtk::Menu* m = manage (new Menu);
2415 MenuList& items = m->items ();
2417 if (_selection.routes.empty()) {
2418 items.push_back (MenuElem (_("No Track/Bus is selected.")));
2420 items.push_back (MenuElem (_("Add at the top"),
2421 sigc::bind (sigc::mem_fun (*this, &Mixer_UI::add_selected_processor), AddTop)));
2422 items.push_back (MenuElem (_("Add Pre-Fader"),
2423 sigc::bind (sigc::mem_fun (*this, &Mixer_UI::add_selected_processor), AddPreFader)));
2424 items.push_back (MenuElem (_("Add Post-Fader"),
2425 sigc::bind (sigc::mem_fun (*this, &Mixer_UI::add_selected_processor), AddPostFader)));
2426 items.push_back (MenuElem (_("Add at the end"),
2427 sigc::bind (sigc::mem_fun (*this, &Mixer_UI::add_selected_processor), AddBottom)));
2430 items.push_back (SeparatorElem());
2432 items.push_back (MenuElem (_("Remove from favorites"), sigc::mem_fun (*this, &Mixer_UI::remove_selected_from_favorites)));
2434 ARDOUR::PluginPresetPtr ppp = selected_plugin();
2435 if (ppp && ppp->_preset.valid && ppp->_preset.user) {
2436 // we cannot currently delete AU presets
2437 if (!ppp->_pip || ppp->_pip->type != AudioUnit) {
2438 items.push_back (MenuElem (_("Delete Preset"), sigc::mem_fun (*this, &Mixer_UI::delete_selected_preset)));
2442 m->popup (ev->button, ev->time);
2446 Mixer_UI::plugin_row_button_press (GdkEventButton *ev)
2448 if ((ev->type == GDK_BUTTON_PRESS) && (ev->button == 3) ) {
2449 TreeModel::Path path;
2450 TreeViewColumn* column;
2452 if (favorite_plugins_display.get_path_at_pos ((int)ev->x, (int)ev->y, path, column, cellx, celly)) {
2453 Glib::RefPtr<Gtk::TreeView::Selection> selection = favorite_plugins_display.get_selection();
2455 selection->unselect_all();
2456 selection->select(path);
2459 ARDOUR::PluginPresetPtr ppp = selected_plugin();
2461 popup_note_context_menu (ev);
2469 Mixer_UI::selected_plugin ()
2471 Glib::RefPtr<Gtk::TreeView::Selection> selection = favorite_plugins_display.get_selection();
2473 return PluginPresetPtr();
2475 Gtk::TreeModel::iterator iter = selection->get_selected();
2477 return PluginPresetPtr();
2479 return (*iter)[favorite_plugins_columns.plugin];
2483 Mixer_UI::add_selected_processor (ProcessorPosition pos)
2485 ARDOUR::PluginPresetPtr ppp = selected_plugin();
2487 add_favorite_processor (ppp, pos);
2492 Mixer_UI::delete_selected_preset ()
2497 ARDOUR::PluginPresetPtr ppp = selected_plugin();
2498 if (!ppp || !ppp->_preset.valid || !ppp->_preset.user) {
2501 PluginPtr plugin = ppp->_pip->load (*_session);
2502 plugin->get_presets();
2503 plugin->remove_preset (ppp->_preset.label);
2507 Mixer_UI::remove_selected_from_favorites ()
2509 ARDOUR::PluginPresetPtr ppp = selected_plugin();
2513 PluginManager::PluginStatusType status = PluginManager::Normal;
2514 PluginManager& manager (PluginManager::instance());
2516 manager.set_status (ppp->_pip->type, ppp->_pip->unique_id, status);
2517 manager.save_statuses ();
2521 Mixer_UI::plugin_row_activated (const TreeModel::Path& path, TreeViewColumn* column)
2524 if (!(iter = favorite_plugins_model->get_iter (path))) {
2527 ARDOUR::PluginPresetPtr ppp = (*iter)[favorite_plugins_columns.plugin];
2528 add_favorite_processor (ppp, AddPreFader); // TODO: preference?!
2532 Mixer_UI::add_favorite_processor (ARDOUR::PluginPresetPtr ppp, ProcessorPosition pos)
2534 if (!_session || _selection.routes.empty()) {
2538 PluginInfoPtr pip = ppp->_pip;
2539 for (RouteUISelection::iterator i = _selection.routes.begin(); i != _selection.routes.end(); ++i) {
2540 boost::shared_ptr<ARDOUR::Route> rt = (*i)->route();
2541 if (!rt) { continue; }
2543 PluginPtr p = pip->load (*_session);
2544 if (!p) { continue; }
2546 if (ppp->_preset.valid) {
2547 p->load_preset (ppp->_preset);
2550 Route::ProcessorStreams err;
2551 boost::shared_ptr<Processor> processor (new PluginInsert (*_session, p));
2555 rt->add_processor_by_index (processor, 0, &err, Config->get_new_plugins_active ());
2558 rt->add_processor (processor, PreFader, &err, Config->get_new_plugins_active ());
2565 boost::shared_ptr<Processor> np = rt->nth_processor (idx);
2569 if (!np->display_to_user()) {
2572 if (boost::dynamic_pointer_cast<Amp> (np) && // Fader, not Trim
2573 boost::dynamic_pointer_cast<Amp> (np)->gain_control()->parameter().type() == GainAutomation) {
2578 rt->add_processor_by_index (processor, ++pos, &err, Config->get_new_plugins_active ());
2582 rt->add_processor_by_index (processor, -1, &err, Config->get_new_plugins_active ());
2589 PluginTreeStore::row_drop_possible_vfunc(const Gtk::TreeModel::Path& dest, const Gtk::SelectionData& data) const
2591 if (data.get_target() != "GTK_TREE_MODEL_ROW") {
2595 // only allow to re-order top-level items
2597 if (TreePath::get_from_selection_data (data, src)) {
2598 if (src.up() && src.up()) {
2603 // don't allow to drop as child-rows.
2604 Gtk::TreeModel::Path _dest = dest; // un const
2605 const bool is_child = _dest.up (); // explicit bool for clang
2606 if (!is_child || _dest.empty ()) {
2613 Mixer_UI::plugin_drop (const Glib::RefPtr<Gdk::DragContext>&, const Gtk::SelectionData& data)
2615 if (data.get_target() != "PluginPresetPtr") {
2618 if (data.get_length() != sizeof (PluginPresetPtr)) {
2621 const void *d = data.get_data();
2622 const PluginPresetPtr ppp = *(static_cast<const PluginPresetPtr*> (d));
2624 PluginManager::PluginStatusType status = PluginManager::Favorite;
2625 PluginManager& manager (PluginManager::instance());
2627 manager.set_status (ppp->_pip->type, ppp->_pip->unique_id, status);
2628 manager.save_statuses ();
2632 Mixer_UI::do_vca_assign (boost::shared_ptr<VCA> vca)
2634 /* call protected MixerActor:: method */
2639 Mixer_UI::do_vca_unassign (boost::shared_ptr<VCA> vca)
2641 /* call protected MixerActor:: method */