Name threads on Linux.
[dcpomatic.git] / src / tools / dcpomatic.cc
index c193366312af286b18908724a9f3111438762703..2fd8edc3dae56280b10f4a44f1e7f174bc69936b 100644 (file)
@@ -1,5 +1,5 @@
 /*
-    Copyright (C) 2012-2016 Carl Hetherington <cth@carlh.net>
+    Copyright (C) 2012-2018 Carl Hetherington <cth@carlh.net>
 
     This file is part of DCP-o-matic.
 
@@ -25,7 +25,7 @@
 #include "wx/film_viewer.h"
 #include "wx/film_editor.h"
 #include "wx/job_manager_view.h"
-#include "wx/config_dialog.h"
+#include "wx/full_config_dialog.h"
 #include "wx/wx_util.h"
 #include "wx/film_name_location_dialog.h"
 #include "wx/wx_signal_manager.h"
@@ -42,6 +42,7 @@
 #include "wx/templates_dialog.h"
 #include "wx/nag_dialog.h"
 #include "wx/export_dialog.h"
+#include "wx/paste_dialog.h"
 #include "lib/film.h"
 #include "lib/config.h"
 #include "lib/util.h"
 #include "lib/dcpomatic_socket.h"
 #include "lib/hints.h"
 #include "lib/dcp_content.h"
-#include "lib/ffmpeg_transcoder.h"
+#include "lib/ffmpeg_encoder.h"
 #include "lib/transcode_job.h"
+#include "lib/dkdm_wrapper.h"
+#include "lib/audio_content.h"
+#include "lib/subtitle_content.h"
 #include <dcp/exceptions.h>
 #include <dcp/raw_convert.h>
 #include <wx/generic/aboutdlgg.h>
@@ -172,11 +176,15 @@ private:
        wxMessageDialog* _dialog;
 };
 
-#define ALWAYS                       0x0
-#define NEEDS_FILM                   0x1
-#define NOT_DURING_DCP_CREATION      0x2
-#define NEEDS_CPL                    0x4
-#define NEEDS_SELECTED_VIDEO_CONTENT 0x8
+#define ALWAYS                        0x0
+#define NEEDS_FILM                    0x1
+#define NOT_DURING_DCP_CREATION       0x2
+#define NEEDS_CPL                     0x4
+#define NEEDS_SINGLE_SELECTED_CONTENT 0x8
+#define NEEDS_SELECTED_CONTENT        0x10
+#define NEEDS_SELECTED_VIDEO_CONTENT  0x20
+#define NEEDS_CLIPBOARD               0x40
+#define NEEDS_ENCRYPTION              0x80
 
 map<wxMenuItem*, int> menu_items;
 
@@ -189,7 +197,9 @@ enum {
        ID_file_duplicate_and_open,
        ID_file_history,
        /* Allow spare IDs after _history for the recent files list */
-       ID_content_scale_to_fit_width = 100,
+       ID_edit_copy = 100,
+       ID_edit_paste,
+       ID_content_scale_to_fit_width,
        ID_content_scale_to_fit_height,
        ID_jobs_make_dcp,
        ID_jobs_make_dcp_batch,
@@ -266,6 +276,8 @@ public:
                Bind (wxEVT_MENU, boost::bind (&DOMFrame::file_duplicate_and_open, this), ID_file_duplicate_and_open);
                Bind (wxEVT_MENU, boost::bind (&DOMFrame::file_history, this, _1),        ID_file_history, ID_file_history + HISTORY_SIZE);
                Bind (wxEVT_MENU, boost::bind (&DOMFrame::file_exit, this),               wxID_EXIT);
+               Bind (wxEVT_MENU, boost::bind (&DOMFrame::edit_copy, this),               ID_edit_copy);
+               Bind (wxEVT_MENU, boost::bind (&DOMFrame::edit_paste, this),              ID_edit_paste);
                Bind (wxEVT_MENU, boost::bind (&DOMFrame::edit_preferences, this),        wxID_PREFERENCES);
                Bind (wxEVT_MENU, boost::bind (&DOMFrame::content_scale_to_fit_width, this), ID_content_scale_to_fit_width);
                Bind (wxEVT_MENU, boost::bind (&DOMFrame::content_scale_to_fit_height, this), ID_content_scale_to_fit_height);
