provide mixer-specific bindings for Alt-(up|down) to match editor
[ardour.git] / gtk2_ardour / mixer_ui.cc
index c775b9b2b9b4dc8b16d4bee92d6a9d276370ab92..6f11e59900beeead23a65ccb3f4760125cc6db51 100644 (file)
 
 #include <boost/foreach.hpp>
 
+#include <glibmm/threads.h>
+
 #include <gtkmm/accelmap.h>
+#include <gtkmm/stock.h>
 
 #include "pbd/convert.h"
 #include "pbd/stacktrace.h"
 #include "pbd/unwind.h"
 
-#include <glibmm/threads.h>
-
-#include <gtkmm2ext/gtk_ui.h>
-#include <gtkmm2ext/keyboard.h>
-#include <gtkmm2ext/utils.h>
-#include <gtkmm2ext/tearoff.h>
-#include <gtkmm2ext/window_title.h>
-#include <gtkmm2ext/doi.h>
-
 #include "ardour/amp.h"
 #include "ardour/debug.h"
 #include "ardour/audio_track.h"
 #include "ardour/vca.h"
 #include "ardour/vca_manager.h"
 
+#include "gtkmm2ext/gtk_ui.h"
+#include "gtkmm2ext/keyboard.h"
+#include "gtkmm2ext/utils.h"
+#include "gtkmm2ext/window_title.h"
+#include "gtkmm2ext/doi.h"
+
+#include "widgets/tearoff.h"
+
 #include "keyboard.h"
 #include "mixer_ui.h"
 #include "mixer_strip.h"
@@ -61,7 +63,6 @@
 #include "public_editor.h"
 #include "mouse_cursors.h"
 #include "ardour_ui.h"
-#include "prompter.h"
 #include "utils.h"
 #include "route_sorter.h"
 #include "actions.h"
@@ -110,6 +111,7 @@ Mixer_UI::Mixer_UI ()
        , _route_deletion_in_progress (false)
        , _maximised (false)
        , _show_mixer_list (true)
