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