@@ -307,21 +319,35 @@ public:
                set_menu_sensitivity ();
 
                _film_editor->FileChanged.connect (bind (&DOMFrame::file_changed, this, _1));
+               _film_editor->content_panel()->SelectionChanged.connect (boost::bind (&DOMFrame::set_menu_sensitivity, this));
                file_changed ("");
 
                JobManager::instance()->ActiveJobsChanged.connect (boost::bind (&DOMFrame::set_menu_sensitivity, this));
 
                overall_panel->SetSizer (main_sizer);
 
-               wxAcceleratorEntry accel[2];
+#ifdef __WXOSX__
+               int accelerators = 3;
+#else
+               int accelerators = 2;
+#endif
+               wxAcceleratorEntry* accel = new wxAcceleratorEntry[accelerators];
                accel[0].Set (wxACCEL_CTRL, static_cast<int>('A'), ID_add_file);
                accel[1].Set (wxACCEL_NORMAL, WXK_DELETE, ID_remove);
+#ifdef __WXOSX__
+               accel[2].Set (wxACCEL_CTRL, static_cast<int>('W'), wxID_EXIT);
+#endif
                Bind (wxEVT_MENU, boost::bind (&ContentPanel::add_file_clicked, _film_editor->content_panel()), ID_add_file);
                Bind (wxEVT_MENU, boost::bind (&DOMFrame::remove_clicked, this, _1), ID_remove);
-               wxAcceleratorTable accel_table (2, accel);
+               wxAcceleratorTable accel_table (accelerators, accel);
                SetAcceleratorTable (accel_table);
+               delete[] accel;
 
                UpdateChecker::instance()->StateChanged.connect (boost::bind (&DOMFrame::update_checker_state_changed, this));
+
+#ifdef DCPOMATIC_LINUX
+               pthread_setname_np(pthread_self(), "gui");
+#endif
        }
 
        void remove_clicked (wxCommandEvent& ev)
@@ -365,7 +391,7 @@ public:
        catch (std::exception& e) {
                wxString p = std_to_wx (file.string ());
                wxCharBuffer b = p.ToUTF8 ();
-               error_dialog (this, wxString::Format (_("Could not open film at %s (%s)"), p.data(), std_to_wx (e.what()).data()));
+               error_dialog (this, wxString::Format (_("Could not open film at %s"), p.data()), std_to_wx (e.what()));
        }
 
        void set_film (shared_ptr<Film> film)
@@ -379,6 +405,7 @@ public:
                if (_film->directory()) {
                        Config::instance()->add_to_history (_film->directory().get());
                }
+               _film->Changed.connect (boost::bind (&DOMFrame::set_menu_sensitivity, this));
        }
 
        shared_ptr<Film> film () const {
@@ -496,10 +523,41 @@ private:
                Close (false);
        }
 
+       void edit_copy ()
+       {
+               ContentList const sel = _film_editor->content_panel()->selected();
+               DCPOMATIC_ASSERT (sel.size() == 1);
+               _clipboard = sel.front()->clone();
+       }
+
+       void edit_paste ()
+       {
+               DCPOMATIC_ASSERT (_clipboard);
+
+               PasteDialog* d = new PasteDialog (this, static_cast<bool>(_clipboard->video), static_cast<bool>(_clipboard->audio), static_cast<bool>(_clipboard->subtitle));
+               if (d->ShowModal() == wxID_OK) {
+                       BOOST_FOREACH (shared_ptr<Content> i, _film_editor->content_panel()->selected()) {
+                               if (d->video() && i->video) {
+                                       DCPOMATIC_ASSERT (_clipboard->video);
+                                       i->video->take_settings_from (_clipboard->video);
+                               }
+                               if (d->audio() && i->audio) {
+                                       DCPOMATIC_ASSERT (_clipboard->audio);
+                                       i->audio->take_settings_from (_clipboard->audio);
+                               }
+                               if (d->subtitle() && i->subtitle) {
+                                       DCPOMATIC_ASSERT (_clipboard->subtitle);
+                                       i->subtitle->take_settings_from (_clipboard->subtitle);
+                               }
+                       }
+               }
+               d->Destroy ();
+       }
+
        void edit_preferences ()
        {
                if (!_config_dialog) {
-                       _config_dialog = create_config_dialog ();
+                       _config_dialog = create_full_config_dialog ();
                }
                _config_dialog->Show (this);
        }
