summaryrefslogtreecommitdiff
path: root/gtk2_ardour/ardour_ui_session.cc
diff options
context:
space:
mode:
authorPaul Davis <paul@linuxaudiosystems.com>2019-09-23 14:49:06 -0600
committerPaul Davis <paul@linuxaudiosystems.com>2019-09-23 14:49:06 -0600
commit5beeca2e95a7ea70a4225eaca979179649cb2e90 (patch)
tree832643fc664d4d82d059dbebb7bd658fd79b9a59 /gtk2_ardour/ardour_ui_session.cc
parent9c0beeb7591302747eee6e28c448314313f8d54a (diff)
split apart ardour_ui.cc into a series of distinct source modules.
Should be a 100% no-op - no code was altered, just moved
Diffstat (limited to 'gtk2_ardour/ardour_ui_session.cc')
-rw-r--r--gtk2_ardour/ardour_ui_session.cc1278
1 files changed, 1278 insertions, 0 deletions
diff --git a/gtk2_ardour/ardour_ui_session.cc b/gtk2_ardour/ardour_ui_session.cc
new file mode 100644
index 0000000000..1bee61b67d
--- /dev/null
+++ b/gtk2_ardour/ardour_ui_session.cc
@@ -0,0 +1,1278 @@
+/*
+ * Copyright (C) 2005-2007 Doug McLain <doug@nostar.net>
+ * Copyright (C) 2005-2017 Tim Mayberry <mojofunk@gmail.com>
+ * Copyright (C) 2005-2019 Paul Davis <paul@linuxaudiosystems.com>
+ * Copyright (C) 2005 Karsten Wiese <fzuuzf@googlemail.com>
+ * Copyright (C) 2005 Taybin Rutkin <taybin@taybin.com>
+ * Copyright (C) 2006-2015 David Robillard <d@drobilla.net>
+ * Copyright (C) 2007-2012 Carl Hetherington <carl@carlh.net>
+ * Copyright (C) 2008-2010 Sakari Bergen <sakari.bergen@beatwaves.net>
+ * Copyright (C) 2012-2019 Robin Gareus <robin@gareus.org>
+ * Copyright (C) 2013-2015 Colin Fletcher <colin.m.fletcher@googlemail.com>
+ * Copyright (C) 2013-2016 John Emmas <john@creativepost.co.uk>
+ * Copyright (C) 2013-2016 Nick Mainsbridge <mainsbridge@gmail.com>
+ * Copyright (C) 2014-2018 Ben Loftis <ben@harrisonconsoles.com>
+ * Copyright (C) 2015 André Nusser <andre.nusser@googlemail.com>
+ * Copyright (C) 2016-2018 Len Ovens <len@ovenwerks.net>
+ * Copyright (C) 2017 Johannes Mueller <github@johannes-mueller.org>
+ *
+ * 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.
+ */
+
+#ifdef WAF_BUILD
+#include "gtk2ardour-config.h"
+#include "gtk2ardour-version.h"
+#endif
+
+#include <gtkmm/progressbar.h>
+#include <gtkmm/stock.h>
+
+#include "pbd/basename.h"
+#include "pbd/i18n.h"
+#include "pbd/unwind.h"
+
+#include "gtkmm2ext/application.h"
+
+#include "widgets/prompter.h"
+
+#include "ardour/audioengine.h"
+#include "ardour/filename_extensions.h"
+#include "ardour/session.h"
+#include "ardour/session_utils.h"
+#include "ardour/session_state_utils.h"
+#include "ardour/session_directory.h"
+
+#include "ardour_ui.h"
+#include "engine_dialog.h"
+#include "missing_plugin_dialog.h"
+#include "opts.h"
+#include "public_editor.h"
+#include "save_as_dialog.h"
+#include "session_dialog.h"
+#include "session_archive_dialog.h"
+#include "timers.h"
+#include "utils.h"
+
+using namespace ARDOUR;
+using namespace ARDOUR_UI_UTILS;
+using namespace PBD;
+using namespace Gtk;
+using namespace std;
+using namespace ArdourWidgets;
+
+bool
+ARDOUR_UI::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;
+}
+
+int
+ARDOUR_UI::build_session_from_dialog (SessionDialog& sd, const std::string& session_path, const std::string& session_name)
+{
+ BusProfile bus_profile;
+
+ if (nsm) {
+ bus_profile.master_out_channels = 2;
+ } else {
+ /* get settings from advanced section of NSD */
+ bus_profile.master_out_channels = (uint32_t) sd.master_channel_count();
+ }
+
+ // NULL profile: no master, no monitor
+ if (build_session (session_path, session_name, bus_profile.master_out_channels > 0 ? &bus_profile : NULL)) {
+ return -1;
+ }
+
+ return 0;
+}
+
+void
+ARDOUR_UI::load_from_application_api (const std::string& path)
+{
+ /* OS X 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;
+ }
+
+ ARDOUR_COMMAND_LINE::session_name = path;
+
+ /* Cancel SessionDialog if it's visible to make OSX delegates work.
+ *
+ * ARDOUR_UI::starting connects app->ShouldLoad signal and then shows a SessionDialog
+ * race-condition:
+ * - ShouldLoad does not arrive in time, ARDOUR_COMMAND_LINE::session_name is empty:
+ * -> ARDOUR_UI::get_session_parameters starts a SessionDialog.
+ * - ShouldLoad signal arrives, this function is called and sets ARDOUR_COMMAND_LINE::session_name
+ * -> SessionDialog is not displayed
+ */
+
+ if (_session_dialog) {
+ std::string session_name = basename_nosuffix (ARDOUR_COMMAND_LINE::session_name);
+ std::string session_path = path;
+ if (Glib::file_test (session_path, Glib::FILE_TEST_IS_REGULAR)) {
+ session_path = Glib::path_get_dirname (session_path);
+ }
+ // signal the existing dialog in ARDOUR_UI::get_session_parameters()
+ _session_dialog->set_provided_session (session_name, session_path);
+ _session_dialog->response (RESPONSE_NONE);
+ _session_dialog->hide();
+ return;
+ }
+
+ int rv;
+ if (Glib::file_test (path, Glib::FILE_TEST_IS_DIR)) {
+ /* /path/to/foo => /path/to/foo, foo */
+ rv = load_session (path, basename_nosuffix (path));
+ } else {
+ /* /path/to/foo/foo.ardour => /path/to/foo, foo */
+ rv =load_session (Glib::path_get_dirname (path), basename_nosuffix (path));
+ }
+
+ // if load_session fails -> pop up SessionDialog.
+ if (rv) {
+ ARDOUR_COMMAND_LINE::session_name = "";
+
+ if (get_session_parameters (true, false)) {
+ exit (EXIT_FAILURE);
+ }
+ }
+}
+
+/** @param quit_on_cancel true if exit() should be called if the user clicks `cancel' in the new session dialog */
+int
+ARDOUR_UI::get_session_parameters (bool quit_on_cancel, bool should_be_new, string load_template)
+{
+ string session_name;
+ string session_path;
+ string template_name;
+ int ret = -1;
+ bool likely_new = false;
+ bool cancel_not_quit;
+
+ /* deal with any existing DIRTY session now, rather than later. don't
+ * treat a non-dirty session this way, so that it stays visible
+ * as we bring up the new session dialog.
+ */
+
+ if (_session && ARDOUR_UI::instance()->video_timeline) {
+ ARDOUR_UI::instance()->video_timeline->sync_session_state();
+ }
+
+ /* if there is already a session, relabel the button
+ on the SessionDialog so that we don't Quit directly
+ */
+ cancel_not_quit = (_session != 0) && !quit_on_cancel;
+
+ if (_session && _session->dirty()) {
+ if (unload_session (false)) {
+ /* unload cancelled by user */
+ return 0;
+ }
+ ARDOUR_COMMAND_LINE::session_name = "";
+ }
+
+ if (!load_template.empty()) {
+ should_be_new = true;
+ template_name = load_template;
+ }
+
+ session_path = ARDOUR_COMMAND_LINE::session_name;
+
+ if (!session_path.empty()) {
+
+ if (Glib::file_test (session_path.c_str(), Glib::FILE_TEST_EXISTS)) {
+
+ session_name = basename_nosuffix (ARDOUR_COMMAND_LINE::session_name);
+
+ if (Glib::file_test (session_path.c_str(), Glib::FILE_TEST_IS_REGULAR)) {
+ /* session/snapshot file, change path to be dir */
+ session_path = Glib::path_get_dirname (session_path);
+ }
+ } else {
+
+ /* session (file or folder) does not exist ... did the
+ * user give us a path or just a name?
+ */
+
+ if (session_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 (ARDOUR_COMMAND_LINE::session_name);
+ }
+ }
+ }
+
+ SessionDialog session_dialog (should_be_new, session_name, session_path, load_template, cancel_not_quit);
+
+ _session_dialog = &session_dialog;
+ while (ret != 0) {
+
+ if (!ARDOUR_COMMAND_LINE::session_name.empty()) {
+
+ /* if they named a specific statefile, use it, otherwise they are
+ just giving a session folder, and we want to use it as is
+ to find the session.
+ */
+
+ string::size_type suffix = ARDOUR_COMMAND_LINE::session_name.find (statefile_suffix);
+
+ if (suffix != string::npos) {
+ session_path = Glib::path_get_dirname (ARDOUR_COMMAND_LINE::session_name);
+ session_name = ARDOUR_COMMAND_LINE::session_name.substr (0, suffix);
+ session_name = Glib::path_get_basename (session_name);
+ } else {
+ session_path = ARDOUR_COMMAND_LINE::session_name;
+ session_name = Glib::path_get_basename (ARDOUR_COMMAND_LINE::session_name);
+ }
+ } else {
+ session_path = "";
+ session_name = "";
+ session_dialog.clear_given ();
+ }
+
+ if (session_name.empty()) {
+ /* need the dialog to get the name (at least) from the user */
+ switch (session_dialog.run()) {
+ case RESPONSE_ACCEPT:
+ break;
+ case RESPONSE_NONE:
+ /* this is used for async * app->ShouldLoad(). */
+ continue; // while loop
+ break;
+ default:
+ if (quit_on_cancel) {
+ ARDOUR_UI::finish ();
+ Gtkmm2ext::Application::instance()->cleanup();
+ ARDOUR::cleanup ();
+ pthread_cancel_all ();
+ return -1; // caller is responsible to call exit()
+ } else {
+ return ret;
+ }
+ }
+
+ session_dialog.hide ();
+ }
+
+ /* if we run the startup dialog again, offer more than just "new session" */
+
+ should_be_new = false;
+
+ session_name = session_dialog.session_name (likely_new);
+ session_path = session_dialog.session_folder ();
+
+ if (nsm) {
+ likely_new = true;
+ }
+
+ if (!likely_new) {
+ 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 ();
+ continue;
+ }
+ else if (rv == 0) {
+ session_dialog.set_provided_session (session_name, session_path);
+ }
+ }
+
+ // XXX check archive, inflate
+ string::size_type suffix = session_name.find (statefile_suffix);
+
+ if (suffix != string::npos) {
+ session_name = session_name.substr (0, suffix);
+ }
+
+ /* this shouldn't happen, but we catch it just in case it does */
+
+ if (session_name.empty()) {
+ continue;
+ }
+
+ if (session_dialog.use_session_template()) {
+ template_name = session_dialog.session_template_name();
+ _session_is_new = true;
+ }
+
+ if (session_name[0] == G_DIR_SEPARATOR ||
+#ifdef PLATFORM_WINDOWS
+ (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)
+#endif
+ )
+ {
+
+ /* absolute path or cwd-relative path specified for session name: infer session folder
+ from what was given.
+ */
+
+ session_path = Glib::path_get_dirname (session_name);
+ session_name = Glib::path_get_basename (session_name);
+
+ } else {
+
+ session_path = session_dialog.session_folder();
+
+ 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
+ continue;
+ }
+ }
+
+ if (Glib::file_test (session_path, Glib::FileTest (G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR))) {
+
+
+ if (likely_new && !nsm) {
+
+ std::string existing = Glib::build_filename (session_path, session_name);
+
+ if (!ask_about_loading_existing_session (existing)) {
+ ARDOUR_COMMAND_LINE::session_name = ""; // cancel that
+ continue;
+ }
+ }
+
+ _session_is_new = false;
+
+ } else {
+
+ if (!likely_new) {
+ pop_back_splash (session_dialog);
+ MessageDialog msg (string_compose (_("There is no existing session at \"%1\""), session_path));
+ msg.run ();
+ ARDOUR_COMMAND_LINE::session_name = ""; // cancel that
+ continue;
+ }
+
+ char illegal = Session::session_name_is_legal(session_name);
+
+ 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));
+ msg.run ();
+ ARDOUR_COMMAND_LINE::session_name = ""; // cancel that
+ continue;
+ }
+
+ _session_is_new = true;
+ }
+
+ 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);
+
+ } else {
+
+ ret = load_session (session_path, session_name, template_name);
+
+ if (ret == -2) {
+ /* not connected to the AudioEngine, so quit to avoid an infinite loop */
+ exit (EXIT_FAILURE);
+ }
+
+ /* clear this to avoid endless attempts to load the
+ same session.
+ */
+
+ ARDOUR_COMMAND_LINE::session_name = "";
+ }
+ }
+
+ _session_dialog = NULL;
+
+ return ret;
+}
+
+void
+ARDOUR_UI::close_session()
+{
+ if (!check_audioengine (_main_window)) {
+ return;
+ }
+
+ if (unload_session (true)) {
+ return;
+ }
+
+ ARDOUR_COMMAND_LINE::session_name = "";
+
+ if (get_session_parameters (true, false)) {
+ exit (EXIT_FAILURE);
+ }
+}
+
+
+/** @param snap_name Snapshot name (without .ardour suffix).
+ * @return -2 if the load failed because we are not connected to the AudioEngine.
+ */
+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;
+
+ if (_session) {
+ unload_status = unload_session ();
+
+ if (unload_status < 0) {
+ goto out;
+ } else if (unload_status > 0) {
+ retval = 0;
+ goto out;
+ }
+ }
+
+ loading_message (string_compose (_("Please wait while %1 loads your session"), PROGRAM_NAME));
+
+ try {
+ new_session = new Session (*AudioEngine::instance(), path, snap_name, 0, mix_template);
+ }
+
+ /* this one is special */
+
+ catch (AudioEngine::PortRegistrationFailure const& err) {
+
+ MessageDialog msg (err.what(),
+ true,
+ Gtk::MESSAGE_INFO,
+ Gtk::BUTTONS_CLOSE);
+
+ msg.set_title (_("Port Registration Error"));
+ msg.set_secondary_text (_("Click the Close button to try again."));
+ msg.set_position (Gtk::WIN_POS_CENTER);
+ pop_back_splash (msg);
+ msg.present ();
+
+ int response = msg.run ();
+
+ msg.hide ();
+
+ switch (response) {
+ case RESPONSE_CANCEL:
+ exit (EXIT_FAILURE);
+ default:
+ break;
+ }
+ goto out;
+ }
+ catch (SessionException const& e) {
+ MessageDialog msg (string_compose(
+ _("Session \"%1 (snapshot %2)\" did not load successfully:\n%3"),
+ path, snap_name, e.what()),
+ true,
+ Gtk::MESSAGE_INFO,
+ BUTTONS_OK);
+
+ msg.set_title (_("Loading Error"));
+ msg.set_position (Gtk::WIN_POS_CENTER);
+ pop_back_splash (msg);
+ msg.present ();
+
+ dump_errors (cerr);
+
+ (void) msg.run ();
+ msg.hide ();
+
+ goto out;
+ }
+ catch (...) {
+
+ MessageDialog msg (string_compose(
+ _("Session \"%1 (snapshot %2)\" did not load successfully."),
+ path, snap_name),
+ true,
+ Gtk::MESSAGE_INFO,
+ BUTTONS_OK);
+
+ msg.set_title (_("Loading Error"));
+ msg.set_position (Gtk::WIN_POS_CENTER);
+ pop_back_splash (msg);
+ msg.present ();
+
+ dump_errors (cerr);
+
+ (void) msg.run ();
+ msg.hide ();
+
+ goto out;
+ }
+
+ {
+ list<string> const u = new_session->unknown_processors ();
+ if (!u.empty()) {
+ MissingPluginDialog d (_session, u);
+ d.run ();
+ }
+ }
+
+ if (!new_session->writable()) {
+ MessageDialog msg (_("This session has been opened in read-only mode.\n\nYou will not be able to record or save."),
+ true,
+ Gtk::MESSAGE_INFO,
+ BUTTONS_OK);
+
+ msg.set_title (_("Read-only Session"));
+ msg.set_position (Gtk::WIN_POS_CENTER);
+ pop_back_splash (msg);
+ msg.present ();
+ (void) msg.run ();
+ msg.hide ();
+ }
+
+
+ /* Now the session been created, add the transport controls */
+ new_session->add_controllable(roll_controllable);
+ new_session->add_controllable(stop_controllable);
+ new_session->add_controllable(goto_start_controllable);
+ new_session->add_controllable(goto_end_controllable);
+ new_session->add_controllable(auto_loop_controllable);
+ new_session->add_controllable(play_selection_controllable);
+ new_session->add_controllable(rec_controllable);
+
+ set_session (new_session);
+
+ if (_session) {
+ _session->set_clean ();
+ }
+
+#ifdef WINDOWS_VST_SUPPORT
+ fst_stop_threading();
+#endif
+
+ {
+ Timers::TimerSuspender t;
+ flush_pending (10);
+ }
+
+#ifdef WINDOWS_VST_SUPPORT
+ fst_start_threading();
+#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;
+}
+
+int
+ARDOUR_UI::build_session (const std::string& path, const std::string& snap_name, BusProfile* bus_profile)
+{
+ Session *new_session;
+ int x;
+
+ x = unload_session ();
+
+ if (x < 0) {
+ return -1;
+ } else if (x > 0) {
+ return 0;
+ }
+
+ _session_is_new = true;
+
+ try {
+ new_session = new Session (*AudioEngine::instance(), path, snap_name, bus_profile);
+ }
+
+ catch (SessionException const& e) {
+ cerr << "Here are the errors associated with this failed session:\n";
+ dump_errors (cerr);
+ cerr << "---------\n";
+ MessageDialog msg (string_compose(_("Could not create session in \"%1\": %2"), path, e.what()));
+ msg.set_title (_("Loading Error"));
+ msg.set_position (Gtk::WIN_POS_CENTER);
+ pop_back_splash (msg);
+ msg.run ();
+ return -1;
+ }
+ catch (...) {
+ cerr << "Here are the errors associated with this failed session:\n";
+ dump_errors (cerr);
+ cerr << "---------\n";
+ MessageDialog msg (string_compose(_("Could not create session in \"%1\""), path));
+ msg.set_title (_("Loading Error"));
+ msg.set_position (Gtk::WIN_POS_CENTER);
+ pop_back_splash (msg);
+ msg.run ();
+ return -1;
+ }
+
+ /* Give the new session the default GUI state, if such things exist */
+
+ XMLNode* n;
+ n = Config->instant_xml (X_("Editor"));
+ if (n) {
+ n->remove_nodes_and_delete ("Selection"); // no not apply selection to new sessions.
+ new_session->add_instant_xml (*n, false);
+ }
+ n = Config->instant_xml (X_("Mixer"));
+ if (n) {
+ new_session->add_instant_xml (*n, false);
+ }
+
+ n = Config->instant_xml (X_("Preferences"));
+ if (n) {
+ new_session->add_instant_xml (*n, false);
+ }
+
+ /* Put the playhead at 0 and scroll fully left */
+ n = new_session->instant_xml (X_("Editor"));
+ if (n) {
+ n->set_property (X_("playhead"), X_("0"));
+ n->set_property (X_("left-frame"), X_("0"));
+ }
+
+ set_session (new_session);
+
+ new_session->save_state(new_session->name());
+
+ return 0;
+}
+
+/** Ask the user for the name of a new snapshot and then take it.
+ */
+
+void
+ARDOUR_UI::snapshot_session (bool switch_to_it)
+{
+ 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 proceed, please use the\n\n\
+\"Don't save now\" option."), PROGRAM_NAME));
+ pop_back_splash(msg);
+ msg.run ();
+ return;
+ }
+ /* fallthrough */
+ 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) {
+ prompter.set_title (_("Snapshot and switch"));
+ prompter.set_prompt (_("New session name"));
+ } else {
+ prompter.set_title (_("Take Snapshot"));
+ prompter.set_prompt (_("Name of new snapshot"));
+ }
+
+ if (switch_to_it) {
+ prompter.set_initial_text (_session->snap_name());
+ } else {
+ Glib::DateTime tm (g_date_time_new_now_local ());
+ prompter.set_initial_text (tm.format ("%FT%H.%M.%S"));
+ }
+
+ bool finished = false;
+ while (!finished) {
+ switch (prompter.run()) {
+ case RESPONSE_ACCEPT:
+ {
+ finished = process_snapshot_session_prompter (prompter, switch_to_it);
+ break;
+ }
+
+ default:
+ finished = true;
+ break;
+ }
+ }
+}
+
+/** Ask the user for a new session name and then rename the session to it.
+ */
+
+void
+ARDOUR_UI::rename_session ()
+{
+ if (!_session) {
+ return;
+ }
+
+ Prompter prompter (true);
+ string name;
+
+ prompter.set_name ("Prompter");
+ prompter.add_button (Gtk::Stock::SAVE, Gtk::RESPONSE_ACCEPT);
+ prompter.set_title (_("Rename Session"));
+ prompter.set_prompt (_("New session name"));
+
+ again:
+ switch (prompter.run()) {
+ case RESPONSE_ACCEPT:
+ {
+ prompter.get_result (name);
+
+ bool do_rename = (name.length() != 0);
+
+ if (do_rename) {
+ char illegal = Session::session_name_is_legal (name);
+
+ if (illegal) {
+ MessageDialog msg (string_compose (_("To ensure compatibility with various systems\n"
+ "session names may not contain a '%1' character"), illegal));
+ msg.run ();
+ goto again;
+ }
+
+ switch (_session->rename (name)) {
+ case -1: {
+ MessageDialog msg (_("That name is already in use by another directory/folder. Please try again."));
+ msg.set_position (WIN_POS_MOUSE);
+ msg.run ();
+ goto again;
+ break;
+ }
+ case 0:
+ break;
+ default: {
+ MessageDialog msg (_("Renaming this session failed.\nThings could be seriously messed up at this point"));
+ msg.set_position (WIN_POS_MOUSE);
+ msg.run ();
+ break;
+ }
+ }
+ }
+
+ break;
+ }
+
+ default:
+ break;
+ }
+}
+
+bool
+ARDOUR_UI::save_as_progress_update (float fraction, int64_t cnt, int64_t total, Gtk::Label* label, Gtk::ProgressBar* bar)
+{
+ char buf[256];
+
+ snprintf (buf, sizeof (buf), _("Copied %" PRId64 " of %" PRId64), cnt, total);
+
+ label->set_text (buf);
+ bar->set_fraction (fraction);
+
+ /* process events, redraws, etc. */
+
+ while (gtk_events_pending()) {
+ gtk_main_iteration ();
+ }
+
+ return true; /* continue with save-as */
+}
+
+void
+ARDOUR_UI::save_session_as ()
+{
+ if (!_session) {
+ 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 proceed, please use the\n\n\
+\"Don't save now\" option."), PROGRAM_NAME));
+ pop_back_splash(msg);
+ msg.run ();
+ return;
+ }
+ /* fallthrough */
+ case 0:
+ _session->remove_pending_capture_state ();
+ break;
+ }
+ }
+
+ if (!save_as_dialog) {
+ save_as_dialog = new SaveAsDialog;
+ }
+
+ save_as_dialog->set_name (_session->name());
+
+ int response = save_as_dialog->run ();
+
+ save_as_dialog->hide ();
+
+ switch (response) {
+ case Gtk::RESPONSE_OK:
+ break;
+ default:
+ return;
+ }
+
+
+ Session::SaveAs sa;
+
+ sa.new_parent_folder = save_as_dialog->new_parent_folder ();
+ sa.new_name = save_as_dialog->new_name ();
+ sa.switch_to = save_as_dialog->switch_to();
+ sa.copy_media = save_as_dialog->copy_media();
+ sa.copy_external = save_as_dialog->copy_external();
+ sa.include_media = save_as_dialog->include_media ();
+
+ /* Only bother with a progress dialog if we're going to copy
+ media into the save-as target. Without that choice, this
+ will be very fast because we're only talking about a few kB's to
+ perhaps a couple of MB's of data.
+ */
+
+ ArdourDialog progress_dialog (_("Save As"), true);
+ ScopedConnection c;
+
+ if (sa.include_media && sa.copy_media) {
+
+ Gtk::Label* label = manage (new Gtk::Label());
+ Gtk::ProgressBar* progress_bar = manage (new Gtk::ProgressBar ());
+
+ progress_dialog.get_vbox()->pack_start (*label);
+ progress_dialog.get_vbox()->pack_start (*progress_bar);
+ label->show ();
+ progress_bar->show ();
+
+ /* this signal will be emitted from within this, the calling thread,
+ * after every file is copied. It provides information on percentage
+ * complete (in terms of total data to copy), the number of files
+ * copied so far, and the total number to copy.
+ */
+
+ sa.Progress.connect_same_thread (c, boost::bind (&ARDOUR_UI::save_as_progress_update, this, _1, _2, _3, label, progress_bar));
+
+ progress_dialog.show_all ();
+ progress_dialog.present ();
+ }
+
+ if (_session->save_as (sa)) {
+ /* ERROR MESSAGE */
+ MessageDialog msg (string_compose (_("Save As failed: %1"), sa.failure_message));
+ msg.run ();
+ }
+
+ /* the logic here may seem odd: why isn't the condition sa.switch_to ?
+ * the trick is this: if the new session was copy with media included,
+ * then Session::save_as() will have already done a neat trick to avoid
+ * us having to unload and load the new state. But if the media was not
+ * included, then this is required (it avoids us having to otherwise
+ * drop all references to media (sources).
+ */
+
+ if (!sa.include_media && sa.switch_to) {
+ unload_session (false);
+ load_session (sa.final_session_folder_name, sa.new_name);
+ }
+}
+
+void
+ARDOUR_UI::archive_session ()
+{
+ if (!_session) {
+ return;
+ }
+
+ time_t n;
+ time (&n);
+ Glib::DateTime gdt (Glib::DateTime::create_now_local (n));
+
+ SessionArchiveDialog sad;
+ sad.set_name (_session->name() + gdt.format ("_%F_%H%M%S"));
+ int response = sad.run ();
+
+ if (response != Gtk::RESPONSE_OK) {
+ sad.hide ();
+ return;
+ }
+
+ if (_session->archive_session (sad.target_folder(), sad.name(), sad.encode_option (), sad.compression_level (), sad.only_used_sources (), &sad)) {
+ MessageDialog msg (_("Session Archiving failed."));
+ msg.run ();
+ }
+}
+
+void
+ARDOUR_UI::quick_snapshot_session (bool switch_to_it)
+{
+ char timebuf[128];
+ time_t n;
+ struct tm local_time;
+
+ 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 (Prompter& prompter, bool switch_to_it)
+{
+ string snapname;
+
+ prompter.get_result (snapname);
+
+ bool do_save = (snapname.length() != 0);
+
+ if (do_save) {
+ 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));
+ msg.run ();
+ return false;
+ }
+ }
+
+ vector<std::string> p;
+ get_state_files_in_directory (_session->session_directory().root_path(), p);
+ vector<string> n = get_file_names_no_extension (p);
+
+ if (find (n.begin(), n.end(), snapname) != n.end()) {
+
+ do_save = overwrite_file_dialog (prompter,
+ _("Confirm Snapshot Overwrite"),
+ _("A snapshot already exists with that name. Do you want to overwrite it?"));
+ }
+
+ if (do_save) {
+ save_state (snapname, switch_to_it);
+ }
+ else {
+ return false;
+ }
+
+ return true;
+}
+
+
+void
+ARDOUR_UI::open_session ()
+{
+ if (!check_audioengine (_main_window)) {
+ return;
+ }
+
+ /* ardour sessions are folders */
+ Gtk::FileChooserDialog open_session_selector(_("Open Session"), FILE_CHOOSER_ACTION_OPEN);
+ open_session_selector.add_button (Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
+ open_session_selector.add_button (Gtk::Stock::OPEN, Gtk::RESPONSE_ACCEPT);
+ open_session_selector.set_default_response(Gtk::RESPONSE_ACCEPT);
+
+ if (_session) {
+ string session_parent_dir = Glib::path_get_dirname(_session->path());
+ open_session_selector.set_current_folder(session_parent_dir);
+ } else {
+ open_session_selector.set_current_folder(Config->get_default_session_parent_dir());
+ }
+
+ Gtkmm2ext::add_volume_shortcuts (open_session_selector);
+ try {
+ /* add_shortcut_folder throws an exception if the folder being added already has a shortcut */
+ string default_session_folder = Config->get_default_session_parent_dir();
+ open_session_selector.add_shortcut_folder (default_session_folder);
+ }
+ catch (Glib::Error const& e) {
+ std::cerr << "open_session_selector.add_shortcut_folder() threw Glib::Error " << e.what() << std::endl;
+ }
+
+ FileFilter session_filter;
+ session_filter.add_pattern (string_compose(X_("*%1"), ARDOUR::statefile_suffix));
+ session_filter.set_name (string_compose (_("%1 sessions"), PROGRAM_NAME));
+ open_session_selector.add_filter (session_filter);
+
+ FileFilter archive_filter;
+ archive_filter.add_pattern (string_compose(X_("*%1"), ARDOUR::session_archive_suffix));
+ archive_filter.set_name (_("Session Archives"));
+
+ open_session_selector.add_filter (archive_filter);
+
+ open_session_selector.set_filter (session_filter);
+
+ int response = open_session_selector.run();
+ open_session_selector.hide ();
+
+ if (response == Gtk::RESPONSE_CANCEL) {
+ return;
+ }
+
+ string session_path = open_session_selector.get_filename();
+ string path, name;
+ bool isnew;
+
+ if (session_path.length() > 0) {
+ int rv = ARDOUR::inflate_session (session_path,
+ Config->get_default_session_parent_dir(), path, name);
+ if (rv == 0) {
+ _session_is_new = false;
+ load_session (path, name);
+ }
+ else if (rv < 0) {
+ MessageDialog msg (_main_window,
+ string_compose (_("Extracting session-archive failed: %1"), inflate_error (rv)));
+ msg.run ();
+ }
+ else if (ARDOUR::find_session (session_path, path, name, isnew) == 0) {
+ _session_is_new = isnew;
+ load_session (path, name);
+ }
+ }
+}
+
+void
+ARDOUR_UI::open_recent_session ()
+{
+ bool can_return = (_session != 0);
+
+ SessionDialog recent_session_dialog;
+
+ while (true) {
+
+ ResponseType r = (ResponseType) recent_session_dialog.run ();
+
+ switch (r) {
+ case RESPONSE_ACCEPT:
+ break;
+ default:
+ if (can_return) {
+ recent_session_dialog.hide();
+ return;
+ } else {
+ exit (EXIT_FAILURE);
+ }
+ }
+
+ recent_session_dialog.hide();
+
+ bool should_be_new;
+
+ std::string path = recent_session_dialog.session_folder();
+ std::string state = recent_session_dialog.session_name (should_be_new);
+
+ if (should_be_new == true) {
+ continue;
+ }
+
+ _session_is_new = false;
+
+ if (load_session (path, state) == 0) {
+ break;
+ }
+
+ can_return = false;
+ }
+}
+
+int
+ARDOUR_UI::ask_about_saving_session (const vector<string>& actions)
+{
+ ArdourDialog window (_("Unsaved Session"));
+ Gtk::HBox dhbox; // the hbox for the image and text
+ Gtk::Label prompt_label;
+ Gtk::Image* dimage = manage (new Gtk::Image(Stock::DIALOG_WARNING, Gtk::ICON_SIZE_DIALOG));
+
+ string msg;
+
+ assert (actions.size() >= 3);
+
+ window.add_button (actions[0], RESPONSE_REJECT);
+ window.add_button (actions[1], RESPONSE_APPLY);
+ window.add_button (actions[2], RESPONSE_ACCEPT);
+
+ window.set_default_response (RESPONSE_ACCEPT);
+
+ Gtk::Button noquit_button (msg);
+ noquit_button.set_name ("EditorGTKButton");
+
+ string prompt;
+
+ if (_session->snap_name() == _session->name()) {
+ prompt = string_compose(_("The session \"%1\"\nhas not been saved.\n\nAny changes made this time\nwill be lost unless you save it.\n\nWhat do you want to do?"),
+ _session->snap_name());
+ } else {
+ prompt = string_compose(_("The snapshot \"%1\"\nhas not been saved.\n\nAny changes made this time\nwill be lost unless you save it.\n\nWhat do you want to do?"),
+ _session->snap_name());
+ }
+
+ prompt_label.set_text (prompt);
+ prompt_label.set_name (X_("PrompterLabel"));
+ prompt_label.set_alignment(ALIGN_LEFT, ALIGN_TOP);
+
+ dimage->set_alignment(ALIGN_CENTER, ALIGN_TOP);
+ dhbox.set_homogeneous (false);
+ dhbox.pack_start (*dimage, false, false, 5);
+ dhbox.pack_start (prompt_label, true, false, 5);
+ window.get_vbox()->pack_start (dhbox);
+
+ window.set_name (_("Prompter"));
+ window.set_modal (true);
+ window.set_resizable (false);
+
+ dhbox.show();
+ prompt_label.show();
+ dimage->show();
+ window.show();
+ window.present ();
+
+ ResponseType r = (ResponseType) window.run();
+
+ window.hide ();
+
+ switch (r) {
+ case RESPONSE_ACCEPT: // save and get out of here
+ return 1;
+ case RESPONSE_APPLY: // get out of here
+ return 0;
+ default:
+ break;
+ }
+
+ return -1;
+}
+
+
+void
+ARDOUR_UI::save_session_at_its_request (std::string snapshot_name)
+{
+ if (_session) {
+ _session->save_state (snapshot_name);
+ }
+}
+
+gint
+ARDOUR_UI::autosave_session ()
+{
+ if (g_main_depth() > 1) {
+ /* inside a recursive main loop,
+ give up because we may not be able to
+ take a lock.
+ */
+ return 1;
+ }
+
+ if (!Config->get_periodic_safety_backups()) {
+ return 1;
+ }
+
+ if (_session) {
+ _session->maybe_write_autosave();
+ }
+
+ return 1;
+}