+       , _strip_selection_change_without_scroll (false)
        , myactions (X_("mixer"))
        , _selection (*this, *this)
 {
@@ -133,8 +135,31 @@ Mixer_UI::Mixer_UI ()
        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
+       /* create a button to add VCA strips ... will get packed in redisplay_track_list() */
+       Widget* w = manage (new Image (Stock::ADD, ICON_SIZE_BUTTON));
+       w->show ();
+       add_vca_button.add (*w);
+       add_vca_button.set_can_focus(false);
+       add_vca_button.signal_clicked().connect (sigc::mem_fun (*this, &Mixer_UI::new_track_or_bus));
+
+       /* create a button to add mixer strips */
+       w = manage (new Image (Stock::ADD, ICON_SIZE_BUTTON));
+       w->show ();
+       add_button.add (*w);
+       add_button.set_can_focus(false);
+       add_button.signal_clicked().connect (sigc::mem_fun (*this, &Mixer_UI::new_track_or_bus));
+
+       /* add as last item of strip packer */
        strip_packer.pack_end (scroller_base, true, true);
+       strip_packer.pack_end (add_button, false, false);
+
+#ifdef MIXBUS
+       /* create a drop-shadow at the end of the mixer strips */
+       mb_shadow.set_size_request( 4, -1 );
+       mb_shadow.set_name("EditorWindow");
+       mb_shadow.show();
+       strip_packer.pack_end (mb_shadow, false, false);
+#endif
 
        _group_tabs = new MixerGroupTabs (this);
        VBox* b = manage (new VBox);
@@ -186,37 +211,13 @@ Mixer_UI::Mixer_UI ()
        group_display_scroller.add (group_display);
        group_display_scroller.set_policy (Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
 
-       HBox* route_group_display_button_box = manage (new HBox());
-
-       Button* route_group_add_button = manage (new Button ());
-       Button* route_group_remove_button = manage (new Button ());
-
-       Widget* w;
-
-       w = manage (new Image (Stock::ADD, ICON_SIZE_BUTTON));
-       w->show();
-       route_group_add_button->add (*w);
-
-       w = manage (new Image (Stock::REMOVE, ICON_SIZE_BUTTON));
-       w->show();
-       route_group_remove_button->add (*w);
-
-       route_group_display_button_box->set_homogeneous (true);
-
-       route_group_add_button->signal_clicked().connect (sigc::mem_fun (*this, &Mixer_UI::new_route_group));
-       route_group_remove_button->signal_clicked().connect (sigc::mem_fun (*this, &Mixer_UI::remove_selected_route_group));
-
-       route_group_display_button_box->add (*route_group_add_button);
-       route_group_display_button_box->add (*route_group_remove_button);
 
        group_display_vbox.pack_start (group_display_scroller, true, true);
-       group_display_vbox.pack_start (*route_group_display_button_box, false, false);
 
        group_display_frame.set_name ("BaseFrame");
        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"));
 
@@ -245,7 +246,11 @@ Mixer_UI::Mixer_UI ()
 
        favorite_plugins_frame.set_name ("BaseFrame");
        favorite_plugins_frame.set_shadow_type (Gtk::SHADOW_IN);
-       favorite_plugins_frame.add (favorite_plugins_scroller);
+       favorite_plugins_frame.add (favorite_plugins_vbox);
+
+       favorite_plugins_vbox.pack_start (favorite_plugins_scroller, true, true);
+       favorite_plugins_vbox.pack_start (favorite_plugins_tag_combo, false, false);
+       favorite_plugins_tag_combo.signal_changed().connect (sigc::mem_fun (*this, &Mixer_UI::tag_combo_changed));
 
        rhs_pane1.add (favorite_plugins_frame);
        rhs_pane1.add (track_display_frame);
@@ -261,7 +266,6 @@ Mixer_UI::Mixer_UI ()
        vca_scroller_base.add_events (Gdk::BUTTON_PRESS_MASK|Gdk::BUTTON_RELEASE_MASK);
        vca_scroller_base.set_name (X_("MixerWindow"));
        vca_scroller_base.signal_button_release_event().connect (sigc::mem_fun(*this, &Mixer_UI::masters_scroller_button_release), false);
-       vca_hpacker.pack_end (vca_scroller_base, true, true);
 
        vca_scroller.add (vca_hpacker);
        vca_scroller.set_policy (Gtk::POLICY_ALWAYS, Gtk::POLICY_AUTOMATIC);
@@ -278,7 +282,7 @@ Mixer_UI::Mixer_UI ()
        list_hpane.set_check_divider_position (true);
        list_hpane.add (list_vpacker);
        list_hpane.add (global_hpacker);
-       list_hpane.set_child_minsize (list_vpacker, 1);
+       list_hpane.set_child_minsize (list_vpacker, 30);
 
        XMLNode const * settings = ARDOUR_UI::instance()->mixer_settings();
        float fract;
@@ -312,10 +316,6 @@ Mixer_UI::Mixer_UI ()
 
        update_title ();
 
-       route_group_display_button_box->show();
-       route_group_add_button->show();
-       route_group_remove_button->show();
-
        _content.show ();
        _content.set_name ("MixerWindow");
 
@@ -326,7 +326,6 @@ Mixer_UI::Mixer_UI ()
        mixer_scroller_vpacker.show();
        list_vpacker.show();
        group_display_button_label.show();
-       group_display_button.show();
        group_display_scroller.show();
        favorite_plugins_scroller.show();
        group_display_vbox.show();
@@ -346,8 +345,10 @@ Mixer_UI::Mixer_UI ()
        list_hpane.show();
        group_display.show();
        favorite_plugins_display.show();
+       add_button.show ();
 
        MixerStrip::CatchDeletion.connect (*this, invalidator (*this), boost::bind (&Mixer_UI::remove_strip, this, _1), gui_context());
+       VCAMasterStrip::CatchDeletion.connect (*this, invalidator (*this), boost::bind (&Mixer_UI::remove_master, this, _1), gui_context());
 
        /* handle escape */
 
@@ -358,8 +359,9 @@ Mixer_UI::Mixer_UI ()
 #else
 #error implement deferred Plugin-Favorite list
 #endif
-       PluginManager::instance ().PluginListChanged.connect (*this, invalidator (*this), boost::bind (&Mixer_UI::refill_favorite_plugins, this), gui_context());
-       PluginManager::instance ().PluginStatusesChanged.connect (*this, invalidator (*this), boost::bind (&Mixer_UI::refill_favorite_plugins, this), gui_context());
+
+       PluginManager::instance ().PluginListChanged.connect (*this, invalidator (*this), boost::bind (&Mixer_UI::plugin_list_changed, this), gui_context());
+       PluginManager::instance ().PluginStatusChanged.connect (*this, invalidator (*this), boost::bind (&Mixer_UI::plugin_list_changed, this), gui_context());
        ARDOUR::Plugin::PresetsChanged.connect (*this, invalidator (*this), boost::bind (&Mixer_UI::refill_favorite_plugins, this), gui_context());
 }
 
@@ -373,12 +375,28 @@ Mixer_UI::~Mixer_UI ()
        delete track_menu;
 }
 
