first vaguely working version using PresentationInfo
[ardour.git] / gtk2_ardour / mixer_ui.cc
index f27041b38674f4101fba2b93c52dffd65c9ff8ea..9f5cb5684f901977bfca48d16203134e2a3b8735 100644 (file)
 
 #include "ardour/amp.h"
 #include "ardour/debug.h"
+#include "ardour/audio_track.h"
 #include "ardour/midi_track.h"
 #include "ardour/plugin_manager.h"
 #include "ardour/route_group.h"
-#include "ardour/route_sorters.h"
 #include "ardour/session.h"
+#include "ardour/vca.h"
+#include "ardour/vca_manager.h"
 
 #include "keyboard.h"
 #include "mixer_ui.h"
@@ -63,6 +65,7 @@
 #include "mixer_group_tabs.h"
 #include "timers.h"
 #include "ui_config.h"
+#include "vca_master_strip.h"
 
 #include "i18n.h"
 
@@ -104,7 +107,7 @@ Mixer_UI::Mixer_UI ()
        , _maximised (false)
        , _show_mixer_list (true)
 {
-       Route::SyncOrderKeys.connect (*this, invalidator (*this), boost::bind (&Mixer_UI::sync_treeview_from_order_keys, this), gui_context());
+       Stripable::PresentationInfoChange.connect (*this, invalidator (*this), boost::bind (&Mixer_UI::sync_treeview_from_presentation_info, this), gui_context());
 
        /* bindings was already set in MixerActor constructor */
 
@@ -117,6 +120,13 @@ Mixer_UI::Mixer_UI ()
        scroller_base.add_events (Gdk::BUTTON_PRESS_MASK|Gdk::BUTTON_RELEASE_MASK);
        scroller_base.set_name ("MixerWindow");
        scroller_base.signal_button_release_event().connect (sigc::mem_fun(*this, &Mixer_UI::strip_scroller_button_release));
+
+       /* set up drag-n-drop */
+       vector<TargetEntry> target_table;
+       target_table.push_back (TargetEntry ("PluginFavoritePtr"));
+       scroller_base.drag_dest_set (target_table);
+       scroller_base.signal_drag_data_received().connect (sigc::mem_fun(*this, &Mixer_UI::scroller_drag_data_received));
+
        // add as last item of strip packer
        strip_packer.pack_end (scroller_base, true, true);
 
@@ -196,6 +206,10 @@ Mixer_UI::Mixer_UI ()
        group_display_frame.set_shadow_type (Gtk::SHADOW_IN);
        group_display_frame.add (group_display_vbox);
 
+
+       list<TargetEntry> target_list;
+       target_list.push_back (TargetEntry ("PluginPresetPtr"));
+
        favorite_plugins_model = PluginTreeStore::create (favorite_plugins_columns);
        favorite_plugins_display.set_model (favorite_plugins_model);
        favorite_plugins_display.append_column (_("Favorite Plugins"), favorite_plugins_columns.name);
@@ -205,8 +219,9 @@ Mixer_UI::Mixer_UI ()
        favorite_plugins_display.set_headers_visible (true);
        favorite_plugins_display.set_rules_hint (true);
        favorite_plugins_display.set_can_focus (false);
-       favorite_plugins_display.add_object_drag (favorite_plugins_columns.plugin.index(), "PluginPresetPtr");
+       favorite_plugins_display.add_object_drag (favorite_plugins_columns.plugin.index(), "PluginFavoritePtr");
        favorite_plugins_display.set_drag_column (favorite_plugins_columns.name.index());
+       favorite_plugins_display.add_drop_targets (target_list);
        favorite_plugins_display.signal_row_activated().connect (sigc::mem_fun (*this, &Mixer_UI::plugin_row_activated));
        favorite_plugins_display.signal_button_press_event().connect (sigc::mem_fun (*this, &Mixer_UI::plugin_row_button_press), false);
        favorite_plugins_display.signal_drop.connect (sigc::mem_fun (*this, &Mixer_UI::plugin_drop));
@@ -228,7 +243,13 @@ Mixer_UI::Mixer_UI ()
 
        list_vpacker.pack_start (rhs_pane2, true, true);
 
-       global_hpacker.pack_start (scroller, true, true);
+       vca_scroller.add (vca_packer);
+       vca_scroller.set_policy (Gtk::POLICY_ALWAYS, Gtk::POLICY_AUTOMATIC);
+
+       inner_pane.pack1 (scroller);
+       inner_pane.pack2 (vca_scroller);
+
+       global_hpacker.pack_start (inner_pane, true, true);
        global_hpacker.pack_start (out_packer, false, false);
 
        list_hpane.pack1(list_vpacker, false, true);
@@ -240,6 +261,8 @@ Mixer_UI::Mixer_UI ()
                                                        static_cast<Gtk::Paned*> (&rhs_pane2)));
        list_hpane.signal_size_allocate().connect (sigc::bind (sigc::mem_fun(*this, &Mixer_UI::pane_allocation_handler),
                                                         static_cast<Gtk::Paned*> (&list_hpane)));
