Temper pants - fix newly added lockup while dragging tempos sometimes.
[ardour.git] / gtk2_ardour / route_ui.cc
index 0d9ed1a958f3175e901dae68e6591754ef04d2ee..a0e9ff5ed176844632b5b43bbf32e0d3e917d0c5 100644 (file)
@@ -17,6 +17,8 @@
 
 */
 
+#include <boost/algorithm/string.hpp>
+
 #include <gtkmm2ext/gtk_ui.h>
 #include <gtkmm2ext/choice.h>
 #include <gtkmm2ext/doi.h>
@@ -38,6 +40,7 @@
 #include "ardour_button.h"
 #include "keyboard.h"
 #include "utils.h"
+#include "plugin_pin_dialog.h"
 #include "prompter.h"
 #include "gui_thread.h"
 #include "ardour_dialog.h"
@@ -54,6 +57,7 @@
 #include "ardour/filename_extensions.h"
 #include "ardour/midi_track.h"
 #include "ardour/internal_send.h"
+#include "ardour/profile.h"
 #include "ardour/send.h"
 #include "ardour/route.h"
 #include "ardour/session.h"
@@ -70,6 +74,7 @@ using namespace std;
 uint32_t RouteUI::_max_invert_buttons = 3;
 PBD::Signal1<void, boost::shared_ptr<Route> > RouteUI::BusSendDisplayChanged;
 boost::weak_ptr<Route> RouteUI::_showing_sends_to;
+std::string RouteUI::program_port_prefix;
 
 RouteUI::RouteUI (ARDOUR::Session* sess)
        : AxisView(sess)
@@ -83,6 +88,12 @@ RouteUI::RouteUI (ARDOUR::Session* sess)
        , output_selector (0)
        , _invert_menu(0)
 {
+       if (program_port_prefix.empty()) {
+               // compare to gtk2_ardour/port_group.cc
+               string lpn (PROGRAM_NAME);
+               boost::to_lower (lpn);
+               program_port_prefix = lpn + ":"; // e.g. "ardour:"
+       }
        if (sess) init ();
 }
 
@@ -98,12 +109,12 @@ RouteUI::~RouteUI()
        delete solo_menu;
        delete mute_menu;
        delete sends_menu;
-        delete record_menu;
+       delete record_menu;
        delete comment_window;
        delete input_selector;
        delete output_selector;
        delete _invert_menu;
-       
+
        send_blink_connection.disconnect ();
        rec_blink_connection.disconnect ();
 }
@@ -115,20 +126,21 @@ RouteUI::init ()
        mute_menu = 0;
        solo_menu = 0;
        sends_menu = 0;
-        record_menu = 0;
+       record_menu = 0;
        _invert_menu = 0;
        pre_fader_mute_check = 0;
        post_fader_mute_check = 0;
        listen_mute_check = 0;
        main_mute_check = 0;
-        solo_safe_check = 0;
-        solo_isolated_check = 0;
-        solo_isolated_led = 0;
-        solo_safe_led = 0;
+       solo_safe_check = 0;
+       solo_isolated_check = 0;
+       solo_isolated_led = 0;
+       solo_safe_led = 0;
        _solo_release = 0;
        _mute_release = 0;
        denormal_menu_item = 0;
-        step_edit_item = 0;
+       step_edit_item = 0;
+       rec_safe_item = 0;
        multiple_mute_change = false;
        multiple_solo_change = false;
        _i_am_the_modifier = 0;
@@ -165,7 +177,7 @@ RouteUI::init ()
        monitor_input_button->set_text (_("In"));
        UI::instance()->set_tip (monitor_input_button, _("Monitor input"), "");
        monitor_input_button->set_no_show_all (true);
-       
+
        monitor_disk_button = manage (new ArdourButton (ArdourButton::default_elements));
        monitor_disk_button->set_name ("monitor button");
        monitor_disk_button->set_text (_("Disk"));
@@ -189,7 +201,7 @@ RouteUI::init ()
        solo_button->signal_button_release_event().connect (sigc::mem_fun(*this, &RouteUI::solo_release), false);
        mute_button->signal_button_press_event().connect (sigc::mem_fun(*this, &RouteUI::mute_press), false);
        mute_button->signal_button_release_event().connect (sigc::mem_fun(*this, &RouteUI::mute_release), false);
-       
+
        monitor_input_button->set_distinct_led_click (false);
        monitor_disk_button->set_distinct_led_click (false);
 
@@ -249,13 +261,14 @@ RouteUI::set_route (boost::shared_ptr<Route> rp)
        _route->active_changed.connect (route_connections, invalidator (*this), boost::bind (&RouteUI::route_active_changed, this), gui_context());
        _route->mute_changed.connect (route_connections, invalidator (*this), boost::bind (&RouteUI::update_mute_display, this), gui_context());
 
