/* * Copyright (C) 2005-2007 Doug McLain * Copyright (C) 2005-2017 Tim Mayberry * Copyright (C) 2005-2019 Paul Davis * Copyright (C) 2005 Karsten Wiese * Copyright (C) 2005 Taybin Rutkin * Copyright (C) 2006-2015 David Robillard * Copyright (C) 2007-2012 Carl Hetherington * Copyright (C) 2008-2010 Sakari Bergen * Copyright (C) 2012-2019 Robin Gareus * Copyright (C) 2013-2015 Colin Fletcher * Copyright (C) 2013-2016 John Emmas * Copyright (C) 2013-2016 Nick Mainsbridge * Copyright (C) 2014-2018 Ben Loftis * Copyright (C) 2015 André Nusser * Copyright (C) 2016-2018 Len Ovens * Copyright (C) 2017 Johannes Mueller * * 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 #include #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" #ifdef WINDOWS_VST_SUPPORT #include #endif 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 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 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 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 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 p; get_state_files_in_directory (_session->session_directory().root_path(), p); vector 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& 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; }