+       inner_pane.signal_size_allocate().connect (sigc::bind (sigc::mem_fun(*this, &Mixer_UI::pane_allocation_handler),
+                                                        static_cast<Gtk::Paned*> (&inner_pane)));
 
        _content.pack_start (list_hpane, true, true);
 
@@ -268,6 +291,9 @@ Mixer_UI::Mixer_UI ()
        rhs_pane1.show();
        rhs_pane2.show();
        strip_packer.show();
+       inner_pane.show ();
+       vca_scroller.show();
+       vca_packer.show();
        out_packer.show();
        list_hpane.show();
        group_display.show();
@@ -336,6 +362,9 @@ Mixer_UI::show_window ()
 
        for (ri = rows.begin(); ri != rows.end(); ++ri) {
                ms = (*ri)[track_columns.strip];
+               if (!ms) {
+                       continue;
+               }
                ms->set_width_enum (ms->get_width_enum (), ms->width_owner());
                /* Fix visibility of mixer strip stuff */
                ms->parameter_changed (X_("mixer-element-visibility"));
@@ -345,22 +374,66 @@ Mixer_UI::show_window ()
        scroller_base.grab_focus ();
 }
 
+void
+Mixer_UI::add_masters (VCAList& vcas)
+{
+       for (VCAList::iterator v = vcas.begin(); v != vcas.end(); ++v) {
+
+               VCAMasterStrip* vms = new VCAMasterStrip (_session, *v);
+
+               TreeModel::Row row = *(track_model->append());
+               row[track_columns.text] = (*v)->name();
+               row[track_columns.visible] = true;
+               row[track_columns.vca] = vms;
+
+               vms->CatchDeletion.connect (*this, invalidator (*this), boost::bind (&Mixer_UI::remove_master, this, _1), gui_context());
+       }
+
+       redisplay_track_list ();
+}
+
+void
+Mixer_UI::remove_master (VCAMasterStrip* vms)
+{
+       if (_session && _session->deletion_in_progress()) {
+               /* its all being taken care of */
+               return;
+       }
+
+       TreeModel::Children rows = track_model->children();
+       TreeModel::Children::iterator ri;
+
+       for (ri = rows.begin(); ri != rows.end(); ++ri) {
+               if ((*ri)[track_columns.vca] == vms) {
+                        PBD::Unwinder<bool> uw (_route_deletion_in_progress, true);
+                       track_model->erase (ri);
+                       break;
+               }
+       }
+}
+
 void
 Mixer_UI::add_strips (RouteList& routes)
 {
-       bool from_scratch = track_model->children().size() == 0;
        Gtk::TreeModel::Children::iterator insert_iter = track_model->children().end();
+       uint32_t nroutes = 0;
 
        for (Gtk::TreeModel::Children::iterator it = track_model->children().begin(); it != track_model->children().end(); ++it) {
                boost::shared_ptr<Route> r = (*it)[track_columns.route];
 
-               if (r->order_key() == (routes.front()->order_key() + routes.size())) {
+               if (!r) {
+                       continue;
+               }
+
+               nroutes++;
+
+               if (r->presentation_info().group_order() == (routes.front()->presentation_info().group_order() + routes.size())) {
                        insert_iter = it;
                        break;
                }
        }
 
-       if(!from_scratch) {
+       if (nroutes) {
                _selection.clear_routes ();
        }
 
@@ -420,8 +493,9 @@ Mixer_UI::add_strips (RouteList& routes)
                        row[track_columns.visible] = strip->route()->is_master() ? true : strip->marked_for_display();
                        row[track_columns.route] = route;
                        row[track_columns.strip] = strip;
+                       row[track_columns.vca] = 0;
 
-                       if (!from_scratch) {
+                       if (nroutes != 0) {
                                _selection.add (strip);
                        }
 
@@ -438,7 +512,7 @@ Mixer_UI::add_strips (RouteList& routes)
        no_track_list_redisplay = false;
        track_display.set_model (track_model);
 
-       sync_order_keys_from_treeview ();
+       sync_presentation_info_from_treeview ();
        redisplay_track_list ();
 }
 
@@ -450,6 +524,16 @@ Mixer_UI::deselect_all_strip_processors ()
        }
 }
 
+void
+Mixer_UI::select_strip (MixerStrip& ms, bool add)
+{
+       if (add) {
+               _selection.add (&ms);
+       } else {
+               _selection.set (&ms);
+       }
+}
+
 void
 Mixer_UI::select_none ()
 {
@@ -492,65 +576,9 @@ Mixer_UI::remove_strip (MixerStrip* strip)
 }
 
 void
-Mixer_UI::reset_remote_control_ids ()
+Mixer_UI::sync_presentation_info_from_treeview ()
 {
-       if (Config->get_remote_model() == UserOrdered || !_session || _session->deletion_in_progress()) {
-               return;
-       }
-
-       TreeModel::Children rows = track_model->children();
-
-       if (rows.empty()) {
-               return;
-       }
-
-       DEBUG_TRACE (DEBUG::OrderKeys, "mixer resets remote control ids after remote model change\n");
-
-       TreeModel::Children::iterator ri;
-       bool rid_change = false;
-       uint32_t rid = 1;
-       uint32_t invisible_key = UINT32_MAX;
-
-       for (ri = rows.begin(); ri != rows.end(); ++ri) {
-
-               /* skip two special values */
-
-               if (rid == Route::MasterBusRemoteControlID) {
-                       rid++;
-               }
-
-               if (rid == Route::MonitorBusRemoteControlID) {
-                       rid++;
-               }
-
-               boost::shared_ptr<Route> route = (*ri)[track_columns.route];
-               bool visible = (*ri)[track_columns.visible];
-
-               if (!route->is_master() && !route->is_monitor()) {
-
-                       uint32_t new_rid = (visible ? rid : invisible_key--);
-
-                       if (new_rid != route->remote_control_id()) {
-                               route->set_remote_control_id_explicit (new_rid);
-                               rid_change = true;
-                       }
-
-                       if (visible) {
-                               rid++;
-                       }
-               }
-       }
-
-       if (rid_change) {
-               /* tell the world that we changed the remote control IDs */
-               _session->notify_remote_id_change ();
-       }
-}
-
-void
-Mixer_UI::sync_order_keys_from_treeview ()
-{
-       if (ignore_reorder || !_session || _session->deletion_in_progress()) {
+       if (ignore_reorder || !_session || _session->deletion_in_progress() || (Config->get_remote_model() != MixerOrdered)) {
                return;
        }
 
@@ -563,54 +591,46 @@ Mixer_UI::sync_order_keys_from_treeview ()
        DEBUG_TRACE (DEBUG::OrderKeys, "mixer sync order keys from model\n");
 
        TreeModel::Children::iterator ri;
-       bool changed = false;
-       bool rid_change = false;
+       bool change = false;
        uint32_t order = 0;
-       uint32_t rid = 1;
-       uint32_t invisible_key = UINT32_MAX;
 
        for (ri = rows.begin(); ri != rows.end(); ++ri) {
                boost::shared_ptr<Route> route = (*ri)[track_columns.route];
                bool visible = (*ri)[track_columns.visible];
 
-               uint32_t old_key = route->order_key ();
 
-               if (order != old_key) {
-                       route->set_order_key (order);
-                       changed = true;
+               if (!route) {
+                       continue;
                }
 
-               if ((Config->get_remote_model() == MixerOrdered) && !route->is_master() && !route->is_monitor()) {
-
-                       uint32_t new_rid = (visible ? rid : invisible_key--);
+               if (route->presentation_info().special()) {
+                       continue;
+               }
 
-                       if (new_rid != route->remote_control_id()) {
-                               route->set_remote_control_id_explicit (new_rid);
-                               rid_change = true;
-                       }
+               if (!visible) {
+                       route->presentation_info().set_flag (PresentationInfo::Hidden);
+               } else {
+                       route->presentation_info().unset_flag (PresentationInfo::Hidden);
+               }
 
-                       if (visible) {
-                               rid++;
-                       }
+               DEBUG_TRACE (DEBUG::OrderKeys, string_compose ("route %1 old order %2 new order %3\n", route->name(), route->presentation_info().group_order(), order));
 
+               if (order != route->presentation_info().group_order()) {
+                       route->set_presentation_group_order_explicit (order);
+                       change = true;
                }
 
                ++order;
        }
 
-       if (changed) {
-               /* tell everyone that we changed the mixer sort keys */
-               _session->sync_order_keys ();
-       }
-
-       if (rid_change) {
-               /* tell the world that we changed the remote control IDs */
-               _session->notify_remote_id_change ();
+       if (change) {
+               DEBUG_TRACE (DEBUG::OrderKeys, "... notify PI change from mixer GUI\n");
+               _session->notify_presentation_info_change ();
        }
 }
 
 void
-Mixer_UI::sync_treeview_from_order_keys ()
+Mixer_UI::sync_treeview_from_presentation_info ()
 {
        if (!_session || _session->deletion_in_progress()) {
                return;
@@ -632,21 +652,43 @@ Mixer_UI::sync_treeview_from_order_keys ()
                return;
        }
 
-       OrderKeySortedRoutes sorted_routes;
+       OrderingKeys sorted;
+       uint32_t vca_cnt = 0;
+       uint32_t max_route_order_key = 0;
+
+       /* count number of Routes in track_model (there may be some odd reason
+          why this is not the same as the number in the session, but here we
+          care about the track model.
+       */
+
+       for (TreeModel::Children::iterator ri = rows.begin(); ri != rows.end(); ++ri) {
+               boost::shared_ptr<Route> route = (*ri)[track_columns.route];
+               if (route) {
+                       max_route_order_key = max (route->presentation_info().group_order(), max_route_order_key);
+               }
+       }
 
        for (TreeModel::Children::iterator ri = rows.begin(); ri != rows.end(); ++ri, ++old_order) {
                boost::shared_ptr<Route> route = (*ri)[track_columns.route];
-               sorted_routes.push_back (RoutePlusOrderKey (route, old_order, route->order_key ()));
+               if (!route) {
+                       /* VCAs need to sort after all routes. We don't display
+                        * them in the same place (March 2016), but we don't
+                        * want them intermixed in the track_model
+                        */
+                       sorted.push_back (OrderKeys (old_order, max_route_order_key + ++vca_cnt));
+               } else {
+                       sorted.push_back (OrderKeys (old_order, route->presentation_info().group_order()));
+               }
        }
 
        SortByNewDisplayOrder cmp;
 
-       sort (sorted_routes.begin(), sorted_routes.end(), cmp);
-       neworder.assign (sorted_routes.size(), 0);
+       sort (sorted.begin(), sorted.end(), cmp);
+       neworder.assign (sorted.size(), 0);
 
        uint32_t n = 0;
 
-       for (OrderKeySortedRoutes::iterator sr = sorted_routes.begin(); sr != sorted_routes.end(); ++sr, ++n) {
+       for (OrderingKeys::iterator sr = sorted.begin(); sr != sorted.end(); ++sr, ++n) {
 
                neworder[n] = sr->old_display_order;
 
@@ -654,8 +696,8 @@ Mixer_UI::sync_treeview_from_order_keys ()
                        changed = true;
                }
 
-               DEBUG_TRACE (DEBUG::OrderKeys, string_compose ("MIXER change order for %1 from %2 to %3\n",
-                                                              sr->route->name(), sr->old_display_order, n));
+               DEBUG_TRACE (DEBUG::OrderKeys, string_compose ("MIXER change order from %1 to %2\n",
+                                                              sr->old_display_order, n));
        }
 
        if (changed) {
@@ -812,6 +854,8 @@ Mixer_UI::set_session (Session* sess)
        _session->DirtyChanged.connect (_session_connections, invalidator (*this), boost::bind (&Mixer_UI::update_title, this), gui_context());
        _session->StateSaved.connect (_session_connections, invalidator (*this), boost::bind (&Mixer_UI::update_title, this), gui_context());
 
+       _session->vca_manager().VCAAdded.connect (_session_connections, invalidator (*this), boost::bind (&Mixer_UI::add_masters, this, _1), gui_context());
+
        Config->ParameterChanged.connect (*this, invalidator (*this), boost::bind (&Mixer_UI::parameter_changed, this, _1), gui_context ());
 
        route_groups_changed ();
@@ -886,13 +930,15 @@ Mixer_UI::update_track_visibility ()
 
                for (i = rows.begin(); i != rows.end(); ++i) {
                        MixerStrip *strip = (*i)[track_columns.strip];
-                       (*i)[track_columns.visible] = strip->marked_for_display ();
+                       if (strip) {
+                               (*i)[track_columns.visible] = strip->marked_for_display ();
+                       }
                }
 
-               /* force route order keys catch up with visibility changes
+               /* force presentation catch up with visibility changes
                 */
 
-               sync_order_keys_from_treeview ();
+               sync_presentation_info_from_treeview ();
        }
 
        redisplay_track_list ();
@@ -970,7 +1016,7 @@ Mixer_UI::set_all_strips_visibility (bool yn)
                        TreeModel::Row row = (*i);
                        MixerStrip* strip = row[track_columns.strip];
 
-                       if (strip == 0) {
+                       if (!strip) {
                                continue;
                        }
 
@@ -999,7 +1045,7 @@ Mixer_UI::set_all_audio_midi_visibility (int tracks, bool yn)
                        TreeModel::Row row = (*i);
                        MixerStrip* strip = row[track_columns.strip];
 
-                       if (strip == 0) {
+                       if (!strip) {
                                continue;
                        }
 
@@ -1089,7 +1135,7 @@ void
 Mixer_UI::track_list_reorder (const TreeModel::Path&, const TreeModel::iterator&, int* /*new_order*/)
 {
        DEBUG_TRACE (DEBUG::OrderKeys, "mixer UI treeview reordered\n");
-       sync_order_keys_from_treeview ();
+       sync_presentation_info_from_treeview ();
 }
 
 void
@@ -1103,7 +1149,7 @@ Mixer_UI::track_list_delete (const Gtk::TreeModel::Path&)
        */
 
        DEBUG_TRACE (DEBUG::OrderKeys, "mixer UI treeview row deleted\n");
-       sync_order_keys_from_treeview ();
+       sync_presentation_info_from_treeview ();
 
         if (_route_deletion_in_progress) {
                 redisplay_track_list ();
@@ -1115,16 +1161,28 @@ Mixer_UI::redisplay_track_list ()
 {
        TreeModel::Children rows = track_model->children();
        TreeModel::Children::iterator i;
+       uint32_t n_masters = 0;
 
        if (no_track_list_redisplay) {
                return;
        }
 
+       container_clear (vca_packer);
+
        for (i = rows.begin(); i != rows.end(); ++i) {
 
+               VCAMasterStrip* vms = (*i)[track_columns.vca];
+
+               if (vms) {
+                       vca_packer.pack_start (*vms, false, false);
+                       vms->show ();
+                       n_masters++;
+                       continue;
+               }
+
                MixerStrip* strip = (*i)[track_columns.strip];
 
-               if (strip == 0) {
+               if (!strip) {
                        /* we're in the middle of changing a row, don't worry */
                        continue;
                }
@@ -1168,6 +1226,16 @@ Mixer_UI::redisplay_track_list ()
                }
        }
 
+       /* update visibility of VCA assign buttons */
+
+       if (n_masters == 0) {
+               UIConfiguration::instance().set_mixer_strip_visibility (VisibilityGroup::remove_element (UIConfiguration::instance().get_mixer_strip_visibility(), X_("VCA")));
+               vca_scroller.hide ();
+       } else {
+               UIConfiguration::instance().set_mixer_strip_visibility (VisibilityGroup::add_element (UIConfiguration::instance().get_mixer_strip_visibility(), X_("VCA")));
+               vca_scroller.show ();
+       }
+
        _group_tabs->set_dirty ();
 }
 
@@ -1198,12 +1266,19 @@ Mixer_UI::strip_width_changed ()
 
 }
 
+struct PresentationInfoRouteSorter
+{
+       bool operator() (boost::shared_ptr<Route> a, boost::shared_ptr<Route> b) {
+               return a->presentation_info().global_order () < b->presentation_info().global_order ();
+       }
+};
+
 void
 Mixer_UI::initial_track_display ()
 {
        boost::shared_ptr<RouteList> routes = _session->get_routes();
        RouteList copy (*routes);
-       ARDOUR::SignalOrderRouteSorter sorter;
+       PresentationInfoRouteSorter sorter;
 
        copy.sort (sorter);
 
@@ -1212,11 +1287,11 @@ Mixer_UI::initial_track_display ()
                Unwinder<bool> uw2 (ignore_reorder, true);
 
                track_model->clear ();
+               VCAList vcas = _session->vca_manager().vcas();
+               add_masters (vcas);
                add_strips (copy);
        }
 
-       _session->sync_order_keys ();
-
        redisplay_track_list ();
 }
 
@@ -1475,8 +1550,9 @@ Mixer_UI::show_mixer_list (bool yn)
 
                //if user wants to show the pane, we should make sure that it is wide enough to be visible
                int width = list_hpane.get_position();
-               if (width < 40)
+               if (width < 40) {
                        list_hpane.set_position(40);
+               }
        } else {
                list_vpacker.hide ();
        }
@@ -1616,6 +1692,38 @@ Mixer_UI::strip_scroller_button_release (GdkEventButton* ev)
        return false;
 }
 
+void
+Mixer_UI::scroller_drag_data_received (const Glib::RefPtr<Gdk::DragContext>& context, int x, int y, const Gtk::SelectionData& data, guint info, guint time)
+{
+       printf ("Mixer_UI::scroller_drag_data_received\n");
+       if (data.get_target() != "PluginFavoritePtr") {
+               context->drag_finish (false, false, time);
+               return;
+       }
+
+       const void * d = data.get_data();
+       const Gtkmm2ext::DnDTreeView<ARDOUR::PluginPresetPtr>* tv = reinterpret_cast<const Gtkmm2ext::DnDTreeView<ARDOUR::PluginPresetPtr>*>(d);                                                   
+
+       PluginPresetList nfos;
+       TreeView* source;
+       tv->get_object_drag_data (nfos, &source);
+
+       Route::ProcessorList pl;
+       bool ok = false;
+
+       for (list<PluginPresetPtr>::const_iterator i = nfos.begin(); i != nfos.end(); ++i) {
+               PluginPresetPtr ppp = (*i);
+               PluginInfoPtr pip = ppp->_pip;
+               if (!pip->is_instrument ()) {
+                       continue;
+               }
+               ARDOUR_UI::instance()->session_add_midi_track (NULL, 1, _("MIDI"), Config->get_strict_io (), pip, ppp->_preset.valid ? &ppp->_preset : 0);
+               ok = true;
+       }
+
+       context->drag_finish (ok, false, time);
+}
+
 void
 Mixer_UI::set_strip_width (Width w, bool save)
 {
@@ -1723,10 +1831,14 @@ Mixer_UI::get_state ()
 
        node->add_child_nocopy (Tabbable::get_state());
 
-       snprintf(buf,sizeof(buf), "%d",gtk_paned_get_position (const_cast<GtkPaned*>(static_cast<const Paned*>(&rhs_pane1)->gobj())));
-       node->add_property(X_("mixer_rhs_pane1_pos"), string(buf));
-       snprintf(buf,sizeof(buf), "%d",gtk_paned_get_position (const_cast<GtkPaned*>(static_cast<const Paned*>(&list_hpane)->gobj())));
-       node->add_property(X_("mixer_list_hpane_pos"), string(buf));
+       snprintf(buf,sizeof(buf), "%f", paned_position_as_fraction (rhs_pane1, true));
+       node->add_property(X_("mixer-rhs-pane1-pos"), string(buf));
+       snprintf(buf,sizeof(buf), "%f", paned_position_as_fraction (rhs_pane2, true));
+       node->add_property(X_("mixer-rhs_pane2-pos"), string(buf));
+       snprintf(buf,sizeof(buf), "%f", paned_position_as_fraction (list_hpane, false));
+       node->add_property(X_("mixer-list-hpane-pos"), string(buf));
+       snprintf(buf,sizeof(buf), "%f", paned_position_as_fraction (inner_pane, false));
+       node->add_property(X_("mixer-inner-pane-pos"), string(buf));
 
        node->add_property ("narrow-strips", _strip_width == Narrow ? "yes" : "no");
        node->add_property ("show-mixer", _visible ? "yes" : "no");
@@ -1751,26 +1863,51 @@ Mixer_UI::get_state ()
 }
 
 void
-Mixer_UI::pane_allocation_handler (Allocation&, Gtk::Paned* which)
+Mixer_UI::pane_allocation_handler (Allocation& allocation, Gtk::Paned* which)
 {
-       int pos;
-       XMLProperty const * prop = 0;
-       XMLNode* node = ARDOUR_UI::instance()->mixer_settings();
-       XMLNode* geometry;
-       int height;
-       static int32_t done[3] = { 0, 0, 0 };
-
-       height = default_height;
-
-       if ((geometry = find_named_node (*node, "geometry")) != 0) {
+       float pos;
+       XMLProperty* prop = 0;
+       XMLNode* geometry = ARDOUR_UI::instance()->mixer_settings();
+       int height = default_height;
+       static bool done[4] = { false, false, false, false };
 
-               if ((prop = geometry->property ("y_size")) == 0) {
-                       prop = geometry->property ("y-size");
-               }
-               if (prop) {
-                       height = atoi (prop->value());
-               }
-       }
+       /* Gtk::Paned behaves very oddly and rather undesirably. Setting the
+        * position is a crapshoot if you time it incorrectly with the overall
+        * sizing of the Paned. For example, if you might retrieve the size with
+        * ::get_position() and then later call ::set_position() on a Paned at
+        * a time when its allocation is different than it was when you retrieved
+        * the position. The position will be interpreted as the size of the
+        * first (top or left) child widget. If packing/size allocation later
+        * resizes the Paned to a (final) smaller size, the position will be
+        * used in ways that mean that the Paned ends up NOT in the same state
+        * that it was in when you originally saved the position.
+        *
+        * Concrete example: Paned is 800 pixels wide, position is 400
+        * (halfway).  Save position as 400. During program restart, set
+        * position to 400, before Paned has been allocated any space. Paned
+        * ends up initially sized to 1200 pixels.  Position is now 1/3 of the
+        * way across/down.  Subsequent resizes will leave the position 1/3 of
+        * the way across, rather than leaving it as a fixed pixel
+        * position. Eventually, the Paned ends up 800 pixels wide/high again,
+        * but the position is now 267, not 400.
+        *
+        * So ...
+        *
+        * We deal with this by using two strategies:
+        *
+        * 1) only set the position if the allocated size of the Paned is at
+        * least as big as it needs to be for the position to make sense.
+        *
+        * 2) in recent versions of Ardour, save the position as a fraction,
+        * and restore it using that fraction.
+        *
+        * So, we will only call ::set_position() AFTER the Paned is of a
+        * sensible size, and then in addition, we will set the position in a
+        * way that will be maintained as/when/if the Paned is resized.
+        *
+        * Once we've called ::set_position() on a Paned, we don't do it
+        * again.
+        */
 
        if (which == static_cast<Gtk::Paned*> (&rhs_pane1)) {
 
@@ -1781,14 +1918,23 @@ Mixer_UI::pane_allocation_handler (Allocation&, Gtk::Paned* which)
                if (!geometry || (prop = geometry->property("mixer-rhs-pane1-pos")) == 0) {
                        pos = height / 3;
                } else {
-                       pos = atoi (prop->value());
+                       pos = atof (prop->value());
                }
 
-               if ((done[0] = GTK_WIDGET(rhs_pane1.gobj())->allocation.height > pos)) {
-                       rhs_pane1.set_position (pos);
+               if (pos > 1.0f) {
+                       /* older versions of Ardour stored absolute position */
+                       if ((done[0] = (allocation.get_height() > pos))) {
+                               rhs_pane1.set_position (pos);
+                       }
+               } else {
+                       if ((done[0] = (allocation.get_height() > 1.0/pos))) {
+                               paned_set_position_as_fraction (rhs_pane1, pos, true);
+                       }
                }
+       }
+
+       if (which == static_cast<Gtk::Paned*> (&rhs_pane2)) {
 
-       } else if (which == static_cast<Gtk::Paned*> (&rhs_pane2)) {
                if (done[1]) {
                        return;
                }
@@ -1796,13 +1942,22 @@ Mixer_UI::pane_allocation_handler (Allocation&, Gtk::Paned* which)
                if (!geometry || (prop = geometry->property("mixer-rhs-pane2-pos")) == 0) {
                        pos = 2 * height / 3;
                } else {
-                       pos = atoi (prop->value());
+                       pos = atof (prop->value());
                }
 
-               if ((done[1] = GTK_WIDGET(rhs_pane2.gobj())->allocation.height > pos)) {
-                       rhs_pane2.set_position (pos);
+               if (pos > 1.0f) {
+                       /* older versions of Ardour stored absolute position */
+                       if ((done[1] = (allocation.get_height() > pos))) {
+                               rhs_pane2.set_position (pos);
+                       }
+               } else {
+                       if ((done[1] = (allocation.get_height() > 1.0/pos))) {
+                               paned_set_position_as_fraction (rhs_pane2, pos, true);
+                       }
                }
-       } else if (which == static_cast<Gtk::Paned*> (&list_hpane)) {
+       }
+
+       if (which == static_cast<Gtk::Paned*> (&list_hpane)) {
 
                if (done[2]) {
                        return;
@@ -1811,14 +1966,45 @@ Mixer_UI::pane_allocation_handler (Allocation&, Gtk::Paned* which)
                if (!geometry || (prop = geometry->property("mixer-list-hpane-pos")) == 0) {
                        pos = std::max ((float)100, rintf ((float) 125 * UIConfiguration::instance().get_ui_scale()));
                } else {
-                       pos = max (36, atoi (prop->value ()));
+                       pos = max (0.1, atof (prop->value ()));
+               }
+
+               if (pos > 1.0f) {
+                       if ((done[2] = (allocation.get_width() > pos))) {
+                               list_hpane.set_position (pos);
+                       }
+               } else {
+                       if ((done[2] = (allocation.get_width() > 1.0/pos))) {
+                               paned_set_position_as_fraction (list_hpane, pos, false);
+                       }
+               }
+       }
+
+       if (which == static_cast<Gtk::Paned*> (&inner_pane)) {
+
+               if (done[3]) {
+                       return;
+               }
+
+               if (!geometry || (prop = geometry->property("mixer-inner-pane-pos")) == 0) {
+                       pos = std::max ((float)100, rintf ((float) 125 * UIConfiguration::instance().get_ui_scale()));
+               } else {
+                       pos = max (0.1, atof (prop->value ()));
                }
 
-               if ((done[2] = GTK_WIDGET(list_hpane.gobj())->allocation.width > pos)) {
-                       list_hpane.set_position (pos);
+               if (pos > 1.0f) {
+                       /* older versions of Ardour stored absolute position */
+                       if ((done[3] = (allocation.get_width() > pos))) {
+                               inner_pane.set_position (pos);
+                       }
+               } else {
+                       if ((done[3] = (allocation.get_width() > 1.0/pos))) {
+                               paned_set_position_as_fraction (inner_pane, pos, false);
+                       }
                }
        }
 }
+
 void
 Mixer_UI::scroll_left ()
 {
@@ -1882,8 +2068,6 @@ Mixer_UI::parameter_changed (string const & p)
                for (list<MixerStrip*>::iterator i = strips.begin(); i != strips.end(); ++i) {
                        (*i)->set_width_enum (s ? Narrow : Wide, this);
                }
-       } else if (p == "remote-model") {
-               reset_remote_control_ids ();
        } else if (p == "use-monitor-bus") {
                if (_session && !_session->monitor_out()) {
                        monitor_section_detached ();
@@ -1968,7 +2152,6 @@ Mixer_UI::new_track_or_bus ()
        ARDOUR_UI::instance()->add_route ();
 }
 
-
 void
 Mixer_UI::update_title ()
 {
@@ -2471,3 +2654,17 @@ Mixer_UI::plugin_drop (const Glib::RefPtr<Gdk::DragContext>&, const Gtk::Selecti
        manager.set_status (ppp->_pip->type, ppp->_pip->unique_id, status);
        manager.save_statuses ();
 }
+
+void
+Mixer_UI::do_vca_assign (boost::shared_ptr<VCA> vca)
+{
+       /* call protected MixerActor:: method */
+       vca_assign (vca);
+}
+
+void
+Mixer_UI::do_vca_unassign (boost::shared_ptr<VCA> vca)
+{
+       /* call protected MixerActor:: method */
+       vca_unassign (vca);
+}