X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;f=gtk2_ardour%2Fardour_ui.cc;h=042706322284db23784cb7fe3dd1c8ad3108a046;hb=93cd0d17e4350958308dd1258d7e3aa0e5417292;hp=a81d817442ffa52a38782fb898170410fcc97217;hpb=21193c6fcab5160f4514b8f071e6cd288add033c;p=ardour.git diff --git a/gtk2_ardour/ardour_ui.cc b/gtk2_ardour/ardour_ui.cc index a81d817442..0427063222 100644 --- a/gtk2_ardour/ardour_ui.cc +++ b/gtk2_ardour/ardour_ui.cc @@ -28,6 +28,8 @@ #include #include +#include + #ifndef PLATFORM_WINDOWS #include #endif @@ -55,6 +57,8 @@ #include "pbd/file_utils.h" #include "pbd/localtime_r.h" #include "pbd/pthread_utils.h" +#include "pbd/replace_all.h" +#include "pbd/xml++.h" #include "gtkmm2ext/application.h" #include "gtkmm2ext/bindings.h" @@ -82,6 +86,7 @@ #include "ardour/session_route.h" #include "ardour/session_state_utils.h" #include "ardour/session_utils.h" +#include "ardour/source_factory.h" #include "ardour/slave.h" #include "ardour/system_exec.h" @@ -97,11 +102,13 @@ typedef uint64_t microseconds_t; #include "about.h" +#include "editing.h" #include "actions.h" #include "add_route_dialog.h" #include "ambiguous_file_dialog.h" #include "ardour_ui.h" #include "audio_clock.h" +#include "audio_region_view.h" #include "big_clock_window.h" #include "bundle_manager.h" #include "engine_dialog.h" @@ -128,6 +135,7 @@ typedef uint64_t microseconds_t; #include "rc_option_editor.h" #include "route_time_axis.h" #include "route_params_ui.h" +#include "save_as_dialog.h" #include "session_dialog.h" #include "session_metadata_dialog.h" #include "session_option_editor.h" @@ -151,51 +159,88 @@ using namespace PBD; using namespace Gtkmm2ext; using namespace Gtk; using namespace std; +using namespace Editing; ARDOUR_UI *ARDOUR_UI::theArdourUI = 0; sigc::signal ARDOUR_UI::Clock; sigc::signal ARDOUR_UI::CloseAllDialogs; +float ARDOUR_UI::ui_scale = 1.0; static bool ask_about_configuration_copy (string const & old_dir, string const & new_dir, int version) { MessageDialog msg (string_compose (_("%1 %2.x has discovered configuration files from %1 %3.x.\n\n" - "Would you like to copy the relevant files before starting to use the program?\n\n" + "Would you like these files to be copied and used for %1 %2.x?\n\n" "(This will require you to restart %1.)"), - PROGRAM_NAME, PROGRAM_VERSION, version), true); - - msg.add_button (Gtk::Stock::NO, Gtk::RESPONSE_NO); + PROGRAM_NAME, PROGRAM_VERSION, version), + false, /* no markup */ + Gtk::MESSAGE_INFO, + Gtk::BUTTONS_YES_NO, + true /* modal, though it hardly matters since it is the only window */ + ); + + msg.set_default_response (Gtk::RESPONSE_YES); msg.show_all (); - return (msg.run() == Gtk::RESPONSE_OK); + return (msg.run() == Gtk::RESPONSE_YES); } -ARDOUR_UI::ARDOUR_UI (int *argcp, char **argvp[], const char* localedir) +static void +libxml_generic_error_func (void* /* parsing_context*/, + const char* msg, + ...) +{ + va_list ap; + char buf[2048]; - : Gtkmm2ext::UI (PROGRAM_NAME, argcp, argvp) - , ui_config (new UIConfiguration) - , gui_object_state (new GUIObjectState) + va_start (ap, msg); + vsnprintf (buf, sizeof (buf), msg, ap); + error << buf << endmsg; + va_end (ap); +} + +static void +libxml_structured_error_func (void* /* parsing_context*/, + xmlErrorPtr err) +{ + string msg; - , primary_clock (new MainClock (X_("primary"), false, X_("transport"), true, true, true, false, true)) - , secondary_clock (new MainClock (X_("secondary"), false, X_("secondary"), true, true, false, false, true)) - - /* big clock */ + if (err->message) + msg = err->message; + replace_all (msg, "\n", ""); + + if (err->file && err->line) { + error << X_("XML error: ") << msg << " in " << err->file << " at line " << err->line; + + if (err->int2) { + error << ':' << err->int2; + } + } + error << endmsg; +} + + +ARDOUR_UI::ARDOUR_UI (int *argcp, char **argvp[], const char* localedir, UIConfiguration* uic) + + : Gtkmm2ext::UI (PROGRAM_NAME, argcp, argvp) + , ui_config (uic->post_gui_init ()) + , session_loaded (false) + , gui_object_state (new GUIObjectState) + , primary_clock (new MainClock (X_("primary"), X_("transport"), true )) + , secondary_clock (new MainClock (X_("secondary"), X_("secondary"), false)) , big_clock (new AudioClock (X_("bigclock"), false, "big", true, true, false, false)) , video_timeline(0) - - /* start of private members */ + , ignore_dual_punch (false) , editor (0) , mixer (0) , nsm (0) , _was_dirty (false) , _mixer_on_top (false) + , _initial_verbose_plugin_scan (false) , first_time_engine_run (true) - - /* transport */ - , roll_controllable (new TransportControllable ("transport roll", *this, TransportControllable::Roll)) , stop_controllable (new TransportControllable ("transport stop", *this, TransportControllable::Stop)) , goto_start_controllable (new TransportControllable ("transport goto start", *this, TransportControllable::GotoStart)) @@ -203,19 +248,21 @@ ARDOUR_UI::ARDOUR_UI (int *argcp, char **argvp[], const char* localedir) , auto_loop_controllable (new TransportControllable ("transport auto loop", *this, TransportControllable::AutoLoop)) , play_selection_controllable (new TransportControllable ("transport play selection", *this, TransportControllable::PlaySelection)) , rec_controllable (new TransportControllable ("transport rec-enable", *this, TransportControllable::RecordEnable)) - , auto_return_button (ArdourButton::led_default_elements) , follow_edits_button (ArdourButton::led_default_elements) , auto_input_button (ArdourButton::led_default_elements) - , auditioning_alert_button (_("Audition")) , solo_alert_button (_("Solo")) , feedback_alert_button (_("Feedback")) , error_alert_button ( ArdourButton::just_led_default_elements ) - , editor_meter(0) , editor_meter_peak_display() - + , session_selector_window (0) + , _numpad_locate_happening (false) + , _session_is_new (false) + , last_key_press_time (0) + , save_as_dialog (0) + , meterbridge (0) , speaker_config_window (X_("speaker-config"), _("Speaker Configuration")) , key_editor (X_("key-editor"), _("Key Bindings")) , rc_option_editor (X_("rc-options-editor"), _("Preferences")) @@ -231,12 +278,17 @@ ARDOUR_UI::ARDOUR_UI (int *argcp, char **argvp[], const char* localedir) , big_clock_window (X_("big-clock"), _("Big Clock"), boost::bind (&ARDOUR_UI::create_big_clock_window, this)) , audio_port_matrix (X_("audio-connection-manager"), _("Audio Connections"), boost::bind (&ARDOUR_UI::create_global_port_matrix, this, ARDOUR::DataType::AUDIO)) , midi_port_matrix (X_("midi-connection-manager"), _("MIDI Connections"), boost::bind (&ARDOUR_UI::create_global_port_matrix, this, ARDOUR::DataType::MIDI)) - + , video_server_process (0) + , splash (0) + , have_configure_timeout (false) + , last_configure_time (0) + , last_peak_grab (0) + , have_disk_speed_dialog_displayed (false) , _status_bar_visibility (X_("status-bar")) , _feedback_exists (false) , _log_not_acknowledged (LogLevelNone) { - Gtkmm2ext::init(localedir); + Gtkmm2ext::init (localedir); if (ARDOUR::handle_old_configuration_files (boost::bind (ask_about_configuration_copy, _1, _2, _3))) { MessageDialog msg (string_compose (_("Your configuration files were copied. You can now restart %1."), PROGRAM_NAME), true); @@ -245,33 +297,19 @@ ARDOUR_UI::ARDOUR_UI (int *argcp, char **argvp[], const char* localedir) _exit (0); } - - splash = 0; - - _numpad_locate_happening = false; - if (theArdourUI == 0) { theArdourUI = this; } + /* stop libxml from spewing to stdout/stderr */ + + xmlSetGenericErrorFunc (this, libxml_generic_error_func); + xmlSetStructuredErrorFunc (this, libxml_structured_error_func); + ui_config->ParameterChanged.connect (sigc::mem_fun (*this, &ARDOUR_UI::parameter_changed)); boost::function pc (boost::bind (&ARDOUR_UI::parameter_changed, this, _1)); ui_config->map_parameters (pc); - editor = 0; - mixer = 0; - meterbridge = 0; - editor = 0; - _session_is_new = false; - session_selector_window = 0; - last_key_press_time = 0; - video_server_process = 0; - open_session_selector = 0; - have_configure_timeout = false; - have_disk_speed_dialog_displayed = false; - session_loaded = false; - ignore_dual_punch = false; - roll_button.set_controllable (roll_controllable); stop_button.set_controllable (stop_controllable); goto_start_button.set_controllable (goto_start_controllable); @@ -289,9 +327,6 @@ ARDOUR_UI::ARDOUR_UI (int *argcp, char **argvp[], const char* localedir) rec_button.set_name ("transport recenable button"); midi_panic_button.set_name ("transport button"); - last_configure_time= 0; - last_peak_grab = 0; - ARDOUR::Diskstream::DiskOverrun.connect (forever_connections, MISSING_INVALIDATOR, boost::bind (&ARDOUR_UI::disk_overrun_handler, this), gui_context()); ARDOUR::Diskstream::DiskUnderrun.connect (forever_connections, MISSING_INVALIDATOR, boost::bind (&ARDOUR_UI::disk_underrun_handler, this), gui_context()); @@ -344,7 +379,7 @@ ARDOUR_UI::ARDOUR_UI (int *argcp, char **argvp[], const char* localedir) setup_gtk_ardour_enums (); setup_profile (); - SessionEvent::create_per_thread_pool ("GUI", 512); + SessionEvent::create_per_thread_pool ("GUI", 4096); /* we like keyboards */ @@ -406,7 +441,7 @@ ARDOUR_UI::ARDOUR_UI (int *argcp, char **argvp[], const char* localedir) /* Trigger setting up the color scheme and loading the GTK RC file */ ARDOUR_UI::config()->load_rc_file (false); - + _process_thread = new ProcessThread (); _process_thread->init (); @@ -445,12 +480,17 @@ ARDOUR_UI::engine_running () if (first_time_engine_run) { post_engine(); first_time_engine_run = false; - } - + } + + if (_session) { + _session->reset_xrun_count (); + } update_disk_space (); update_cpu_load (); + update_xrun_count (); update_sample_rate (AudioEngine::instance()->sample_rate()); update_timecode_format (); + update_peak_thread_work (); } void @@ -490,7 +530,7 @@ the audio backend and save the session."), PROGRAM_NAME); MessageDialog msg (*editor, msgstr); pop_back_splash (msg); msg.run (); - + if (free_reason) { free (const_cast (reason)); } @@ -511,13 +551,14 @@ ARDOUR_UI::post_engine () #endif ARDOUR::init_post_engine (); - + /* connect to important signals */ AudioEngine::instance()->Stopped.connect (forever_connections, MISSING_INVALIDATOR, boost::bind (&ARDOUR_UI::engine_stopped, this), gui_context()); AudioEngine::instance()->SampleRateChanged.connect (forever_connections, MISSING_INVALIDATOR, boost::bind (&ARDOUR_UI::update_sample_rate, this, _1), gui_context()); AudioEngine::instance()->BufferSizeChanged.connect (forever_connections, MISSING_INVALIDATOR, boost::bind (&ARDOUR_UI::update_sample_rate, this, _1), gui_context()); AudioEngine::instance()->Halted.connect_same_thread (halt_connection, boost::bind (&ARDOUR_UI::engine_halted, this, _1, false)); + AudioEngine::instance()->BecameSilent.connect (forever_connections, MISSING_INVALIDATOR, boost::bind (&ARDOUR_UI::audioengine_became_silent, this), gui_context()); _tooltips.enable(); @@ -530,7 +571,7 @@ ARDOUR_UI::post_engine () if (n) { _status_bar_visibility.set_state (*n); } - + check_memory_locking(); /* this is the first point at which all the keybindings are available */ @@ -601,9 +642,23 @@ ARDOUR_UI::~ARDOUR_UI () delete primary_clock; delete secondary_clock; delete _process_thread; + delete meterbridge; + delete editor; + delete mixer; + delete nsm; delete gui_object_state; FastMeter::flush_pattern_cache (); + PixFader::flush_pattern_cache (); } + +#ifndef NDEBUG + /* Small trick to flush main-thread event pool. + * Other thread-pools are destroyed at pthread_exit(), + * but tmain thread termination is too late to trigger Pool::~Pool() + */ + SessionEvent* ev = new SessionEvent (SessionEvent::SetTransportSpeed, SessionEvent::Clear, SessionEvent::Immediate, 0, 0); // get the pool reference, values don't matter since the event is never queued. + delete ev->event_pool(); +#endif } void @@ -762,19 +817,27 @@ ARDOUR_UI::check_announcements () #ifdef __APPLE__ _annc_filename = PROGRAM_NAME "_announcements_osx_"; +#elif defined PLATFORM_WINDOWS + _annc_filename = PROGRAM_NAME "_announcements_windows_"; #else _annc_filename = PROGRAM_NAME "_announcements_linux_"; #endif _annc_filename.append (VERSIONSTRING); + _announce_string = ""; + std::string path = Glib::build_filename (user_config_directory(), _annc_filename); - std::ifstream announce_file (path.c_str()); - if ( announce_file.fail() ) - _announce_string = ""; - else { - std::stringstream oss; - oss << announce_file.rdbuf(); - _announce_string = oss.str(); + FILE* fin = g_fopen (path.c_str(), "rb"); + if (fin) { + while (!feof (fin)) { + char tmp[1024]; + size_t len; + if ((len = fread (tmp, sizeof(char), 1024, fin)) == 0 || ferror (fin)) { + break; + } + _announce_string.append (tmp, len); + } + fclose (fin); } pingback (VERSIONSTRING, path); @@ -800,7 +863,7 @@ ARDOUR_UI::starting () /* we need to create this early because it may need to set the * audio backend end up. */ - + try { audio_midi_setup.get (true); } catch (...) { @@ -811,7 +874,16 @@ ARDOUR_UI::starting () if ((nsm_url = g_getenv ("NSM_URL")) != 0) { nsm = new NSM_Client; if (!nsm->init (nsm_url)) { - nsm->announce (PROGRAM_NAME, ":dirty:", "ardour3"); + /* the ardour executable may have different names: + * + * waf's obj.target for distro versions: eg ardour4, ardourvst4 + * Ardour4, Mixbus3 for bundled versions + full path on OSX & windows + * argv[0] does not apply since we need the wrapper-script (not the binary itself) + * + * The wrapper startup script should set the environment variable 'ARDOUR_SELF' + */ + const char *process_name = g_getenv ("ARDOUR_SELF"); + nsm->announce (PROGRAM_NAME, ":dirty:", process_name ? process_name : "ardour4"); unsigned int i = 0; // wait for announce reply from nsm server @@ -872,12 +944,14 @@ ARDOUR_UI::starting () } } else { - + if (brand_new_user) { + _initial_verbose_plugin_scan = true; ArdourStartup s; s.present (); main().run(); s.hide (); + _initial_verbose_plugin_scan = false; switch (s.response ()) { case Gtk::RESPONSE_OK: break; @@ -892,16 +966,16 @@ ARDOUR_UI::starting () ARDOUR::read_recent_sessions (rs); string path = Glib::build_filename (user_config_directory(), ".iknowaboutfreeversion"); - + if (!Glib::file_test (path, Glib::FILE_TEST_EXISTS) && !rs.empty()) { - + /* already used Ardour, have sessions ... warn about plugin state */ - + ArdourDialog d (_("Free/Demo Version Warning"), true); Label l; Button b (string_compose (_("Subscribe and support development of %1"), PROGRAM_NAME)); CheckButton c (_("Don't warn me about this again")); - + l.set_markup (string_compose (_("%1\n\n%2\n\n%3\n\n%4"), string_compose (_("This is a free/demo version of %1"), PROGRAM_NAME), _("It will not restore OR save any plugin settings"), @@ -910,26 +984,26 @@ ARDOUR_UI::starting () _("To get full access to updates without this limitation\n" "consider becoming a subscriber for a low cost every month."))); l.set_justify (JUSTIFY_CENTER); - + b.signal_clicked().connect (mem_fun(*this, &ARDOUR_UI::launch_subscribe)); - + d.get_vbox()->pack_start (l, true, true); d.get_vbox()->pack_start (b, false, false, 12); d.get_vbox()->pack_start (c, false, false, 12); - + d.add_button (_("Quit now"), RESPONSE_CANCEL); d.add_button (string_compose (_("Continue using %1"), PROGRAM_NAME), RESPONSE_OK); - + d.show_all (); c.signal_toggled().connect (sigc::hide_return (sigc::bind (sigc::ptr_fun (toggle_file_existence), path))); - + if (d.run () != RESPONSE_OK) { _exit (0); } } #endif - + /* go get a session */ const bool new_session_required = (ARDOUR_COMMAND_LINE::new_session || brand_new_user); @@ -998,7 +1072,7 @@ ARDOUR_UI::check_memory_locking () "runs out of memory. \n\n" "You can view the memory limit with 'ulimit -l', " "and it is normally controlled by %2"), - PROGRAM_NAME, + PROGRAM_NAME, #ifdef __FreeBSD__ X_("/etc/login.conf") #else @@ -1185,9 +1259,11 @@ void ARDOUR_UI::every_second () { update_cpu_load (); + update_xrun_count (); update_buffer_load (); update_disk_space (); update_timecode_format (); + update_peak_thread_work (); if (nsm && nsm->is_active ()) { nsm->check (); @@ -1324,10 +1400,16 @@ ARDOUR_UI::update_format () case RF64: s << _("RF64"); break; + case RF64_WAV: + s << _("RF64/WAV"); + break; + case MBWF: + s << _("MBWF"); + break; } s << " "; - + switch (_session->config.get_native_file_data_format ()) { case FormatFloat: s << _("32-float"); @@ -1345,6 +1427,29 @@ ARDOUR_UI::update_format () format_label.set_markup (s.str ()); } +void +ARDOUR_UI::update_xrun_count () +{ + char buf[64]; + + /* If this text is changed, the set_size_request_to_display_given_text call in ARDOUR_UI::resize_text_widgets + should also be changed. + */ + + if (_session) { + const unsigned int x = _session->get_xrun_count (); + if (x > 9999) { + snprintf (buf, sizeof (buf), _("X: >10K"), X_("red")); + } else { + snprintf (buf, sizeof (buf), _("X: %u"), x > 0 ? X_("red") : X_("green"), x); + } + } else { + snprintf (buf, sizeof (buf), _("X: ?"), X_("yellow")); + } + xrun_label.set_markup (buf); + set_tip (xrun_label, _("Audio dropouts. Shift+click to reset")); +} + void ARDOUR_UI::update_cpu_load () { @@ -1359,6 +1464,19 @@ ARDOUR_UI::update_cpu_load () cpu_load_label.set_markup (buf); } +void +ARDOUR_UI::update_peak_thread_work () +{ + char buf[64]; + const int c = SourceFactory::peak_work_queue_length (); + if (c > 0) { + snprintf (buf, sizeof (buf), _("PkBld: %d"), c >= 2 ? X_("red") : X_("green"), c); + peak_thread_work_label.set_markup (buf); + } else { + peak_thread_work_label.set_markup (X_("")); + } +} + void ARDOUR_UI::update_buffer_load () { @@ -1370,7 +1488,7 @@ ARDOUR_UI::update_buffer_load () /* If this text is changed, the set_size_request_to_display_given_text call in ARDOUR_UI::resize_text_widgets should also be changed. */ - + if (_session) { snprintf ( buf, sizeof (buf), @@ -1441,9 +1559,9 @@ ARDOUR_UI::update_disk_space() mins = frames / (fr * 60); frames -= mins * fr * 60; secs = frames / fr; - + bool const low = (hrs == 0 && mins <= 30); - + snprintf ( buf, sizeof(buf), _("Disk: %02dh:%02dm:%02ds"), @@ -1471,7 +1589,7 @@ ARDOUR_UI::update_timecode_format () } else { matching = true; } - + snprintf (buf, sizeof (buf), S_("Timecode|TC: %s"), matching ? X_("green") : X_("red"), Timecode::timecode_format_name (_session->config.get_timecode_format()).c_str()); @@ -1480,7 +1598,7 @@ ARDOUR_UI::update_timecode_format () } timecode_format_label.set_markup (buf); -} +} gint ARDOUR_UI::update_wall_clock () @@ -1699,55 +1817,47 @@ ARDOUR_UI::open_session () } - /* popup selector window */ + /* 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 (open_session_selector == 0) { - /* ardour sessions are folders */ - 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); - - if (_session) { - string session_parent_dir = Glib::path_get_dirname(_session->path()); - string::size_type last_dir_sep = session_parent_dir.rfind(G_DIR_SEPARATOR); - session_parent_dir = session_parent_dir.substr(0, last_dir_sep); - open_session_selector->set_current_folder(session_parent_dir); - } else { - open_session_selector->set_current_folder(Config->get_default_session_parent_dir()); - } + 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()); + } + try { + /* add_shortcut_folder throws an exception if the folder being added already has a shortcut */ +#ifdef GTKOSX + open_session_selector.add_shortcut_folder_uri("file:///Volumes"); +#endif string default_session_folder = Config->get_default_session_parent_dir(); - try { - /* add_shortcut_folder throws an exception if the folder being added already has a shortcut */ - open_session_selector->add_shortcut_folder (default_session_folder); - } - catch (Glib::Error & e) { - std::cerr << "open_session_selector->add_shortcut_folder (" << default_session_folder << ") threw Glib::Error " << e.what() << std::endl; - } + open_session_selector.add_shortcut_folder (default_session_folder); + } + catch (Glib::Error & 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); - open_session_selector->set_filter (session_filter); - } + 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); + open_session_selector.set_filter (session_filter); - int response = open_session_selector->run(); - open_session_selector->hide (); + int response = open_session_selector.run(); + open_session_selector.hide (); - switch (response) { - case RESPONSE_ACCEPT: - break; - default: - open_session_selector->hide(); + if (response == Gtk::RESPONSE_CANCEL) { return; } - open_session_selector->hide(); - string session_path = open_session_selector->get_filename(); + string session_path = open_session_selector.get_filename(); string path, name; bool isnew; @@ -1761,7 +1871,7 @@ ARDOUR_UI::open_session () void -ARDOUR_UI::session_add_mixed_track (const ChanCount& input, const ChanCount& output, RouteGroup* route_group, +ARDOUR_UI::session_add_mixed_track (const ChanCount& input, const ChanCount& output, RouteGroup* route_group, uint32_t how_many, const string& name_template, PluginInfoPtr instrument) { list > tracks; @@ -1773,7 +1883,7 @@ ARDOUR_UI::session_add_mixed_track (const ChanCount& input, const ChanCount& out try { tracks = _session->new_midi_track (input, output, instrument, ARDOUR::Normal, route_group, how_many, name_template); - + if (tracks.size() != how_many) { error << string_compose(P_("could not create %1 new mixed track", "could not create %1 new mixed tracks", how_many), how_many) << endmsg; } @@ -1788,7 +1898,7 @@ restart with more ports."), PROGRAM_NAME)); msg.run (); } } - + void ARDOUR_UI::session_add_midi_route (bool disk, RouteGroup* route_group, uint32_t how_many, const string& name_template, PluginInfoPtr instrument) @@ -1825,7 +1935,7 @@ ARDOUR_UI::session_add_audio_route ( tracks = _session->new_audio_track (input_channels, output_channels, mode, route_group, how_many, name_template); if (tracks.size() != how_many) { - error << string_compose (P_("could not create %1 new audio track", "could not create %1 new audio tracks", how_many), how_many) + error << string_compose (P_("could not create %1 new audio track", "could not create %1 new audio tracks", how_many), how_many) << endmsg; } @@ -1894,9 +2004,9 @@ ARDOUR_UI::transport_goto_wallclock () time (&now); localtime_r (&now, &tmnow); - - int frame_rate = _session->frame_rate(); - + + framecnt_t frame_rate = _session->frame_rate(); + if (frame_rate == 0) { /* no frame rate available */ return; @@ -1952,7 +2062,7 @@ ARDOUR_UI::transport_stop () /** Check if any tracks are record enabled. If none are, record enable all of them. * @return true if track record-enabled status was changed, false otherwise. - */ + */ bool ARDOUR_UI::trx_record_enable_all_tracks () { @@ -1975,7 +2085,7 @@ ARDOUR_UI::trx_record_enable_all_tracks () if (none_record_enabled) { _session->set_record_enabled (rl, true, Session::rt_cleanup); - } + } return none_record_enabled; } @@ -2056,7 +2166,7 @@ ARDOUR_UI::transport_roll () /* stop loop playback but keep rolling */ _session->request_play_loop (false, false); } - } + } } else if (_session->get_play_range () ) { /* stop playing a range if we currently are */ @@ -2078,7 +2188,6 @@ ARDOUR_UI::get_smart_mode() const void ARDOUR_UI::toggle_roll (bool with_abort, bool roll_out_of_bounded_mode) { - if (!_session) { return; } @@ -2163,7 +2272,7 @@ ARDOUR_UI::toggle_session_auto_loop () _session->request_play_loop (true, true); } } - + //show the loop markers looploc->set_hidden (false, this); } @@ -2220,9 +2329,9 @@ ARDOUR_UI::transport_forward (int option) if (!_session) { return; } - + float current_transport_speed = _session->transport_speed(); - + if (current_transport_speed <= 0.0f) { switch (option) { case 0: @@ -2341,7 +2450,7 @@ ARDOUR_UI::update_clocks () if (!_session) return; if (editor && !editor->dragging_playhead()) { - Clock (_session->audible_frame(), false, editor->get_preferred_edit_position()); /* EMIT_SIGNAL */ + Clock (_session->audible_frame(), false, editor->get_preferred_edit_position (EDIT_IGNORE_PHEAD)); /* EMIT_SIGNAL */ } } @@ -2361,6 +2470,103 @@ ARDOUR_UI::stop_clocking () clock_signal_connection.disconnect (); } +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 (!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); + + if (sa.include_media && sa.copy_media) { + + Gtk::Label label; + Gtk::ProgressBar progress_bar; + + 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. + */ + + ScopedConnection c; + + 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 (); + } + + if (!sa.include_media) { + unload_session (false); + load_session (sa.final_session_folder_name, sa.new_name); + } +} + /** Ask the user for the name of a new snapshot and then take it. */ @@ -2491,7 +2697,7 @@ ARDOUR_UI::rename_session () } } } - + break; } @@ -2737,7 +2943,7 @@ ARDOUR_UI::get_session_parameters (bool quit_on_cancel, bool should_be_new, stri 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 + * treat a non-dirty session this way, so that it stays visible * as we bring up the new session dialog. */ @@ -2765,7 +2971,7 @@ ARDOUR_UI::get_session_parameters (bool quit_on_cancel, bool should_be_new, stri session_name = basename_nosuffix (ARDOUR_COMMAND_LINE::session_name); session_path = ARDOUR_COMMAND_LINE::session_name; - + if (!session_path.empty()) { if (Glib::file_test (session_path.c_str(), Glib::FILE_TEST_EXISTS)) { if (Glib::file_test (session_path.c_str(), Glib::FILE_TEST_IS_REGULAR)) { @@ -2801,7 +3007,7 @@ ARDOUR_UI::get_session_parameters (bool quit_on_cancel, bool should_be_new, stri session_name = ""; session_dialog.clear_given (); } - + if (should_be_new || session_name.empty()) { /* need the dialog to get info from user */ @@ -2829,9 +3035,9 @@ ARDOUR_UI::get_session_parameters (bool quit_on_cancel, bool should_be_new, stri } /* 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 (); @@ -2840,22 +3046,22 @@ ARDOUR_UI::get_session_parameters (bool quit_on_cancel, bool should_be_new, stri } 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) @@ -2865,20 +3071,20 @@ ARDOUR_UI::get_session_parameters (bool quit_on_cancel, bool should_be_new, stri #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" @@ -2889,7 +3095,7 @@ ARDOUR_UI::get_session_parameters (bool quit_on_cancel, bool should_be_new, stri continue; } } - + if (Glib::file_test (session_path, Glib::FileTest (G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR))) { @@ -2990,7 +3196,7 @@ ARDOUR_UI::load_session (const std::string& path, const std::string& snap_name, if (_session) { unload_status = unload_session (); - + if (unload_status < 0) { goto out; } else if (unload_status > 0) { @@ -3034,7 +3240,26 @@ ARDOUR_UI::load_session (const std::string& path, const std::string& snap_name, } goto out; } + catch (SessionException e) { + MessageDialog msg (string_compose( + _("Session \"%1 (snapshot %2)\" did not load successfully: %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( @@ -3048,6 +3273,9 @@ ARDOUR_UI::load_session (const std::string& path, const std::string& snap_name, msg.set_position (Gtk::WIN_POS_CENTER); pop_back_splash (msg); msg.present (); + + dump_errors (cerr); + (void) msg.run (); msg.hide (); @@ -3075,7 +3303,7 @@ ARDOUR_UI::load_session (const std::string& path, const std::string& snap_name, (void) msg.run (); msg.hide (); } - + /* Now the session been created, add the transport controls */ new_session->add_controllable(roll_controllable); @@ -3132,9 +3360,20 @@ ARDOUR_UI::build_session (const std::string& path, const std::string& snap_name, new_session = new Session (*AudioEngine::instance(), path, snap_name, &bus_profile); } + catch (SessionException e) { + dump_errors (cerr); + 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 (...) { - + dump_errors (cerr); 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; @@ -3490,46 +3729,80 @@ ARDOUR_UI::flush_trash () display_cleanup_results (rep, _("deleted file"), true); } +void +ARDOUR_UI::cleanup_peakfiles () +{ + if (_session == 0) { + /* shouldn't happen: menu item is insensitive */ + return; + } + + if (! _session->can_cleanup_peakfiles ()) { + return; + } + + // get all region-views in this session + RegionSelection rs; + TrackViewList empty; + empty.clear(); + editor->get_regions_after(rs, (framepos_t) 0, empty); + std::list views = rs.by_layer(); + + // remove displayed audio-region-views waveforms + for (list::iterator i = views.begin(); i != views.end(); ++i) { + AudioRegionView* arv = dynamic_cast (*i); + if (!arv) { continue ; } + arv->delete_waves(); + } + + // cleanup peak files: + // - stop pending peakfile threads + // - close peakfiles if any + // - remove peak dir in session + // - setup peakfiles (background thread) + _session->cleanup_peakfiles (); + + // re-add waves to ARV + for (list::iterator i = views.begin(); i != views.end(); ++i) { + AudioRegionView* arv = dynamic_cast (*i); + if (!arv) { continue ; } + arv->create_waves(); + } +} + void ARDOUR_UI::setup_order_hint (AddRouteDialog::InsertAt place) { - uint32_t order_hint = 0; + uint32_t order_hint = UINT32_MAX; + + if (editor->get_selection().tracks.empty()) { + return; + } /* - we want the new routes to have their order keys set starting from + we want the new routes to have their order keys set starting from the highest order key in the selection + 1 (if available). */ - if (place == AddRouteDialog::MixerSelection) { - for (RouteUISelection::iterator s = mixer->selection().routes.begin(); s != mixer->selection().routes.end(); ++s) { - if ((*s)->route()->order_key() > order_hint) { - order_hint = (*s)->route()->order_key(); - } - } - if (!mixer->selection().routes.empty()) { + if (place == AddRouteDialog::AfterSelection) { + RouteTimeAxisView *rtav = dynamic_cast (editor->get_selection().tracks.back()); + if (rtav) { + order_hint = rtav->route()->order_key(); order_hint++; - } else { - return; - } - - } else if (place == AddRouteDialog::EditorSelection){ - for (TrackSelection::iterator s = editor->get_selection().tracks.begin(); s != editor->get_selection().tracks.end(); ++s) { - RouteTimeAxisView* tav = dynamic_cast (*s); - if (tav && tav->route() && tav->route()->order_key() > order_hint) { - order_hint = tav->route()->order_key(); - } } - - if (!editor->get_selection().tracks.empty()) { - order_hint++; - } else { - return; + } else if (place == AddRouteDialog::BeforeSelection) { + RouteTimeAxisView *rtav = dynamic_cast (editor->get_selection().tracks.front()); + if (rtav) { + order_hint = rtav->route()->order_key(); } - } else if (place == AddRouteDialog::First) { order_hint = 0; } else { - /** AddRouteDialog::Last + /* leave order_hint at UINT32_MAX */ + } + + if (order_hint == UINT32_MAX) { + /** AddRouteDialog::Last or selection with first/last not a RouteTimeAxisView * not setting an order hint will place new routes last. */ return; @@ -3740,7 +4013,7 @@ ARDOUR_UI::start_video_server (Gtk::Window* float_window, bool popup_msg) Config->set_video_advanced_setup(false); } else { std::ostringstream osstream; - osstream << "http://localhost:" << video_server_dialog->get_listenport() << "/"; + osstream << "http://127.0.0.1:" << video_server_dialog->get_listenport() << "/"; Config->set_video_server_url(osstream.str()); Config->set_video_server_docroot(icsd_docroot); Config->set_video_advanced_setup(true); @@ -4002,7 +4275,10 @@ ARDOUR_UI::keyboard_settings () const void ARDOUR_UI::create_xrun_marker (framepos_t where) { - editor->mouse_add_new_marker (where, false, true); + if (_session) { + Location *location = new Location (*_session, where, where, _("xrun"), Location::IsMark); + _session->locations()->add (location); + } } void @@ -4054,6 +4330,7 @@ quickly enough to keep up with recording.\n"), PROGRAM_NAME)); static MessageDialog *scan_dlg = NULL; static ProgressBar *scan_pbar = NULL; static HBox *scan_tbox = NULL; +static Gtk::Button *scan_timeout_button; void ARDOUR_UI::cancel_plugin_scan () @@ -4065,7 +4342,7 @@ void ARDOUR_UI::cancel_plugin_timeout () { PluginManager::instance().cancel_plugin_timeout(); - scan_tbox->hide(); + scan_timeout_button->set_sensitive (false); } void @@ -4075,10 +4352,13 @@ ARDOUR_UI::plugin_scan_timeout (int timeout) return; } if (timeout > 0) { + scan_pbar->set_sensitive (false); + scan_timeout_button->set_sensitive (true); scan_pbar->set_fraction ((float) timeout / (float) Config->get_vst_scan_timeout()); scan_tbox->show(); } else { - scan_tbox->hide(); + scan_pbar->set_sensitive (false); + scan_timeout_button->set_sensitive (false); } gui_idle_handler(); } @@ -4091,7 +4371,7 @@ ARDOUR_UI::plugin_scan_dialog (std::string type, std::string plugin, bool can_ca } const bool cancelled = PluginManager::instance().cancelled(); - if (type != X_("closeme") && !ui_config->get_show_plugin_scan_window()) { + if (type != X_("closeme") && (!ui_config->get_show_plugin_scan_window()) && !_initial_verbose_plugin_scan) { if (cancelled && scan_dlg->is_mapped()) { scan_dlg->hide(); gui_idle_handler(); @@ -4103,7 +4383,6 @@ ARDOUR_UI::plugin_scan_dialog (std::string type, std::string plugin, bool can_ca } static Gtk::Button *cancel_button; - static Gtk::Button *timeout_button; if (!scan_dlg) { scan_dlg = new MessageDialog("", false, MESSAGE_INFO, BUTTONS_NONE); // TODO manage VBox* vbox = scan_dlg->get_vbox(); @@ -4119,10 +4398,10 @@ ARDOUR_UI::plugin_scan_dialog (std::string type, std::string plugin, bool can_ca scan_tbox = manage( new HBox() ); - timeout_button = manage(new Gtk::Button(_("Stop Timeout"))); - timeout_button->set_name ("EditorGTKButton"); - timeout_button->signal_clicked().connect ( mem_fun (*this, &ARDOUR_UI::cancel_plugin_timeout) ); - timeout_button->show(); + scan_timeout_button = manage(new Gtk::Button(_("Stop Timeout"))); + scan_timeout_button->set_name ("EditorGTKButton"); + scan_timeout_button->signal_clicked().connect ( mem_fun (*this, &ARDOUR_UI::cancel_plugin_timeout) ); + scan_timeout_button->show(); scan_pbar = manage(new ProgressBar()); scan_pbar->set_orientation(Gtk::PROGRESS_RIGHT_TO_LEFT); @@ -4130,7 +4409,7 @@ ARDOUR_UI::plugin_scan_dialog (std::string type, std::string plugin, bool can_ca scan_pbar->show(); scan_tbox->pack_start (*scan_pbar, PACK_EXPAND_WIDGET, 4); - scan_tbox->pack_start (*timeout_button, PACK_SHRINK, 4); + scan_tbox->pack_start (*scan_timeout_button, PACK_SHRINK, 4); scan_dlg->get_vbox()->pack_start (*scan_tbox, PACK_SHRINK, 4); } @@ -4138,13 +4417,14 @@ ARDOUR_UI::plugin_scan_dialog (std::string type, std::string plugin, bool can_ca assert(scan_dlg && scan_tbox && cancel_button); if (type == X_("closeme")) { + scan_tbox->hide(); scan_dlg->hide(); } else { scan_dlg->set_message(type + ": " + Glib::path_get_basename(plugin)); scan_dlg->show(); } if (!can_cancel || !cancelled) { - scan_tbox->hide(); + scan_timeout_button->set_sensitive(false); } cancel_button->set_sensitive(can_cancel && !cancelled); @@ -4279,7 +4559,7 @@ ARDOUR_UI::disconnect_from_engine () */ halt_connection.disconnect (); - + if (AudioEngine::instance()->stop ()) { MessageDialog msg (*editor, _("Could not disconnect from Audio/MIDI engine")); msg.run (); @@ -4287,7 +4567,7 @@ ARDOUR_UI::disconnect_from_engine () } else { AudioEngine::instance()->Halted.connect_same_thread (halt_connection, boost::bind (&ARDOUR_UI::engine_halted, this, _1, false)); } - + update_sample_rate (0); return 0; } @@ -4306,7 +4586,7 @@ ARDOUR_UI::reconnect_to_engine () } return -1; } - + update_sample_rate (0); return 0; } @@ -4324,13 +4604,13 @@ void ARDOUR_UI::update_transport_clocks (framepos_t pos) { if (ui_config->get_primary_clock_delta_edit_cursor()) { - primary_clock->set (pos, false, editor->get_preferred_edit_position()); + primary_clock->set (pos, false, editor->get_preferred_edit_position (EDIT_IGNORE_PHEAD)); } else { primary_clock->set (pos); } if (ui_config->get_secondary_clock_delta_edit_cursor()) { - secondary_clock->set (pos, false, editor->get_preferred_edit_position()); + secondary_clock->set (pos, false, editor->get_preferred_edit_position (EDIT_IGNORE_PHEAD)); } else { secondary_clock->set (pos); } @@ -4395,7 +4675,7 @@ ARDOUR_UI::store_clock_modes () for (vector::iterator x = AudioClock::clocks.begin(); x != AudioClock::clocks.end(); ++x) { XMLNode* child = new XMLNode (X_("Clock")); - + child->add_property (X_("name"), (*x)->name()); child->add_property (X_("mode"), enum_2_string ((*x)->mode())); child->add_property (X_("on"), ((*x)->off() ? X_("no") : X_("yes"))); @@ -4670,7 +4950,7 @@ ARDOUR_UI::transport_numpad_event (int num) if ( _numpad_locate_happening ) { _pending_locate_num = _pending_locate_num*10 + num; } else { - switch (num) { + switch (num) { case 0: toggle_roll(false, false); break; case 1: transport_rewind(1); break; case 2: transport_forward(1); break; @@ -4690,3 +4970,66 @@ ARDOUR_UI::set_flat_buttons () { CairoWidget::set_flat_buttons( config()->get_flat_buttons() ); } + +void +ARDOUR_UI::audioengine_became_silent () +{ + MessageDialog msg (string_compose (_("This is a free/demo copy of %1. It has just switched to silent mode."), PROGRAM_NAME), + true, + Gtk::MESSAGE_WARNING, + Gtk::BUTTONS_NONE, + true); + + msg.set_title (string_compose (_("%1 is now silent"), PROGRAM_NAME)); + + Gtk::Label pay_label (string_compose (_("Please consider paying for a copy of %1 - you can pay whatever you want."), PROGRAM_NAME)); + Gtk::Label subscribe_label (_("Better yet become a subscriber - subscriptions start at US$1 per month.")); + Gtk::Button pay_button (_("Pay for a copy (via the web)")); + Gtk::Button subscribe_button (_("Become a subscriber (via the web)")); + Gtk::HBox pay_button_box; + Gtk::HBox subscribe_button_box; + + pay_button_box.pack_start (pay_button, true, false); + subscribe_button_box.pack_start (subscribe_button, true, false); + + bool (*openuri)(const char*) = PBD::open_uri; /* this forces selection of the const char* variant of PBD::open_uri(), which we need to avoid ambiguity below */ + + pay_button.signal_clicked().connect (sigc::hide_return (sigc::bind (sigc::ptr_fun (openuri), (const char*) "https://ardour.org/download"))); + subscribe_button.signal_clicked().connect (sigc::hide_return (sigc::bind (sigc::ptr_fun (openuri), (const char*) "https://community.ardour.org/s/subscribe"))); + + msg.get_vbox()->pack_start (pay_label); + msg.get_vbox()->pack_start (pay_button_box); + msg.get_vbox()->pack_start (subscribe_label); + msg.get_vbox()->pack_start (subscribe_button_box); + + msg.get_vbox()->show_all (); + + msg.add_button (_("Remain silent"), Gtk::RESPONSE_CANCEL); + msg.add_button (_("Save and quit"), Gtk::RESPONSE_NO); + msg.add_button (_("Give me more time"), Gtk::RESPONSE_YES); + + int r = msg.run (); + + switch (r) { + case Gtk::RESPONSE_YES: + AudioEngine::instance()->reset_silence_countdown (); + break; + + case Gtk::RESPONSE_NO: + /* save and quit */ + save_state_canfail (""); + exit (0); + break; + + case Gtk::RESPONSE_CANCEL: + default: + /* don't reset, save session and exit */ + break; + } +} + +void +ARDOUR_UI::hide_application () +{ + Application::instance ()-> hide (); +}