add initial version of StartupFSM along with its owners/users
authorPaul Davis <paul@linuxaudiosystems.com>
Thu, 10 Oct 2019 02:50:34 +0000 (20:50 -0600)
committerPaul Davis <paul@linuxaudiosystems.com>
Thu, 10 Oct 2019 22:52:00 +0000 (16:52 -0600)
gtk2_ardour/ardour_ui.cc
gtk2_ardour/ardour_ui.h
gtk2_ardour/ardour_ui_startup.cc
gtk2_ardour/startup_fsm.cc [new file with mode: 0644]
gtk2_ardour/startup_fsm.h [new file with mode: 0644]
gtk2_ardour/wscript

index eb6529f4531e3c7d4a2028fdc123cff4e2e6621f..c7ebbe847e8b0dc140db2276ef1dd512aec3aafd 100644 (file)
@@ -301,6 +301,7 @@ ARDOUR_UI::ARDOUR_UI (int *argcp, char **argvp[], const char* localedir)
        , _mixer_on_top (false)
        , _initial_verbose_plugin_scan (false)
        , _shared_popup_menu (0)
+       , startup_fsm (0)
        , secondary_clock_spacer (0)
        , auto_input_button (ArdourButton::led_default_elements)
        , latency_disable_button (ArdourButton::led_default_elements)
index 890572c2c7f664b53b5113ef07b571b4d4b2a5e8..8d35fa737d7d89ce55f76a388b5d644e78836fd4 100644 (file)
@@ -91,6 +91,7 @@
 #include "enums.h"
 #include "mini_timeline.h"
 #include "shuttle_control.h"
+#include "startup_fsm.h"
 #include "transport_control.h"
 #include "transport_control_ui.h"
 #include "visibility_group.h"
@@ -223,6 +224,7 @@ public:
        int get_session_parameters (bool quit_on_cancel, bool should_be_new = false, std::string load_template = "");
        int  build_session_from_dialog (SessionDialog&, const std::string& session_name, const std::string& session_path);
        bool ask_about_loading_existing_session (const std::string& session_path);
+       void load_session_from_startup_fsm ();
 
        /// @return true if session was successfully unloaded.
        int unload_session (bool hide_stuff = false);
@@ -438,8 +440,12 @@ private:
        static ARDOUR_UI *theArdourUI;
        SessionDialog *_session_dialog;
 
+       StartupFSM* startup_fsm;
+
        int starting ();
        int nsm_init ();
+       void startup_done ();
+       void sfsm_response (StartupFSM::Result);
 
        int  ask_about_saving_session (const std::vector<std::string>& actions);
 
index 732252c242d151235c703e4686d7c15c1f691c20..e89e5a2f11df141890911c1796c6eaed5623db24 100644 (file)
@@ -57,7 +57,6 @@
 #include "ardour/filename_extensions.h"
 #include "ardour/filesystem_paths.h"
 #include "ardour/profile.h"
-#include "ardour/recent_sessions.h"
 
 #include "gtkmm2ext/application.h"
 
@@ -480,27 +479,37 @@ ARDOUR_UI::nsm_init ()
        return 0;
 }
 