+struct MixerStripSorter {
+       bool operator() (const MixerStrip* ms_a, const MixerStrip* ms_b)
+       {
+               boost::shared_ptr<ARDOUR::Stripable> const& a = ms_a->stripable ();
+               boost::shared_ptr<ARDOUR::Stripable> const& b = ms_b->stripable ();
+               return ARDOUR::Stripable::Sorter(true)(a, b);
+       }
+};
+
+
 void
 Mixer_UI::escape ()
 {
        select_none ();
 }
 
+void
+Mixer_UI::tag_combo_changed ()
+{
+       refill_favorite_plugins();
+}
+
 Gtk::Window*
 Mixer_UI::use_own_window (bool and_fill_it)
 {
@@ -534,7 +552,7 @@ Mixer_UI::add_stripables (StripableList& slist)
                                row[stripable_columns.strip] = vms;
                                row[stripable_columns.stripable] = vca;
 
-                               vms->CatchDeletion.connect (*this, invalidator (*this), boost::bind (&Mixer_UI::remove_master, this, _1), gui_context());
+                               vms->signal_button_release_event().connect (sigc::bind (sigc::mem_fun(*this, &Mixer_UI::vca_button_release_event), vms));
 
                        } else if ((route = boost::dynamic_pointer_cast<Route> (*s))) {
 
@@ -584,7 +602,12 @@ Mixer_UI::add_stripables (StripableList& slist)
 
                                show_strip (strip);
 
-                               if (!route->is_master()) {
+                               if (route->is_master()) {
+
+                                       out_packer.pack_start (*strip, false, false);
+                                       strip->set_packed (true);
+
+                               } else {
 
                                        TreeModel::Row row = *(track_model->insert (insert_iter));
 
@@ -592,11 +615,6 @@ Mixer_UI::add_stripables (StripableList& slist)
                                        row[stripable_columns.visible] = strip->marked_for_display();
                                        row[stripable_columns.stripable] = route;
                                        row[stripable_columns.strip] = strip;
-
-                               } else {
-
-                                       out_packer.pack_start (*strip, false, false);
-                                       strip->set_packed (true);
                                }
 
                                strip->WidthChanged.connect (sigc::mem_fun(*this, &Mixer_UI::strip_width_changed));
@@ -638,6 +656,62 @@ Mixer_UI::select_none ()
        deselect_all_strip_processors();
 }
 
+ void
+Mixer_UI::select_next_strip ()
+{
+       deselect_all_strip_processors();
+       strips.sort (MixerStripSorter());
+
+       if (_selection.empty()) {
+               _selection.set (strips.front());
+               return;
+       }
+
+       bool select_me = false;
+
+       for (list<MixerStrip*>::iterator i = strips.begin(); i != strips.end(); ++i) {
+
+               if (select_me) {
+                       _selection.set (*i);
+                       return;
+               }
+
+               if ((*i)->selected()) {
+                       select_me = true;
+               }
+       }
+
+       _selection.set (strips.front());
+}
+
+void
+Mixer_UI::select_prev_strip ()
+{
+       deselect_all_strip_processors();
+       strips.sort (MixerStripSorter());
+
+       if (_selection.empty()) {
+               _selection.set (strips.back());
+               return;
+       }
+
+       bool select_me = false;
+
+       for (list<MixerStrip*>::reverse_iterator i = strips.rbegin(); i != strips.rend(); ++i) {
+
+               if (select_me) {
+                       _selection.set (*i);
+                       return;
+               }
+
+               if ((*i)->selected()) {
+                       select_me = true;
+               }
+       }
+
+       _selection.set (strips.back());
+}
+
 void
 Mixer_UI::delete_processors ()
 {
@@ -818,9 +892,23 @@ Mixer_UI::sync_treeview_from_presentation_info (PropertyChange const & what_chan
                        }
                }
 
-               if (!_selection.axes.empty() && !PublicEditor::instance().track_selection_change_without_scroll ()) {
+               if (!_selection.axes.empty() && !PublicEditor::instance().track_selection_change_without_scroll () && !_strip_selection_change_without_scroll) {
                        move_stripable_into_view ((*_selection.axes.begin())->stripable());
                }
+
+               TreeModel::Children rows = track_model->children();
+               for (TreeModel::Children::const_iterator i = rows.begin(); i != rows.end(); ++i) {
+                       AxisView* av = (*i)[stripable_columns.strip];
+                       VCAMasterStrip* vms = dynamic_cast<VCAMasterStrip*> (av);
+                       if (!vms) {
+                               continue;
+                       }
+                       if (vms->vca() && vms->vca()->is_selected()) {
+                               _selection.add (vms);
+                       } else {
+                               _selection.remove (vms);
+                       }
+               }
        }
 
        redisplay_track_list ();
@@ -860,6 +948,15 @@ Mixer_UI::axis_view_by_stripable (boost::shared_ptr<Stripable> s) const
                }
        }
 