-       _route->comment_changed.connect (route_connections, invalidator (*this), boost::bind (&RouteUI::comment_changed, this, _1), gui_context());
+       _route->comment_changed.connect (route_connections, invalidator (*this), boost::bind (&RouteUI::comment_changed, this), gui_context());
 
        _route->solo_changed.connect (route_connections, invalidator (*this), boost::bind (&RouteUI::update_solo_display, this), gui_context());
        _route->solo_safe_changed.connect (route_connections, invalidator (*this), boost::bind (&RouteUI::update_solo_display, this), gui_context());
        _route->listen_changed.connect (route_connections, invalidator (*this), boost::bind (&RouteUI::update_solo_display, this), gui_context());
        _route->solo_isolated_changed.connect (route_connections, invalidator (*this), boost::bind (&RouteUI::update_solo_display, this), gui_context());
        if (is_track()) {
+               track()->FreezeChange.connect (*this, invalidator (*this), boost::bind (&RouteUI::map_frozen, this), gui_context());
                track()->TrackModeChanged.connect (route_connections, invalidator (*this), boost::bind (&RouteUI::track_mode_changed, this), gui_context());
                track_mode_changed();
        }
@@ -270,9 +283,10 @@ RouteUI::set_route (boost::shared_ptr<Route> rp)
                boost::shared_ptr<Track> t = boost::dynamic_pointer_cast<Track>(_route);
 
                t->RecordEnableChanged.connect (route_connections, invalidator (*this), boost::bind (&RouteUI::route_rec_enable_changed, this), gui_context());
-               
+               t->RecordSafeChanged.connect (route_connections, invalidator (*this), boost::bind (&RouteUI::route_rec_enable_changed, this), gui_context());
+
                rec_enable_button->show();
-               rec_enable_button->set_controllable (t->rec_enable_control());
+               rec_enable_button->set_controllable (t->rec_enable_control());
 
                 if (is_midi_track()) {
                         midi_track()->StepEditStatusChange.connect (route_connections, invalidator (*this),
@@ -318,6 +332,8 @@ RouteUI::set_route (boost::shared_ptr<Route> rp)
                blink_rec_display(true); // set initial rec-en button state
        }
 
+       check_rec_enable_sensitivity ();
+       maybe_add_route_print_mgr ();
        route_color_changed();
 }
 
@@ -341,7 +357,7 @@ RouteUI::mute_press (GdkEventButton* ev)
        //if this is a binding action, let the ArdourButton handle it
        if ( BindingProxy::is_bind_action(ev) )
                return false;
-                       
+
        multiple_mute_change = false;
 
        if (Keyboard::is_context_menu_event (ev)) {
@@ -351,7 +367,7 @@ RouteUI::mute_press (GdkEventButton* ev)
                }
 
                mute_menu->popup(0,ev->time);
-               
+
                return true;
 
        } else {
@@ -373,7 +389,7 @@ RouteUI::mute_press (GdkEventButton* ev)
                                 * elements of the list we need to work
                                 * on a copy.
                                 */
-                                       
+
                                boost::shared_ptr<RouteList> copy (new RouteList);
 
                                *copy = *_session->get_routes ();
@@ -395,7 +411,16 @@ RouteUI::mute_press (GdkEventButton* ev)
 
                        } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
 
-                               /* Primary-button1 applies change to the mix group even if it is not active
+                               /* Primary-button1 inverts the implication of
+                                  the group being active. If the group is
+                                  active (for mute), then this modifier means
+                                  "do not apply to mute". If the group is
+                                  inactive (for mute), then this modifier
+                                  means "apply to route". This is all
+                                  accomplished by passing just the actual
+                                  route, along with the InverseGroup group
+                                  control disposition.
+
                                   NOTE: Primary-button2 is MIDI learn.
                                */
 
