Add system information dialog with OpenGL version.
[dcpomatic.git] / src / tools / dcpomatic.cc
index ecb8ea6031183686136180285077f51643e2db0d..ee51d0717b52204f2671f8b3583a8944b7082e9f 100644 (file)
@@ -1,5 +1,5 @@
 /*
-    Copyright (C) 2012-2018 Carl Hetherington <cth@carlh.net>
+    Copyright (C) 2012-2019 Carl Hetherington <cth@carlh.net>
 
     This file is part of DCP-o-matic.
 
 #include "wx/content_panel.h"
 #include "wx/report_problem_dialog.h"
 #include "wx/video_waveform_dialog.h"
+#include "wx/system_information_dialog.h"
 #include "wx/save_template_dialog.h"
 #include "wx/templates_dialog.h"
 #include "wx/nag_dialog.h"
 #include "wx/export_dialog.h"
 #include "wx/paste_dialog.h"
 #include "wx/focus_manager.h"
+#include "wx/html_dialog.h"
 #include "wx/initial_setup_dialog.h"
 #include "wx/send_i18n_dialog.h"
 #include "wx/i18n_hook.h"
 #include "lib/film.h"
+#include "lib/analytics.h"
 #include "lib/emailer.h"
 #include "lib/config.h"
 #include "lib/util.h"
@@ -87,6 +90,7 @@
 #include <wx/cmdline.h>
 #include <wx/preferences.h>
 #include <wx/splash.h>
+#include <wx/wxhtml.h>
 #ifdef __WXMSW__
 #include <shellapi.h>
 #endif
@@ -211,7 +215,8 @@ enum {
        ID_file_duplicate_and_open,
        ID_file_history,
        /* Allow spare IDs after _history for the recent files list */
-       ID_edit_copy = 100,
+       ID_file_close = 100,
+       ID_edit_copy,
        ID_edit_paste,
        ID_content_scale_to_fit_width,
        ID_content_scale_to_fit_height,
@@ -225,6 +230,7 @@ enum {
        ID_jobs_open_dcp_in_player,
        ID_view_closed_captions,
        ID_view_video_waveform,
+       ID_view_system_information,
        ID_tools_hints,
        ID_tools_encoding_servers,
        ID_tools_manage_templates,
@@ -247,6 +253,7 @@ public:
        explicit DOMFrame (wxString const & title)
                : wxFrame (NULL, -1, title)
                , _video_waveform_dialog (0)
+               , _system_information_dialog (0)
                , _hints_dialog (0)
                , _servers_list_dialog (0)
                , _config_dialog (0)
@@ -289,12 +296,15 @@ public:
                _config_changed_connection = Config::instance()->Changed.connect (boost::bind (&DOMFrame::config_changed, this, _1));
                config_changed (Config::OTHER);
 
+               _analytics_message_connection = Analytics::instance()->Message.connect(boost::bind(&DOMFrame::analytics_message, this, _1, _2));
+
                Bind (wxEVT_MENU, boost::bind (&DOMFrame::file_new, this),                ID_file_new);
                Bind (wxEVT_MENU, boost::bind (&DOMFrame::file_open, this),               ID_file_open);
                Bind (wxEVT_MENU, boost::bind (&DOMFrame::file_save, this),               ID_file_save);
                Bind (wxEVT_MENU, boost::bind (&DOMFrame::file_save_as_template, this),   ID_file_save_as_template);
                Bind (wxEVT_MENU, boost::bind (&DOMFrame::file_duplicate, this),          ID_file_duplicate);
                Bind (wxEVT_MENU, boost::bind (&DOMFrame::file_duplicate_and_open, this), ID_file_duplicate_and_open);
+               Bind (wxEVT_MENU, boost::bind (&DOMFrame::file_close, this),              ID_file_close);
                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);
