Add support for scripted meta-templates.
[ardour.git] / gtk2_ardour / ardour_ui.cc
index bbfeebcdfa9a65e979215ed743614b7a8872b40d..0748d418c269a17aea0ad7253ea5c957815e21cb 100644 (file)
 #include <glib.h>
 #include "pbd/gstdio_compat.h"
 
-#include <gtkmm/messagedialog.h>
 #include <gtkmm/accelmap.h>
+#include <gtkmm/messagedialog.h>
 #include <gtkmm/stock.h>
+#include <gtkmm/uimanager.h>
 
 #include "pbd/error.h"
 #include "pbd/basename.h"
@@ -61,6 +62,8 @@
 #include "pbd/memento_command.h"
 #include "pbd/openuri.h"
 #include "pbd/stl_delete.h"
+#include "pbd/types_convert.h"
+#include "pbd/unwind.h"
 #include "pbd/file_utils.h"
 #include "pbd/localtime_r.h"
 #include "pbd/pthread_utils.h"
 #include "gtkmm2ext/bindings.h"
 #include "gtkmm2ext/gtk_ui.h"
 #include "gtkmm2ext/utils.h"
-#include "gtkmm2ext/click_box.h"
-#include "gtkmm2ext/fastmeter.h"
-#include "gtkmm2ext/popup.h"
 #include "gtkmm2ext/window_title.h"
 
+#include "widgets/fastmeter.h"
+#include "widgets/prompter.h"
+
 #include "ardour/ardour.h"
 #include "ardour/audio_backend.h"
 #include "ardour/audio_track.h"
@@ -94,6 +97,7 @@
 #include "ardour/profile.h"
 #include "ardour/recent_sessions.h"
 #include "ardour/record_enable_control.h"
+#include "ardour/revision.h"
 #include "ardour/session_directory.h"
 #include "ardour/session_route.h"
 #include "ardour/session_state_utils.h"
@@ -125,6 +129,7 @@ typedef uint64_t microseconds_t;
 
 #include "about.h"
 #include "editing.h"
+#include "enums_convert.h"
 #include "actions.h"
 #include "add_route_dialog.h"
 #include "ambiguous_file_dialog.h"
@@ -159,7 +164,6 @@ typedef uint64_t microseconds_t;
 #include "opts.h"
 #include "pingback.h"
 #include "processor_box.h"
-#include "prompter.h"
 #include "public_editor.h"
 #include "rc_option_editor.h"
 #include "route_time_axis.h"
@@ -173,6 +177,7 @@ typedef uint64_t microseconds_t;
 #include "speaker_dialog.h"
 #include "splash.h"
 #include "startup.h"
+#include "template_dialog.h"
 #include "time_axis_view_item.h"
 #include "time_info_box.h"
 #include "timers.h"
@@ -188,6 +193,7 @@ using namespace ARDOUR;
 using namespace ARDOUR_UI_UTILS;
 using namespace PBD;
 using namespace Gtkmm2ext;
+using namespace ArdourWidgets;
 using namespace Gtk;
 using namespace std;
 using namespace Editing;
@@ -195,7 +201,7 @@ using namespace Editing;
 ARDOUR_UI *ARDOUR_UI::theArdourUI = 0;
 
 sigc::signal<void, framepos_t, bool, framepos_t> ARDOUR_UI::Clock;
-sigc::signal<void>      ARDOUR_UI::CloseAllDialogs;
+sigc::signal<void> ARDOUR_UI::CloseAllDialogs;
 
 static bool
 ask_about_configuration_copy (string const & old_dir, string const & new_dir, int version)
@@ -208,7 +214,7 @@ ask_about_configuration_copy (string const & old_dir, string const & new_dir, in
                           Gtk::MESSAGE_INFO,
                           Gtk::BUTTONS_YES_NO,
                           true /* modal, though it hardly matters since it is the only window */
-               );
+       );
 
        msg.set_default_response (Gtk::RESPONSE_YES);
        msg.show_all ();