@@ -403,20 +428,15 @@ RouteUI::mute_press (GdkEventButton* ev)
 
                                if (ev->button == 1) {
 
-                                       if (_route->route_group()) {
-                                                       
-                                               rl = _route->route_group()->route_list();
-                                                       
-                                               if (_mute_release) {
-                                                       _mute_release->routes = rl;
-                                               }
-                                       } else {
-                                               rl.reset (new RouteList);
-                                               rl->push_back (_route);
+                                       rl.reset (new RouteList);
+                                       rl->push_back (_route);
+
+                                       if (_mute_release) {
+                                               _mute_release->routes = rl;
                                        }
 
                                        DisplaySuspender ds;
-                                       _session->set_mute (rl, !_route->muted(), Session::rt_cleanup, true);
+                                       _session->set_mute (rl, !_route->muted(), Session::rt_cleanup, Controllable::InverseGroup);
                                }
 
                        } else {
@@ -425,7 +445,7 @@ RouteUI::mute_press (GdkEventButton* ev)
 
                                boost::shared_ptr<RouteList> rl (new RouteList);
                                rl->push_back (_route);
-                                       
+
                                if (_mute_release) {
                                        _mute_release->routes = rl;
                                }
@@ -444,7 +464,7 @@ RouteUI::mute_release (GdkEventButton* /*ev*/)
 {
        if (_mute_release){
                DisplaySuspender ds;
-               _session->set_mute (_mute_release->routes, _mute_release->active, Session::rt_cleanup, true);
+               _session->set_mute (_mute_release->routes, _mute_release->active, Session::rt_cleanup, Controllable::UseGroup);
                delete _mute_release;
                _mute_release = 0;
        }
@@ -510,7 +530,7 @@ RouteUI::solo_press(GdkEventButton* ev)
        //if this is a binding action, let the ArdourButton handle it
        if ( BindingProxy::is_bind_action(ev) )
                return false;
-                       
+
        multiple_solo_change = false;
 
        if (Keyboard::is_context_menu_event (ev)) {
@@ -545,9 +565,9 @@ RouteUI::solo_press(GdkEventButton* ev)
 
                                DisplaySuspender ds;
                                if (Config->get_solo_control_is_listen_control()) {
-                                       _session->set_listen (_session->get_routes(), !_route->listening_via_monitor(),  Session::rt_cleanup, true);
+                                       _session->set_listen (_session->get_routes(), !_route->listening_via_monitor(),  Session::rt_cleanup, Controllable::UseGroup);
                                } else {
-                                       _session->set_solo (_session->get_routes(), !_route->self_soloed(),  Session::rt_cleanup, true);
+                                       _session->set_solo (_session->get_routes(), !_route->self_soloed(),  Session::rt_cleanup, Controllable::UseGroup);
                                }
 
                        } else if (Keyboard::modifier_state_contains (ev->state, Keyboard::ModifierMask (Keyboard::PrimaryModifier|Keyboard::SecondaryModifier))) {
@@ -579,7 +599,7 @@ RouteUI::solo_press(GdkEventButton* ev)
 
                                // shift-click: toggle solo isolated status
 
-                               _route->set_solo_isolated (!_route->solo_isolated(), this);
+                               _route->set_solo_isolated (!_route->solo_isolated(), Controllable::UseGroup);
                                delete _solo_release;
                                _solo_release = 0;
 
@@ -597,26 +617,38 @@ RouteUI::solo_press(GdkEventButton* ev)
 
                                if (ev->button == 1) {
 
-                                       if (_route->route_group()) {
-                                                       
-                                               rl = _route->route_group()->route_list();
-                                                       
-                                               if (_solo_release) {
-                                                       _solo_release->routes = rl;
-                                               }
-                                       } else {
-                                               rl.reset (new RouteList);
-                                               rl->push_back (_route);
+                                       /* Primary-button1 inverts the implication of
+                                          the group being active. If the group is
+                                          active (for solo), then this modifier means
+                                          "do not apply to solo". If the group is
+                                          inactive (for mute), then this modifier
+                                          means "apply to route". This is all
+                                          accomplished by passing just the actual
+                                          route, along with the InverseGroup group
+                                          control disposition.
+
+                                          NOTE: Primary-button2 is MIDI learn.
+                                       */
+
+                                       rl.reset (new RouteList);
+                                       rl->push_back (_route);
+
+                                       if (_solo_release) {
+                                               _solo_release->routes = rl;
                                        }
 
                                        DisplaySuspender ds;
+
                                        if (Config->get_solo_control_is_listen_control()) {
-                                               _session->set_listen (rl, !_route->listening_via_monitor(),  Session::rt_cleanup, true);
+                                               _session->set_listen (rl, !_route->listening_via_monitor(),  Session::rt_cleanup, Controllable::InverseGroup);
                                        } else {
-                                               _session->set_solo (rl, !_route->self_soloed(),  Session::rt_cleanup, true);
+                                               _session->set_solo (rl, !_route->self_soloed(),  Session::rt_cleanup, Controllable::InverseGroup);
                                        }
                                }
 
+                               delete _solo_release;
+                               _solo_release = 0;
+
                        } else {
 
                                /* click: solo this route */
@@ -651,9 +683,9 @@ RouteUI::solo_release (GdkEventButton* /*ev*/)
                } else {
                        DisplaySuspender ds;
                        if (Config->get_solo_control_is_listen_control()) {
-                               _session->set_listen (_solo_release->routes, _solo_release->active, Session::rt_cleanup, true);
+                               _session->set_listen (_solo_release->routes, _solo_release->active, Session::rt_cleanup, Controllable::UseGroup);
                        } else {
-                               _session->set_solo (_solo_release->routes, _solo_release->active, Session::rt_cleanup, true);
+                               _session->set_solo (_solo_release->routes, _solo_release->active, Session::rt_cleanup, Controllable::UseGroup);
                        }
                }
 
@@ -674,7 +706,7 @@ RouteUI::rec_enable_press(GdkEventButton* ev)
        //if this is a binding action, let the ArdourButton handle it
        if ( BindingProxy::is_bind_action(ev) )
                return false;
-                       
+
        if (!_session->engine().connected()) {
                MessageDialog msg (_("Not connected to AudioEngine - cannot engage record"));
                msg.run ();
@@ -694,7 +726,7 @@ RouteUI::rec_enable_press(GdkEventButton* ev)
        if (is_track() && rec_enable_button) {
 
                if (Keyboard::is_button2_event (ev)) {
-                       
+
                        //rec arm does not have a momentary mode
                        return false;
 
@@ -712,18 +744,12 @@ RouteUI::rec_enable_press(GdkEventButton* ev)
                        if (ev->button == 1) {
 
                                boost::shared_ptr<RouteList> rl;
-                               
-                               if (_route->route_group()) {
-                                       
-                                       rl = _route->route_group()->route_list();
-                                       
-                               } else {
-                                       rl.reset (new RouteList);
-                                       rl->push_back (_route);
-                               }
+
+                               rl.reset (new RouteList);
+                               rl->push_back (_route);
 
                                DisplaySuspender ds;
-                               _session->set_record_enabled (rl, !_route->record_enabled(), Session::rt_cleanup, true);
+                               _session->set_record_enabled (rl, !_route->record_enabled(), Session::rt_cleanup, Controllable::InverseGroup);
                        }
 
                } else if (Keyboard::is_context_menu_event (ev)) {
@@ -810,7 +836,7 @@ RouteUI::monitor_disk_release (GdkEventButton* ev)
 
 bool
 RouteUI::monitor_release (GdkEventButton* ev, MonitorChoice monitor_choice)
-{      
+{
        if (ev->button != 1) {
                return false;
        }
@@ -823,7 +849,7 @@ RouteUI::monitor_release (GdkEventButton* ev, MonitorChoice monitor_choice)
 
        MonitorChoice mc;
        boost::shared_ptr<RouteList> rl;
-       
+
        /* XXX for now, monitoring choices are orthogonal. cue monitoring
           will follow in 3.X but requires mixing the input and playback (disk)
           signal together, which requires yet more buffers.
@@ -837,7 +863,7 @@ RouteUI::monitor_release (GdkEventButton* ev, MonitorChoice monitor_choice)
                mc = monitor_choice;
        }
 
-       if (Keyboard::modifier_state_equals (ev->state, Keyboard::ModifierMask (Keyboard::PrimaryModifier|Keyboard::TertiaryModifier))) {       
+       if (Keyboard::modifier_state_equals (ev->state, Keyboard::ModifierMask (Keyboard::PrimaryModifier|Keyboard::TertiaryModifier))) {
                rl = _session->get_routes ();
 
        } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
@@ -853,7 +879,7 @@ RouteUI::monitor_release (GdkEventButton* ev, MonitorChoice monitor_choice)
        }
 
        DisplaySuspender ds;
-       _session->set_monitoring (rl, mc, Session::rt_cleanup, true);           
+       _session->set_monitoring (rl, mc, Session::rt_cleanup, Controllable::UseGroup);
 
        return false;
 }
@@ -861,29 +887,30 @@ RouteUI::monitor_release (GdkEventButton* ev, MonitorChoice monitor_choice)
 void
 RouteUI::build_record_menu ()
 {
-       if (record_menu) {
-               return;
-       }
-
-       /* no rec-button context menu for non-MIDI tracks
-        */
-
-       if (is_midi_track()) {
+       if (!record_menu) {
                record_menu = new Menu;
                record_menu->set_name ("ArdourContextMenu");
-
                using namespace Menu_Helpers;
                MenuList& items = record_menu->items();
 
-               items.push_back (CheckMenuElem (_("Step Entry"), sigc::mem_fun (*this, &RouteUI::toggle_step_edit)));
-               step_edit_item = dynamic_cast<Gtk::CheckMenuItem*> (&items.back());
+               items.push_back (CheckMenuElem (_("Rec-Safe"), sigc::mem_fun (*this, &RouteUI::toggle_rec_safe)));
+               rec_safe_item = dynamic_cast<Gtk::CheckMenuItem*> (&items.back());
 
-               if (_route->record_enabled()) {
-                       step_edit_item->set_sensitive (false);
+               if (is_midi_track()) {
+                       items.push_back (SeparatorElem());
+                       items.push_back (CheckMenuElem (_("Step Entry"), sigc::mem_fun (*this, &RouteUI::toggle_step_edit)));
+                       step_edit_item = dynamic_cast<Gtk::CheckMenuItem*> (&items.back());
                }
+       }
 
+       if (step_edit_item) {
+               step_edit_item->set_sensitive (!_route->record_enabled());
                step_edit_item->set_active (midi_track()->step_editing());
        }
+       if (rec_safe_item) {
+               rec_safe_item->set_sensitive (!_route->record_enabled());
+               rec_safe_item->set_active (_route->record_safe());
+       }
 }
 
 void
@@ -896,6 +923,18 @@ RouteUI::toggle_step_edit ()
        midi_track()->set_step_editing (step_edit_item->get_active());
 }
 
+void
+RouteUI::toggle_rec_safe ()
+{
+       if (_route->record_enabled()) {
+               return;
+       }
+       DisplaySuspender ds;
+       boost::shared_ptr<RouteList> rl (new RouteList);
+       rl->push_back (_route);
+       _session->set_record_safe (rl, rec_safe_item->get_active (), Session::rt_cleanup);
+}
+
 void
 RouteUI::step_edit_changed (bool yn)
 {
@@ -1285,7 +1324,6 @@ RouteUI::blink_rec_display (bool blinkOn)
                 }
        }
 
-
        check_rec_enable_sensitivity ();
 }
 
@@ -1425,11 +1463,11 @@ RouteUI::solo_isolate_button_release (GdkEventButton* ev)
                        if (model) {
                                /* disable isolate for all routes */
                                DisplaySuspender ds;
-                               _session->set_solo_isolated (_session->get_routes(), false, Session::rt_cleanup, true);
+                               _session->set_solo_isolated (_session->get_routes(), false, Session::rt_cleanup, Controllable::NoGroup);
                        } else {
                                /* enable isolate for all routes */
                                DisplaySuspender ds;
-                               _session->set_solo_isolated (_session->get_routes(), true, Session::rt_cleanup, true);
+                               _session->set_solo_isolated (_session->get_routes(), true, Session::rt_cleanup, Controllable::NoGroup);
                        }
 
                } else {
@@ -1441,7 +1479,7 @@ RouteUI::solo_isolate_button_release (GdkEventButton* ev)
                                boost::shared_ptr<RouteList> rl (new RouteList);
                                rl->push_back (_route);
                                DisplaySuspender ds;
-                               _session->set_solo_isolated (rl, !view, Session::rt_cleanup, true);
+                               _session->set_solo_isolated (rl, !view, Session::rt_cleanup, Controllable::NoGroup);
                        }
                }
        }
@@ -1466,20 +1504,20 @@ RouteUI::solo_safe_button_release (GdkEventButton* ev)
                                /* disable solo safe for all routes */
                                DisplaySuspender ds;
                                for (RouteList::iterator i = rl->begin(); i != rl->end(); ++i) {
-                                       (*i)->set_solo_safe (false, this);
+                                       (*i)->set_solo_safe (false, Controllable::NoGroup);
                                }
                        } else {
                                /* enable solo safe for all routes */
                                DisplaySuspender ds;
                                for (RouteList::iterator i = rl->begin(); i != rl->end(); ++i) {
-                                       (*i)->set_solo_safe (true, this);
+                                       (*i)->set_solo_safe (true, Controllable::NoGroup);
                                }
                        }
                }
                else {
                        if (model == view) {
                                /* flip just this route */
-                               _route->set_solo_safe (!view, this);
+                               _route->set_solo_safe (!view, Controllable::NoGroup);
                        }
                }
        }