+void
+ARDOUR_UI::sfsm_response (StartupFSM::Result r)
+{
+       switch (r) {
+       case StartupFSM::ExitProgram:
+               queue_finish ();
+               break;
+       case StartupFSM::LoadSession:
+               _initial_verbose_plugin_scan = false;
+               load_session_from_startup_fsm ();
+               break;
+       case StartupFSM::DoNothing:
+               break;
+       }
+}
+
 int
 ARDOUR_UI::starting ()
 {
-       Application* app = Application::instance ();
-       bool brand_new_user = ArdourStartup::required ();
-
-       app->ShouldQuit.connect (sigc::mem_fun (*this, &ARDOUR_UI::queue_finish));
-       app->ShouldLoad.connect (sigc::mem_fun (*this, &ARDOUR_UI::load_from_application_api));
-
        if (ARDOUR_COMMAND_LINE::check_announcements) {
                check_announcements ();
        }
 
-       app->ready ();
-
        /* we need to create this early because it may need to set the
         *  audio backend end up.
         */
 
+       EngineControl* amd;
+
        try {
-               audio_midi_setup.get (true);
+               amd = dynamic_cast<EngineControl*> (audio_midi_setup.get (true));
        } catch (...) {
                std::cerr << "audio-midi engine setup failed."<< std::endl;
                return -1;
@@ -510,68 +519,53 @@ ARDOUR_UI::starting ()
                return -1;
        } else  {
 
-               if (brand_new_user) {
+
+               startup_fsm = new StartupFSM (*amd);
+               startup_fsm->start ();
+               startup_fsm->signal_response().connect (sigc::mem_fun (*this, &ARDOUR_UI::sfsm_response));
+
+               if (startup_fsm->brand_new_user()) {
                        _initial_verbose_plugin_scan = true;
-                       ArdourStartup s;
-                       s.present ();
-                       main().run();
-                       s.hide ();
-                       _initial_verbose_plugin_scan = false;
-                       switch (s.response ()) {
-                       case Gtk::RESPONSE_OK:
-                               break;
-                       default:
-                               return -1;
-                       }
                }
+       }
 
-               // TODO: maybe IFF brand_new_user
-               if (ARDOUR::Profile->get_mixbus () && Config->get_copy_demo_sessions ()) {
-                       std::string dspd (Config->get_default_session_parent_dir());
-                       Searchpath ds (ARDOUR::ardour_data_search_path());
-                       ds.add_subdirectory_to_paths ("sessions");
-                       vector<string> demos;
-                       find_files_matching_pattern (demos, ds, ARDOUR::session_archive_suffix);
-
-                       ARDOUR::RecentSessions rs;
-                       ARDOUR::read_recent_sessions (rs);
-
-                       for (vector<string>::iterator i = demos.begin(); i != demos.end (); ++i) {
-                               /* "demo-session" must be inside "demo-session.<session_archive_suffix>" */
-                               std::string name = basename_nosuffix (basename_nosuffix (*i));
-                               std::string path = Glib::build_filename (dspd, name);
-                               /* skip if session-dir already exists */
-                               if (Glib::file_test(path.c_str(), Glib::FILE_TEST_IS_DIR)) {
-                                       continue;
-                               }
-                               /* skip sessions that are already in 'recent'.
-                                * eg. a new user changed <session-default-dir> shorly after installation
-                                */
-                               for (ARDOUR::RecentSessions::iterator r = rs.begin(); r != rs.end(); ++r) {
-                                       if ((*r).first == name) {
-                                               continue;
-                                       }
-                               }
-                               try {
-                                       PBD::FileArchive ar (*i);
-                                       if (0 == ar.inflate (dspd)) {
-                                               store_recent_sessions (name, path);
-                                               info << string_compose (_("Copied Demo Session %1."), name) << endmsg;
-                                       }
-                               } catch (...) {}
-                       }
+       return 0;
+}
+
+void
+ARDOUR_UI::load_session_from_startup_fsm ()
+{
+       string session_path = startup_fsm->session_path;
+       string session_name = startup_fsm->session_name;
+       string session_template = startup_fsm->session_template;
+       bool   session_is_new = startup_fsm->session_is_new;
+       BusProfile bus_profile = startup_fsm->bus_profile;
+
+       std::cerr  << " loading from " << session_path << " as " << session_name << " templ " << session_template << " is_new " << session_is_new << " bp " << bus_profile.master_out_channels << std::endl;
+
+       if (session_is_new) {
+
+               if (build_session (session_path, session_name, &bus_profile)) {
                }
 
-               /* go get a session */
+               if (!session_template.empty() && session_template.substr (0, 11) == "urn:ardour:") {
+                       meta_session_setup (session_template.substr (11));
+               }
+
+       } else {
 
-               const bool new_session_required = (ARDOUR_COMMAND_LINE::new_session || (!ARDOUR::Profile->get_mixbus() && brand_new_user));
+               int ret = load_session (session_path, session_name, session_template);
 
-               if (get_session_parameters (false, new_session_required, ARDOUR_COMMAND_LINE::load_template)) {
-                       std::cerr << "Cannot get session parameters."<< std::endl;
-                       return -1;
+               if (ret == -2) {
+                       /* not connected to the AudioEngine, so quit to avoid an infinite loop */
+                       exit (EXIT_FAILURE);
                }
        }
+}
 
+void
+ARDOUR_UI::startup_done ()
+{
        use_config ();
 
        WM::Manager::instance().show_visible ();
@@ -582,10 +576,6 @@ ARDOUR_UI::starting ()
        _status_bar_visibility.update ();
 
        BootMessage (string_compose (_("%1 is ready for use"), PROGRAM_NAME));
-
-       /* all other dialogs are created conditionally */
-
-       return 0;
 }
 
 void
diff --git a/gtk2_ardour/startup_fsm.cc b/gtk2_ardour/startup_fsm.cc
new file mode 100644 (file)
index 0000000..53610b6
--- /dev/null
@@ -0,0 +1,662 @@
+/*
+ * Copyright (C) 2019 Paul Davis <paul@linuxaudiosystems.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <vector>
+
+#include <gtkmm/dialog.h>
+#include <gtkmm/liststore.h>
+#include <gtkmm/messagedialog.h>
+
+#include "pbd/basename.h"
+#include "pbd/file_archive.h"
+#include "pbd/file_utils.h"
+#include "pbd/i18n.h"
+
+#include "ardour/audioengine.h"
+#include "ardour/filename_extensions.h"
+#include "ardour/filesystem_paths.h"
+#include "ardour/profile.h"
+#include "ardour/recent_sessions.h"
+#include "ardour/rc_configuration.h"
+#include "ardour/search_paths.h"
+#include "ardour/session.h"
+#include "ardour/session_utils.h"
+#include "ardour/template_utils.h"
+
+#include "gtkmm2ext/application.h"
+#include <gtkmm2ext/doi.h>
+
+#include "engine_dialog.h"
+#include "new_user_wizard.h"
+#include "opts.h"
+#include "session_dialog.h"
+#include "startup_fsm.h"
+
+using namespace ARDOUR;
+using namespace Gtk;
+using namespace Gtkmm2ext;
+using namespace PBD;
+
+using std::string;
+using std::vector;
+
+StartupFSM::StartupFSM (EngineControl& amd)
+       : session_existing_sample_rate (0)
+       , session_is_new (false)
+       , new_user (true /*NewUserWizard::required()*/)
+       , new_session (true)
+       , _state (NeedWizard)
+       , new_user_wizard (0)
+       , audiomidi_dialog (amd)
+       , session_dialog (0)
+{
+       Application* app = Application::instance ();
+
+       app->ShouldQuit.connect (sigc::mem_fun (*this, &StartupFSM::queue_finish));
+       app->ShouldLoad.connect (sigc::mem_fun (*this, &StartupFSM::load_from_application_api));
+
+       /* this may cause the delivery of ShouldLoad etc if we were invoked in
+        * particular ways. It will happen when the event loop runs again.
+        */
+
+       app->ready ();
+}
+
+StartupFSM::~StartupFSM ()
+{
+       delete session_dialog;
+}
+
+void
+StartupFSM::queue_finish ()
+{
+       _signal_response (ExitProgram);
+}
+
+void
+StartupFSM::start ()
+{
+       if (new_user) {
+               /* show new user wizard */
+               _state = NeedSessionPath;
+               show_new_user_wizard ();
+       } else {
+               /* pretend we just showed the new user wizard and we're done
+                  with it
+               */
+               dialog_response_handler (RESPONSE_OK, NewUserDialog);
+       }
+}
+
+void
+StartupFSM::dialog_response_handler (int response, StartupFSM::DialogID dialog_id)
+{
+       const bool new_session_required = (ARDOUR_COMMAND_LINE::new_session || (!ARDOUR::Profile->get_mixbus() && new_user));
+       int csp;
+
+       std::cerr << "SFSM state = " << _state << " r = " << response << " did " << dialog_id << std::endl;
+
+       switch (_state) {
+       case NeedSessionPath:
+               switch (dialog_id) {
+               case NewUserDialog:
+
+                       current_dialog_connection.disconnect ();
+                       delete_when_idle (new_user_wizard);
+
+                       switch (response) {
+                       case RESPONSE_OK:
+                               break;
+                       default:
+                               exit (1);
+                       }
+
+                       /* new user wizard done, now lets get session params
+                        * either from the command line (if given) or a dialog
+                        * (if nothing given on the command line or if the
+                        * command line arguments don't work for some reason
+                        */
+
+                       if (ARDOUR_COMMAND_LINE::session_name.empty()) {
+
+                               /* nothing given on the command line ... show new session dialog */
+
+                               session_path = string();
+                               session_name = string();
+                               session_template = string();
+
+                               _state = NeedSessionPath;
+                               session_dialog = new SessionDialog (new_session_required, string(), string(), string(), false);
+                               show_session_dialog ();
+
+                       } else {
+
+                               if (get_session_parameters_from_command_line (new_session_required)) {
+
+                                       /* command line arguments all OK. Get engine parameters */
+
+                                       _state = NeedEngineParams;
+
+                                       if (!new_session_required && session_existing_sample_rate > 0) {
+                                               audiomidi_dialog.set_desired_sample_rate (session_existing_sample_rate);
+                                       }
+
+                                       show_audiomidi_dialog ();
+
+                               } else {
+
+                                       /* command line arguments not good. Use
+                                        * dialog, but prime the dialog with
+                                        * the information we set up in
+                                        * get_session_parameters_from_command_line()
+                                        */
+
+                                       _state = NeedSessionPath;
+                                       session_dialog = new SessionDialog (new_session_required, session_name, session_path, session_template, false);
+                                       show_session_dialog ();
+                               }
+                       }
+                       break;
+
+               case NewSessionDialog:
+                       switch (response) {
+                       case RESPONSE_OK:
+                       case RESPONSE_ACCEPT:
+                               csp = check_session_parameters (new_session_required);
+                               std::cerr << "csp = " << csp << std::endl;
+                               switch (csp) {
+                               case -1:
+                                       /* Unrecoverable error */
+                                       _signal_response (ExitProgram);
+                                       break;
+                               case 1:
+                                       /* do nothing - keep dialog up for a
+                                        * retry. Errors were addressed by
+                                        * ::check_session_parameters()
+                                        */
+                                       break;
+                               case 0:
+                                       _state = NeedEngineParams;
+                                       session_dialog->hide ();
+                                       delete session_dialog;
+                                       session_dialog = 0;
+                                       current_dialog_connection.disconnect();
+                                       if (!session_is_new && session_existing_sample_rate > 0) {
+                                               audiomidi_dialog.set_desired_sample_rate (session_existing_sample_rate);
+                                       }
+                                       show_audiomidi_dialog ();
+                                       break;
+                               }
+                               break;
+
+                       default:
+                               _signal_response (ExitProgram);
+                               break;
+                       }
+                       break;
+
+               default:
+                       /* ERROR */
+                       break;
+               }
+               break;
+
+       case NeedEngineParams:
+               switch (dialog_id) {
+               case AudioMIDISetup:
+                       std::cerr << "AMS done, r = " << response << std::endl;
+                       switch (response) {
+                       case RESPONSE_OK:
+                       case RESPONSE_ACCEPT:
+                               audiomidi_dialog.hide ();
+                               current_dialog_connection.disconnect();
+                               /* fallthru */
+                       case RESPONSE_DELETE_EVENT:
+                               if (AudioEngine::instance()->running()) {
+                                       _signal_response (LoadSession);
+                               }
+                               break;
+                       default:
+                               _signal_response (ExitProgram);
+                       }
+                       break;
+               default:
+                       /* ERROR */
+                       break;
+               }
+
+       case NeedWizard:
+               break;
+
+       case NeedSessionSR:
+               break;
+
+       case NeedEngine:
+               break;
+       }
+}
+
+void
+StartupFSM::show_new_user_wizard ()
+{
+       new_user_wizard = new NewUserWizard;
+       current_dialog_connection = new_user_wizard->signal_response().connect (sigc::bind (sigc::mem_fun (*this, &StartupFSM::dialog_response_handler), NewUserDialog));
+       new_user_wizard->present ();
+}
+
+void
+StartupFSM::show_session_dialog ()
+{
+       current_dialog_connection = session_dialog->signal_response().connect (sigc::bind (sigc::mem_fun (*this, &StartupFSM::dialog_response_handler), NewSessionDialog));
+       session_dialog->present ();
+}
+
+void
+StartupFSM::show_audiomidi_dialog ()
+{
+       current_dialog_connection = audiomidi_dialog.signal_response().connect (sigc::bind (sigc::mem_fun (*this, &StartupFSM::dialog_response_handler), AudioMIDISetup));
+       audiomidi_dialog.present ();
+}
+
+bool
+StartupFSM::get_session_parameters_from_command_line (bool new_session_required)
+{
+       return get_session_parameters_from_path (ARDOUR_COMMAND_LINE::session_name, ARDOUR_COMMAND_LINE::load_template, new_session_required);
+}
+
+bool
+StartupFSM::get_session_parameters_from_path (string const & path, string const & template_name, bool new_session_required)
+{
+       if (path.empty()) {
+               /* use GUI to ask the user */
+               return false;
+       }
+
+       if (Glib::file_test (path.c_str(), Glib::FILE_TEST_EXISTS)) {
+
+               if (new_session_required) {
+                       /* wait! it already exists */
+
+                       if (!ask_about_loading_existing_session (path)) {
+                               return false;
+                       } else {
+                               /* load it anyway */
+                       }
+               }
+
+               session_name = basename_nosuffix (path);
+
+               if (Glib::file_test (path.c_str(), Glib::FILE_TEST_IS_REGULAR)) {
+                       /* session/snapshot file, change path to be dir */
+                       session_path = Glib::path_get_dirname (path);
+               }
+
+               float sr;
+               SampleFormat fmt;
+               string program_version;
+
+               if (Session::get_info_from_path (session_path, sr, fmt, program_version)) {
+                       /* exists but we can't read it */
+                       return false;
+               }
+
+               session_existing_sample_rate = sr;
+               return true;
+
+       }
+
+       /*  Everything after this involves a new session
+        *
+        *  ... did the  user give us a path or just a name?
+        */
+
+       if (path.find (G_DIR_SEPARATOR) == string::npos) {
+               /* user gave session name with no path info, use
+                  default session folder.
+               */
+               session_name = ARDOUR_COMMAND_LINE::session_name;
+               session_path = Glib::build_filename (Config->get_default_session_parent_dir (), session_name);
+       } else {
+               session_name = basename_nosuffix (path);
+       }
+
+
+       if (!template_name.empty()) {
+
+               /* Allow the user to specify a template via path or name on the
+                * command line
+                */
+
+               bool have_resolved_template_name = false;
+
+               /* compare by name (path may or may not be UTF-8) */
+
+               vector<TemplateInfo> templates;
+               find_session_templates (templates, false);
+               for (vector<TemplateInfo>::iterator x = templates.begin(); x != templates.end(); ++x) {
+                       if ((*x).name == template_name) {
+                               session_template = (*x).path;
+                               have_resolved_template_name = true;
+                               break;
+                       }
+               }
+
+               /* look up script by name */
+               LuaScriptList scripts (LuaScripting::instance ().scripts (LuaScriptInfo::SessionInit));
+               LuaScriptList& as (LuaScripting::instance ().scripts (LuaScriptInfo::EditorAction));
+               for (LuaScriptList::const_iterator s = as.begin(); s != as.end(); ++s) {
+                       if ((*s)->subtype & LuaScriptInfo::SessionSetup) {
+                               scripts.push_back (*s);
+                       }
+               }
+               std::sort (scripts.begin(), scripts.end(), LuaScripting::Sorter());
+               for (LuaScriptList::const_iterator s = scripts.begin(); s != scripts.end(); ++s) {
+                       if ((*s)->name == template_name) {
+                               session_template = "urn:ardour:" + (*s)->path;
+                               have_resolved_template_name = true;
+                               break;
+                       }
+               }
+
+               if (!have_resolved_template_name) {
+                       /* this will produce a more or less meaninful error later:
+                        * "ERROR: Could not open session template [abs-path to user-config dir]"
+                        */
+                       session_template = Glib::build_filename (ARDOUR::user_template_directory (), template_name);
+               }
+       }
+
+       /* We don't know what this is, because the session is new and the
+        * command line doesn't let us specify it. The user will get to decide
+        * in the audio/MIDI dialog.
+        */
+
+       session_existing_sample_rate = 0;
+
+       return true;
+}
+
+/** return values:
+ * -1: failure
+ *  1: failure but user can retry
+ *  0: success, seesion parameters ready for use
+ */
+int
+StartupFSM::check_session_parameters (bool must_be_new)
+{
+       bool requested_new = false;
+
+       session_name = session_dialog->session_name (requested_new);
+       session_path = session_dialog->session_folder ();
+
+       if (must_be_new) {
+               assert (requested_new);
+       }
+
+       if (!must_be_new) {
+
+               /* See if the specified session is a session archive */
+
+               int rv = ARDOUR::inflate_session (session_name, Config->get_default_session_parent_dir(), session_path, session_name);
+               if (rv < 0) {
+                       MessageDialog msg (*session_dialog, string_compose (_("Extracting session-archive failed: %1"), inflate_error (rv)));
+                       msg.run ();
+
+                       return 1;
+               } else if (rv == 0) {
+                       /* names are good (and session is unarchived/inflated */
+                       return 0;
+               }
+       }
+
+       /* check for ".ardour" in statefile name, because we don't want
+        * it
+        *
+        * XXX Note this wierd conflation of a
+        * file-name-without-a-suffix and the session name. It's not
+        * really a session name at all, but rather the suffix-free
+        * name of a statefile (snapshot).
+        */
+
+       const string::size_type suffix_at = session_name.find (statefile_suffix);
+
+       if (suffix_at != string::npos) {
+               session_name = session_name.substr (0, suffix_at);
+       }
+
+       /* this shouldn't happen, but we catch it just in case it does */
+
+       if (session_name.empty()) {
+               return 1; /* keep running dialog */
+       }
+
+       if (session_dialog->use_session_template()) {
+               session_template = session_dialog->session_template_name();
+       }
+
+       if (session_name[0] == G_DIR_SEPARATOR ||
+#ifdef PLATFORM_WINDOWS
+           // Windows file system .. detect absolute path
+           // C:/*
+           (session_name.length() > 3 && session_name[1] == ':' && session_name[2] == G_DIR_SEPARATOR)
+#else
+           // Sensible file systems
+           // /* or ./* or ../*
+           (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
+               )
+       {
+
+               /* user typed absolute path or cwd-relative path
+                  specified into session name field. So ... infer
+                  session path and name from what was given.
+               */
+
+               session_path = Glib::path_get_dirname (session_name);
+               session_name = Glib::path_get_basename (session_name);
+
+       } else {
+
+               /* session name is just a name */
+       }
+
+       /* check if name is legal */
+
+       const char illegal = Session::session_name_is_legal (session_name);
+
+       if (illegal) {
+               MessageDialog msg (*session_dialog,
+                                  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
+               return 1; /* keep running dialog */
+       }
+
+
+       /* check if the currently-exists status matches whether or not
+        * it should be new
+        */
+
+       if (Glib::file_test (session_path, Glib::FileTest (G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR))) {
+
+               if (requested_new /*&& !nsm*/) {
+
+                       std::string existing = Glib::build_filename (session_path, session_name);
+
+                       if (!ask_about_loading_existing_session (existing)) {
+                               session_dialog->clear_name ();
+                               return 1; /* try again */
+                       }
+               }
+
+               session_is_new = false;
+
+       } else {
+
+               /* does not exist at present */
+
+               if (!requested_new) {
+                       // pop_back_splash (session_dialog);
+                       MessageDialog msg (string_compose (_("There is no existing session at \"%1\""), session_path));
+                       msg.run ();
+                       session_dialog->clear_name();
+                       return 1;
+               }
+
+               session_is_new = true;
+       }
+
+       float sr;
+       SampleFormat fmt;
+       string program_version;
+
+       if (!session_is_new && Session::get_info_from_path (session_path, sr, fmt, program_version)) {
+               /* exists but we can't read it */
+               return -1;
+       }
+
+       session_existing_sample_rate = sr;
+
+       return 0;
+}
+
+void
+StartupFSM::copy_demo_sessions ()
+{
+       // TODO: maybe IFF brand_new_user
+       if (ARDOUR::Profile->get_mixbus () && Config->get_copy_demo_sessions ()) {
+               std::string dspd (Config->get_default_session_parent_dir());
+               Searchpath ds (ARDOUR::ardour_data_search_path());
+               ds.add_subdirectory_to_paths ("sessions");
+               vector<string> demos;
+               find_files_matching_pattern (demos, ds, ARDOUR::session_archive_suffix);
+
+               ARDOUR::RecentSessions rs;
+               ARDOUR::read_recent_sessions (rs);
+
+               for (vector<string>::iterator i = demos.begin(); i != demos.end (); ++i) {
+                       /* "demo-session" must be inside "demo-session.<session_archive_suffix>" */
+                       std::string name = basename_nosuffix (basename_nosuffix (*i));
+                       std::string path = Glib::build_filename (dspd, name);
+                       /* skip if session-dir already exists */
+                       if (Glib::file_test(path.c_str(), Glib::FILE_TEST_IS_DIR)) {
+                               continue;
+                       }
+                       /* skip sessions that are already in 'recent'.
+                        * eg. a new user changed <session-default-dir> shorly after installation
+                        */
+                       for (ARDOUR::RecentSessions::iterator r = rs.begin(); r != rs.end(); ++r) {
+                               if ((*r).first == name) {
+                                       continue;
+                               }
+                       }
+                       try {
+                               PBD::FileArchive ar (*i);
+                               if (0 == ar.inflate (dspd)) {
+                                       store_recent_sessions (name, path);
+                                       info << string_compose (_("Copied Demo Session %1."), name) << endmsg;
+                               }
+                       } catch (...) {
+                               /* relax ? */
+                       }
+               }
+       }
+}
+
+void
+StartupFSM::load_from_application_api (const std::string& path)
+{
+       /* macOS El Capitan (and probably later) now somehow passes the command
+          line arguments to an app via the openFile delegate protocol. Ardour
+          already does its own command line processing, and having both
+          pathways active causes crashes. So, if the command line was already
+          set, do nothing here.
+       */
+
+       if (!ARDOUR_COMMAND_LINE::session_name.empty()) {
+               return;
+       }
+
+       /* Cancel SessionDialog if it's visible to make macOS delegates work.
+        *
+        * there's a race condition here: we connect to app->ShouldLoad
+        * and then at some point (might) show a session dialog. The race is
+        * caused by the non-deterministic interaction between the macOS event
+        * loop(s) and the GDK one(s).
+        *
+        *  - ShouldLoad does not arrive before we show the session dialog
+        *          -> here we should hide the session dialog, then use the
+        *             supplied path as if it was provided on the command line
+        *  - ShouldLoad signal arrives before we show a session dialog
+        *          -> don't bother showing the session dialog, just use the
+        *             supplied path as if it was provided on the command line
+        *
+        */
+
+       if (session_dialog) {
+               session_dialog->hide ();
+               delete_when_idle (session_dialog);
+               session_dialog = 0;
+       }
+
+       /* no command line argument given ... must just be via
+        * desktop/finder/window manager API (e.g. double click on "foo.ardour"
+        * icon)
+        */
+
+       if (get_session_parameters_from_path (path, string(), false)) {
+               _signal_response (LoadSession);
+               return;
+       }
+
+       /* given parameters failed for some reason. This is probably true
+        * anyway, but force it to be true and then carry on with whatever the
+        * main event loop is doing.
+        */
+
+       _state = NeedSessionPath;
+}
+
+bool
+StartupFSM::ask_about_loading_existing_session (const std::string& session_path)
+{
+       std::string str = string_compose (_("This session\n%1\nalready exists. Do you want to open it?"), session_path);
+
+       MessageDialog msg (str,
+                          false,
+                          Gtk::MESSAGE_WARNING,
+                          Gtk::BUTTONS_YES_NO,
+                          true);
+
+
+       msg.set_name (X_("OpenExistingDialog"));
+       msg.set_title (_("Open Existing Session"));
+       msg.set_wmclass (X_("existing_session"), PROGRAM_NAME);
+       msg.set_position (Gtk::WIN_POS_CENTER);
+       // pop_back_splash (msg);
+
+       switch (msg.run()) {
+       case RESPONSE_YES:
+               return true;
+               break;
+       }
+       return false;
+}
diff --git a/gtk2_ardour/startup_fsm.h b/gtk2_ardour/startup_fsm.h
new file mode 100644 (file)
index 0000000..a1d52eb
--- /dev/null
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2019 Paul Davis <paul@linuxaudiosystems.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef __gtk2_ardour_startup_fsm_h__
+#define __gtk2_ardour_startup_fsm_h__
+
+#include <string>
+
+#include <sigc++/trackable.h>
+
+#include "ardour/types.h"
+
+class NewUserWizard;
+class EngineControl;
+class SessionDialog;
+
+class StartupFSM : public sigc::trackable
+{
+  public:
+       enum DialogID {
+               NewUserDialog,
+               NewSessionDialog,
+               AudioMIDISetup
+       };
+
+       enum Result {
+               LoadSession,
+               ExitProgram,
+               DoNothing, /* seriously? how can this be an option */
+       };
+
+       StartupFSM (EngineControl&);
+       ~StartupFSM ();
+
+       void start ();
+
+       std::string session_path;
+       std::string session_name;
+       std::string session_template;
+       int         session_existing_sample_rate;
+       bool        session_is_new;
+       ARDOUR::BusProfile bus_profile;
+
+       /* It's not a dialog but we provide this to make it behave like a (non-modal)
+        * dialog
+        */
+
+       sigc::signal1<void,Result>& signal_response() { return _signal_response; }
+
+       bool brand_new_user() const { return new_user; }
+       
+  private:
+       enum MainState {
+               NeedWizard,
+               NeedSessionPath,
+               NeedSessionSR,
+               NeedEngineParams,
+               NeedEngine
+       };
+
+       bool new_user;
+       bool new_session;
+
+       MainState _state;
+
+       void dialog_response_handler (int response, DialogID);
+
+       void show_new_user_wizard ();
+       void show_session_dialog ();
+       void show_audiomidi_dialog ();
+       void copy_demo_sessions ();
+       void load_from_application_api (std::string const &);
+       bool get_session_parameters_from_command_line (bool new_session_required);
+       bool get_session_parameters_from_path (std::string const & path, std::string const & template_name, bool new_session_required);
+       void queue_finish ();
+       bool ask_about_loading_existing_session (const std::string& session_path);
+       int  check_session_parameters (bool must_be_new);
+
+       NewUserWizard* new_user_wizard;
+       EngineControl& audiomidi_dialog;
+       SessionDialog* session_dialog;
+
+       sigc::connection current_dialog_connection;
+
+       sigc::signal1<void,Result> _signal_response;
+};
+
+#endif /* __gtk2_ardour_startup_fsm_h__ */
index 7d5dbb1c2c91044873944bde183256bb3a8e70c2..e80e5da4fcd7bda8532d828e7a7c849a01b77359 100644 (file)
@@ -175,6 +175,7 @@ gtk2_ardour_sources = [
         'mouse_cursors.cc',
         'nag.cc',
         'new_plugin_preset_dialog.cc',
+        'new_user_wizard.cc',
         'normalize_dialog.cc',
         'note.cc',
         'note_base.cc',
@@ -251,7 +252,7 @@ gtk2_ardour_sources = [
         'soundcloud_export_selector.cc',
         'splash.cc',
         'speaker_dialog.cc',
-        'startup.cc',
+        'startup_fsm.cc',
         'step_editor.cc',
         'step_entry.cc',
         'stereo_panner.cc',