@@ -312,6 +322,7 @@ public:
                Bind (wxEVT_MENU, boost::bind (&DOMFrame::jobs_open_dcp_in_player, this), ID_jobs_open_dcp_in_player);
                Bind (wxEVT_MENU, boost::bind (&DOMFrame::view_closed_captions, this),    ID_view_closed_captions);
                Bind (wxEVT_MENU, boost::bind (&DOMFrame::view_video_waveform, this),     ID_view_video_waveform);
+               Bind (wxEVT_MENU, boost::bind (&DOMFrame::view_system_information, this), ID_view_system_information);
                Bind (wxEVT_MENU, boost::bind (&DOMFrame::tools_hints, this),             ID_tools_hints);
                Bind (wxEVT_MENU, boost::bind (&DOMFrame::tools_encoding_servers, this),  ID_tools_encoding_servers);
                Bind (wxEVT_MENU, boost::bind (&DOMFrame::tools_manage_templates, this),  ID_tools_manage_templates);
@@ -339,8 +350,8 @@ public:
                right_sizer->Add (job_manager_view, 1, wxEXPAND | wxALL, 6);
 
                wxBoxSizer* main_sizer = new wxBoxSizer (wxHORIZONTAL);
-               main_sizer->Add (_film_editor, 1, wxEXPAND | wxALL, 6);
-               main_sizer->Add (right_sizer, 2, wxEXPAND | wxALL, 6);
+               main_sizer->Add (_film_editor, 0, wxEXPAND | wxALL, 6);
+               main_sizer->Add (right_sizer, 1, wxEXPAND | wxALL, 6);
 
                set_menu_sensitivity ();
 
@@ -374,7 +385,7 @@ public:
                accel[4].Set (wxACCEL_NORMAL, WXK_LEFT, ID_back_frame);
                accel[5].Set (wxACCEL_NORMAL, WXK_RIGHT, ID_forward_frame);
 #ifdef __WXOSX__
-               accel[6].Set (wxACCEL_CTRL, static_cast<int>('W'), wxID_EXIT);
+               accel[6].Set (wxACCEL_CTRL, static_cast<int>('W'), ID_file_close);
 #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);
@@ -408,7 +419,6 @@ public:
                film->set_name (path.filename().generic_string());
                film->write_metadata ();
                set_film (film);
-               dcpomatic_log = film->log ();
        }
 
        void load_film (boost::filesystem::path file)
@@ -425,8 +435,8 @@ public:
                                );
                }
 
-               for (list<string>::const_iterator i = notes.begin(); i != notes.end(); ++i) {
-                       error_dialog (0, std_to_wx (*i));
+               BOOST_FOREACH (string i, notes) {
+                       error_dialog (0, std_to_wx(i));
                }
 
                set_film (film);
@@ -450,10 +460,14 @@ public:
                        _video_waveform_dialog = 0;
                }
                set_menu_sensitivity ();