@@ -575,9 +633,9 @@ private:
                        _film->write_metadata ();
                        _film->make_dcp ();
                } catch (BadSettingError& e) {
-                       error_dialog (this, wxString::Format (_("Bad setting for %s (%s)"), std_to_wx(e.setting()).data(), std_to_wx(e.what()).data()));
+                       error_dialog (this, wxString::Format (_("Bad setting for %s."), std_to_wx(e.setting()).data()), std_to_wx(e.what()));
                } catch (std::exception& e) {
-                       error_dialog (this, wxString::Format (_("Could not make DCP: %s."), std_to_wx(e.what()).data()));
+                       error_dialog (this, wxString::Format (_("Could not make DCP.")), std_to_wx(e.what()));
                }
        }
 
@@ -666,7 +724,7 @@ private:
                                _("You are making a DKDM which is encrypted by a private key held in"
                                  "\n\n<tt>%s</tt>\n\nIt is <span weight=\"bold\" size=\"larger\">VITALLY IMPORTANT</span> "
                                  "that you <span weight=\"bold\" size=\"larger\">BACK UP THIS FILE</span> since if it is lost "
-                                 "your DKDMs (and the DCPs they protect) will become useless."), std_to_wx(Config::config_path().string()).data()
+                                 "your DKDMs (and the DCPs they protect) will become useless."), std_to_wx(Config::config_file().string()).data()
                                )
                        );
 
@@ -678,7 +736,9 @@ private:
                                d->cpl (),
                                dcp::LocalTime ("2012-01-01T01:00:00+00:00"),
                                dcp::LocalTime ("2112-01-01T01:00:00+00:00"),