@@ -218,8 +224,8 @@ ask_about_configuration_copy (string const & old_dir, string const & new_dir, in
 
 static void
 libxml_generic_error_func (void* /* parsing_context*/,
-                   const char* msg,
-                   ...)
+                           const char* msg,
+                           ...)
 {
        va_list ap;
        char buf[2048];
@@ -260,6 +266,7 @@ libxml_structured_error_func (void* /* parsing_context*/,
 ARDOUR_UI::ARDOUR_UI (int *argcp, char **argvp[], const char* localedir)
        : Gtkmm2ext::UI (PROGRAM_NAME, X_("gui"), argcp, argvp)
        , session_loaded (false)
+       , session_load_in_progress (false)
        , gui_object_state (new GUIObjectState)
        , primary_clock   (new MainClock (X_("primary"),   X_("transport"), true ))
        , secondary_clock (new MainClock (X_("secondary"), X_("secondary"), false))
@@ -291,8 +298,8 @@ ARDOUR_UI::ARDOUR_UI (int *argcp, char **argvp[], const char* localedir)
        , solo_alert_button (_("Solo"))
        , feedback_alert_button (_("Feedback"))
        , error_alert_button ( ArdourButton::just_led_default_elements )
-       , editor_meter(0)
        , editor_meter_peak_display()
+       , editor_meter(0)
        , _suspend_editor_meter_callbacks (false)
        , _numpad_locate_happening (false)
        , _session_is_new (false)
@@ -458,7 +465,7 @@ ARDOUR_UI::ARDOUR_UI (int *argcp, char **argvp[], const char* localedir)
 
        TimeAxisViewItem::set_constant_heights ();
 
-        /* Set this up so that our window proxies can register actions */
+       /* Set this up so that our window proxies can register actions */
 
        ActionManager::init ();
 
@@ -524,8 +531,8 @@ ARDOUR_UI::pre_release_dialog ()
        ArdourDialog d (_("Pre-Release Warning"), true, false);
        d.add_button (Gtk::Stock::OK, Gtk::RESPONSE_OK);
 
-        Label* label = manage (new Label);
-        label->set_markup (string_compose (_("<b>Welcome to this pre-release build of %1 %2</b>\n\n\
+       Label* label = manage (new Label);
+       label->set_markup (string_compose (_("<b>Welcome to this pre-release build of %1 %2</b>\n\n\
 There are still several issues and bugs to be worked on,\n\
 as well as general workflow improvements, before this can be considered\n\
 release software. So, a few guidelines:\n\
@@ -545,11 +552,11 @@ Full information on all the above can be found on the support page at\n\
                 http://ardour.org/support\n\
 "), PROGRAM_NAME, VERSIONSTRING));
 
-        d.get_vbox()->set_border_width (12);
-        d.get_vbox()->pack_start (*label, false, false, 12);
-        d.get_vbox()->show_all ();
+       d.get_vbox()->set_border_width (12);
+       d.get_vbox()->pack_start (*label, false, false, 12);
+       d.get_vbox()->show_all ();
 
-        d.run ();
+       d.run ();
 }
 
 GlobalPortMatrixWindow*
@@ -685,6 +692,55 @@ ARDOUR_UI::post_engine ()
         */
 
        if (ARDOUR_COMMAND_LINE::show_key_actions) {
+               stringstream sstr;
+               Bindings::save_all_bindings_as_html (sstr);
+
+               if (sstr.str().empty()) {
+                       return;
+               }
+               gchar* file_name;
+               GError *err = NULL;
+               gint fd;
+
+               if ((fd = g_file_open_tmp ("akprintXXXXXX.html", &file_name, &err)) < 0) {
+                       if (err) {
+                               error << string_compose (_("Could not open temporary file to print bindings (%1)"), err->message) << endmsg;
+                               g_error_free (err);
+                       }
+                       return;
+               }
+
+#ifdef PLATFORM_WINDOWS
+               ::close (fd);
+#endif
+
+               err = NULL;
+
+               if (!g_file_set_contents (file_name, sstr.str().c_str(), sstr.str().size(), &err)) {
+#ifndef PLATFORM_WINDOWS
+                       ::close (fd);
+#endif
+                       g_unlink (file_name);
+                       if (err) {
+                               error << string_compose (_("Could not save bindings to file (%1)"), err->message) << endmsg;
+                               g_error_free (err);
+                       }
+                       return;
+               }
+
+#ifndef PLATFORM_WINDOWS
+               ::close (fd);
+#endif
+
+               PBD::open_uri (string_compose ("file:///%1", file_name));
+
+               halt_connection.disconnect ();
+               AudioEngine::instance()->stop ();
+               exit (0);
+
+       }
+
+       if (ARDOUR_COMMAND_LINE::show_actions) {
 
 
                vector<string> paths;
@@ -692,21 +748,70 @@ ARDOUR_UI::post_engine ()
                vector<string> tooltips;
                vector<string> keys;
                vector<Glib::RefPtr<Gtk::Action> > actions;
+               string ver_in = revision;
+               string ver = ver_in.substr(0, ver_in.find("-"));
+
+               stringstream output;
+               output << "\n<h2>Menu actions</h2>" << endl;
+               output << "<p>\n  Every single menu item in " << PROGRAM_NAME << "'s GUI is accessible by control" << endl;
+               output << "  surfaces or scripts.\n</p>\n" << endl;
+               output << "<p>\n  The list below shows all available values of <em>action-name</em> as of" << endl;
+               output << "  " << PROGRAM_NAME << " " << ver << ". You can get the current list at any" << endl;
+               output << "  time by running " << PROGRAM_NAME << " with the -A flag.\n</p>\n" << endl;
+               output << "<table class=\"dl\">\n  <thead>" << endl;
+               output << "      <tr><th>Action Name</th><th>Menu Name</th></tr>" << endl;
+               output << "  </thead>\n  <tbody>" << endl;
 
                Gtkmm2ext::ActionMap::get_all_actions (paths, labels, tooltips, keys, actions);
 
-               vector<string>::iterator k;
                vector<string>::iterator p;
+               vector<string>::iterator l;
+
+               for (p = paths.begin(), l = labels.begin(); p != paths.end(); ++p, ++l) {
+                       output << "     <tr><th><kbd class=\"osc\">" << (*p).substr (9, string::npos);
+                       output << "</kbd></th><td>" << *l << "</td></tr>" << endl;
+               }
+               output << "  </tbody>\n  </table>" << endl;
+
+               // output this mess to a browser for easiest X-platform use
+               // it is not pretty HTML, but it works and it's main purpose
+               // is to create raw html to fit in Ardour's manual with no editing
+               gchar* file_name;
+               GError *err = NULL;
+               gint fd;
+
+               if ((fd = g_file_open_tmp ("akprintXXXXXX.html", &file_name, &err)) < 0) {
+                       if (err) {
+                               error << string_compose (_("Could not open temporary file to print bindings (%1)"), err->message) << endmsg;
+                               g_error_free (err);
+                       }
+                       return;
+               }
 
-               for (p = paths.begin(), k = keys.begin(); p != paths.end(); ++k, ++p) {
+#ifdef PLATFORM_WINDOWS
+               ::close (fd);
+#endif
 
-                       if ((*k).empty()) {
-                               cout << *p << endl;
-                       } else {
-                               cout << *p << " => " << *k << endl;
+               err = NULL;
+
+               if (!g_file_set_contents (file_name, output.str().c_str(), output.str().size(), &err)) {
+#ifndef PLATFORM_WINDOWS
+                       ::close (fd);
+#endif
+                       g_unlink (file_name);
+                       if (err) {
+                               error << string_compose (_("Could not save bindings to file (%1)"), err->message) << endmsg;
+                               g_error_free (err);
                        }
+                       return;
                }
 
+#ifndef PLATFORM_WINDOWS
+               ::close (fd);
+#endif
+
+               PBD::open_uri (string_compose ("file:///%1", file_name));
+
                halt_connection.disconnect ();
                AudioEngine::instance()->stop ();
                exit (0);
@@ -762,7 +867,7 @@ ARDOUR_UI::~ARDOUR_UI ()
                delete gui_object_state; gui_object_state = 0;
                delete main_window_visibility;
                FastMeter::flush_pattern_cache ();
-               PixFader::flush_pattern_cache ();
+               ArdourFader::flush_pattern_cache ();
        }
 
 #ifndef NDEBUG
@@ -819,31 +924,31 @@ ARDOUR_UI::configure_handler (GdkEventConfigure* /*conf*/)
 void
 ARDOUR_UI::set_transport_controllable_state (const XMLNode& node)
 {
-       XMLProperty const * prop;
+       std::string str;
 
-       if ((prop = node.property ("roll")) != 0) {
-               roll_controllable->set_id (prop->value());
+       if (node.get_property ("roll", str)){
+               roll_controllable->set_id (str);
        }
-       if ((prop = node.property ("stop")) != 0) {
-               stop_controllable->set_id (prop->value());
+       if (node.get_property ("stop", str)) {
+               stop_controllable->set_id (str);
        }
-       if ((prop = node.property ("goto-start")) != 0) {
-               goto_start_controllable->set_id (prop->value());
+       if (node.get_property ("goto-start", str)) {
+               goto_start_controllable->set_id (str);
        }
-       if ((prop = node.property ("goto-end")) != 0) {
-               goto_end_controllable->set_id (prop->value());
+       if (node.get_property ("goto-end", str)) {
+               goto_end_controllable->set_id (str);
        }
-       if ((prop = node.property ("auto-loop")) != 0) {
-               auto_loop_controllable->set_id (prop->value());
+       if (node.get_property ("auto-loop", str)) {
+               auto_loop_controllable->set_id (str);
        }
-       if ((prop = node.property ("play-selection")) != 0) {
-               play_selection_controllable->set_id (prop->value());
+       if (node.get_property ("play-selection", str)) {
+               play_selection_controllable->set_id (str);
        }
-       if ((prop = node.property ("rec")) != 0) {
-               rec_controllable->set_id (prop->value());
+       if (node.get_property ("rec", str)) {
+               rec_controllable->set_id (str);
        }
-       if ((prop = node.property ("shuttle")) != 0) {
-               shuttle_box.controllable()->set_id (prop->value());
+       if (node.get_property ("shuttle", str)) {
+               shuttle_box.controllable()->set_id (str);
        }
 }
 
@@ -852,14 +957,14 @@ ARDOUR_UI::get_transport_controllable_state ()
 {
        XMLNode* node = new XMLNode(X_("TransportControllables"));
 
-       node->add_property (X_("roll"), roll_controllable->id().to_s());
-       node->add_property (X_("stop"), stop_controllable->id().to_s());
-       node->add_property (X_("goto_start"), goto_start_controllable->id().to_s());
-       node->add_property (X_("goto_end"), goto_end_controllable->id().to_s());
-       node->add_property (X_("auto_loop"), auto_loop_controllable->id().to_s());
-       node->add_property (X_("play_selection"), play_selection_controllable->id().to_s());
-       node->add_property (X_("rec"), rec_controllable->id().to_s());
-       node->add_property (X_("shuttle"), shuttle_box.controllable()->id().to_s());
+       node->set_property (X_("roll"), roll_controllable->id());
+       node->set_property (X_("stop"), stop_controllable->id());
+       node->set_property (X_("goto-start"), goto_start_controllable->id());
+       node->set_property (X_("goto-end"), goto_end_controllable->id());
+       node->set_property (X_("auto-loop"), auto_loop_controllable->id());
+       node->set_property (X_("play-selection"), play_selection_controllable->id());
+       node->set_property (X_("rec"), rec_controllable->id());
+       node->set_property (X_("shuttle"), shuttle_box.controllable()->id());
 
        return *node;
 }
@@ -1181,11 +1286,6 @@ ARDOUR_UI::starting ()
 
        BootMessage (string_compose (_("%1 is ready for use"), PROGRAM_NAME));
 
-       if (splash && splash->is_visible()) {
-               // in 1 second, hide the splash screen
-               Glib::signal_timeout().connect (sigc::bind (sigc::ptr_fun (_hide_splash), this), 1000);
-       }
-
        /* all other dialogs are created conditionally */
 
        return 0;
@@ -1826,10 +1926,6 @@ ARDOUR_UI::open_recent_session ()
 
                can_return = false;
        }
-       if (splash && splash->is_visible()) {
-               // in 1 second, hide the splash screen
-               Glib::signal_timeout().connect (sigc::bind (sigc::ptr_fun (_hide_splash), this), 1000);
-       }
 }
 
 bool
@@ -1837,9 +1933,9 @@ ARDOUR_UI::check_audioengine (Gtk::Window& parent)
 {
        if (!AudioEngine::instance()->connected()) {
                MessageDialog msg (parent, string_compose (
-                                          _("%1 is not connected to any audio backend.\n"
-                                            "You cannot open or close sessions in this condition"),
-                                          PROGRAM_NAME));
+                                          _("%1 is not connected to any audio backend.\n"
+                                          "You cannot open or close sessions in this condition"),
+                                          PROGRAM_NAME));
                pop_back_splash (msg);
                msg.run ();
                return false;
@@ -2670,6 +2766,33 @@ ARDOUR_UI::save_session_as ()
                return;
        }
 
+       if (_session->dirty()) {
+               vector<string> actions;
+               actions.push_back (_("Abort save-as"));
+               actions.push_back (_("Don't save now, just save-as"));
+               actions.push_back (_("Save it first"));
+               switch (ask_about_saving_session(actions)) {
+                       case -1:
+                               return;
+                               break;
+                       case 1:
+                               if (save_state_canfail ("")) {
+                                       MessageDialog msg (_main_window,
+                                                       string_compose (_("\
+%1 was unable to save your session.\n\n\
+If you still wish to proceeed, please use the\n\n\
+\"Don't save now\" option."), PROGRAM_NAME));
+                                       pop_back_splash(msg);
+                                       msg.run ();
+                                       return;
+                               }
+                               // no break
+                       case 0:
+                               _session->remove_pending_capture_state ();
+                               break;
+               }
+       }
+
        if (!save_as_dialog) {
                save_as_dialog = new SaveAsDialog;
        }
@@ -2745,7 +2868,6 @@ ARDOUR_UI::save_session_as ()
        if (!sa.include_media && sa.switch_to) {
                unload_session (false);
                load_session (sa.final_session_folder_name, sa.new_name);
-               hide_splash ();
        }
 }
 
@@ -2785,13 +2907,16 @@ ARDOUR_UI::quick_snapshot_session (bool switch_to_it)
                time (&n);
                localtime_r (&n, &local_time);
                strftime (timebuf, sizeof(timebuf), "%FT%H.%M.%S", &local_time);
+               if (switch_to_it && _session->dirty ()) {
+                       save_state_canfail ("");
+               }
 
                save_state (timebuf, switch_to_it);
 }
 
 
 bool
-ARDOUR_UI::process_snapshot_session_prompter (ArdourPrompter& prompter, bool switch_to_it)
+ARDOUR_UI::process_snapshot_session_prompter (Prompter& prompter, bool switch_to_it)
 {
        string snapname;
 
@@ -2803,7 +2928,7 @@ ARDOUR_UI::process_snapshot_session_prompter (ArdourPrompter& prompter, bool swi
                char illegal = Session::session_name_is_legal(snapname);
                if (illegal) {
                        MessageDialog msg (string_compose (_("To ensure compatibility with various systems\n"
-                                            "snapshot names may not contain a '%1' character"), illegal));
+                                                            "snapshot names may not contain a '%1' character"), illegal));
                        msg.run ();
                        return false;
                }
@@ -2837,8 +2962,34 @@ ARDOUR_UI::process_snapshot_session_prompter (ArdourPrompter& prompter, bool swi
 void
 ARDOUR_UI::snapshot_session (bool switch_to_it)
 {
-       ArdourPrompter prompter (true);
+       if (switch_to_it && _session->dirty()) {
+               vector<string> actions;
+               actions.push_back (_("Abort saving snapshot"));
+               actions.push_back (_("Don't save now, just snapshot"));
+               actions.push_back (_("Save it first"));
+               switch (ask_about_saving_session(actions)) {
+                       case -1:
+                               return;
+                               break;
+                       case 1:
+                               if (save_state_canfail ("")) {
+                                       MessageDialog msg (_main_window,
+                                                       string_compose (_("\
+%1 was unable to save your session.\n\n\
+If you still wish to proceeed, please use the\n\n\
+\"Don't save now\" option."), PROGRAM_NAME));
+                                       pop_back_splash(msg);
+                                       msg.run ();
+                                       return;
+                               }
+                               // no break
+                       case 0:
+                               _session->remove_pending_capture_state ();
+                               break;
+               }
+       }
 
+       Prompter prompter (true);
        prompter.set_name ("Prompter");
        prompter.add_button (Gtk::Stock::SAVE, Gtk::RESPONSE_ACCEPT);
        if (switch_to_it) {
@@ -2882,7 +3033,7 @@ ARDOUR_UI::rename_session ()
                return;
        }
 
-       ArdourPrompter prompter (true);
+       Prompter prompter (true);
        string name;
 
        prompter.set_name ("Prompter");
@@ -2903,7 +3054,7 @@ ARDOUR_UI::rename_session ()
 
                        if (illegal) {
                                MessageDialog msg (string_compose (_("To ensure compatibility with various systems\n"
-                                                                    "session names may not contain a '%1' character"), illegal));
+                                                                    "session names may not contain a '%1' character"), illegal));
                                msg.run ();
                                goto again;
                        }
@@ -3024,7 +3175,7 @@ ARDOUR_UI::transport_rec_enable_blink (bool onoff)
 }
 
 bool
-ARDOUR_UI::process_save_template_prompter (ArdourPrompter& prompter)
+ARDOUR_UI::process_save_template_prompter (Prompter& prompter)
 {
        string name;
 
@@ -3053,7 +3204,7 @@ ARDOUR_UI::process_save_template_prompter (ArdourPrompter& prompter)
 void
 ARDOUR_UI::save_template ()
 {
-       ArdourPrompter prompter (true);
+       Prompter prompter (true);
 
        if (!check_audioengine (_main_window)) {
                return;
@@ -3079,6 +3230,12 @@ ARDOUR_UI::save_template ()
        }
 }
 
+void ARDOUR_UI::manage_templates ()
+{
+       TemplateDialog td;
+       td.run();
+}
+
 void
 ARDOUR_UI::edit_metadata ()
 {
@@ -3253,7 +3410,7 @@ ARDOUR_UI::get_session_parameters (bool quit_on_cancel, bool should_be_new, stri
        /* if there is already a session, relabel the button
           on the SessionDialog so that we don't Quit directly
        */
-       cancel_not_quit = (_session != 0);
+       cancel_not_quit = (_session != 0) && !quit_on_cancel;
 
        if (_session && _session->dirty()) {
                if (unload_session (false)) {
@@ -3326,6 +3483,7 @@ ARDOUR_UI::get_session_parameters (bool quit_on_cancel, bool should_be_new, stri
                                        // user quits from the main 'Session Setup' dialog (i.e. reaching this
                                        // point does NOT indicate an abnormal termination). Therefore, let's
                                        // behave gracefully (i.e. let's do some cleanup) before we call exit()
+                                       assert (!_session);
                                        ARDOUR::cleanup ();
                                        pthread_cancel_all ();
 
@@ -3346,7 +3504,7 @@ ARDOUR_UI::get_session_parameters (bool quit_on_cancel, bool should_be_new, stri
                session_path = session_dialog.session_folder ();
 
                if (nsm) {
-                       likely_new = true;
+                       likely_new = true;
                }
 
                if (!likely_new) {
@@ -3383,12 +3541,12 @@ ARDOUR_UI::get_session_parameters (bool quit_on_cancel, bool should_be_new, stri
 
                if (session_name[0] == G_DIR_SEPARATOR ||
 #ifdef PLATFORM_WINDOWS
-                   (session_name.length() > 3 && session_name[1] == ':' && session_name[2] == G_DIR_SEPARATOR)
+                  (session_name.length() > 3 && session_name[1] == ':' && session_name[2] == G_DIR_SEPARATOR)
 #else
-                   (session_name.length() > 2 && session_name[0] == '.' && session_name[1] == G_DIR_SEPARATOR) ||
-                   (session_name.length() > 3 && session_name[0] == '.' && session_name[1] == '.' && session_name[2] == G_DIR_SEPARATOR)
+                  (session_name.length() > 2 && session_name[0] == '.' && session_name[1] == G_DIR_SEPARATOR) ||
+                  (session_name.length() > 3 && session_name[0] == '.' && session_name[1] == '.' && session_name[2] == G_DIR_SEPARATOR)
 #endif
-                        )
+               )
                {
 
                        /* absolute path or cwd-relative path specified for session name: infer session folder
@@ -3406,9 +3564,9 @@ ARDOUR_UI::get_session_parameters (bool quit_on_cancel, bool should_be_new, stri
 
                        if (illegal) {
                                MessageDialog msg (session_dialog,
-                                                  string_compose (_("To ensure compatibility with various systems\n"
-                                                                    "session names may not contain a '%1' character"),
-                                                                  illegal));
+                                                  string_compose (_("To ensure compatibility with various systems\n"
+                                                                    "session names may not contain a '%1' character"),
+                                                                  illegal));
                                msg.run ();
                                ARDOUR_COMMAND_LINE::session_name = ""; // cancel that
                                continue;
@@ -3445,7 +3603,7 @@ ARDOUR_UI::get_session_parameters (bool quit_on_cancel, bool should_be_new, stri
                        if (illegal) {
                                pop_back_splash (session_dialog);
                                MessageDialog msg (session_dialog, string_compose(_("To ensure compatibility with various systems\n"
-                                                                                   "session names may not contain a '%1' character"), illegal));
+                                                                                   "session names may not contain a '%1' character"), illegal));
                                msg.run ();
                                ARDOUR_COMMAND_LINE::session_name = ""; // cancel that
                                continue;
@@ -3454,7 +3612,12 @@ ARDOUR_UI::get_session_parameters (bool quit_on_cancel, bool should_be_new, stri
                        _session_is_new = true;
                }
 
-               if (likely_new && template_name.empty()) {
+               if (!template_name.empty() && template_name.substr (0, 11) == "urn:ardour:") {
+
+                       ret = build_session_from_dialog (session_dialog, session_path, session_name);
+                       meta_session_setup (template_name.substr (11));
+
+               } else if (likely_new && template_name.empty()) {
 
                        ret = build_session_from_dialog (session_dialog, session_path, session_name);
 
@@ -3501,10 +3664,6 @@ ARDOUR_UI::close_session()
        if (get_session_parameters (true, false)) {
                exit (1);
        }
-       if (splash && splash->is_visible()) {
-               // in 1 second, hide the splash screen
-               Glib::signal_timeout().connect (sigc::bind (sigc::ptr_fun (_hide_splash), this), 1000);
-       }
 }
 
 /** @param snap_name Snapshot name (without .ardour suffix).
@@ -3513,6 +3672,18 @@ ARDOUR_UI::close_session()
 int
 ARDOUR_UI::load_session (const std::string& path, const std::string& snap_name, std::string mix_template)
 {
+       /* load_session calls flush_pending() which allows
+        * GUI interaction and potentially loading another session
+        * (that was easy via snapshot sidebar).
+        * Recursing into load_session() from load_session() and recusive
+        * event loops causes all kind of crashes.
+        */
+       assert (!session_load_in_progress);
+       if (session_load_in_progress) {
+               return -1;
+       }
+       PBD::Unwinder<bool> lsu (session_load_in_progress, true);
+
        Session *new_session;
        int unload_status;
        int retval = -1;
@@ -3586,8 +3757,8 @@ ARDOUR_UI::load_session (const std::string& path, const std::string& snap_name,
        catch (...) {
 
                MessageDialog msg (string_compose(
-                                          _("Session \"%1 (snapshot %2)\" did not load successfully"),
-                                          path, snap_name),
+                                          _("Session \"%1 (snapshot %2)\" did not load successfully"),
+                                          path, snap_name),
                                   true,
                                   Gtk::MESSAGE_INFO,
                                   BUTTONS_OK);
@@ -3659,7 +3830,22 @@ ARDOUR_UI::load_session (const std::string& path, const std::string& snap_name,
 #endif
        retval = 0;
 
+       if (!mix_template.empty ()) {
+               /* if mix_template is given, assume this is a new session */
+               string metascript = Glib::build_filename (mix_template, "template.lua");
+               meta_session_setup (metascript);
+       }
+
+
   out:
+       /* For successful session load the splash is hidden by ARDOUR_UI::first_idle,
+        * which is queued by set_session().
+        * If session-loading fails we hide it explicitly.
+        * This covers both cases in a central place.
+        */
+       if (retval) {
+               hide_splash ();
+       }
        return retval;
 }
 
@@ -3728,8 +3914,8 @@ ARDOUR_UI::build_session (const std::string& path, const std::string& snap_name,
        /* Put the playhead at 0 and scroll fully left */
        n = new_session->instant_xml (X_("Editor"));
        if (n) {
-               n->add_property (X_("playhead"), X_("0"));
-               n->add_property (X_("left-frame"), X_("0"));
+               n->set_property (X_("playhead"), X_("0"));
+               n->set_property (X_("left-frame"), X_("0"));
        }
 
        set_session (new_session);
@@ -3741,6 +3927,34 @@ ARDOUR_UI::build_session (const std::string& path, const std::string& snap_name,
        return 0;
 }
 
+void
+ARDOUR_UI::meta_session_setup (const std::string& script_path)
+{
+       if (!Glib::file_test (script_path, Glib::FILE_TEST_EXISTS | Glib::FILE_TEST_IS_REGULAR)) {
+               return;
+       }
+
+       LuaState lua;
+       lua.Print.connect (&LuaInstance::_lua_print);
+       lua.sandbox (true);
+
+       lua_State* L = lua.getState();
+       LuaInstance::register_classes (L);
+       LuaBindings::set_session (L, _session);
+       luabridge::push <PublicEditor *> (L, &PublicEditor::instance());
+       lua_setglobal (L, "Editor");
+
+       lua.do_command ("function ardour () end");
+       lua.do_file (script_path);
+
+       try {
+               luabridge::LuaRef fn = luabridge::getGlobal (L, "session_setup");
+               if (fn.isFunction()) {
+                       fn ();
+               }
+       } catch (luabridge::LuaException const& e) { }
+}
+
 void
 ARDOUR_UI::launch_chat ()
 {
@@ -3853,8 +4067,8 @@ ARDOUR_UI::show_splash ()
 void
 ARDOUR_UI::hide_splash ()
 {
-        delete splash;
-        splash = 0;
+       delete splash;
+       splash = 0;
 }
 
 void
@@ -3866,12 +4080,12 @@ ARDOUR_UI::display_cleanup_results (ARDOUR::CleanupReport& rep, const gchar* lis
 
        if (removed == 0) {
                MessageDialog msgd (_main_window,
-                                   _("No files were ready for clean-up"),
-                                   true,
-                                   Gtk::MESSAGE_INFO,
-                                   Gtk::BUTTONS_OK);
+                                   _("No files were ready for clean-up"),
+                                   true,
+                                   Gtk::MESSAGE_INFO,
+                                   Gtk::BUTTONS_OK);
                msgd.set_title (_("Clean-up"));
-               msgd.set_secondary_text (_("If this seems suprising, \n\
+               msgd.set_secondary_text (_("If this seems surprising, \n\
 check for any existing snapshots.\n\
 These may still include regions that\n\
 require some unused files to continue to exist."));
@@ -3883,12 +4097,12 @@ require some unused files to continue to exist."));
        ArdourDialog results (_("Clean-up"), true, false);
 
        struct CleanupResultsModelColumns : public Gtk::TreeModel::ColumnRecord {
-           CleanupResultsModelColumns() {
-                   add (visible_name);
-                   add (fullpath);
-           }
-           Gtk::TreeModelColumn<std::string> visible_name;
-           Gtk::TreeModelColumn<std::string> fullpath;
+               CleanupResultsModelColumns() {
+                       add (visible_name);
+                       add (fullpath);
+               }
+               Gtk::TreeModelColumn<std::string> visible_name;
+               Gtk::TreeModelColumn<std::string> fullpath;
        };
 
 
@@ -4118,7 +4332,7 @@ PresentationInfo::order_t
 ARDOUR_UI::translate_order (RouteDialogs::InsertAt place)
 {
        if (editor->get_selection().tracks.empty()) {
-               return PresentationInfo::max_order;
+               return place == RouteDialogs::First ? 0 : PresentationInfo::max_order;
        }
 
        PresentationInfo::order_t order_hint = PresentationInfo::max_order;
@@ -4166,7 +4380,7 @@ void
 ARDOUR_UI::add_route ()
 {
        if (!add_route_dialog.get (false)) {
-               add_route_dialog->signal_response().connect (sigc::mem_fun (*this, &ARDOUR_UI::add_route_dialog_finished));
+               add_route_dialog->signal_response().connect (sigc::mem_fun (*this, &ARDOUR_UI::add_route_dialog_response));
        }
 
        if (!_session) {
@@ -4183,18 +4397,19 @@ ARDOUR_UI::add_route ()
 }
 
 void
-ARDOUR_UI::add_route_dialog_finished (int r)
+ARDOUR_UI::add_route_dialog_response (int r)
 {
        int count;
 
-       add_route_dialog->hide();
-
        switch (r) {
-               case RESPONSE_ACCEPT:
-                       break;
-               default:
-                       return;
-                       break;
+       case AddRouteDialog::Add:
+               break;
+       case AddRouteDialog::AddAndClose:
+               add_route_dialog->ArdourDialog::on_response (r);
+               break;
+       default:
+               add_route_dialog->ArdourDialog::on_response (r);
+               return;
        }
 
        if ((count = add_route_dialog->count()) <= 0) {
@@ -4374,9 +4589,8 @@ ARDOUR_UI::start_video_server (Gtk::Window* float_window, bool popup_msg)
                if (icsd_docroot == X_("/") || icsd_docroot == X_("C:\\")) {
                        Config->set_video_advanced_setup(false);
                } else {
-                       std::ostringstream osstream;
-                       osstream << "http://127.0.0.1:" << video_server_dialog->get_listenport() << "/";
-                       Config->set_video_server_url(osstream.str());
+                       std::string url_str = "http://127.0.0.1:" + to_string(video_server_dialog->get_listenport()) + "/";
+                       Config->set_video_server_url(url_str);
                        Config->set_video_server_docroot(icsd_docroot);
                        Config->set_video_advanced_setup(true);
                }
@@ -4508,11 +4722,11 @@ ARDOUR_UI::add_video (Gtk::Window* float_window)
 
        if (video_timeline->video_file_info(path, local_file)) {
                XMLNode* node = new XMLNode(X_("Videotimeline"));
-               node->add_property (X_("Filename"), path);
-               node->add_property (X_("AutoFPS"), auto_set_session_fps?X_("1"):X_("0"));
-               node->add_property (X_("LocalFile"), local_file?X_("1"):X_("0"));
+               node->set_property (X_("Filename"), path);
+               node->set_property (X_("AutoFPS"), auto_set_session_fps);
+               node->set_property (X_("LocalFile"), local_file);
                if (orig_local_file) {
-                       node->add_property (X_("OriginalVideoFile"), orig_path);
+                       node->set_property (X_("OriginalVideoFile"), orig_path);
                } else {
                        node->remove_property (X_("OriginalVideoFile"));
                }
@@ -4732,8 +4946,8 @@ ARDOUR_UI::create_xrun_marker (framepos_t where)
 void
 ARDOUR_UI::halt_on_xrun_message ()
 {
-        cerr << "HALT on xrun\n";
-        MessageDialog msg (_main_window, _("Recording was stopped because your system could not keep up."));
+       cerr << "HALT on xrun\n";
+       MessageDialog msg (_main_window, _("Recording was stopped because your system could not keep up."));
        msg.run ();
 }
 
@@ -4988,10 +5202,10 @@ audio may be played at the wrong sample rate.\n"), desired, PROGRAM_NAME, actual
        case RESPONSE_ACCEPT:
                return 0;
        default:
-                break;
+               break;
        }
 
-        return 1;
+       return 1;
 }
 
 void
@@ -5087,6 +5301,15 @@ ARDOUR_UI::first_idle ()
                editor->first_idle();
        }
 
+       /* in 1 second, hide the splash screen
+        *
+        * Consider hiding it *now*. If a user opens opens a dialog
+        * during that one second while the splash is still visible,
+        * the dialog will push-back the splash.
+        * Closing the dialog later will pop it back.
+        */
+       Glib::signal_timeout().connect (sigc::bind (sigc::ptr_fun (_hide_splash), this), 1000);
+
        Keyboard::set_can_save_keybindings (true);
        return false;
 }
@@ -5099,9 +5322,9 @@ ARDOUR_UI::store_clock_modes ()
        for (vector<AudioClock*>::iterator x = AudioClock::clocks.begin(); x != AudioClock::clocks.end(); ++x) {
                XMLNode* child = new XMLNode (X_("Clock"));
 
-               child->add_property (X_("name"), (*x)->name());
-               child->add_property (X_("mode"), enum_2_string ((*x)->mode()));
-               child->add_property (X_("on"), ((*x)->off() ? X_("no") : X_("yes")));
+               child->set_property (X_("name"), (*x)->name());
+               child->set_property (X_("mode"), (*x)->mode());
+               child->set_property (X_("on"), (*x)->on());
 
                node->add_child_nocopy (*child);
        }
@@ -5285,11 +5508,11 @@ ARDOUR_UI::session_format_mismatch (std::string xml_path, std::string backup_pat
        const char* end_mono = "</tt>";
 
        MessageDialog msg (string_compose (_("%4This is a session from an older version of %3%5\n\n"
-                                            "%3 has copied the old session file\n\n%6%1%7\n\nto\n\n%6%2%7\n\n"
-                                            "From now on, use the backup copy with older versions of %3"),
-                                          xml_path, backup_path, PROGRAM_NAME,
-                                          start_big, end_big,
-                                          start_mono, end_mono), true);
+                                            "%3 has copied the old session file\n\n%6%1%7\n\nto\n\n%6%2%7\n\n"
+                                            "From now on, use the backup copy with older versions of %3"),
+                                          xml_path, backup_path, PROGRAM_NAME,
+                                          start_big, end_big,
+                                          start_mono, end_mono), true);
 
        msg.run ();
 }
@@ -5385,10 +5608,16 @@ ARDOUR_UI::do_audio_midi_setup (uint32_t desired_sample_rate)
 
        while (true) {
                int response = audio_midi_setup->run();
-               printf("RESPONSE %d\n", response);
                switch (response) {
                case Gtk::RESPONSE_DELETE_EVENT:
-                       return -1;
+                       // after latency callibration engine may run,
+                       // Running() signal was emitted, but dialog will not
+                       // have emitted a response. The user needs to close
+                       // the dialog -> Gtk::RESPONSE_DELETE_EVENT
+                       if (!AudioEngine::instance()->running()) {
+                               return -1;
+                       }
+                       // fall through
                default:
                        if (!AudioEngine::instance()->running()) {
                                continue;
@@ -5511,7 +5740,7 @@ ARDOUR_UI::audioengine_became_silent ()
 void
 ARDOUR_UI::hide_application ()
 {
-    Application::instance ()-> hide ();
+       Application::instance ()-> hide ();
 }
 
 void
@@ -5637,7 +5866,7 @@ ARDOUR_UI::key_press_focus_accelerator_handler (Gtk::Window& window, GdkEventKey
        bool special_handling_of_unmodified_accelerators = false;
        const guint mask = (Keyboard::RelevantModifierKeyMask & ~(Gdk::SHIFT_MASK|Gdk::LOCK_MASK));
 
-        if (focus) {
+       if (focus) {
 
                /* some widget has keyboard focus */
 
@@ -5660,13 +5889,13 @@ ARDOUR_UI::key_press_focus_accelerator_handler (Gtk::Window& window, GdkEventKey
                }
        }
 
-        DEBUG_TRACE (DEBUG::Accelerators, string_compose ("Win = %1 [title = %9] focus = %7 (%8) Key event: code = %2  state = %3 special handling ? %4 magic widget focus ? %5 focus widget %6 named %7 mods ? %8\n",
-                                                          win,
-                                                          ev->keyval,
-                                                         Gtkmm2ext::show_gdk_event_state (ev->state),
+       DEBUG_TRACE (DEBUG::Accelerators, string_compose ("Win = %1 [title = %9] focus = %7 (%8) Key event: code = %2  state = %3 special handling ? %4 magic widget focus ? %5 focus widget %6 named %7 mods ? %8\n",
+                                                         win,
+                                                         ev->keyval,
+                                                         Gtkmm2ext::show_gdk_event_state (ev->state),
                                                           special_handling_of_unmodified_accelerators,
                                                           Keyboard::some_magic_widget_has_focus(),
-                                                         focus,
+                                                         focus,
                                                           (focus ? gtk_widget_get_name (focus) : "no focus widget"),
                                                           ((ev->state & mask) ? "yes" : "no"),
                                                           window.get_title()));
@@ -5707,7 +5936,7 @@ ARDOUR_UI::key_press_focus_accelerator_handler (Gtk::Window& window, GdkEventKey
 
                /* no special handling or there are modifiers in effect: accelerate first */
 
-                DEBUG_TRACE (DEBUG::Accelerators, "\tactivate, then propagate\n");
+               DEBUG_TRACE (DEBUG::Accelerators, "\tactivate, then propagate\n");
                DEBUG_TRACE (DEBUG::Accelerators, string_compose ("\tevent send-event:%1 time:%2 length:%3 name %7 string:%4 hardware_keycode:%5 group:%6\n",
                                                                  ev->send_event, ev->time, ev->length, ev->string, ev->hardware_keycode, ev->group, gdk_keyval_name (ev->keyval)));
 
@@ -5742,12 +5971,12 @@ ARDOUR_UI::key_press_focus_accelerator_handler (Gtk::Window& window, GdkEventKey
                        return true;
                }
 
-                DEBUG_TRACE (DEBUG::Accelerators, "\tnot accelerated, now propagate\n");
+               DEBUG_TRACE (DEBUG::Accelerators, "\tnot accelerated, now propagate\n");
 
-                if (gtk_window_propagate_key_event (win, ev)) {
-                       DEBUG_TRACE (DEBUG::Accelerators, "\tpropagate handled\n");
-                       return true;
-                }
+               if (gtk_window_propagate_key_event (win, ev)) {
+                       DEBUG_TRACE (DEBUG::Accelerators, "\tpropagate handled\n");
+                       return true;
+               }
 
        } else {