Initial and not-working information panel in player.
[dcpomatic.git] / src / tools / dcpomatic_player.cc
1 /*
2     Copyright (C) 2017 Carl Hetherington <cth@carlh.net>
3
4     This file is part of DCP-o-matic.
5
6     DCP-o-matic is free software; you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation; either version 2 of the License, or
9     (at your option) any later version.
10
11     DCP-o-matic is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15
16     You should have received a copy of the GNU General Public License
17     along with DCP-o-matic.  If not, see <http://www.gnu.org/licenses/>.
18
19 */
20
21 #include "lib/cross.h"
22 #include "lib/config.h"
23 #include "lib/util.h"
24 #include "lib/update_checker.h"
25 #include "lib/compose.hpp"
26 #include "lib/encode_server_finder.h"
27 #include "lib/dcp_content.h"
28 #include "lib/job_manager.h"
29 #include "lib/job.h"
30 #include "wx/wx_signal_manager.h"
31 #include "wx/wx_util.h"
32 #include "wx/about_dialog.h"
33 #include "wx/report_problem_dialog.h"
34 #include "wx/film_viewer.h"
35 #include "wx/player_information.h"
36 #include "wx/update_dialog.h"
37 #include <wx/wx.h>
38 #include <wx/stdpaths.h>
39 #include <wx/splash.h>
40 #include <wx/cmdline.h>
41 #include <boost/bind.hpp>
42 #include <iostream>
43
44 using std::string;
45 using std::cout;
46 using std::exception;
47 using boost::shared_ptr;
48 using boost::optional;
49
50 enum {
51         ID_file_open = 1,
52         ID_help_report_a_problem,
53         ID_tools_check_for_updates,
54 };
55
56 class DOMFrame : public wxFrame
57 {
58 public:
59         DOMFrame ()
60                 : wxFrame (0, -1, _("DCP-o-matic Player"))
61                 , _update_news_requested (false)
62         {
63
64 #if defined(DCPOMATIC_WINDOWS)
65                 maybe_open_console ();
66                 cout << "DCP-o-matic Player is starting." << "\n";
67 #endif
68
69                 wxMenuBar* bar = new wxMenuBar;
70                 setup_menu (bar);
71                 SetMenuBar (bar);
72
73 #ifdef DCPOMATIC_WINDOWS
74                 SetIcon (wxIcon (std_to_wx ("id")));
75 #endif
76
77                 Bind (wxEVT_MENU, boost::bind (&DOMFrame::file_open, this), ID_file_open);
78                 Bind (wxEVT_MENU, boost::bind (&DOMFrame::file_exit, this), wxID_EXIT);
79                 Bind (wxEVT_MENU, boost::bind (&DOMFrame::help_about, this), wxID_ABOUT);
80                 Bind (wxEVT_MENU, boost::bind (&DOMFrame::help_report_a_problem, this), ID_help_report_a_problem);
81                 Bind (wxEVT_MENU, boost::bind (&DOMFrame::tools_check_for_updates, this), ID_tools_check_for_updates);
82
83                 /* Use a panel as the only child of the Frame so that we avoid
84                    the dark-grey background on Windows.
85                 */
86                 wxPanel* overall_panel = new wxPanel (this, wxID_ANY);
87
88                 _viewer = new FilmViewer (overall_panel, false, false);
89                 _info = new PlayerInformation (overall_panel, _viewer);
90                 wxBoxSizer* main_sizer = new wxBoxSizer (wxVERTICAL);
91                 main_sizer->Add (_viewer, 1, wxEXPAND | wxALL, 6);
92                 main_sizer->Add (_info, 0, wxALL, 6);
93                 overall_panel->SetSizer (main_sizer);
94
95                 UpdateChecker::instance()->StateChanged.connect (boost::bind (&DOMFrame::update_checker_state_changed, this));
96         }
97
98         void load_dcp (boost::filesystem::path dir)
99         {
100                 _film.reset (new Film (optional<boost::filesystem::path>()));
101                 shared_ptr<DCPContent> dcp (new DCPContent (_film, dir));
102                 _film->examine_and_add_content (dcp);
103
104                 JobManager* jm = JobManager::instance ();
105                 while (jm->work_to_do ()) {
106                         /* XXX: progress dialog */
107                         while (signal_manager->ui_idle ()) {}
108                         dcpomatic_sleep (1);
109                 }
110
111                 while (signal_manager->ui_idle ()) {}
112
113                 if (jm->errors ()) {
114                         wxString errors;
115                         BOOST_FOREACH (shared_ptr<Job> i, jm->get()) {
116                                 if (i->finished_in_error()) {
117                                         errors += std_to_wx (i->error_summary()) + "\n";
118                                 }
119                         }
120                         error_dialog (this, errors);
121                         return;
122                 }
123
124                 _viewer->set_film (_film);
125                 _info->update ();
126         }
127
128 private:
129
130         void setup_menu (wxMenuBar* m)
131         {
132                 wxMenu* file = new wxMenu;
133                 file->Append (ID_file_open, _("&Open...\tCtrl-O"));
134
135 #ifdef __WXOSX__
136                 file->Append (wxID_EXIT, _("&Exit"));
137 #else
138                 file->Append (wxID_EXIT, _("&Quit"));
139 #endif
140
141 #ifdef __WXOSX__
142                 file->Append (wxID_PREFERENCES, _("&Preferences...\tCtrl-P"));
143 #else
144                 wxMenu* edit = new wxMenu;
145                 edit->Append (wxID_PREFERENCES, _("&Preferences...\tCtrl-P"));
146 #endif
147
148                 wxMenu* tools = new wxMenu;
149                 tools->Append (ID_tools_check_for_updates, _("Check for updates"));
150
151                 wxMenu* help = new wxMenu;
152 #ifdef __WXOSX__
153                 help->Append (wxID_ABOUT, _("About DCP-o-matic"));
154 #else
155                 help->Append (wxID_ABOUT, _("About"));
156 #endif
157                 help->Append (ID_help_report_a_problem, _("Report a problem..."));
158
159                 m->Append (file, _("&File"));
160                 m->Append (tools, _("&Tools"));
161                 m->Append (help, _("&Help"));
162         }
163
164         void file_open ()
165         {
166                 wxDirDialog* c = new wxDirDialog (
167                         this,
168                         _("Select DCP to open"),
169                         wxStandardPaths::Get().GetDocumentsDir(),
170                         wxDEFAULT_DIALOG_STYLE | wxDD_DIR_MUST_EXIST
171                         );
172
173                 int r;
174                 while (true) {
175                         r = c->ShowModal ();
176                         if (r == wxID_OK && c->GetPath() == wxStandardPaths::Get().GetDocumentsDir()) {
177                                 error_dialog (this, _("You did not select a folder.  Make sure that you select a folder before clicking Open."));
178                         } else {
179                                 break;
180                         }
181                 }
182
183                 if (r == wxID_OK) {
184                         load_dcp (wx_to_std (c->GetPath ()));
185                 }
186
187                 c->Destroy ();
188         }
189
190         void file_exit ()
191         {
192                 Close ();
193         }
194
195         void tools_check_for_updates ()
196         {
197                 UpdateChecker::instance()->run ();
198                 _update_news_requested = true;
199         }
200
201         void help_about ()
202         {
203                 AboutDialog* d = new AboutDialog (this);
204                 d->ShowModal ();
205                 d->Destroy ();
206         }
207
208         void help_report_a_problem ()
209         {
210                 ReportProblemDialog* d = new ReportProblemDialog (this);
211                 if (d->ShowModal () == wxID_OK) {
212                         d->report ();
213                 }
214                 d->Destroy ();
215         }
216
217         void update_checker_state_changed ()
218         {
219                 UpdateChecker* uc = UpdateChecker::instance ();
220
221                 bool const announce =
222                         _update_news_requested ||
223                         (uc->stable() && Config::instance()->check_for_updates()) ||
224                         (uc->test() && Config::instance()->check_for_updates() && Config::instance()->check_for_test_updates());
225
226                 _update_news_requested = false;
227
228                 if (!announce) {
229                         return;
230                 }
231
232                 if (uc->state() == UpdateChecker::YES) {
233                         UpdateDialog* dialog = new UpdateDialog (this, uc->stable (), uc->test ());
234                         dialog->ShowModal ();
235                         dialog->Destroy ();
236                 } else if (uc->state() == UpdateChecker::FAILED) {
237                         error_dialog (this, _("The DCP-o-matic download server could not be contacted."));
238                 } else {
239                         error_dialog (this, _("There are no new versions of DCP-o-matic available."));
240                 }
241
242                 _update_news_requested = false;
243         }
244
245         bool _update_news_requested;
246         PlayerInformation* _info;
247         FilmViewer* _viewer;
248         boost::shared_ptr<Film> _film;
249 };
250
251 static const wxCmdLineEntryDesc command_line_description[] = {
252         { wxCMD_LINE_PARAM, 0, 0, "DCP to load or create", wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL },
253         { wxCMD_LINE_NONE, "", "", "", wxCmdLineParamType (0), 0 }
254 };
255
256 /** @class App
257  *  @brief The magic App class for wxWidgets.
258  */
259 class App : public wxApp
260 {
261 public:
262         App ()
263                 : wxApp ()
264                 , _frame (0)
265         {}
266
267 private:
268
269         bool OnInit ()
270         try
271         {
272                 wxInitAllImageHandlers ();
273
274                 Config::FailedToLoad.connect (boost::bind (&App::config_failed_to_load, this));
275
276                 wxSplashScreen* splash = 0;
277                 try {
278                         if (!Config::have_existing ("config.xml")) {
279                                 wxBitmap bitmap;
280                                 boost::filesystem::path p = shared_path () / "splash.png";
281                                 if (bitmap.LoadFile (std_to_wx (p.string ()), wxBITMAP_TYPE_PNG)) {
282                                         splash = new wxSplashScreen (bitmap, wxSPLASH_CENTRE_ON_SCREEN | wxSPLASH_NO_TIMEOUT, 0, 0, -1);
283                                         wxYield ();
284                                 }
285                         }
286                 } catch (boost::filesystem::filesystem_error& e) {
287                         /* Maybe we couldn't find the splash image; never mind */
288                 }
289
290                 SetAppName (_("DCP-o-matic Player"));
291
292                 if (!wxApp::OnInit()) {
293                         return false;
294                 }
295
296 #ifdef DCPOMATIC_LINUX
297                 unsetenv ("UBUNTU_MENUPROXY");
298 #endif
299
300 #ifdef __WXOSX__
301                 ProcessSerialNumber serial;
302                 GetCurrentProcess (&serial);
303                 TransformProcessType (&serial, kProcessTransformToForegroundApplication);
304 #endif
305
306                 dcpomatic_setup_path_encoding ();
307
308                 /* Enable i18n; this will create a Config object
309                    to look for a force-configured language.  This Config
310                    object will be wrong, however, because dcpomatic_setup
311                    hasn't yet been called and there aren't any filters etc.
312                    set up yet.
313                 */
314                 dcpomatic_setup_i18n ();
315
316                 /* Set things up, including filters etc.
317                    which will now be internationalised correctly.
318                 */
319                 dcpomatic_setup ();
320
321                 /* Force the configuration to be re-loaded correctly next
322                    time it is needed.
323                 */
324                 Config::drop ();
325
326                 _frame = new DOMFrame ();
327                 SetTopWindow (_frame);
328                 _frame->Maximize ();
329                 if (splash) {
330                         splash->Destroy ();
331                 }
332                 _frame->Show ();
333
334                 signal_manager = new wxSignalManager (this);
335
336                 if (!_dcp_to_load.empty() && boost::filesystem::is_directory (_dcp_to_load)) {
337                         try {
338                                 _frame->load_dcp (_dcp_to_load);
339                         } catch (exception& e) {
340                                 error_dialog (0, std_to_wx (String::compose (wx_to_std (_("Could not load DCP %1 (%2)")), _dcp_to_load, e.what())));
341                         }
342                 }
343
344                 Bind (wxEVT_IDLE, boost::bind (&App::idle, this));
345
346                 Bind (wxEVT_TIMER, boost::bind (&App::check, this));
347                 _timer.reset (new wxTimer (this));
348                 _timer->Start (1000);
349
350                 if (Config::instance()->check_for_updates ()) {
351                         UpdateChecker::instance()->run ();
352                 }
353
354                 return true;
355         }
356         catch (exception& e)
357         {
358                 error_dialog (0, wxString::Format ("DCP-o-matic Player could not start: %s", e.what ()));
359                 return true;
360         }
361
362         void OnInitCmdLine (wxCmdLineParser& parser)
363         {
364                 parser.SetDesc (command_line_description);
365                 parser.SetSwitchChars (wxT ("-"));
366         }
367
368         bool OnCmdLineParsed (wxCmdLineParser& parser)
369         {
370                 if (parser.GetParamCount() > 0) {
371                         _dcp_to_load = wx_to_std (parser.GetParam (0));
372                 }
373
374                 return true;
375         }
376
377         void report_exception ()
378         {
379                 try {
380                         throw;
381                 } catch (FileError& e) {
382                         error_dialog (
383                                 0,
384                                 wxString::Format (
385                                         _("An exception occurred: %s (%s)\n\n") + REPORT_PROBLEM,
386                                         std_to_wx (e.what()),
387                                         std_to_wx (e.file().string().c_str ())
388                                         )
389                                 );
390                 } catch (exception& e) {
391                         error_dialog (
392                                 0,
393                                 wxString::Format (
394                                         _("An exception occurred: %s.\n\n") + REPORT_PROBLEM,
395                                         std_to_wx (e.what ())
396                                         )
397                                 );
398                 } catch (...) {
399                         error_dialog (0, _("An unknown exception occurred.") + "  " + REPORT_PROBLEM);
400                 }
401         }
402
403         /* An unhandled exception has occurred inside the main event loop */
404         bool OnExceptionInMainLoop ()
405         {
406                 report_exception ();
407                 /* This will terminate the program */
408                 return false;
409         }
410
411         void OnUnhandledException ()
412         {
413                 report_exception ();
414         }
415
416         void idle ()
417         {
418                 signal_manager->ui_idle ();
419         }
420
421         void check ()
422         {
423                 try {
424                         EncodeServerFinder::instance()->rethrow ();
425                 } catch (exception& e) {
426                         error_dialog (0, std_to_wx (e.what ()));
427                 }
428         }
429
430         void config_failed_to_load ()
431         {
432                 message_dialog (_frame, _("The existing configuration failed to load.  Default values will be used instead.  These may take a short time to create."));
433         }
434
435         DOMFrame* _frame;
436         shared_ptr<wxTimer> _timer;
437         string _dcp_to_load;
438 };
439
440 IMPLEMENT_APP (App)