Use dcp::filesystem to wrap filesystem calls and fix_long_path
[dcpomatic.git] / src / tools / dcpomatic_playlist.cc
1 /*
2     Copyright (C) 2018-2021 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
22 #include "wx/about_dialog.h"
23 #include "wx/content_view.h"
24 #include "wx/dcpomatic_button.h"
25 #include "wx/playlist_editor_config_dialog.h"
26 #include "wx/wx_signal_manager.h"
27 #include "wx/wx_util.h"
28 #include "lib/config.h"
29 #include "lib/constants.h"
30 #include "lib/cross.h"
31 #include "lib/dcp_content.h"
32 #include "lib/film.h"
33 #include "lib/spl.h"
34 #include "lib/spl_entry.h"
35 #include <dcp/filesystem.h>
36 #include <dcp/warnings.h>
37 LIBDCP_DISABLE_WARNINGS
38 #include <wx/imaglist.h>
39 #include <wx/listctrl.h>
40 #include <wx/preferences.h>
41 #include <wx/spinctrl.h>
42 #include <wx/wx.h>
43 LIBDCP_ENABLE_WARNINGS
44
45
46 using std::cout;
47 using std::exception;
48 using std::make_pair;
49 using std::make_shared;
50 using std::map;
51 using std::shared_ptr;
52 using std::string;
53 using std::vector;
54 using std::weak_ptr;
55 using boost::bind;
56 using boost::optional;
57 using std::dynamic_pointer_cast;
58 #if BOOST_VERSION >= 106100
59 using namespace boost::placeholders;
60 #endif
61
62
63 static
64 void
65 save_playlist(shared_ptr<const SPL> playlist)
66 {
67         if (auto dir = Config::instance()->player_playlist_directory()) {
68                 playlist->write(*dir / (playlist->id() + ".xml"));
69         }
70 }
71
72
73 class ContentDialog : public wxDialog, public ContentStore
74 {
75 public:
76         ContentDialog (wxWindow* parent)
77                 : wxDialog (parent, wxID_ANY, _("Add content"), wxDefaultPosition, wxSize(800, 640))
78                 , _content_view (new ContentView(this))
79         {
80                 _content_view->update ();
81
82                 auto overall_sizer = new wxBoxSizer (wxVERTICAL);
83                 SetSizer (overall_sizer);
84
85                 overall_sizer->Add (_content_view, 1, wxEXPAND | wxALL, DCPOMATIC_DIALOG_BORDER);
86
87                 auto buttons = CreateSeparatedButtonSizer (wxOK | wxCANCEL);
88                 if (buttons) {
89                         overall_sizer->Add (buttons, wxSizerFlags().Expand().DoubleBorder());
90                 }
91
92                 overall_sizer->Layout ();
93
94                 _content_view->Bind(wxEVT_LIST_ITEM_ACTIVATED, boost::bind(&ContentDialog::EndModal, this, wxID_OK));
95                 _config_changed_connection = Config::instance()->Changed.connect(boost::bind(&ContentView::update, _content_view));
96         }
97
98         shared_ptr<Content> selected () const
99         {
100                 return _content_view->selected ();
101         }
102
103         shared_ptr<Content> get (string digest) const override
104         {
105                 return _content_view->get (digest);
106         }
107
108 private:
109         ContentView* _content_view;
110         boost::signals2::scoped_connection _config_changed_connection;
111 };
112
113
114
115 class PlaylistList
116 {
117 public:
118         PlaylistList (wxPanel* parent, ContentStore* content_store)
119                 : _sizer (new wxBoxSizer(wxVERTICAL))
120                 , _content_store (content_store)
121                 , _parent(parent)
122         {
123                 auto label = new wxStaticText (parent, wxID_ANY, wxEmptyString);
124                 label->SetLabelMarkup (_("<b>Playlists</b>"));
125                 _sizer->Add (label, 0, wxTOP | wxLEFT, DCPOMATIC_SIZER_GAP * 2);
126
127                 _list = new wxListCtrl (
128                         parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLC_REPORT | wxLC_SINGLE_SEL
129                         );
130
131                 _list->AppendColumn (_("Name"), wxLIST_FORMAT_LEFT, 840);
132                 _list->AppendColumn (_("Length"), wxLIST_FORMAT_LEFT, 100);
133
134                 auto button_sizer = new wxBoxSizer (wxVERTICAL);
135                 _new = new Button (parent, _("New"));
136                 button_sizer->Add (_new, 0, wxEXPAND | wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
137                 _delete = new Button (parent, _("Delete"));
138                 button_sizer->Add (_delete, 0, wxEXPAND | wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
139
140                 auto list = new wxBoxSizer (wxHORIZONTAL);
141                 list->Add (_list, 1, wxEXPAND | wxALL, DCPOMATIC_SIZER_GAP);
142                 list->Add (button_sizer, 0, wxALL, DCPOMATIC_SIZER_GAP);
143
144                 _sizer->Add (list);
145
146                 load_playlists ();
147
148                 _list->Bind (wxEVT_COMMAND_LIST_ITEM_SELECTED, bind(&PlaylistList::selection_changed, this));
149                 _list->Bind (wxEVT_COMMAND_LIST_ITEM_DESELECTED, bind(&PlaylistList::selection_changed, this));
150                 _new->Bind (wxEVT_BUTTON, bind(&PlaylistList::new_playlist, this));
151                 _delete->Bind (wxEVT_BUTTON, bind(&PlaylistList::delete_playlist, this));
152
153                 setup_sensitivity();
154         }
155
156         wxSizer* sizer ()
157         {
158                 return _sizer;
159         }
160
161         shared_ptr<SignalSPL> first_playlist () const
162         {
163                 if (_playlists.empty()) {
164                         return {};
165                 }
166
167                 return _playlists.front ();
168         }
169
170         boost::signals2::signal<void (shared_ptr<SignalSPL>)> Edit;
171
172 private:
173         void setup_sensitivity()
174         {
175                 _delete->Enable(static_cast<bool>(selected()));
176         }
177
178         void add_playlist_to_view (shared_ptr<const SignalSPL> playlist)
179         {
180                 wxListItem item;
181                 item.SetId (_list->GetItemCount());
182                 long const N = _list->InsertItem (item);
183                 _list->SetItem (N, 0, std_to_wx(playlist->name()));
184         }
185
186         void add_playlist_to_model (shared_ptr<SignalSPL> playlist)
187         {
188                 _playlists.push_back (playlist);
189                 playlist->Changed.connect(bind(&PlaylistList::changed, this, weak_ptr<SignalSPL>(playlist), _1));
190         }
191
192         void changed(weak_ptr<SignalSPL> wp, SignalSPL::Change change)
193         {
194                 auto playlist = wp.lock ();
195                 if (!playlist) {
196                         return;
197                 }
198
199                 switch (change) {
200                 case SignalSPL::Change::NAME:
201                 {
202                         int N = 0;
203                         for (auto i: _playlists) {
204                                 if (i == playlist) {
205                                         _list->SetItem (N, 0, std_to_wx(i->name()));
206                                 }
207                                 ++N;
208                         }
209                         break;
210                 }
211                 case SignalSPL::Change::CONTENT:
212                         save_playlist(playlist);
213                         break;
214                 }
215         }
216
217         void load_playlists ()
218         {
219                 auto path = Config::instance()->player_playlist_directory();
220                 if (!path) {
221                         return;
222                 }
223
224                 _list->DeleteAllItems ();
225                 _playlists.clear ();
226                 for (auto i: dcp::filesystem::directory_iterator(*path)) {
227                         auto spl = make_shared<SignalSPL>();
228                         try {
229                                 spl->read (i, _content_store);
230                                 add_playlist_to_model (spl);
231                         } catch (...) {}
232                 }
233
234                 for (auto i: _playlists) {
235                         add_playlist_to_view (i);
236                 }
237         }
238
239         void new_playlist ()
240         {
241                 auto dir = Config::instance()->player_playlist_directory();
242                 if (!dir) {
243                         error_dialog(_parent, _("No playlist folder is specified in preferences.  Please set one and then try again."));
244                         return;
245                 }
246
247                 shared_ptr<SignalSPL> spl (new SignalSPL(wx_to_std(_("New Playlist"))));
248                 add_playlist_to_model (spl);
249                 add_playlist_to_view (spl);
250                 _list->SetItemState (_list->GetItemCount() - 1, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED);
251         }
252
253         boost::optional<int> selected() const
254         {
255                 long int selected = _list->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
256                 if (selected < 0 || selected >= int(_playlists.size())) {
257                         return {};
258                 }
259
260                 return selected;
261         }
262
263         void delete_playlist ()
264         {
265                 auto index = selected();
266                 if (!index) {
267                         return;
268                 }
269
270                 auto dir = Config::instance()->player_playlist_directory();
271                 if (!dir) {
272                         return;
273                 }
274
275                 dcp::filesystem::remove(*dir / (_playlists[*index]->id() + ".xml"));
276                 _list->DeleteItem(*index);
277                 _playlists.erase(_playlists.begin() + *index);
278
279                 Edit(shared_ptr<SignalSPL>());
280         }
281
282         void selection_changed ()
283         {
284                 long int selected = _list->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
285                 if (selected < 0 || selected >= int(_playlists.size())) {
286                         Edit (shared_ptr<SignalSPL>());
287                 } else {
288                         Edit (_playlists[selected]);
289                 }
290
291                 setup_sensitivity();
292         }
293
294         wxBoxSizer* _sizer;
295         wxListCtrl* _list;
296         wxButton* _new;
297         wxButton* _delete;
298         vector<shared_ptr<SignalSPL>> _playlists;
299         ContentStore* _content_store;
300         wxWindow* _parent;
301 };
302
303
304 class PlaylistContent
305 {
306 public:
307         PlaylistContent (wxPanel* parent, ContentDialog* content_dialog)
308                 : _content_dialog (content_dialog)
309                 , _sizer (new wxBoxSizer(wxVERTICAL))
310         {
311                 auto title = new wxBoxSizer (wxHORIZONTAL);
312                 auto label = new wxStaticText (parent, wxID_ANY, wxEmptyString);
313                 label->SetLabelMarkup (_("<b>Playlist:</b>"));
314                 title->Add (label, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, DCPOMATIC_SIZER_GAP);
315                 _name = new wxTextCtrl (parent, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(400, -1));
316                 title->Add (_name, 0, wxRIGHT, DCPOMATIC_SIZER_GAP);
317                 _save_name = new Button(parent, _("Save"));
318                 title->Add(_save_name);
319                 _sizer->Add (title, 0, wxTOP | wxLEFT, DCPOMATIC_SIZER_GAP * 2);
320
321                 auto list = new wxBoxSizer (wxHORIZONTAL);
322
323                 _list = new wxListCtrl (
324                         parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLC_REPORT | wxLC_SINGLE_SEL
325                         );
326
327                 _list->AppendColumn (_("Name"), wxLIST_FORMAT_LEFT, 400);
328                 _list->AppendColumn (_("CPL"), wxLIST_FORMAT_LEFT, 350);
329                 _list->AppendColumn (_("Type"), wxLIST_FORMAT_LEFT, 100);
330                 _list->AppendColumn (_("Encrypted"), wxLIST_FORMAT_CENTRE, 90);
331
332                 auto images = new wxImageList (16, 16);
333                 wxIcon tick_icon;
334                 wxIcon no_tick_icon;
335                 tick_icon.LoadFile (bitmap_path("tick.png"), wxBITMAP_TYPE_PNG);
336                 no_tick_icon.LoadFile (bitmap_path("no_tick.png"), wxBITMAP_TYPE_PNG);
337                 images->Add (tick_icon);
338                 images->Add (no_tick_icon);
339
340                 _list->SetImageList (images, wxIMAGE_LIST_SMALL);
341
342                 list->Add (_list, 1, wxEXPAND | wxALL, DCPOMATIC_SIZER_GAP);
343
344                 auto button_sizer = new wxBoxSizer (wxVERTICAL);
345                 _up = new Button (parent, _("Up"));
346                 _down = new Button (parent, _("Down"));
347                 _add = new Button (parent, _("Add"));
348                 _remove = new Button (parent, _("Remove"));
349                 button_sizer->Add (_up, 0, wxEXPAND | wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
350                 button_sizer->Add (_down, 0, wxEXPAND | wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
351                 button_sizer->Add (_add, 0, wxEXPAND | wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
352                 button_sizer->Add (_remove, 0, wxEXPAND | wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
353
354                 list->Add (button_sizer, 0, wxALL, DCPOMATIC_SIZER_GAP);
355
356                 _sizer->Add (list);
357
358                 _list->Bind (wxEVT_COMMAND_LIST_ITEM_SELECTED, bind(&PlaylistContent::setup_sensitivity, this));
359                 _list->Bind (wxEVT_COMMAND_LIST_ITEM_DESELECTED, bind(&PlaylistContent::setup_sensitivity, this));
360                 _name->Bind (wxEVT_TEXT, bind(&PlaylistContent::name_changed, this));
361                 _save_name->bind(&PlaylistContent::save_name_clicked, this);
362                 _up->Bind (wxEVT_BUTTON, bind(&PlaylistContent::up_clicked, this));
363                 _down->Bind (wxEVT_BUTTON, bind(&PlaylistContent::down_clicked, this));
364                 _add->Bind (wxEVT_BUTTON, bind(&PlaylistContent::add_clicked, this));
365                 _remove->Bind (wxEVT_BUTTON, bind(&PlaylistContent::remove_clicked, this));
366
367                 setup_sensitivity();
368         }
369
370         wxSizer* sizer ()
371         {
372                 return _sizer;
373         }
374
375         void set (shared_ptr<SignalSPL> playlist)
376         {
377                 _playlist = playlist;
378                 _list->DeleteAllItems ();
379                 if (_playlist) {
380                         for (auto i: _playlist->get()) {
381                                 add (i);
382                         }
383                         _name->SetValue (std_to_wx(_playlist->name()));
384                 } else {
385                         _name->SetValue (wxT(""));
386                 }
387                 setup_sensitivity ();
388         }
389
390         shared_ptr<SignalSPL> playlist () const
391         {
392                 return _playlist;
393         }
394
395
396 private:
397         void save_name_clicked()
398         {
399                 if (_playlist) {
400                         _playlist->set_name(wx_to_std(_name->GetValue()));
401                         save_playlist(_playlist);
402                 }
403                 setup_sensitivity();
404         }
405
406         void name_changed ()
407         {
408                 setup_sensitivity();
409         }
410
411         void add (SPLEntry e)
412         {
413                 wxListItem item;
414                 item.SetId (_list->GetItemCount());
415                 long const N = _list->InsertItem (item);
416                 set_item (N, e);
417         }
418
419         void set_item (long N, SPLEntry e)
420         {
421                 _list->SetItem (N, 0, std_to_wx(e.name));
422                 _list->SetItem (N, 1, std_to_wx(e.id));
423                 _list->SetItem (N, 2, std_to_wx(e.kind->name()));
424                 _list->SetItem (N, 3, e.encrypted ? S_("Question|Y") : S_("Question|N"));
425         }
426
427         void setup_sensitivity ()
428         {
429                 bool const have_list = static_cast<bool>(_playlist);
430                 int const num_selected = _list->GetSelectedItemCount ();
431                 long int selected = _list->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
432                 _name->Enable (have_list);
433                 _save_name->Enable(_playlist && _playlist->name() != wx_to_std(_name->GetValue()));
434                 _list->Enable (have_list);
435                 _up->Enable (have_list && selected > 0);
436                 _down->Enable (have_list && selected != -1 && selected < (_list->GetItemCount() - 1));
437                 _add->Enable (have_list);
438                 _remove->Enable (have_list && num_selected > 0);
439         }
440
441         void add_clicked ()
442         {
443                 int const r = _content_dialog->ShowModal ();
444                 if (r == wxID_OK) {
445                         auto content = _content_dialog->selected ();
446                         if (content) {
447                                 SPLEntry e (content);
448                                 add (e);
449                                 DCPOMATIC_ASSERT (_playlist);
450                                 _playlist->add (e);
451                         }
452                 }
453         }
454
455         void up_clicked ()
456         {
457                 long int s = _list->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
458                 if (s < 1) {
459                         return;
460                 }
461
462                 DCPOMATIC_ASSERT (_playlist);
463
464                 _playlist->swap(s, s - 1);
465
466                 set_item (s - 1, (*_playlist)[s-1]);
467                 set_item (s, (*_playlist)[s]);
468         }
469
470         void down_clicked ()
471         {
472                 long int s = _list->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
473                 if (s > (_list->GetItemCount() - 1)) {
474                         return;
475                 }
476
477                 DCPOMATIC_ASSERT (_playlist);
478
479                 _playlist->swap(s, s + 1);
480
481                 set_item (s + 1, (*_playlist)[s+1]);
482                 set_item (s, (*_playlist)[s]);
483         }
484
485         void remove_clicked ()
486         {
487                 long int s = _list->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
488                 if (s == -1) {
489                         return;
490                 }
491
492                 DCPOMATIC_ASSERT (_playlist);
493                 _playlist->remove (s);
494                 _list->DeleteItem (s);
495         }
496
497         ContentDialog* _content_dialog;
498         wxBoxSizer* _sizer;
499         wxTextCtrl* _name;
500         Button* _save_name;
501         wxListCtrl* _list;
502         wxButton* _up;
503         wxButton* _down;
504         wxButton* _add;
505         wxButton* _remove;
506         shared_ptr<SignalSPL> _playlist;
507 };
508
509
510 class DOMFrame : public wxFrame
511 {
512 public:
513         explicit DOMFrame (wxString const & title)
514                 : wxFrame (nullptr, wxID_ANY, title)
515                 , _content_dialog (new ContentDialog(this))
516                 , _config_dialog (nullptr)
517         {
518                 auto bar = new wxMenuBar;
519                 setup_menu (bar);
520                 SetMenuBar (bar);
521
522                 /* Use a panel as the only child of the Frame so that we avoid
523                    the dark-grey background on Windows.
524                 */
525                 auto overall_panel = new wxPanel (this, wxID_ANY);
526                 auto sizer = new wxBoxSizer (wxVERTICAL);
527
528                 _playlist_list = new PlaylistList (overall_panel, _content_dialog);
529                 _playlist_content = new PlaylistContent (overall_panel, _content_dialog);
530
531                 sizer->Add (_playlist_list->sizer());
532                 sizer->Add (_playlist_content->sizer());
533
534                 overall_panel->SetSizer (sizer);
535
536                 _playlist_list->Edit.connect (bind(&DOMFrame::change_playlist, this, _1));
537
538                 Bind (wxEVT_MENU, boost::bind (&DOMFrame::file_exit, this), wxID_EXIT);
539                 Bind (wxEVT_MENU, boost::bind (&DOMFrame::help_about, this), wxID_ABOUT);
540                 Bind (wxEVT_MENU, boost::bind (&DOMFrame::edit_preferences, this), wxID_PREFERENCES);
541
542                 _config_changed_connection = Config::instance()->Changed.connect(boost::bind(&DOMFrame::config_changed, this));
543         }
544
545 private:
546
547         void file_exit ()
548         {
549                 /* false here allows the close handler to veto the close request */
550                 Close (false);
551         }
552
553         void help_about ()
554         {
555                 auto d = make_wx<AboutDialog>(this);
556                 d->ShowModal ();
557         }
558
559         void edit_preferences ()
560         {
561                 if (!_config_dialog) {
562                         _config_dialog = create_playlist_editor_config_dialog ();
563                 }
564                 _config_dialog->Show (this);
565         }
566
567         void change_playlist (shared_ptr<SignalSPL> playlist)
568         {
569                 auto old = _playlist_content->playlist ();
570                 if (old) {
571                         save_playlist (old);
572                 }
573                 _playlist_content->set (playlist);
574         }
575
576         void setup_menu (wxMenuBar* m)
577         {
578                 auto file = new wxMenu;
579 #ifdef __WXOSX__
580                 file->Append (wxID_PREFERENCES, _("&Preferences...\tCtrl-P"));
581                 file->Append (wxID_EXIT, _("&Exit"));
582 #else
583                 file->Append (wxID_EXIT, _("&Quit"));
584 #endif
585
586 #ifndef __WXOSX__
587                 auto edit = new wxMenu;
588                 edit->Append (wxID_PREFERENCES, _("&Preferences...\tCtrl-P"));
589 #endif
590
591                 auto help = new wxMenu;
592 #ifdef __WXOSX__
593                 help->Append (wxID_ABOUT, _("About DCP-o-matic"));
594 #else
595                 help->Append (wxID_ABOUT, _("About"));
596 #endif
597
598                 m->Append (file, _("&File"));
599 #ifndef __WXOSX__
600                 m->Append (edit, _("&Edit"));
601 #endif
602                 m->Append (help, _("&Help"));
603         }
604
605
606         void config_changed ()
607         {
608                 try {
609                         Config::instance()->write_config();
610                 } catch (exception& e) {
611                         error_dialog (
612                                 this,
613                                 wxString::Format (
614                                         _("Could not write to config file at %s.  Your changes have not been saved."),
615                                         std_to_wx (Config::instance()->cinemas_file().string()).data()
616                                         )
617                                 );
618                 }
619         }
620
621         ContentDialog* _content_dialog;
622         PlaylistList* _playlist_list;
623         PlaylistContent* _playlist_content;
624         wxPreferencesEditor* _config_dialog;
625         boost::signals2::scoped_connection _config_changed_connection;
626 };
627
628
629 /** @class App
630  *  @brief The magic App class for wxWidgets.
631  */
632 class App : public wxApp
633 {
634 public:
635         App ()
636                 : wxApp ()
637                 , _frame (nullptr)
638         {}
639
640 private:
641
642         bool OnInit () override
643         try
644         {
645                 wxInitAllImageHandlers ();
646                 SetAppName (_("DCP-o-matic Playlist Editor"));
647
648                 if (!wxApp::OnInit()) {
649                         return false;
650                 }
651
652 #ifdef DCPOMATIC_LINUX
653                 unsetenv ("UBUNTU_MENUPROXY");
654 #endif
655
656 #ifdef DCPOMATIC_OSX
657                 make_foreground_application ();
658 #endif
659
660                 dcpomatic_setup_path_encoding ();
661
662                 /* Enable i18n; this will create a Config object
663                    to look for a force-configured language.  This Config
664                    object will be wrong, however, because dcpomatic_setup
665                    hasn't yet been called and there aren't any filters etc.
666                    set up yet.
667                 */
668                 dcpomatic_setup_i18n ();
669
670                 /* Set things up, including filters etc.
671                    which will now be internationalised correctly.
672                 */
673                 dcpomatic_setup ();
674
675                 /* Force the configuration to be re-loaded correctly next
676                    time it is needed.
677                 */
678                 Config::drop ();
679
680                 _frame = new DOMFrame (_("DCP-o-matic Playlist Editor"));
681                 SetTopWindow (_frame);
682                 _frame->Maximize ();
683                 _frame->Show ();
684
685                 signal_manager = new wxSignalManager (this);
686                 Bind (wxEVT_IDLE, boost::bind (&App::idle, this));
687
688                 return true;
689         }
690         catch (exception& e)
691         {
692                 error_dialog (0, _("DCP-o-matic could not start"), std_to_wx(e.what()));
693                 return true;
694         }
695
696         /* An unhandled exception has occurred inside the main event loop */
697         bool OnExceptionInMainLoop () override
698         {
699                 try {
700                         throw;
701                 } catch (FileError& e) {
702                         error_dialog (
703                                 0,
704                                 wxString::Format (
705                                         _("An exception occurred: %s (%s)\n\n") + REPORT_PROBLEM,
706                                         std_to_wx (e.what()),
707                                         std_to_wx (e.file().string().c_str ())
708                                         )
709                                 );
710                 } catch (exception& e) {
711                         error_dialog (
712                                 0,
713                                 wxString::Format (
714                                         _("An exception occurred: %s.\n\n") + " " + REPORT_PROBLEM,
715                                         std_to_wx (e.what ())
716                                         )
717                                 );
718                 } catch (...) {
719                         error_dialog (0, _("An unknown exception occurred.") + "  " + REPORT_PROBLEM);
720                 }
721
722                 /* This will terminate the program */
723                 return false;
724         }
725
726         void OnUnhandledException () override
727         {
728                 error_dialog (0, _("An unknown exception occurred.") + "  " + REPORT_PROBLEM);
729         }
730
731         void idle ()
732         {
733                 signal_manager->ui_idle ();
734         }
735
736         DOMFrame* _frame;
737 };
738
739 IMPLEMENT_APP (App)