@@ -1496,14 +1534,14 @@ RouteUI::toggle_solo_isolated (Gtk::CheckMenuItem* check)
         /* called AFTER the view has changed */
 
         if (model != view) {
-                _route->set_solo_isolated (view, this);
+               _route->set_solo_isolated (view, Controllable::UseGroup);
         }
 }
 
 void
 RouteUI::toggle_solo_safe (Gtk::CheckMenuItem* check)
 {
-       _route->set_solo_safe (check->get_active(), this);
+       _route->set_solo_safe (check->get_active(), Controllable::UseGroup);
 }
 
 /** Ask the user to choose a colour, and then apply that color to my route
@@ -1532,11 +1570,11 @@ RouteUI::set_color (const Gdk::Color & c)
        char buf[64];
        _color = c;
        snprintf (buf, sizeof (buf), "%d:%d:%d", c.get_red(), c.get_green(), c.get_blue());
-       
+
        /* note: we use the route state ID here so that color is the same for both
           the time axis view and the mixer strip
        */
-       
+
        gui_object_state().set_property<string> (route_state_id(), X_("color"), buf);
        _route->gui_changed ("color", (void *) 0); /* EMIT_SIGNAL */
 }
@@ -1575,12 +1613,12 @@ RouteUI::verify_new_route_name (const std::string& name)
        if (name.find (':') == string::npos) {
                return true;
        }
