cleanup a couple of audio file format names as reported by libsndfile
[ardour.git] / gtk2_ardour / ardour_ui.cc
index 32088800df228d4245a3d8523579b9a666741f34..2d4e94883494a1bf291d578e1b5bb2bbd489e667 100644 (file)
@@ -25,6 +25,7 @@
 #include <time.h>
 #include <cerrno>
 #include <fstream>
+#include <stdlib.h>
 
 #include <iostream>
 
@@ -58,6 +59,7 @@
 #include <ardour/audioengine.h>
 #include <ardour/playlist.h>
 #include <ardour/utils.h>
+#include <ardour/plugin.h>
 #include <ardour/audio_diskstream.h>
 #include <ardour/audiofilesource.h>
 #include <ardour/recent_sessions.h>
@@ -78,6 +80,7 @@ typedef uint64_t microseconds_t;
 #include "new_session_dialog.h"
 #include "about.h"
 #include "splash.h"
+#include "nag.h"
 #include "utils.h"
 #include "gui_thread.h"
 #include "theme_manager.h"
@@ -225,6 +228,8 @@ ARDOUR_UI::ARDOUR_UI (int *argcp, char **argvp[])
        ARDOUR::Diskstream::DiskOverrun.connect (mem_fun(*this, &ARDOUR_UI::disk_overrun_handler));
        ARDOUR::Diskstream::DiskUnderrun.connect (mem_fun(*this, &ARDOUR_UI::disk_underrun_handler));
 
+       ARDOUR::Plugin::PresetFileExists.connect (mem_fun(*this, &ARDOUR_UI::preset_file_exists_handler));
+
        /* handle dialog requests */
 
        ARDOUR::Session::Dialog.connect (mem_fun(*this, &ARDOUR_UI::session_dialog));
