diff options
author | Paul Davis <paul@linuxaudiosystems.com> | 2019-09-23 14:49:06 -0600 |
---|---|---|
committer | Paul Davis <paul@linuxaudiosystems.com> | 2019-09-23 14:49:06 -0600 |
commit | 5beeca2e95a7ea70a4225eaca979179649cb2e90 (patch) | |
tree | 832643fc664d4d82d059dbebb7bd658fd79b9a59 /gtk2_ardour/ardour_ui_session.cc | |
parent | 9c0beeb7591302747eee6e28c448314313f8d54a (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.cc | 1278 |
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; +} |