-       
+
        MessageDialog colon_msg (
                _("The use of colons (':') is discouraged in track and bus names.\nDo you want to use this new name?"),
                false, MESSAGE_QUESTION, BUTTONS_NONE
                );
-       
+
        colon_msg.add_button (_("Use the new name"), Gtk::RESPONSE_ACCEPT);
        colon_msg.add_button (_("Re-edit the name"), Gtk::RESPONSE_CANCEL);
 
@@ -1690,17 +1728,13 @@ RouteUI::setup_comment_editor ()
 }
 
 void
-RouteUI::comment_changed (void *src)
+RouteUI::comment_changed ()
 {
-       ENSURE_GUI_THREAD (*this, &MixerStrip::comment_changed, src)
-
-       if (src != this) {
-               ignore_comment_edit = true;
-               if (comment_area) {
-                       comment_area->get_buffer()->set_text (_route->comment());
-               }
-               ignore_comment_edit = false;
+       ignore_comment_edit = true;
+       if (comment_area) {
+               comment_area->get_buffer()->set_text (_route->comment());
        }
+       ignore_comment_edit = false;
 }
 
 void
@@ -1726,6 +1760,12 @@ RouteUI::set_route_active (bool a, bool apply_to_selection)
        }
 }
 
+void
+RouteUI::duplicate_selected_routes ()
+{
+       ARDOUR_UI::instance()->start_duplicate_routes();
+}
+
 void
 RouteUI::toggle_denormal_protection ()
 {
@@ -1814,17 +1854,10 @@ RouteUI::map_frozen ()
 {
        ENSURE_GUI_THREAD (*this, &RouteUI::map_frozen)
 
-       AudioTrack* at = dynamic_cast<AudioTrack*>(_route.get());
+       AudioTrack* at = dynamic_cast<AudioTrack*>(_route.get());
 
        if (at) {
-               switch (at->freeze_state()) {
-               case AudioTrack::Frozen:
-                       rec_enable_button->set_sensitive (false);
-                       break;
-               default:
-                       rec_enable_button->set_sensitive (true);
-                       break;
-               }
+               check_rec_enable_sensitivity ();
        }
 }
 
@@ -1834,52 +1867,90 @@ RouteUI::adjust_latency ()
        LatencyDialog dialog (_route->name() + _(" latency"), *(_route->output()), _session->frame_rate(), AudioEngine::instance()->samples_per_cycle());
 }
 