-                               dcp::MODIFIED_TRANSITIONAL_1
+                               dcp::MODIFIED_TRANSITIONAL_1,
+                               true,
+                               0
                                );
                } catch (dcp::NotEncryptedError& e) {
                        error_dialog (this, _("CPL's content is not encrypted."));
@@ -690,9 +750,9 @@ private:
 
                if (kdm) {
                        if (d->internal ()) {
-                               vector<dcp::EncryptedKDM> dkdms = Config::instance()->dkdms ();
-                               dkdms.push_back (kdm.get());
-                               Config::instance()->set_dkdms (dkdms);
+                               shared_ptr<DKDMGroup> dkdms = Config::instance()->dkdms ();
+                               dkdms->add (shared_ptr<DKDM> (new DKDM (kdm.get())));
+                               Config::instance()->changed ();
                        } else {
                                boost::filesystem::path path = d->directory() / (_film->dcp_name(false) + "_DKDM.xml");
                                kdm->as_xml (path);
@@ -707,8 +767,7 @@ private:
                ExportDialog* d = new ExportDialog (this);
                if (d->ShowModal() == wxID_OK) {
                        shared_ptr<TranscodeJob> job (new TranscodeJob (_film));
-                       shared_ptr<FFmpegTranscoder> tx (new FFmpegTranscoder (_film, job, d->path(), d->format()));
-                       job->set_transcoder (tx);
+                       job->set_encoder (shared_ptr<FFmpegEncoder> (new FFmpegEncoder (_film, job, d->path(), d->format(), d->mixdown_to_stereo())));
                        JobManager::instance()->add (job);
                }
                d->Destroy ();
@@ -747,23 +806,23 @@ private:
 #ifdef DCPOMATIC_LINUX
                int r = system ("which nautilus");
                if (WEXITSTATUS (r) == 0) {
-                       r = system (string ("nautilus " + _film->directory()->string()).c_str ());
+                       r = system (String::compose("nautilus \"%1\"", _film->directory()->string()).c_str());
                        if (WEXITSTATUS (r)) {
-                               error_dialog (this, _("Could not show DCP (could not run nautilus)"));
+                               error_dialog (this, _("Could not show DCP."), _("Could not run nautilus"));
                        }
                } else {
                        int r = system ("which konqueror");
                        if (WEXITSTATUS (r) == 0) {
-                               r = system (string ("konqueror " + _film->directory()->string()).c_str ());
+                               r = system (String::compose ("konqueror \"%1\"", _film->directory()->string()).c_str());
                                if (WEXITSTATUS (r)) {
-                                       error_dialog (this, _("Could not show DCP (could not run konqueror)"));
+                                       error_dialog (this, _("Could not show DCP"), _("Could not run konqueror"));
                                }
                        }
                }
 #endif
 
 #ifdef DCPOMATIC_OSX
-               int r = system (string ("open -R " + _film->dir (_film->dcp_name (false)).string ()).c_str ());
+               int r = system (String::compose ("open -R \"%1\"", _film->dir (_film->dcp_name(false)).string()).c_str());
                if (WEXITSTATUS (r)) {
                        error_dialog (this, _("Could not show DCP"));
                }
@@ -892,6 +951,8 @@ private:
                }
                bool const dcp_creation = (i != jobs.end ()) && !(*i)->finished ();
                bool const have_cpl = _film && !_film->cpls().empty ();
+               bool const have_single_selected_content = _film_editor->content_panel()->selected().size() == 1;
+               bool const have_selected_content = !_film_editor->content_panel()->selected().empty();
                bool const have_selected_video_content = !_film_editor->content_panel()->selected_video().empty();
 
                for (map<wxMenuItem*, int>::iterator j = menu_items.begin(); j != menu_items.end(); ++j) {
@@ -910,10 +971,26 @@ private:
                                enabled = false;
                        }
 
+                       if ((j->second & NEEDS_SELECTED_CONTENT) && !have_selected_content) {
+                               enabled = false;
+                       }
+
+                       if ((j->second & NEEDS_SINGLE_SELECTED_CONTENT) && !have_single_selected_content) {
+                               enabled = false;
+                       }
+
                        if ((j->second & NEEDS_SELECTED_VIDEO_CONTENT) && !have_selected_video_content) {
                                enabled = false;
                        }
 
+                       if ((j->second & NEEDS_CLIPBOARD) && !_clipboard) {
+                               enabled = false;
+                       }
+
+                       if ((j->second & NEEDS_ENCRYPTION) && (!_film || !_film->encrypted())) {
+                               enabled = false;
+                       }
+
                        j->first->Enable (enabled);
                }
        }
@@ -984,10 +1061,13 @@ private:
                add_item (_file_menu, _("&Quit"), wxID_EXIT, ALWAYS);
 #endif
 
+               wxMenu* edit = new wxMenu;
+               add_item (edit, _("Copy settings\tCtrl-C"), ID_edit_copy, NEEDS_FILM | NOT_DURING_DCP_CREATION | NEEDS_SINGLE_SELECTED_CONTENT);
+               add_item (edit, _("Paste settings...\tCtrl-V"), ID_edit_paste, NEEDS_FILM | NOT_DURING_DCP_CREATION | NEEDS_SELECTED_CONTENT | NEEDS_CLIPBOARD);
+
 #ifdef __WXOSX__
                add_item (_file_menu, _("&Preferences...\tCtrl-P"), wxID_PREFERENCES, ALWAYS);
 #else
-               wxMenu* edit = new wxMenu;
                add_item (edit, _("&Preferences...\tCtrl-P"), wxID_PREFERENCES, ALWAYS);
 #endif
 
@@ -1000,9 +1080,9 @@ private:
                add_item (jobs_menu, _("Make DCP in &batch converter\tCtrl-B"), ID_jobs_make_dcp_batch, NEEDS_FILM | NOT_DURING_DCP_CREATION);
                jobs_menu->AppendSeparator ();
                add_item (jobs_menu, _("Make &KDMs...\tCtrl-K"), ID_jobs_make_kdms, NEEDS_FILM);