@@ -565,7 +570,7 @@ ARDOUR_UI::update_autosave ()
 {
         ENSURE_GUI_THREAD (mem_fun (*this, &ARDOUR_UI::update_autosave));
         
-        if (session->dirty()) {
+        if (session && session->dirty()) {
                 if (_autosave_connection.connected()) {
                         _autosave_connection.disconnect();
                 }
@@ -898,8 +903,14 @@ void
 ARDOUR_UI::update_buffer_load ()
 {
        char buf[64];
+       uint32_t c, p;
 
        if (session) {
+               c = session->capture_load ();
+               p = session->playback_load ();
+               
+               push_buffer_stats (c, p);
+
                snprintf (buf, sizeof (buf), _("Buffers p:%" PRIu32 "%% c:%" PRIu32 "%%"), 
                          session->playback_load(), session->capture_load());
                buffer_load_label.set_text (buf);
@@ -1150,6 +1161,7 @@ ARDOUR_UI::check_audioengine ()
                        MessageDialog msg (_("Ardour is not connected to JACK\n"
                                             "You cannot open or close sessions in this condition"));
                        pop_back_splash ();
+                       msg.set_position (WIN_POS_CENTER);
                        msg.run ();
                        return false;
                }
@@ -1172,7 +1184,7 @@ ARDOUR_UI::open_session ()
 
                /* ardour sessions are folders */
 
-               open_session_selector = new Gtk::FileChooserDialog (_("open session"), FILE_CHOOSER_ACTION_OPEN);
+               open_session_selector = new Gtk::FileChooserDialog (_("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);
@@ -1838,13 +1850,27 @@ ARDOUR_UI::snapshot_session ()
 
        prompter.set_name ("Prompter");
        prompter.add_button (Gtk::Stock::SAVE, Gtk::RESPONSE_ACCEPT);
+       prompter.set_title (_("Take Snapshot"));
        prompter.set_prompt (_("Name of New Snapshot"));
        prompter.set_initial_text (timebuf);
-       
+
+  again:
        switch (prompter.run()) {
        case RESPONSE_ACCEPT:
                prompter.get_result (snapname);
                if (snapname.length()){
+                       if (snapname.find ('/') != string::npos) {
+                               MessageDialog msg (_("To ensure compatibility with various systems\n"
+                                                    "snapshot names may not contain a '/' character"));
+                               msg.run ();
+                               goto again;
+                       }
+                       if (snapname.find ('\\') != string::npos) {
+                               MessageDialog msg (_("To ensure compatibility with various systems\n"
+                                                    "snapshot names may not contain a '\\' character"));
+                               msg.run ();
+                               goto again;
+                       }
                        save_state (snapname);
                }
                break;
@@ -1930,7 +1956,6 @@ ARDOUR_UI::transport_rec_enable_blink (bool onoff)
 
 void
 ARDOUR_UI::save_template ()
-
 {
        ArdourPrompter prompter (true);
        string name;
@@ -1940,6 +1965,7 @@ ARDOUR_UI::save_template ()
        }
 
        prompter.set_name (X_("Prompter"));
+       prompter.set_title (_("Save Mix Template"));
        prompter.set_prompt (_("Name for mix template:"));
        prompter.set_initial_text(session->name() + _("-template"));
        prompter.add_button (Gtk::Stock::SAVE, Gtk::RESPONSE_ACCEPT);
@@ -1961,6 +1987,9 @@ ARDOUR_UI::save_template ()
 void
 ARDOUR_UI::fontconfig_dialog ()
 {
+#if 0
+  /* this issue seems to have gone away with changes to font handling in GTK/Quartz
+   */
 #ifdef GTKOSX
        /* X11 users will always have fontconfig info around, but new GTK-OSX users 
           may not and it can take a while to build it. Warn them.
@@ -1984,6 +2013,7 @@ ARDOUR_UI::fontconfig_dialog ()
                msg.run ();
        }
 #endif
+#endif
 }
 
 void
@@ -2067,6 +2097,7 @@ ARDOUR_UI::ask_about_loading_existing_session (const Glib::ustring& session_path
        
        
        msg.set_name (X_("CleanupDialog"));
+       msg.set_title (_("Cleanup Unused Sources"));
        msg.set_wmclass (X_("existing_session"), "Ardour");
        msg.set_position (Gtk::WIN_POS_MOUSE);
        pop_back_splash ();
@@ -2200,7 +2231,7 @@ ARDOUR_UI::get_session_parameters (bool backend_audio_is_running, bool should_be
        Glib::ustring session_path;
        Glib::ustring template_name;
        int response;
-
+       
   begin:
        response = Gtk::RESPONSE_NONE;
 
@@ -2278,15 +2309,21 @@ ARDOUR_UI::get_session_parameters (bool backend_audio_is_running, bool should_be
                                response = Gtk::RESPONSE_REJECT;
                                goto try_again;
                        }
+                       
+                       /* hide the NSD while we start up the engine */
+
+                       new_session_dialog->hide ();
+                       flush_pending ();
                }
-               
+
                if (create_engine ()) {
 
                        backend_audio_error (!backend_audio_is_running, new_session_dialog);
                        flush_pending ();
 
                        new_session_dialog->set_existing_session (false);
-                       new_session_dialog->set_current_page (2);
+                       new_session_dialog->set_current_page (0); // new engine page
+                       new_session_dialog->engine_control.unset_interface_chosen ();
 
                        response = Gtk::RESPONSE_NONE;
                        goto try_again;
@@ -2317,27 +2354,53 @@ ARDOUR_UI::get_session_parameters (bool backend_audio_is_running, bool should_be
                        } else {
 
                                session_path = new_session_dialog->session_folder();
+
                        }
 
                        template_name = Glib::ustring();                        
                        switch (new_session_dialog->which_page()) {
 
                        case NewSessionDialog::OpenPage: 
-                       case NewSessionDialog::EnginePage:
                                goto loadit;
                                break;
 
+                       case NewSessionDialog::EnginePage:
+                               if (new_session_dialog->engine_control.interface_chosen() && !session_path.empty()) {
+                                       goto loadit;
+                               } else {
+                                       goto try_again;
+                               }
+                               break;
+
                        case NewSessionDialog::NewPage: /* nominally the "new" session creator, but could be in use for an old session */
                                
                                should_be_new = true;
-                               
+
+                               if (session_name.find ('/') != Glib::ustring::npos) {
+                                       MessageDialog msg (*new_session_dialog, _("To ensure compatibility with various systems\n"
+                                                        "session names may not contain a '/' character"));
+                                       msg.run ();
+                                       response = RESPONSE_NONE;
+                                       goto try_again;
+                               }
+
+                               if (session_name.find ('\\') != Glib::ustring::npos) {
+                                       MessageDialog msg (*new_session_dialog, _("To ensure compatibility with various systems\n"
+                                                        "session names may not contain a '\\' character"));
+                                       msg.run ();
+                                       response = RESPONSE_NONE;
+                                       goto try_again;
+                               }
+
                                //XXX This is needed because session constructor wants a 
                                //non-existant path. hopefully this will be fixed at some point.
-                               
+
                                session_path = Glib::build_filename (session_path, session_name);
 
                                if (Glib::file_test (session_path, Glib::FileTest (G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR))) {
 
+                                       new_session_dialog->hide ();
+
                                        if (ask_about_loading_existing_session (session_path)) {
                                                goto loadit;
                                        } else {
@@ -2398,7 +2461,9 @@ ARDOUR_UI::close_session ()
                return;
        }
 
-       unload_session (true);
+       if (unload_session (true)) {
+               return;
+       }
        
        get_session_parameters (true, false);
 }
@@ -2425,16 +2490,6 @@ ARDOUR_UI::load_session (const Glib::ustring& path, const Glib::ustring& snap_na
                goto out;
        }
 
-       /* if it already exists, we must have write access */
-
-       if (Glib::file_test (path.c_str(), Glib::FILE_TEST_EXISTS) && ::access (path.c_str(), W_OK)) {
-               MessageDialog msg (*editor, _("You do not have write access to this session.\n"
-                                             "This prevents the session from being loaded."));
-               pop_back_splash ();
-               msg.run ();
-               goto out;
-       }
-
        loading_message (_("Please wait while Ardour loads your session"));
 
        try {
@@ -2469,6 +2524,12 @@ ARDOUR_UI::load_session (const Glib::ustring& path, const Glib::ustring& snap_na
                goto out;
        }
 
+       /* this exception is also special */
+
+       catch (Session::SRMismatchRejected& err) {
+               goto out; /* just go back and reload something else, etc. */
+       }
+
        catch (...) {
 
                MessageDialog msg (string_compose(_("Session \"%1 (snapshot %2)\" did not load successfully"), path, snap_name),
@@ -2587,9 +2648,21 @@ ARDOUR_UI::show_about ()
                about->signal_response().connect(mem_fun (*this, &ARDOUR_UI::about_signal_response) );
        }
 
+       about->set_transient_for(*editor);
+
        about->show_all ();
 }
 
+void
+ARDOUR_UI::launch_chat ()
+{
+#ifdef __APPLE__
+       NagScreen::open_uri("http://webchat.freenode.net/?channels=ardour-osx");
+#else
+       NagScreen::open_uri("http://webchat.freenode.net/?channels=ardour");
+#endif
+}
+
 void
 ARDOUR_UI::hide_about ()
 {
@@ -2632,7 +2705,8 @@ ARDOUR_UI::hide_splash ()
 }
 
 void
-ARDOUR_UI::display_cleanup_results (Session::cleanup_report& rep, const gchar* list_title, const string & msg)
+ARDOUR_UI::display_cleanup_results (Session::cleanup_report& rep, const gchar* list_title, 
+                                   const string& plural_msg, const string& singular_msg)
 {
        size_t removed;
 
@@ -2687,18 +2761,32 @@ require some unused files to continue to exist."));
 
        dimage->set_alignment(ALIGN_LEFT, ALIGN_TOP);
 
-       if (rep.space < 1048576.0f) {
-               if (removed > 1) {
-                 txt.set_text (string_compose (msg, removed, _("files were"), session->path() + "dead_sounds", (float) rep.space / 1024.0f, "kilo"));
-               } else {
-                       txt.set_text (string_compose (msg, removed, _("file was"), session->path() + "dead_sounds", (float) rep.space / 1024.0f, "kilo"));
-               }
+
+       /* subst:
+          %1 - number of files removed
+          %2 - location of "dead_sounds"
+          %3 - size of files affected
+          %4 - prefix for "bytes" to produce sensible results (e.g. mega, kilo, giga)
+       */
+
+       const char* bprefix;
+       float space_adjusted;
+
+       if (rep.space < 1000000.0f) {
+               bprefix = X_("kilo");
+               space_adjusted = truncf((float)rep.space / 1000.0f);
+       } else if (rep.space < (1000000.0f * 1000)) {
+               bprefix = X_("mega");
+               space_adjusted = truncf((float)rep.space / (1000000.0f));
        } else {
-               if (removed > 1) {
-                       txt.set_text (string_compose (msg, removed, _("files were"), session->path() + "dead_sounds", (float) rep.space / 1048576.0f, "mega"));
-               } else {
-                       txt.set_text (string_compose (msg, removed, _("file was"), session->path() + "dead_sounds", (float) rep.space / 1048576.0f, "mega"));
-               }
+               bprefix = X_("giga");
+               space_adjusted = truncf((float)rep.space / (1000000.0f * 1000));
+       }
+
+       if (removed > 1) {
+               txt.set_text (string_compose (plural_msg, removed, session->path() + "dead_sounds", space_adjusted, bprefix));
+       } else {
+               txt.set_text (string_compose (singular_msg, removed, session->path() + "dead_sounds", space_adjusted, bprefix));
        }
 
        dhbox.pack_start (*dimage, true, false, 5);
@@ -2785,12 +2873,19 @@ After cleanup, unused audio files will be moved to a \
        display_cleanup_results (rep, 
                                 _("cleaned files"),
                                 _("\
-The following %1 %2 not in use and \n\
+The following %1 files were not in use and \n\
 have been moved to:\n\
-%3. \n\n\
+%2. \n\n\
 Flushing the wastebasket will \n\
 release an additional\n\
-%4 %5bytes of disk space.\n"
+%3 %4bytes of disk space.\n"),
+                                _("\
+The following file was not in use and \n       \
+has been moved to:\n                           \
+%2. \n\n\
+Flushing the wastebasket will \n\
+release an additional\n\
+%3 %4bytes of disk space.\n"
                                         ));
 
 }
@@ -2811,9 +2906,12 @@ ARDOUR_UI::flush_trash ()
 
        display_cleanup_results (rep, 
                                 _("deleted file"),
-                                _("The following %1 %2 deleted from\n\
-%3,\n\
-releasing %4 %5bytes of disk space"));
+                                _("The following %1 files were deleted from\n\
+%2,\n\
+releasing %3 %4bytes of disk space"),
+                                _("The following file was deleted from\n\
+%2,\n\
+releasing %3 %4bytes of disk space"));
 }
 
 void
@@ -2853,6 +2951,13 @@ ARDOUR_UI::add_route (Gtk::Window* float_window)
                return;
        }
 
+       string template_path = add_route_dialog->track_template();
+
+       if (!template_path.empty()) {
+         session->new_route_from_template (count, template_path);
+         return;
+       }
+       
        uint32_t input_chan = add_route_dialog->channels ();
        uint32_t output_chan;
        string name_template = add_route_dialog->name_template ();
@@ -2961,11 +3066,103 @@ ARDOUR_UI::xrun_handler(nframes_t where)
        }
 }
 
+bool
+ARDOUR_UI::preset_file_exists_handler ()
+{
+       /* if driven from another thread, say "do not overwrite" and show the user nothing.
+        */
+
+       if (!Gtkmm2ext::UI::instance()->caller_is_ui_thread()) {        \
+               return false;
+       }
+       
+       HBox* hbox = new HBox();
+       Image* image = new Image (Stock::DIALOG_QUESTION, ICON_SIZE_DIALOG);
+       Gtk::Dialog dialog (_("Preset Exists"), true, false);
+       Label  message (_("\
+A preset with this name already exists for this plugin.\n\
+\n\
+What you would like to do?\n"));
+       image->set_alignment(ALIGN_CENTER, ALIGN_TOP);
+       hbox->pack_start (*image, PACK_EXPAND_WIDGET, 12);
+       hbox->pack_end (message, PACK_EXPAND_PADDING, 12);
+       dialog.get_vbox()->pack_start(*hbox, PACK_EXPAND_PADDING, 6);
+       dialog.add_button (_("Overwrite the existing preset"), RESPONSE_ACCEPT);
+       dialog.add_button (_("Leave the existing preset alone"), RESPONSE_REJECT);
+       dialog.set_default_response (RESPONSE_ACCEPT);
+       dialog.set_position (WIN_POS_MOUSE);
+       dialog.set_type_hint (Gdk::WINDOW_TYPE_HINT_UTILITY); // need to make it float above the preset name dialog
+
+       message.show();
+       image->show();
+       hbox->show();
+
+       switch (dialog.run ()) {
+       case RESPONSE_ACCEPT:
+               return true;
+       default:
+               return false;
+       }
+}
+
+void
+ARDOUR_UI::push_buffer_stats (uint32_t capture, uint32_t playback)
+{
+       time_t now;
+       time (&now);
+
+       while (disk_buffer_stats.size() > 60) {
+               disk_buffer_stats.pop_front ();
+       }
+
+       disk_buffer_stats.push_back (DiskBufferStat (now, capture, playback));
+}
+
+void
+ARDOUR_UI::write_buffer_stats ()
+{
+       std::ofstream fout;
+       struct tm tm;
+       char buf[64];
+       char path[PATH_MAX+1];  int fd;
+
+       strcpy (path, "ardourBufferingXXXXXX");
+
+       if ((fd = mkstemp (path )) < 0) {
+               cerr << X_("cannot find temporary name for ardour buffer stats") << endl;
+               return;
+       }
+       
+       fout.open (path);
+       close (fd);
+
+       if (!fout) {
+               cerr << string_compose (X_("cannot open file %1 for ardour buffer stats"), path) << endl;
+               return;
+       }
+
+       for (list<DiskBufferStat>::iterator i = disk_buffer_stats.begin(); i != disk_buffer_stats.end(); ++i) {
+               localtime_r (&(*i).when, &tm);
+               strftime (buf, sizeof (buf), "%T", &tm);
+               fout << buf << ' ' << (*i).capture << ' ' << (*i).playback << endl;
+       }
+       
+       disk_buffer_stats.clear ();
+
+       fout.close ();
+
+       cerr << "Ardour buffering statistics can be found in: " << path << endl;
+       free (path);
+}
+
 void
 ARDOUR_UI::disk_overrun_handler ()
 {
+
        ENSURE_GUI_THREAD (mem_fun(*this, &ARDOUR_UI::disk_overrun_handler));
 
+       write_buffer_stats ();
+
        if (!have_disk_speed_dialog_displayed) {
                have_disk_speed_dialog_displayed = true;
                MessageDialog* msg = new MessageDialog (*editor, _("\
@@ -2982,8 +3179,11 @@ quickly enough to keep up with recording.\n"));
 void
 ARDOUR_UI::disk_underrun_handler ()
 {
+
        ENSURE_GUI_THREAD (mem_fun(*this, &ARDOUR_UI::disk_underrun_handler));
 
+       write_buffer_stats ();
+
        if (!have_disk_speed_dialog_displayed) {
                have_disk_speed_dialog_displayed = true;
                MessageDialog* msg = new MessageDialog (*editor,
@@ -3048,6 +3248,8 @@ what you would like to do.\n"));
        image->show();
        hbox->show();
 
+       pop_back_splash ();
+
        switch (dialog.run ()) {
        case RESPONSE_ACCEPT:
                return 1;