-void
-RouteUI::save_as_template ()
+bool
+RouteUI::process_save_template_prompter (ArdourPrompter& prompter, const std::string& dir)
 {
        std::string path;
        std::string safe_name;
-       string name;
+       std::string name;
 
-       path = ARDOUR::user_route_template_directory ();
+       prompter.get_result (name, true);
 
-       if (g_mkdir_with_parents (path.c_str(), 0755)) {
-               error << string_compose (_("Cannot create route template directory %1"), path) << endmsg;
-               return;
+       safe_name = legalize_for_path (name);
+       safe_name += template_suffix;
+
+       path = Glib::build_filename (dir, safe_name);
+
+       if (Glib::file_test (path, Glib::FILE_TEST_EXISTS)) {
+               bool overwrite = overwrite_file_dialog (prompter,
+                                                       _("Confirm Template Overwrite"),
+                                                       _("A template already exists with that name. Do you want to overwrite it?"));
+
+               if (!overwrite) {
+                       return false;
+               }
        }
 
-       Prompter p (true); // modal
+       _route->save_as_template (path, name);
+
+       return true;
+}
+
+void
+RouteUI::save_as_template ()
+{
+       std::string dir;
 
-       p.set_title (_("Save As Template"));
-       p.set_prompt (_("Template name:"));
-       p.add_button (Gtk::Stock::SAVE, Gtk::RESPONSE_ACCEPT);
-       switch (p.run()) {
-       case RESPONSE_ACCEPT:
-               break;
-       default:
+       dir = ARDOUR::user_route_template_directory ();
+
+       if (g_mkdir_with_parents (dir.c_str(), 0755)) {
+               error << string_compose (_("Cannot create route template directory %1"), dir) << endmsg;
                return;
        }
 
-       p.hide ();
-       p.get_result (name, true);
-
-       safe_name = legalize_for_path (name);
-       safe_name += template_suffix;
+       ArdourPrompter prompter (true); // modal
 
-       path = Glib::build_filename (path, safe_name);
+       prompter.set_title (_("Save As Template"));
+       prompter.set_prompt (_("Template name:"));
+       prompter.add_button (Gtk::Stock::SAVE, Gtk::RESPONSE_ACCEPT);
 
-       _route->save_as_template (path, name);
+       bool finished = false;
+       while (!finished) {
+               switch (prompter.run()) {
+               case RESPONSE_ACCEPT:
+                       finished = process_save_template_prompter (prompter, dir);
+                       break;
+               default:
+                       finished = true;
+                       break;
+               }
+       }
 }
 
 void
 RouteUI::check_rec_enable_sensitivity ()
 {
+       if (!rec_enable_button) {
+               assert (0); // This should not happen
+               return;
+       }
+       if (!_session->writable()) {
+               rec_enable_button->set_sensitive (false);
+               return;
+       }
+
        if (_session->transport_rolling() && rec_enable_button->active_state() && Config->get_disable_disarm_during_roll()) {
                rec_enable_button->set_sensitive (false);
+       } else if (is_audio_track ()  && track()->freeze_state() == AudioTrack::Frozen) {
+               rec_enable_button->set_sensitive (false);
        } else {
                rec_enable_button->set_sensitive (true);
        }
-
+       if (_route && _route->record_safe ()) {
+               rec_enable_button->set_visual_state (Gtkmm2ext::VisualState (solo_button->visual_state() | Gtkmm2ext::Insensitive));
+       } else {
+               rec_enable_button->set_visual_state (Gtkmm2ext::VisualState (solo_button->visual_state() & ~Gtkmm2ext::Insensitive));
+       }
        update_monitoring_display ();
 }
 
@@ -1908,25 +1979,25 @@ RouteUI::parameter_changed (string const & p)
 void
 RouteUI::step_gain_up ()
 {
-       _route->set_gain (dB_to_coefficient (accurate_coefficient_to_dB (_route->gain_control()->get_value()) + 0.1), this);
+       _route->set_gain (dB_to_coefficient (accurate_coefficient_to_dB (_route->gain_control()->get_value()) + 0.1), Controllable::UseGroup);
 }
 
 void
 RouteUI::page_gain_up ()
 {
-       _route->set_gain (dB_to_coefficient (accurate_coefficient_to_dB (_route->gain_control()->get_value()) + 0.5), this);
+       _route->set_gain (dB_to_coefficient (accurate_coefficient_to_dB (_route->gain_control()->get_value()) + 0.5), Controllable::UseGroup);
 }
 
 void
 RouteUI::step_gain_down ()
 {
-       _route->set_gain (dB_to_coefficient (accurate_coefficient_to_dB (_route->gain_control()->get_value()) - 0.1), this);
+       _route->set_gain (dB_to_coefficient (accurate_coefficient_to_dB (_route->gain_control()->get_value()) - 0.1), Controllable::UseGroup);
 }
 
 void
 RouteUI::page_gain_down ()
 {
-       _route->set_gain (dB_to_coefficient (accurate_coefficient_to_dB (_route->gain_control()->get_value()) - 0.5), this);
+       _route->set_gain (dB_to_coefficient (accurate_coefficient_to_dB (_route->gain_control()->get_value()) - 0.5), Controllable::UseGroup);
 }
 
 void
@@ -1939,7 +2010,7 @@ RouteUI::open_remote_control_id_dialog ()
 
        if (Config->get_remote_model() == UserOrdered) {
                uint32_t const limit = _session->ntracks() + _session->nbusses () + 4;
-               
+
                HBox* hbox = manage (new HBox);
                hbox->set_spacing (6);
                hbox->pack_start (*manage (new Label (_("Remote control ID:"))));
@@ -1950,7 +2021,7 @@ RouteUI::open_remote_control_id_dialog ()
                spin->set_value (_route->remote_control_id());
                hbox->pack_start (*spin);
                dialog.get_vbox()->pack_start (*hbox);
-               
+
                dialog.add_button (Stock::CANCEL, RESPONSE_CANCEL);
                dialog.add_button (Stock::APPLY, RESPONSE_ACCEPT);
        } else {
@@ -1958,7 +2029,7 @@ RouteUI::open_remote_control_id_dialog ()
                if (_route->is_master() || _route->is_monitor()) {
                        l->set_markup (string_compose (_("The remote control ID of %1 is: %2\n\n\n"
                                                         "The remote control ID of %3 cannot be changed."),
-                                                      Glib::Markup::escape_text (_route->name()),
+                                                      Gtkmm2ext::markup_escape_text (_route->name()),
                                                       _route->remote_control_id(),
                                                       (_route->is_master() ? _("the master bus") : _("the monitor bus"))));
                } else {
@@ -1969,7 +2040,7 @@ RouteUI::open_remote_control_id_dialog ()
                                                       _route->remote_control_id(),
                                                       "<span size=\"small\" style=\"italic\">",
                                                       "</span>",
-                                                      Glib::Markup::escape_text (_route->name()),
+                                                      Gtkmm2ext::markup_escape_text (_route->name()),
                                                       PROGRAM_NAME));
                }
                dialog.get_vbox()->pack_start (*l);
@@ -2043,7 +2114,7 @@ RouteUI::set_invert_button_state ()
                */
 
                ArdourButton* b = _invert_buttons.front ();
-               
+
                if (_route->phase_invert().count() == _route->phase_invert().size()) {
                        b->set_active_state (Gtkmm2ext::ExplicitActive);
                } else if (_route->phase_invert().any()) {
@@ -2060,7 +2131,7 @@ RouteUI::set_invert_button_state ()
                for (vector<ArdourButton*>::iterator i = _invert_buttons.begin(); i != _invert_buttons.end(); ++i, ++j) {
                        (*i)->set_active (_route->phase_invert (j));
                }
-               
+
        }
 }
 