-               add_item (jobs_menu, _("Make DKDM for DCP-o-matic..."), ID_jobs_make_self_dkdm, NEEDS_FILM);
+               add_item (jobs_menu, _("Make DKDM for DCP-o-matic..."), ID_jobs_make_self_dkdm, NEEDS_FILM | NEEDS_ENCRYPTION);
                jobs_menu->AppendSeparator ();
-               add_item (jobs_menu, _("Export..."), ID_jobs_export, NEEDS_FILM);
+               add_item (jobs_menu, _("Export...\tCtrl-E"), ID_jobs_export, NEEDS_FILM);
                jobs_menu->AppendSeparator ();
                add_item (jobs_menu, _("&Send DCP to TMS"), ID_jobs_send_dcp_to_tms, NEEDS_FILM | NOT_DURING_DCP_CREATION | NEEDS_CPL);
                add_item (jobs_menu, _("S&how DCP"), ID_jobs_show_dcp, NEEDS_FILM | NOT_DURING_DCP_CREATION | NEEDS_CPL);
@@ -1025,9 +1105,7 @@ private:
                add_item (help, _("Report a problem..."), ID_help_report_a_problem, NEEDS_FILM);
 
                m->Append (_file_menu, _("&File"));
-#ifndef __WXOSX__
                m->Append (edit, _("&Edit"));
-#endif
                m->Append (content, _("&Content"));
                m->Append (jobs_menu, _("&Jobs"));
                m->Append (tools, _("&Tools"));
@@ -1137,6 +1215,7 @@ private:
        wxMenuItem* _history_separator;
        boost::signals2::scoped_connection _config_changed_connection;
        bool _update_news_requested;
+       shared_ptr<Content> _clipboard;
 };
 
 static const wxCmdLineEntryDesc command_line_description[] = {
@@ -1166,20 +1245,9 @@ private:
                wxInitAllImageHandlers ();
 
                Config::FailedToLoad.connect (boost::bind (&App::config_failed_to_load, this));
+               Config::Warning.connect (boost::bind (&App::config_warning, this, _1));
 
-               wxSplashScreen* splash = 0;
-               try {
-                       if (!Config::have_existing ("config.xml")) {
-                               wxBitmap bitmap;
-                               boost::filesystem::path p = shared_path () / "splash.png";
-                               if (bitmap.LoadFile (std_to_wx (p.string ()), wxBITMAP_TYPE_PNG)) {
-                                       splash = new wxSplashScreen (bitmap, wxSPLASH_CENTRE_ON_SCREEN | wxSPLASH_NO_TIMEOUT, 0, 0, -1);
-                                       wxYield ();
-                               }
-                       }
-               } catch (boost::filesystem::filesystem_error& e) {
-                       /* Maybe we couldn't find the splash image; never mind */
-               }
+               wxSplashScreen* splash = maybe_show_splash ();
 
                SetAppName (_("DCP-o-matic"));
 
@@ -1229,7 +1297,7 @@ private:
                        try {
                                _frame->load_film (_film_to_load);
                        } catch (exception& e) {
-                               error_dialog (0, std_to_wx (String::compose (wx_to_std (_("Could not load film %1 (%2)")), _film_to_load, e.what())));
+                               error_dialog (0, std_to_wx (String::compose (wx_to_std (_("Could not load film %1 (%2)")), _film_to_load)), std_to_wx(e.what()));
                        }
                }
 
@@ -1260,7 +1328,7 @@ private:
        }
        catch (exception& e)
        {
-               error_dialog (0, wxString::Format ("DCP-o-matic could not start: %s", e.what ()));
+               error_dialog (0, wxString::Format ("DCP-o-matic could not start."), std_to_wx(e.what()));
                return true;
        }
 
@@ -1351,6 +1419,11 @@ private:
                message_dialog (_frame, _("The existing configuration failed to load.  Default values will be used instead.  These may take a short time to create."));
        }
 
+       void config_warning (string m)
+       {
+               message_dialog (_frame, std_to_wx (m));
+       }
+
        DOMFrame* _frame;
        shared_ptr<wxTimer> _timer;
        string _film_to_load;