-               if (_film->directory()) {
+               if (_film && _film->directory()) {
                        Config::instance()->add_to_history (_film->directory().get());
                }
-               _film->Change.connect (boost::bind (&DOMFrame::film_change, this, _1));
+               if (_film) {
+                       _film->Change.connect (boost::bind (&DOMFrame::film_change, this, _1));
+                       _film->Message.connect (boost::bind(&DOMFrame::film_message, this, _1));
+                       dcpomatic_log = _film->log ();
+               }
        }
 
        shared_ptr<Film> film () const {
@@ -462,6 +476,11 @@ public:
 
 private:
 
+       void film_message (string m)
+       {
+               message_dialog (this, std_to_wx(m));
+       }
+
        void film_change (ChangeType type)
        {
                if (type == CHANGE_TYPE_DONE) {
@@ -584,6 +603,31 @@ private:
                d->Destroy ();
        }
 
+       void file_close ()
+       {
+               if (_film && _film->dirty ()) {
+
+                       FilmChangedClosingDialog* dialog = new FilmChangedClosingDialog (_film->name ());
+                       int const r = dialog->run ();
+                       delete dialog;
+
+                       switch (r) {
+                       case wxID_NO:
+                               /* Don't save and carry on to close */
+                               break;
+                       case wxID_YES:
+                               /* Save and carry on to close */
+                               _film->write_metadata ();
+                               break;
+                       case wxID_CANCEL:
+                               /* Stop */
+                               return;
+                       }
+               }
+
+               set_film (shared_ptr<Film>());
+       }
+
        void file_history (wxCommandEvent& event)
        {
                vector<boost::filesystem::path> history = Config::instance()->history ();
@@ -670,9 +714,9 @@ private:
                if (!_film->should_be_enough_disk_space (required, available, can_hard_link)) {
                        wxString message;
                        if (can_hard_link) {
-                               message = wxString::Format (_("The DCP for this film will take up about %.1f Gb, and the disk that you are using only has %.1f Gb available.  Do you want to continue anyway?"), required, available);
+                               message = wxString::Format (_("The DCP for this film will take up about %.1f GB, and the disk that you are using only has %.1f GB available.  Do you want to continue anyway?"), required, available);
                        } else {
-                               message = wxString::Format (_("The DCP and intermediate files for this film will take up about %.1f Gb, and the disk that you are using only has %.1f Gb available.  You would need half as much space if the filesystem supported hard links, but it does not.  Do you want to continue anyway?"), required, available);
+                               message = wxString::Format (_("The DCP and intermediate files for this film will take up about %.1f GB, and the disk that you are using only has %.1f GB available.  You would need half as much space if the filesystem supported hard links, but it does not.  Do you want to continue anyway?"), required, available);
                        }
                        if (!confirm_dialog (this, message)) {
                                return;
@@ -947,6 +991,15 @@ private:
                _video_waveform_dialog->Show ();
        }
 
+       void view_system_information ()
+       {
+               if (!_system_information_dialog) {
+                       _system_information_dialog = new SystemInformationDialog (this);
+               }
+
+               _system_information_dialog->Show ();
+       }
+
        void tools_hints ()
        {
                if (!_hints_dialog) {
@@ -987,14 +1040,16 @@ private:
                        string body;
                        body += d->name() + "\n";
                        body += d->language() + "\n";
+                       body += string(dcpomatic_version) + " " + string(dcpomatic_git_commit) + "\n";
+                       body += "--\n";
                        map<string, string> translations = I18NHook::translations ();
                        for (map<string, string>::const_iterator i = translations.begin(); i != translations.end(); ++i) {
-                               body += i->first + "\n" + i->second + "\n";
+                               body += i->first + "\n" + i->second + "\n\n";
                        }
                        list<string> to;
                        to.push_back ("carl@dcpomatic.com");
                        Emailer emailer (d->email(), to, "DCP-o-matic translations", body);
-                       emailer.send ("main.carlh.net", 2525);
+                       emailer.send ("main.carlh.net", 2525, EMAIL_PROTOCOL_STARTTLS);
                }
 
                d->Destroy ();
@@ -1068,6 +1123,9 @@ private:
                */
                _config_changed_connection.disconnect ();
 
+               /* Also stop hearing about analytics-related stuff */
+               _analytics_message_connection.disconnect ();
+
                ev.Skip ();
        }
 
@@ -1180,6 +1238,9 @@ private:
 
                _history_position = _file_menu->GetMenuItems().GetCount();
 
+               _file_menu->AppendSeparator ();
+               add_item (_file_menu, _("&Close\tCtrl-W"), ID_file_close, NEEDS_FILM);
+
 #ifndef __WXOSX__
                _file_menu->AppendSeparator ();
 #endif
@@ -1220,9 +1281,10 @@ private:
                wxMenu* view = new wxMenu;
                add_item (view, _("Closed captions..."), ID_view_closed_captions, NEEDS_FILM);
                add_item (view, _("Video waveform..."), ID_view_video_waveform, NEEDS_FILM);
+               add_item (view, _("System information..."), ID_view_system_information, 0);
 
                wxMenu* tools = new wxMenu;
-               add_item (tools, _("Hints..."), ID_tools_hints, 0);
+               add_item (tools, _("Hints..."), ID_tools_hints, NEEDS_FILM);
                add_item (tools, _("Encoding servers..."), ID_tools_encoding_servers, 0);
                add_item (tools, _("Manage templates..."), ID_tools_manage_templates, 0);
                add_item (tools, _("Check for updates"), ID_tools_check_for_updates, 0);
@@ -1288,6 +1350,8 @@ private:
 
                int pos = _history_position;
 
+               /* Clear out non-existant history items before we re-build the menu */
+               Config::instance()->clean_history ();
                vector<boost::filesystem::path> history = Config::instance()->history ();
 
                if (!history.empty ()) {
@@ -1305,6 +1369,8 @@ private:
                }
 
                _history_items = history.size ();
+
+               dcpomatic_log->set_types (Config::instance()->log_types());
        }
 
        void update_checker_state_changed ()
@@ -1359,10 +1425,18 @@ private:
                _film_viewer->seek_by (_film_viewer->one_video_frame(), true);
        }
 
+       void analytics_message (string title, string html)
+       {
+               HTMLDialog* d = new HTMLDialog(this, std_to_wx(title), std_to_wx(html));
+               d->ShowModal();
+               d->Destroy();
+       }
+
        FilmEditor* _film_editor;
        boost::shared_ptr<FilmViewer> _film_viewer;
        StandardControls* _controls;
        VideoWaveformDialog* _video_waveform_dialog;
+       SystemInformationDialog* _system_information_dialog;
        HintsDialog* _hints_dialog;
        ServersListDialog* _servers_list_dialog;
        wxPreferencesEditor* _config_dialog;
@@ -1374,6 +1448,7 @@ private:
        int _history_position;
        wxMenuItem* _history_separator;
        boost::signals2::scoped_connection _config_changed_connection;
+       boost::signals2::scoped_connection _analytics_message_connection;
        bool _update_news_requested;
        shared_ptr<Content> _clipboard;
 };
@@ -1395,20 +1470,20 @@ public:
        App ()
                : wxApp ()
                , _frame (0)
+               , _splash (0)
        {}
 
 private:
 
        bool OnInit ()
        {
-               wxSplashScreen* splash = 0;
                try {
                        wxInitAllImageHandlers ();
 
                        Config::FailedToLoad.connect (boost::bind (&App::config_failed_to_load, this));
                        Config::Warning.connect (boost::bind (&App::config_warning, this, _1));
 
-                       wxSplashScreen* splash = maybe_show_splash ();
+                       _splash = maybe_show_splash ();
 
                        SetAppName (_("DCP-o-matic"));
 
@@ -1446,15 +1521,16 @@ private:
                        */
                        Config::drop ();
 
-                       Config::BadSignerChain.connect (boost::bind (&App::config_bad_signer_chain, this));
+                       /* We only look out for bad configuration from here on, as before
+                          dcpomatic_setup() we haven't got OpenSSL ready so there will be
+                          incorrect certificate chain validity errors.
+                       */
+                       Config::Bad.connect (boost::bind(&App::config_bad, this, _1));
 
                        _frame = new DOMFrame (_("DCP-o-matic"));
                        SetTopWindow (_frame);
                        _frame->Maximize ();
-                       if (splash) {
-                               splash->Destroy ();
-                               splash = 0;
-                       }
+                       close_splash ();
 
                        if (!Config::instance()->nagged(Config::NAG_INITIAL_SETUP)) {
                                InitialSetupDialog* d = new InitialSetupDialog ();
@@ -1495,17 +1571,17 @@ private:
                        if (Config::instance()->check_for_updates ()) {
                                UpdateChecker::instance()->run ();
                        }
-
-                       return true;
                }
                catch (exception& e)
                {
-                       if (splash) {
-                               splash->Destroy ();
+                       if (_splash) {
+                               _splash->Destroy ();
+                               _splash = 0;
                        }
                        error_dialog (0, wxString::Format ("DCP-o-matic could not start."), std_to_wx(e.what()));
-                       return true;
                }
+
+               return true;
        }
 
        void OnInitCmdLine (wxCmdLineParser& parser)
@@ -1590,29 +1666,93 @@ private:
                }
        }
 
+       void close_splash ()
+       {
+               if (_splash) {
+                       _splash->Destroy ();
+                       _splash = 0;
+               }
+       }
+
        void config_failed_to_load ()
        {
+               close_splash ();
                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)
        {
+               close_splash ();
                message_dialog (_frame, std_to_wx (m));
        }
 
-       bool config_bad_signer_chain ()
+       bool config_bad (Config::BadReason reason)
        {
-               if (Config::instance()->nagged(Config::NAG_BAD_SIGNER_CHAIN)) {
-                       return false;
-               }
+               /* Destroy the splash screen here, as otherwise bad things seem to happen (for reasons unknown)
+                  when we open our recreate dialog, close it, *then* try to Destroy the splash (the Destroy fails).
+               */
+               _splash->Destroy ();
+               _splash = 0;
 
-               RecreateChainDialog* d = new RecreateChainDialog (_frame);
-               int const r = d->ShowModal ();
-               d->Destroy ();
-               return r == wxID_OK;
+               Config* config = Config::instance();
+               switch (reason) {
+               case Config::BAD_SIGNER_UTF8_STRINGS:
+               {
+                       if (config->nagged(Config::NAG_BAD_SIGNER_CHAIN)) {
+                               return false;
+                       }
+                       RecreateChainDialog* d = new RecreateChainDialog (
+                               _frame, _("Recreate signing certificates"),
+                               _("The certificate chain that DCP-o-matic uses for signing DCPs and KDMs contains a small error\n"
+                                 "which will prevent DCPs from being validated correctly on some systems.  Do you want to re-create\n"
+                                 "the certificate chain for signing DCPs and KDMs?"),
+                               _("Do nothing"),
+                               Config::NAG_BAD_SIGNER_CHAIN
+                               );
+                       int const r = d->ShowModal ();
+                       d->Destroy ();
+                       return r == wxID_OK;
+               }
+               case Config::BAD_SIGNER_INCONSISTENT:
+               {
+                       RecreateChainDialog* d = new RecreateChainDialog (
+                               _frame, _("Recreate signing certificates"),
+                               _("The certificate chain that DCP-o-matic uses for signing DCPs and KDMs is inconsistent and\n"
+                                 "cannot be used.  DCP-o-matic cannot start unless you re-create it.  Do you want to re-create\n"
+                                 "the certificate chain for signing DCPs and KDMs?"),
+                               _("Close DCP-o-matic")
+                               );
+                       int const r = d->ShowModal ();
+                       d->Destroy ();
+                       if (r != wxID_OK) {
+                               exit (EXIT_FAILURE);
+                       }
+                       return true;
+               }
+               case Config::BAD_DECRYPTION_INCONSISTENT:
+               {
+                       RecreateChainDialog* d = new RecreateChainDialog (
+                               _frame, _("Recreate KDM decryption chain"),
+                               _("The certificate chain that DCP-o-matic uses for decrypting KDMs is inconsistent and\n"
+                                 "cannot be used.  DCP-o-matic cannot start unless you re-create it.  Do you want to re-create\n"
+                                 "the certificate chain for decrypting KDMs?  You may want to say \"No\" here and back up your\n"
+                                 "configuration before continuing."),
+                               _("Close DCP-o-matic")
+                               );
+                       int const r = d->ShowModal ();
+                       d->Destroy ();
+                       if (r != wxID_OK) {
+                               exit (EXIT_FAILURE);
+                       }
+                       return true;
+               }
+               default:
+                       DCPOMATIC_ASSERT (false);
+               }
        }
 
        DOMFrame* _frame;
+       wxSplashScreen* _splash;
        shared_ptr<wxTimer> _timer;
        string _film_to_load;
        string _film_to_create;