@@ -2092,7 +2163,7 @@ RouteUI::invert_press (GdkEventButton* ev)
                */
                return false;
        }
-       
+
        delete _invert_menu;
        _invert_menu = new Menu;
        _invert_menu->set_name ("ArdourContextMenu");
@@ -2171,7 +2242,7 @@ Gdk::Color
 RouteUI::color () const
 {
        RouteGroup* g = _route->route_group ();
-       
+
        if (g && g->is_color()) {
                Gdk::Color c;
                set_color_from_rgba (c, GroupTabs::group_color (g));
@@ -2205,3 +2276,91 @@ RouteUI::route_group() const
 {
        return _route->route_group();
 }
+
+
+RoutePinWindowProxy::RoutePinWindowProxy(std::string const &name, boost::shared_ptr<ARDOUR::Route> route)
+       : WM::ProxyBase (name, string())
+       , _route (boost::weak_ptr<Route> (route))
+{
+       route->DropReferences.connect (going_away_connection, MISSING_INVALIDATOR, boost::bind (&RoutePinWindowProxy::route_going_away, this), gui_context());
+}
+
+RoutePinWindowProxy::~RoutePinWindowProxy()
+{
+       _window = 0;
+}
+
+ARDOUR::SessionHandlePtr*
+RoutePinWindowProxy::session_handle ()
+{
+       ArdourWindow* aw = dynamic_cast<ArdourWindow*> (_window);
+       if (aw) { return aw; }
+       return 0;
+}
+
+Gtk::Window*
+RoutePinWindowProxy::get (bool create)
+{
+       boost::shared_ptr<Route> r = _route.lock ();
+       if (!r) {
+               return 0;
+       }
+
+       if (!_window) {
+               if (!create) {
+                       return 0;
+               }
+               _window = new PluginPinDialog (r);
+               ArdourWindow* aw = dynamic_cast<ArdourWindow*> (_window);
+               if (aw) {
+                       aw->set_session (_session);
+               }
+               _window->show_all ();
+       }
+       return _window;
+}
+
+void
+RoutePinWindowProxy::route_going_away ()
+{
+       delete _window;
+       _window = 0;
+       WM::Manager::instance().remove (this);
+       going_away_connection.disconnect();
+}
+
+void
+RouteUI::maybe_add_route_print_mgr ()
+{
+       if (_route->pinmgr_proxy ()) {
+               return;
+       }
+       RoutePinWindowProxy* wp = new RoutePinWindowProxy (
+                       string_compose ("RPM-%1", _route->id()), _route);
+       wp->set_session (_session);
+
+       const XMLNode* ui_xml = _session->extra_xml (X_("UI"));
+       if (ui_xml) {
+               wp->set_state (*ui_xml, 0);
+       }
+
+#if 0
+       void* existing_ui = _route->pinmgr_proxy ();
+       if (existing_ui) {
+               wp->use_window (*(reinterpret_cast<Gtk::Window*>(existing_ui)));
+       }
+#endif
+       _route->set_pingmgr_proxy (wp);
+
+       WM::Manager::instance().register_window (wp);
+}
+
+void
+RouteUI::manage_pins ()
+{
+       RoutePinWindowProxy* proxy = _route->pinmgr_proxy ();
+       if (proxy) {
+               proxy->get (true);
+               proxy->present();
+       }
+}