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