+       TreeModel::Children rows = track_model->children();
+       for (TreeModel::Children::const_iterator i = rows.begin(); i != rows.end(); ++i) {
+               AxisView* av = (*i)[stripable_columns.strip];
+               VCAMasterStrip* vms = dynamic_cast<VCAMasterStrip*> (av);
+               if (vms && vms->stripable () == s) {
+                       return av;
+               }
+       }
+
        return 0;
 }
 
@@ -875,30 +972,37 @@ Mixer_UI::axis_view_by_control (boost::shared_ptr<AutomationControl> c) const
        return 0;
 }
 
-struct MixerStripSorter {
-       bool operator() (const MixerStrip* ms_a, const MixerStrip* ms_b)
-       {
-               boost::shared_ptr<ARDOUR::Stripable> const& a = ms_a->stripable ();
-               boost::shared_ptr<ARDOUR::Stripable> const& b = ms_b->stripable ();
-               return ARDOUR::Stripable::Sorter(true)(a, b);
-       }
-};
-
 bool
 Mixer_UI::strip_button_release_event (GdkEventButton *ev, MixerStrip *strip)
 {
+       /* Selecting a mixer-strip may also select grouped-tracks, and
+        * presentation_info_changed() being emitted and
+        * _selection.axes.begin() is being moved into view. This may
+        * effectively move the track that was clicked-on out of view.
+        *
+        * So here only the track that is actually clicked-on is moved into
+        * view (in case it's partially visible)
+        */
+       PBD::Unwinder<bool> uw (_strip_selection_change_without_scroll, true);
+       move_stripable_into_view (strip->stripable());
+
        if (ev->button == 1) {
                if (_selection.selected (strip)) {
                        /* primary-click: toggle selection state of strip */
                        if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
-                               _selection.remove (strip);
+                               _selection.remove (strip, true);
                        } else if (_selection.axes.size() > 1) {
                                /* de-select others */
                                _selection.set (strip);
                        }
+                       PublicEditor& pe = PublicEditor::instance();
+                       TimeAxisView* tav = pe.time_axis_view_from_stripable (strip->stripable());
+                       if (tav) {
+                               pe.set_selected_mixer_strip (*tav);
+                       }
                } else {
                        if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
-                               _selection.add (strip);
+                               _selection.add (strip, true);
                        } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::RangeSelectModifier)) {
 
                                /* extend selection */
@@ -946,7 +1050,7 @@ Mixer_UI::strip_button_release_event (GdkEventButton *ev, MixerStrip *strip)
                                if (found_another) {
                                        PresentationInfo::ChangeSuspender cs;
                                        for (vector<MixerStrip*>::iterator i = tmp.begin(); i != tmp.end(); ++i) {
-                                               _selection.add (*i);
+                                               _selection.add (*i, true);
                                        }
                                } else {
                                        _selection.set (strip);  //user wants to start a range selection, but there aren't any others selected yet
@@ -960,6 +1064,13 @@ Mixer_UI::strip_button_release_event (GdkEventButton *ev, MixerStrip *strip)
        return true;
 }
 
+bool
+Mixer_UI::vca_button_release_event (GdkEventButton *ev, VCAMasterStrip *strip)
+{
+       _selection.set (strip);
+       return true;
+}
+
 void
 Mixer_UI::set_session (Session* sess)
 {
@@ -981,6 +1092,7 @@ Mixer_UI::set_session (Session* sess)
        }
 
        refill_favorite_plugins();
+       refill_tag_combo();
 
        XMLNode* node = ARDOUR_UI::instance()->mixer_settings();
        set_state (*node, 0);
@@ -1390,7 +1502,12 @@ Mixer_UI::redisplay_track_list ()
        uint32_t n_masters = 0;
 
        container_clear (vca_hpacker);
+
        vca_hpacker.pack_end (vca_scroller_base, true, true);
+       vca_hpacker.pack_end (add_vca_button, false, false);
+
+       add_vca_button.show ();
+       vca_scroller_base.show();
 
        for (i = rows.begin(); i != rows.end(); ++i) {
 
@@ -1447,9 +1564,22 @@ Mixer_UI::redisplay_track_list ()
        if (n_masters == 0) {
                UIConfiguration::instance().set_mixer_strip_visibility (VisibilityGroup::remove_element (UIConfiguration::instance().get_mixer_strip_visibility(), X_("VCA")));
                vca_vpacker.hide ();
+               Glib::RefPtr<Action> act = ActionManager::get_action ("Common", "ToggleVCAPane");
+               if (act) {
+                       act->set_sensitive (false);
+               }
+
        } else {
                UIConfiguration::instance().set_mixer_strip_visibility (VisibilityGroup::add_element (UIConfiguration::instance().get_mixer_strip_visibility(), X_("VCA")));
-               vca_vpacker.show ();
+
+               Glib::RefPtr<Action> act = ActionManager::get_action ("Common", "ToggleVCAPane");
+               if (act) {
+                       act->set_sensitive (true);
+                       Glib::RefPtr<ToggleAction> tact = Glib::RefPtr<ToggleAction>::cast_dynamic(act);
+                       showhide_vcas (tact->get_active());
+               } else {
+                       vca_vpacker.show ();
+               }
        }
 
        _group_tabs->set_dirty ();
@@ -1990,7 +2120,6 @@ Mixer_UI::strip_scroller_button_release (GdkEventButton* ev)
 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;
@@ -2012,7 +2141,7 @@ Mixer_UI::scroller_drag_data_received (const Glib::RefPtr<Gdk::DragContext>& con
                if (!pip->is_instrument ()) {
                        continue;
                }
-               ARDOUR_UI::instance()->session_add_midi_track ((RouteGroup*) 0, 1, _("MIDI"), Config->get_strict_io (), pip, ppp->_preset.valid ? &ppp->_preset : 0, PresentationInfo::max_order);
+               ARDOUR_UI::instance()->session_add_midi_route (true, (RouteGroup*) 0, 1, _("MIDI"), Config->get_strict_io (), pip, ppp->_preset.valid ? &ppp->_preset : 0, PresentationInfo::max_order);
                ok = true;
        }
 
@@ -2097,12 +2226,12 @@ Mixer_UI::set_state (const XMLNode& node, int version)
                show_monitor_section (yn);
        }
 
-
-       XMLNode* plugin_order;
-       if ((plugin_order = find_named_node (node, "PluginOrder")) != 0) {
+       //check for the user's plugin_order file
+       XMLNode plugin_order_new(X_("PO"));
+       if (PluginManager::instance().load_plugin_order_file(plugin_order_new)) {
                store_current_favorite_order ();
                std::list<string> order;
-               const XMLNodeList& kids = plugin_order->children("PluginInfo");
+               const XMLNodeList& kids = plugin_order_new.children("PluginInfo");
                XMLNodeConstIterator i;
                for (i = kids.begin(); i != kids.end(); ++i) {
                        std::string unique_id;
@@ -2116,10 +2245,59 @@ Mixer_UI::set_state (const XMLNode& node, int version)
                PluginStateSorter cmp (order);
                favorite_order.sort (cmp);
                sync_treeview_from_favorite_order ();
+
+       } else {
+               //if there is no user file, then use an existing one from instant.xml
+               //NOTE: if you are loading an old session, this might come from the session's instant.xml
+               //Todo:  in the next major version, we should probably stop doing the instant.xml check, and just use the new file
+               XMLNode* plugin_order;
+               if ((plugin_order = find_named_node (node, "PluginOrder")) != 0) {
+                       store_current_favorite_order ();
+                       std::list<string> order;
+                       const XMLNodeList& kids = plugin_order->children("PluginInfo");
+                       XMLNodeConstIterator i;
+                       for (i = kids.begin(); i != kids.end(); ++i) {
+                               std::string unique_id;
+                               if ((*i)->get_property ("unique-id", unique_id)) {
+                                       order.push_back (unique_id);
+                                       if ((*i)->get_property ("expanded", yn)) {
+                                               favorite_ui_state[unique_id] = yn;
+                                       }
+                               }
+                       }
+
+                       PluginStateSorter cmp (order);
+                       favorite_order.sort (cmp);
+                       sync_treeview_from_favorite_order ();
+               }
        }
+
        return 0;
 }
 
+void
+Mixer_UI::save_plugin_order_file ()
+{
+       //this writes the plugin order to the user's preference file ( plugin_metadata/plugin_order )
+
+       //NOTE:  this replaces the old code that stores info in instant.xml
+       //why?  because instant.xml prefers the per-session settings, and we want this to be a global pref
+
+       store_current_favorite_order ();
+       XMLNode plugin_order ("PluginOrder");
+       uint32_t cnt = 0;
+       for (PluginInfoList::const_iterator i = favorite_order.begin(); i != favorite_order.end(); ++i, ++cnt) {
+               XMLNode* p = new XMLNode ("PluginInfo");
+               p->set_property ("sort", cnt);
+               p->set_property ("unique-id", (*i)->unique_id);
+               if (favorite_ui_state.find ((*i)->unique_id) != favorite_ui_state.end ()) {
+                       p->set_property ("expanded", favorite_ui_state[(*i)->unique_id]);
+               }
+               plugin_order.add_child_nocopy (*p);
+       }
+       PluginManager::instance().save_plugin_order_file( plugin_order );
+}
+
 XMLNode&
 Mixer_UI::get_state ()
 {
@@ -2142,20 +2320,6 @@ Mixer_UI::get_state ()
        assert (tact);
        node->set_property ("monitor-section-visible", tact->get_active ());
 
-       store_current_favorite_order ();
-       XMLNode* plugin_order = new XMLNode ("PluginOrder");
-       uint32_t cnt = 0;
-       for (PluginInfoList::const_iterator i = favorite_order.begin(); i != favorite_order.end(); ++i, ++cnt) {
-               XMLNode* p = new XMLNode ("PluginInfo");
-               p->set_property ("sort", cnt);
-               p->set_property ("unique-id", (*i)->unique_id);
-               if (favorite_ui_state.find ((*i)->unique_id) != favorite_ui_state.end ()) {
-                       p->set_property ("expanded", favorite_ui_state[(*i)->unique_id]);
-               }
-               plugin_order->add_child_nocopy (*p);
-       }
-       node->add_child_nocopy (*plugin_order);
-
        return *node;
 }
 
@@ -2174,6 +2338,14 @@ Mixer_UI::scroll_left ()
        using namespace Gtk::Box_Helpers;
        const BoxList& strips = strip_packer.children();
        for (BoxList::const_iterator i = strips.begin(); i != strips.end(); ++i) {
+               if (i->get_widget() == & add_button) {
+                       continue;
+               }
+#ifdef MIXBUS
+               if (i->get_widget() == &mb_shadow) {
+                       continue;
+               }
+#endif
                lm += i->get_widget()->get_width ();
                if (lm >= lp) {
                        lm -= i->get_widget()->get_width ();
@@ -2198,6 +2370,14 @@ Mixer_UI::scroll_right ()
        using namespace Gtk::Box_Helpers;
        const BoxList& strips = strip_packer.children();
        for (BoxList::const_iterator i = strips.begin(); i != strips.end(); ++i) {
+               if (i->get_widget() == & add_button) {
+                       continue;
+               }
+#ifdef MIXBUS
+               if (i->get_widget() == &mb_shadow) {
+                       continue;
+               }
+#endif
                lm += i->get_widget()->get_width ();
                if (lm > lp + 1) {
                        break;
@@ -2306,16 +2486,6 @@ Mixer_UI::setup_track_display ()
        v->show ();
        v->pack_start (track_display_scroller, true, true);
 
-       Button* b = manage (new Button);
-       b->show ();
-       Widget* w = manage (new Image (Stock::ADD, ICON_SIZE_BUTTON));
-       w->show ();
-       b->add (*w);
-
-       b->signal_clicked().connect (sigc::mem_fun (*this, &Mixer_UI::new_track_or_bus));
-
-       v->pack_start (*b, false, false);
-
        track_display_frame.set_name("BaseFrame");
        track_display_frame.set_shadow_type (Gtk::SHADOW_IN);
        track_display_frame.add (*v);
@@ -2519,9 +2689,24 @@ Mixer_UI::refiller (PluginInfoList& result, const PluginInfoList& plugs)
 {
        PluginManager& manager (PluginManager::instance());
        for (PluginInfoList::const_iterator i = plugs.begin(); i != plugs.end(); ++i) {
+
+               /* not a Favorite? skip it */
                if (manager.get_status (*i) != PluginManager::Favorite) {
                        continue;
                }
+
+               /* Check the tag combo selection, and skip this plugin if it doesn't match the selected tag(s) */
+               string test = favorite_plugins_tag_combo.get_active_text();
+               if (test != _("Show All")) {
+                       vector<string> tags = manager.get_tags(*i);
+
+                       //does the selected tag match any of the tags in the plugin?
+                       vector<string>::iterator tt =  find (tags.begin(), tags.end(), test);
+                       if (tt == tags.end()) {
+                               continue;
+                       }
+               }
+
                result.push_back (*i);
        }
 }
@@ -2584,6 +2769,30 @@ Mixer_UI::refill_favorite_plugins ()
        sync_treeview_from_favorite_order ();
 }
 
+void
+Mixer_UI::plugin_list_changed ()
+{
+       refill_favorite_plugins();
+       refill_tag_combo();
+}
+
+void
+Mixer_UI::refill_tag_combo ()
+{
+       PluginManager& mgr (PluginManager::instance());
+
+       std::vector<std::string> tags = mgr.get_all_tags (PluginManager::OnlyFavorites);
+
+       favorite_plugins_tag_combo.clear();
+       favorite_plugins_tag_combo.append_text (_("Show All"));
+
+       for (vector<string>::iterator t = tags.begin (); t != tags.end (); ++t) {
+               favorite_plugins_tag_combo.append_text (*t);
+       }
+
+       favorite_plugins_tag_combo.set_active_text (_("Show All"));
+}
+
 void
 Mixer_UI::sync_treeview_favorite_ui_state (const TreeModel::Path& path, const TreeModel::iterator&)
 {
@@ -2670,7 +2879,7 @@ Mixer_UI::popup_note_context_menu (GdkEventButton *ev)
 bool
 Mixer_UI::plugin_row_button_press (GdkEventButton *ev)
 {
-       if ((ev->type == GDK_BUTTON_PRESS) && (ev->button == 3) ) {
+       if ((ev->type == GDK_BUTTON_PRESS) && (ev->button == 3)) {
                TreeModel::Path path;
                TreeViewColumn* column;
                int cellx, celly;
@@ -2925,6 +3134,9 @@ Mixer_UI::register_actions ()
        myactions.register_action (group, "ab-plugins", _("Toggle Selected Plugins"), sigc::mem_fun (*this, &Mixer_UI::ab_plugins));
        myactions.register_action (group, "select-none", _("Deselect all strips and processors"), sigc::mem_fun (*this, &Mixer_UI::select_none));
 
+       myactions.register_action (group, "select-next-stripable", _("Select Next Mixer Strip"), sigc::mem_fun (*this, &Mixer_UI::select_next_strip));
+       myactions.register_action (group, "select-prev-stripable", _("Scroll Previous Mixer Strip"), sigc::mem_fun (*this, &Mixer_UI::select_prev_strip));
+
        myactions.register_action (group, "scroll-left", _("Scroll Mixer Window to the left"), sigc::mem_fun (*this, &Mixer_UI::scroll_left));
        myactions.register_action (group, "scroll-right", _("Scroll Mixer Window to the right"), sigc::mem_fun (*this, &Mixer_UI::scroll_right));