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