Add sound output configuration.
[dcpomatic.git] / src / wx / config_dialog.cc
1 /*
2     Copyright (C) 2012-2016 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 /** @file src/config_dialog.cc
22  *  @brief A dialogue to edit DCP-o-matic configuration.
23  */
24
25 #include "config_dialog.h"
26 #include "wx_util.h"
27 #include "editable_list.h"
28 #include "filter_dialog.h"
29 #include "dir_picker_ctrl.h"
30 #include "file_picker_ctrl.h"
31 #include "isdcf_metadata_dialog.h"
32 #include "server_dialog.h"
33 #include "make_chain_dialog.h"
34 #include "email_dialog.h"
35 #include "name_format_editor.h"
36 #include "lib/config.h"
37 #include "lib/ratio.h"
38 #include "lib/filter.h"
39 #include "lib/dcp_content_type.h"
40 #include "lib/log.h"
41 #include "lib/util.h"
42 #include "lib/cross.h"
43 #include "lib/exceptions.h"
44 #include <dcp/locale_convert.h>
45 #include <dcp/exceptions.h>
46 #include <dcp/certificate_chain.h>
47 #include <wx/stdpaths.h>
48 #include <wx/preferences.h>
49 #include <wx/spinctrl.h>
50 #include <wx/filepicker.h>
51 #include <RtAudio.h>
52 #include <boost/filesystem.hpp>
53 #include <boost/foreach.hpp>
54 #include <iostream>
55
56 using std::vector;
57 using std::string;
58 using std::list;
59 using std::cout;
60 using std::pair;
61 using std::make_pair;
62 using std::map;
63 using boost::bind;
64 using boost::shared_ptr;
65 using boost::function;
66 using boost::optional;
67 using dcp::locale_convert;
68
69 class Page
70 {
71 public:
72         Page (wxSize panel_size, int border)
73                 : _border (border)
74                 , _panel (0)
75                 , _panel_size (panel_size)
76                 , _window_exists (false)
77         {
78                 _config_connection = Config::instance()->Changed.connect (boost::bind (&Page::config_changed_wrapper, this));
79         }
80
81         virtual ~Page () {}
82
83 protected:
84         wxWindow* create_window (wxWindow* parent)
85         {
86                 _panel = new wxPanel (parent, wxID_ANY, wxDefaultPosition, _panel_size);
87                 wxBoxSizer* s = new wxBoxSizer (wxVERTICAL);
88                 _panel->SetSizer (s);
89
90                 setup ();
91                 _window_exists = true;
92                 config_changed ();
93
94                 _panel->Bind (wxEVT_DESTROY, boost::bind (&Page::window_destroyed, this));
95
96                 return _panel;
97         }
98
99         int _border;
100         wxPanel* _panel;
101
102 private:
103         virtual void config_changed () = 0;
104         virtual void setup () = 0;
105
106         void config_changed_wrapper ()
107         {
108                 if (_window_exists) {
109                         config_changed ();
110                 }
111         }
112
113         void window_destroyed ()
114         {
115                 _window_exists = false;
116         }
117
118         wxSize _panel_size;
119         boost::signals2::scoped_connection _config_connection;
120         bool _window_exists;
121 };
122
123 class StockPage : public wxStockPreferencesPage, public Page
124 {
125 public:
126         StockPage (Kind kind, wxSize panel_size, int border)
127                 : wxStockPreferencesPage (kind)
128                 , Page (panel_size, border)
129         {}
130
131         wxWindow* CreateWindow (wxWindow* parent)
132         {
133                 return create_window (parent);
134         }
135 };
136
137 class StandardPage : public wxPreferencesPage, public Page
138 {
139 public:
140         StandardPage (wxSize panel_size, int border)
141                 : Page (panel_size, border)
142         {}
143
144         wxWindow* CreateWindow (wxWindow* parent)
145         {
146                 return create_window (parent);
147         }
148 };
149
150 class GeneralPage : public StockPage
151 {
152 public:
153         GeneralPage (wxSize panel_size, int border)
154                 : StockPage (Kind_General, panel_size, border)
155         {}
156
157 private:
158         void setup ()
159         {
160                 wxGridBagSizer* table = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
161                 _panel->GetSizer()->Add (table, 1, wxALL | wxEXPAND, _border);
162
163                 int r = 0;
164                 _set_language = new wxCheckBox (_panel, wxID_ANY, _("Set language"));
165                 table->Add (_set_language, wxGBPosition (r, 0));
166                 _language = new wxChoice (_panel, wxID_ANY);
167                 vector<pair<string, string> > languages;
168                 languages.push_back (make_pair ("Čeština", "cs_CZ"));
169                 languages.push_back (make_pair ("汉语/漢語", "zh_CN"));
170                 languages.push_back (make_pair ("Dansk", "da_DK"));
171                 languages.push_back (make_pair ("Deutsch", "de_DE"));
172                 languages.push_back (make_pair ("English", "en_GB"));
173                 languages.push_back (make_pair ("Español", "es_ES"));
174                 languages.push_back (make_pair ("Français", "fr_FR"));
175                 languages.push_back (make_pair ("Italiano", "it_IT"));
176                 languages.push_back (make_pair ("Nederlands", "nl_NL"));
177                 languages.push_back (make_pair ("Русский", "ru_RU"));
178                 languages.push_back (make_pair ("Polski", "pl_PL"));
179                 languages.push_back (make_pair ("Português europeu", "pt_PT"));
180                 languages.push_back (make_pair ("Português do Brasil", "pt_BR"));
181                 languages.push_back (make_pair ("Svenska", "sv_SE"));
182                 languages.push_back (make_pair ("Slovenský jazyk", "sk_SK"));
183                 languages.push_back (make_pair ("українська мова", "uk_UA"));
184                 checked_set (_language, languages);
185                 table->Add (_language, wxGBPosition (r, 1));
186                 ++r;
187
188                 wxStaticText* restart = add_label_to_sizer (
189                         table, _panel, _("(restart DCP-o-matic to see language changes)"), false, wxGBPosition (r, 0), wxGBSpan (1, 2)
190                         );
191                 wxFont font = restart->GetFont();
192                 font.SetStyle (wxFONTSTYLE_ITALIC);
193                 font.SetPointSize (font.GetPointSize() - 1);
194                 restart->SetFont (font);
195                 ++r;
196
197                 add_label_to_sizer (table, _panel, _("Threads to use for encoding on this host"), true, wxGBPosition (r, 0));
198                 _num_local_encoding_threads = new wxSpinCtrl (_panel);
199                 table->Add (_num_local_encoding_threads, wxGBPosition (r, 1));
200                 ++r;
201
202                 add_label_to_sizer (table, _panel, _("Cinema and screen database file"), true, wxGBPosition (r, 0));
203                 _cinemas_file = new FilePickerCtrl (_panel, _("Select cinema and screen database file"), "*.xml");
204                 table->Add (_cinemas_file, wxGBPosition (r, 1));
205                 ++r;
206
207                 add_label_to_sizer (table, _panel, _("Sound output"), true, wxGBPosition (r, 0));
208                 _sound_output = new wxChoice (_panel, wxID_ANY);
209                 table->Add (_sound_output, wxGBPosition (r, 1));
210                 ++r;
211
212 #ifdef DCPOMATIC_HAVE_EBUR128_PATCHED_FFMPEG
213                 _analyse_ebur128 = new wxCheckBox (_panel, wxID_ANY, _("Find integrated loudness, true peak and loudness range when analysing audio"));
214                 table->Add (_analyse_ebur128, wxGBPosition (r, 0), wxGBSpan (1, 2));
215                 ++r;
216 #endif
217
218                 _automatic_audio_analysis = new wxCheckBox (_panel, wxID_ANY, _("Automatically analyse content audio"));
219                 table->Add (_automatic_audio_analysis, wxGBPosition (r, 0), wxGBSpan (1, 2));
220                 ++r;
221
222                 _check_for_updates = new wxCheckBox (_panel, wxID_ANY, _("Check for updates on startup"));
223                 table->Add (_check_for_updates, wxGBPosition (r, 0), wxGBSpan (1, 2));
224                 ++r;
225
226                 _check_for_test_updates = new wxCheckBox (_panel, wxID_ANY, _("Check for testing updates on startup"));
227                 table->Add (_check_for_test_updates, wxGBPosition (r, 0), wxGBSpan (1, 2));
228                 ++r;
229
230                 wxFlexGridSizer* bottom_table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
231                 bottom_table->AddGrowableCol (1, 1);
232
233                 add_label_to_sizer (bottom_table, _panel, _("Issuer"), true);
234                 _issuer = new wxTextCtrl (_panel, wxID_ANY);
235                 bottom_table->Add (_issuer, 1, wxALL | wxEXPAND);
236
237                 add_label_to_sizer (bottom_table, _panel, _("Creator"), true);
238                 _creator = new wxTextCtrl (_panel, wxID_ANY);
239                 bottom_table->Add (_creator, 1, wxALL | wxEXPAND);
240
241                 table->Add (bottom_table, wxGBPosition (r, 0), wxGBSpan (2, 2), wxEXPAND);
242                 ++r;
243
244                 RtAudio audio (DCPOMATIC_RTAUDIO_API);
245                 for (unsigned int i = 0; i < audio.getDeviceCount(); ++i) {
246                         RtAudio::DeviceInfo dev = audio.getDeviceInfo (i);
247                         if (dev.probed && dev.outputChannels > 0) {
248                                 _sound_output->Append (std_to_wx (dev.name));
249                         }
250                 }
251
252                 _set_language->Bind (wxEVT_CHECKBOX,           boost::bind (&GeneralPage::set_language_changed, this));
253                 _language->Bind     (wxEVT_CHOICE,             boost::bind (&GeneralPage::language_changed,     this));
254                 _cinemas_file->Bind (wxEVT_FILEPICKER_CHANGED, boost::bind (&GeneralPage::cinemas_file_changed, this));
255                 _sound_output->Bind (wxEVT_CHOICE,             boost::bind (&GeneralPage::sound_output_changed, this));
256
257                 _num_local_encoding_threads->SetRange (1, 128);
258                 _num_local_encoding_threads->Bind (wxEVT_SPINCTRL, boost::bind (&GeneralPage::num_local_encoding_threads_changed, this));
259
260 #ifdef DCPOMATIC_HAVE_EBUR128_PATCHED_FFMPEG
261                 _analyse_ebur128->Bind (wxEVT_CHECKBOX, boost::bind (&GeneralPage::analyse_ebur128_changed, this));
262 #endif
263                 _automatic_audio_analysis->Bind (wxEVT_CHECKBOX, boost::bind (&GeneralPage::automatic_audio_analysis_changed, this));
264                 _check_for_updates->Bind (wxEVT_CHECKBOX, boost::bind (&GeneralPage::check_for_updates_changed, this));
265                 _check_for_test_updates->Bind (wxEVT_CHECKBOX, boost::bind (&GeneralPage::check_for_test_updates_changed, this));
266
267                 _issuer->Bind (wxEVT_TEXT, boost::bind (&GeneralPage::issuer_changed, this));
268                 _creator->Bind (wxEVT_TEXT, boost::bind (&GeneralPage::creator_changed, this));
269         }
270
271         void config_changed ()
272         {
273                 Config* config = Config::instance ();
274
275                 checked_set (_set_language, static_cast<bool>(config->language()));
276
277                 /* Backwards compatibility of config file */
278
279                 map<string, string> compat_map;
280                 compat_map["fr"] = "fr_FR";
281                 compat_map["it"] = "it_IT";
282                 compat_map["es"] = "es_ES";
283                 compat_map["sv"] = "sv_SE";
284                 compat_map["de"] = "de_DE";
285                 compat_map["nl"] = "nl_NL";
286                 compat_map["ru"] = "ru_RU";
287                 compat_map["pl"] = "pl_PL";
288                 compat_map["da"] = "da_DK";
289                 compat_map["pt"] = "pt_PT";
290                 compat_map["sk"] = "sk_SK";
291                 compat_map["cs"] = "cs_CZ";
292                 compat_map["uk"] = "uk_UA";
293
294                 string lang = config->language().get_value_or ("en_GB");
295                 if (compat_map.find (lang) != compat_map.end ()) {
296                         lang = compat_map[lang];
297                 }
298
299                 checked_set (_language, lang);
300
301                 checked_set (_num_local_encoding_threads, config->num_local_encoding_threads ());
302 #ifdef DCPOMATIC_HAVE_EBUR128_PATCHED_FFMPEG
303                 checked_set (_analyse_ebur128, config->analyse_ebur128 ());
304 #endif
305                 checked_set (_automatic_audio_analysis, config->automatic_audio_analysis ());
306                 checked_set (_check_for_updates, config->check_for_updates ());
307                 checked_set (_check_for_test_updates, config->check_for_test_updates ());
308                 checked_set (_issuer, config->dcp_issuer ());
309                 checked_set (_creator, config->dcp_creator ());
310                 checked_set (_cinemas_file, config->cinemas_file());
311
312                 optional<string> const current_so = get_sound_output ();
313                 string configured_so;
314
315                 if (config->sound_output()) {
316                         configured_so = config->sound_output().get();
317                 } else {
318                         /* No configured output means we should use the default */
319                         RtAudio audio (DCPOMATIC_RTAUDIO_API);
320                         configured_so = audio.getDeviceInfo(audio.getDefaultOutputDevice()).name;
321                 }
322
323                 if (!current_so || *current_so != configured_so) {
324                         /* Update _sound_output with the configured value */
325                         unsigned int i = 0;
326                         while (i < _sound_output->GetCount()) {
327                                 if (_sound_output->GetString(i) == std_to_wx(configured_so)) {
328                                         _sound_output->SetSelection (i);
329                                         break;
330                                 }
331                                 ++i;
332                         }
333                 }
334
335                 setup_sensitivity ();
336         }
337
338         /** @return Currently-selected sound output in the dialogue */
339         optional<string> get_sound_output ()
340         {
341                 int const sel = _sound_output->GetSelection ();
342                 if (sel == wxNOT_FOUND) {
343                         return optional<string> ();
344                 }
345
346                 return wx_to_std (_sound_output->GetString (sel));
347         }
348
349         void setup_sensitivity ()
350         {
351                 _language->Enable (_set_language->GetValue ());
352                 _check_for_test_updates->Enable (_check_for_updates->GetValue ());
353         }
354
355         void set_language_changed ()
356         {
357                 setup_sensitivity ();
358                 if (_set_language->GetValue ()) {
359                         language_changed ();
360                 } else {
361                         Config::instance()->unset_language ();
362                 }
363         }
364
365         void language_changed ()
366         {
367                 int const sel = _language->GetSelection ();
368                 if (sel != -1) {
369                         Config::instance()->set_language (string_client_data (_language->GetClientObject (sel)));
370                 } else {
371                         Config::instance()->unset_language ();
372                 }
373         }
374
375 #ifdef DCPOMATIC_HAVE_EBUR128_PATCHED_FFMPEG
376         void analyse_ebur128_changed ()
377         {
378                 Config::instance()->set_analyse_ebur128 (_analyse_ebur128->GetValue ());
379         }
380 #endif
381
382         void automatic_audio_analysis_changed ()
383         {
384                 Config::instance()->set_automatic_audio_analysis (_automatic_audio_analysis->GetValue ());
385         }
386
387         void check_for_updates_changed ()
388         {
389                 Config::instance()->set_check_for_updates (_check_for_updates->GetValue ());
390         }
391
392         void check_for_test_updates_changed ()
393         {
394                 Config::instance()->set_check_for_test_updates (_check_for_test_updates->GetValue ());
395         }
396
397         void num_local_encoding_threads_changed ()
398         {
399                 Config::instance()->set_num_local_encoding_threads (_num_local_encoding_threads->GetValue ());
400         }
401
402         void issuer_changed ()
403         {
404                 Config::instance()->set_dcp_issuer (wx_to_std (_issuer->GetValue ()));
405         }
406
407         void creator_changed ()
408         {
409                 Config::instance()->set_dcp_creator (wx_to_std (_creator->GetValue ()));
410         }
411
412         void cinemas_file_changed ()
413         {
414                 Config::instance()->set_cinemas_file (wx_to_std (_cinemas_file->GetPath ()));
415         }
416
417         void sound_output_changed ()
418         {
419                 RtAudio audio (DCPOMATIC_RTAUDIO_API);
420                 optional<string> const so = get_sound_output();
421                 if (!so || *so == audio.getDeviceInfo(audio.getDefaultOutputDevice()).name) {
422                         Config::instance()->unset_sound_output ();
423                 } else {
424                         Config::instance()->set_sound_output (*so);
425                 }
426         }
427
428         wxCheckBox* _set_language;
429         wxChoice* _language;
430         wxSpinCtrl* _num_local_encoding_threads;
431         FilePickerCtrl* _cinemas_file;
432         wxChoice* _sound_output;
433 #ifdef DCPOMATIC_HAVE_EBUR128_PATCHED_FFMPEG
434         wxCheckBox* _analyse_ebur128;
435 #endif
436         wxCheckBox* _automatic_audio_analysis;
437         wxCheckBox* _check_for_updates;
438         wxCheckBox* _check_for_test_updates;
439         wxTextCtrl* _issuer;
440         wxTextCtrl* _creator;
441 };
442
443 class DefaultsPage : public StandardPage
444 {
445 public:
446         DefaultsPage (wxSize panel_size, int border)
447                 : StandardPage (panel_size, border)
448         {}
449
450         wxString GetName () const
451         {
452                 return _("Defaults");
453         }
454
455 #ifdef DCPOMATIC_OSX
456         wxBitmap GetLargeIcon () const
457         {
458                 return wxBitmap ("defaults", wxBITMAP_TYPE_PNG_RESOURCE);
459         }
460 #endif
461
462 private:
463         void setup ()
464         {
465                 wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
466                 table->AddGrowableCol (1, 1);
467                 _panel->GetSizer()->Add (table, 1, wxALL | wxEXPAND, _border);
468
469                 {
470                         add_label_to_sizer (table, _panel, _("Default duration of still images"), true);
471                         wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
472                         _still_length = new wxSpinCtrl (_panel);
473                         s->Add (_still_length);
474                         add_label_to_sizer (s, _panel, _("s"), false);
475                         table->Add (s, 1);
476                 }
477
478                 add_label_to_sizer (table, _panel, _("Default directory for new films"), true);
479 #ifdef DCPOMATIC_USE_OWN_PICKER
480                 _directory = new DirPickerCtrl (_panel);
481 #else
482                 _directory = new wxDirPickerCtrl (_panel, wxDD_DIR_MUST_EXIST);
483 #endif
484                 table->Add (_directory, 1, wxEXPAND);
485
486                 add_label_to_sizer (table, _panel, _("Default ISDCF name details"), true);
487                 _isdcf_metadata_button = new wxButton (_panel, wxID_ANY, _("Edit..."));
488                 table->Add (_isdcf_metadata_button);
489
490                 add_label_to_sizer (table, _panel, _("Default container"), true);
491                 _container = new wxChoice (_panel, wxID_ANY);
492                 table->Add (_container);
493
494                 add_label_to_sizer (table, _panel, _("Default content type"), true);
495                 _dcp_content_type = new wxChoice (_panel, wxID_ANY);
496                 table->Add (_dcp_content_type);
497
498                 add_label_to_sizer (table, _panel, _("Default DCP audio channels"), true);
499                 _dcp_audio_channels = new wxChoice (_panel, wxID_ANY);
500                 table->Add (_dcp_audio_channels);
501
502                 {
503                         add_label_to_sizer (table, _panel, _("Default JPEG2000 bandwidth"), true);
504                         wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
505                         _j2k_bandwidth = new wxSpinCtrl (_panel);
506                         s->Add (_j2k_bandwidth);
507                         add_label_to_sizer (s, _panel, _("Mbit/s"), false);
508                         table->Add (s, 1);
509                 }
510
511                 {
512                         add_label_to_sizer (table, _panel, _("Default audio delay"), true);
513                         wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
514                         _audio_delay = new wxSpinCtrl (_panel);
515                         s->Add (_audio_delay);
516                         add_label_to_sizer (s, _panel, _("ms"), false);
517                         table->Add (s, 1);
518                 }
519
520                 add_label_to_sizer (table, _panel, _("Default standard"), true);
521                 _standard = new wxChoice (_panel, wxID_ANY);
522                 table->Add (_standard);
523
524                 add_label_to_sizer (table, _panel, _("Default KDM directory"), true);
525 #ifdef DCPOMATIC_USE_OWN_PICKER
526                 _kdm_directory = new DirPickerCtrl (_panel);
527 #else
528                 _kdm_directory = new wxDirPickerCtrl (_panel, wxDD_DIR_MUST_EXIST);
529 #endif
530                 table->Add (_kdm_directory, 1, wxEXPAND);
531
532                 _still_length->SetRange (1, 3600);
533                 _still_length->Bind (wxEVT_SPINCTRL, boost::bind (&DefaultsPage::still_length_changed, this));
534
535                 _directory->Bind (wxEVT_DIRPICKER_CHANGED, boost::bind (&DefaultsPage::directory_changed, this));
536                 _kdm_directory->Bind (wxEVT_DIRPICKER_CHANGED, boost::bind (&DefaultsPage::kdm_directory_changed, this));
537
538                 _isdcf_metadata_button->Bind (wxEVT_BUTTON, boost::bind (&DefaultsPage::edit_isdcf_metadata_clicked, this));
539
540                 vector<Ratio const *> ratios = Ratio::all ();
541                 for (size_t i = 0; i < ratios.size(); ++i) {
542                         _container->Append (std_to_wx (ratios[i]->nickname ()));
543                 }
544
545                 _container->Bind (wxEVT_CHOICE, boost::bind (&DefaultsPage::container_changed, this));
546
547                 vector<DCPContentType const *> const ct = DCPContentType::all ();
548                 for (size_t i = 0; i < ct.size(); ++i) {
549                         _dcp_content_type->Append (std_to_wx (ct[i]->pretty_name ()));
550                 }
551
552                 setup_audio_channels_choice (_dcp_audio_channels, 2);
553
554                 _dcp_content_type->Bind (wxEVT_CHOICE, boost::bind (&DefaultsPage::dcp_content_type_changed, this));
555                 _dcp_audio_channels->Bind (wxEVT_CHOICE, boost::bind (&DefaultsPage::dcp_audio_channels_changed, this));
556
557                 _j2k_bandwidth->SetRange (50, 250);
558                 _j2k_bandwidth->Bind (wxEVT_SPINCTRL, boost::bind (&DefaultsPage::j2k_bandwidth_changed, this));
559
560                 _audio_delay->SetRange (-1000, 1000);
561                 _audio_delay->Bind (wxEVT_SPINCTRL, boost::bind (&DefaultsPage::audio_delay_changed, this));
562
563                 _standard->Append (_("SMPTE"));
564                 _standard->Append (_("Interop"));
565                 _standard->Bind (wxEVT_CHOICE, boost::bind (&DefaultsPage::standard_changed, this));
566         }
567
568         void config_changed ()
569         {
570                 Config* config = Config::instance ();
571
572                 vector<Ratio const *> ratios = Ratio::all ();
573                 for (size_t i = 0; i < ratios.size(); ++i) {
574                         if (ratios[i] == config->default_container ()) {
575                                 _container->SetSelection (i);
576                         }
577                 }
578
579                 vector<DCPContentType const *> const ct = DCPContentType::all ();
580                 for (size_t i = 0; i < ct.size(); ++i) {
581                         if (ct[i] == config->default_dcp_content_type ()) {
582                                 _dcp_content_type->SetSelection (i);
583                         }
584                 }
585
586                 checked_set (_still_length, config->default_still_length ());
587                 _directory->SetPath (std_to_wx (config->default_directory_or (wx_to_std (wxStandardPaths::Get().GetDocumentsDir())).string ()));
588                 _kdm_directory->SetPath (std_to_wx (config->default_kdm_directory_or (wx_to_std (wxStandardPaths::Get().GetDocumentsDir())).string ()));
589                 checked_set (_j2k_bandwidth, config->default_j2k_bandwidth() / 1000000);
590                 _j2k_bandwidth->SetRange (50, config->maximum_j2k_bandwidth() / 1000000);
591                 checked_set (_dcp_audio_channels, locale_convert<string> (config->default_dcp_audio_channels()));
592                 checked_set (_audio_delay, config->default_audio_delay ());
593                 checked_set (_standard, config->default_interop() ? 1 : 0);
594         }
595
596         void j2k_bandwidth_changed ()
597         {
598                 Config::instance()->set_default_j2k_bandwidth (_j2k_bandwidth->GetValue() * 1000000);
599         }
600
601         void audio_delay_changed ()
602         {
603                 Config::instance()->set_default_audio_delay (_audio_delay->GetValue());
604         }
605
606         void dcp_audio_channels_changed ()
607         {
608                 int const s = _dcp_audio_channels->GetSelection ();
609                 if (s != wxNOT_FOUND) {
610                         Config::instance()->set_default_dcp_audio_channels (
611                                 locale_convert<int> (string_client_data (_dcp_audio_channels->GetClientObject (s)))
612                                 );
613                 }
614         }
615
616         void directory_changed ()
617         {
618                 Config::instance()->set_default_directory (wx_to_std (_directory->GetPath ()));
619         }
620
621         void kdm_directory_changed ()
622         {
623                 Config::instance()->set_default_kdm_directory (wx_to_std (_kdm_directory->GetPath ()));
624         }
625
626         void edit_isdcf_metadata_clicked ()
627         {
628                 ISDCFMetadataDialog* d = new ISDCFMetadataDialog (_panel, Config::instance()->default_isdcf_metadata (), false);
629                 d->ShowModal ();
630                 Config::instance()->set_default_isdcf_metadata (d->isdcf_metadata ());
631                 d->Destroy ();
632         }
633
634         void still_length_changed ()
635         {
636                 Config::instance()->set_default_still_length (_still_length->GetValue ());
637         }
638
639         void container_changed ()
640         {
641                 vector<Ratio const *> ratio = Ratio::all ();
642                 Config::instance()->set_default_container (ratio[_container->GetSelection()]);
643         }
644
645         void dcp_content_type_changed ()
646         {
647                 vector<DCPContentType const *> ct = DCPContentType::all ();
648                 Config::instance()->set_default_dcp_content_type (ct[_dcp_content_type->GetSelection()]);
649         }
650
651         void standard_changed ()
652         {
653                 Config::instance()->set_default_interop (_standard->GetSelection() == 1);
654         }
655
656         wxSpinCtrl* _j2k_bandwidth;
657         wxSpinCtrl* _audio_delay;
658         wxButton* _isdcf_metadata_button;
659         wxSpinCtrl* _still_length;
660 #ifdef DCPOMATIC_USE_OWN_PICKER
661         DirPickerCtrl* _directory;
662         DirPickerCtrl* _kdm_directory;
663 #else
664         wxDirPickerCtrl* _directory;
665         wxDirPickerCtrl* _kdm_directory;
666 #endif
667         wxChoice* _container;
668         wxChoice* _dcp_content_type;
669         wxChoice* _dcp_audio_channels;
670         wxChoice* _standard;
671 };
672
673 class EncodingServersPage : public StandardPage
674 {
675 public:
676         EncodingServersPage (wxSize panel_size, int border)
677                 : StandardPage (panel_size, border)
678         {}
679
680         wxString GetName () const
681         {
682                 return _("Servers");
683         }
684
685 #ifdef DCPOMATIC_OSX
686         wxBitmap GetLargeIcon () const
687         {
688                 return wxBitmap ("servers", wxBITMAP_TYPE_PNG_RESOURCE);
689         }
690 #endif
691
692 private:
693         void setup ()
694         {
695                 _use_any_servers = new wxCheckBox (_panel, wxID_ANY, _("Search network for servers"));
696                 _panel->GetSizer()->Add (_use_any_servers, 0, wxALL, _border);
697
698                 vector<string> columns;
699                 columns.push_back (wx_to_std (_("IP address / host name")));
700                 _servers_list = new EditableList<string, ServerDialog> (
701                         _panel,
702                         columns,
703                         boost::bind (&Config::servers, Config::instance()),
704                         boost::bind (&Config::set_servers, Config::instance(), _1),
705                         boost::bind (&EncodingServersPage::server_column, this, _1)
706                         );
707
708                 _panel->GetSizer()->Add (_servers_list, 1, wxEXPAND | wxALL, _border);
709
710                 _use_any_servers->Bind (wxEVT_CHECKBOX, boost::bind (&EncodingServersPage::use_any_servers_changed, this));
711         }
712
713         void config_changed ()
714         {
715                 checked_set (_use_any_servers, Config::instance()->use_any_servers ());
716                 _servers_list->refresh ();
717         }
718
719         void use_any_servers_changed ()
720         {
721                 Config::instance()->set_use_any_servers (_use_any_servers->GetValue ());
722         }
723
724         string server_column (string s)
725         {
726                 return s;
727         }
728
729         wxCheckBox* _use_any_servers;
730         EditableList<string, ServerDialog>* _servers_list;
731 };
732
733 class CertificateChainEditor : public wxPanel
734 {
735 public:
736         CertificateChainEditor (
737                 wxWindow* parent,
738                 wxString title,
739                 int border,
740                 function<void (shared_ptr<dcp::CertificateChain>)> set,
741                 function<shared_ptr<const dcp::CertificateChain> (void)> get
742                 )
743                 : wxPanel (parent)
744                 , _set (set)
745                 , _get (get)
746         {
747                 wxFont subheading_font (*wxNORMAL_FONT);
748                 subheading_font.SetWeight (wxFONTWEIGHT_BOLD);
749
750                 _sizer = new wxBoxSizer (wxVERTICAL);
751
752                 {
753                         wxStaticText* m = new wxStaticText (this, wxID_ANY, title);
754                         m->SetFont (subheading_font);
755                         _sizer->Add (m, 0, wxALL, border);
756                 }
757
758                 wxBoxSizer* certificates_sizer = new wxBoxSizer (wxHORIZONTAL);
759                 _sizer->Add (certificates_sizer, 0, wxLEFT | wxRIGHT, border);
760
761                 _certificates = new wxListCtrl (this, wxID_ANY, wxDefaultPosition, wxSize (440, 150), wxLC_REPORT | wxLC_SINGLE_SEL);
762
763                 {
764                         wxListItem ip;
765                         ip.SetId (0);
766                         ip.SetText (_("Type"));
767                         ip.SetWidth (100);
768                         _certificates->InsertColumn (0, ip);
769                 }
770
771                 {
772                         wxListItem ip;
773                         ip.SetId (1);
774                         ip.SetText (_("Thumbprint"));
775                         ip.SetWidth (340);
776
777                         wxFont font = ip.GetFont ();
778                         font.SetFamily (wxFONTFAMILY_TELETYPE);
779                         ip.SetFont (font);
780
781                         _certificates->InsertColumn (1, ip);
782                 }
783
784                 certificates_sizer->Add (_certificates, 1, wxEXPAND);
785
786                 {
787                         wxSizer* s = new wxBoxSizer (wxVERTICAL);
788                         _add_certificate = new wxButton (this, wxID_ANY, _("Add..."));
789                         s->Add (_add_certificate, 0, wxTOP | wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
790                         _remove_certificate = new wxButton (this, wxID_ANY, _("Remove"));
791                         s->Add (_remove_certificate, 0, wxTOP | wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
792                         _export_certificate = new wxButton (this, wxID_ANY, _("Export"));
793                         s->Add (_export_certificate, 0, wxTOP | wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
794                         certificates_sizer->Add (s, 0, wxLEFT, DCPOMATIC_SIZER_X_GAP);
795                 }
796
797                 wxGridBagSizer* table = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
798                 _sizer->Add (table, 1, wxALL | wxEXPAND, border);
799                 int r = 0;
800
801                 add_label_to_sizer (table, this, _("Leaf private key"), true, wxGBPosition (r, 0));
802                 _private_key = new wxStaticText (this, wxID_ANY, wxT (""));
803                 wxFont font = _private_key->GetFont ();
804                 font.SetFamily (wxFONTFAMILY_TELETYPE);
805                 _private_key->SetFont (font);
806                 table->Add (_private_key, wxGBPosition (r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
807                 _load_private_key = new wxButton (this, wxID_ANY, _("Load..."));
808                 table->Add (_load_private_key, wxGBPosition (r, 2));
809                 _export_private_key = new wxButton (this, wxID_ANY, _("Export..."));
810                 table->Add (_export_private_key, wxGBPosition (r, 3));
811                 ++r;
812
813                 _button_sizer = new wxBoxSizer (wxHORIZONTAL);
814                 _remake_certificates = new wxButton (this, wxID_ANY, _("Re-make certificates\nand key..."));
815                 _button_sizer->Add (_remake_certificates, 1, wxRIGHT, border);
816                 table->Add (_button_sizer, wxGBPosition (r, 0), wxGBSpan (1, 4));
817                 ++r;
818
819                 _add_certificate->Bind     (wxEVT_BUTTON,       boost::bind (&CertificateChainEditor::add_certificate, this));
820                 _remove_certificate->Bind  (wxEVT_BUTTON,       boost::bind (&CertificateChainEditor::remove_certificate, this));
821                 _export_certificate->Bind  (wxEVT_BUTTON,       boost::bind (&CertificateChainEditor::export_certificate, this));
822                 _certificates->Bind        (wxEVT_LIST_ITEM_SELECTED,   boost::bind (&CertificateChainEditor::update_sensitivity, this));
823                 _certificates->Bind        (wxEVT_LIST_ITEM_DESELECTED, boost::bind (&CertificateChainEditor::update_sensitivity, this));
824                 _remake_certificates->Bind (wxEVT_BUTTON,       boost::bind (&CertificateChainEditor::remake_certificates, this));
825                 _load_private_key->Bind    (wxEVT_BUTTON,       boost::bind (&CertificateChainEditor::load_private_key, this));
826                 _export_private_key->Bind  (wxEVT_BUTTON,       boost::bind (&CertificateChainEditor::export_private_key, this));
827
828                 SetSizerAndFit (_sizer);
829         }
830
831         void config_changed ()
832         {
833                 _chain.reset (new dcp::CertificateChain (*_get().get ()));
834
835                 update_certificate_list ();
836                 update_private_key ();
837                 update_sensitivity ();
838         }
839
840         void add_button (wxWindow* button)
841         {
842                 _button_sizer->Add (button, 0, wxLEFT | wxRIGHT, DCPOMATIC_SIZER_X_GAP);
843                 _sizer->Layout ();
844         }
845
846 private:
847         void add_certificate ()
848         {
849                 wxFileDialog* d = new wxFileDialog (this, _("Select Certificate File"));
850
851                 if (d->ShowModal() == wxID_OK) {
852                         try {
853                                 dcp::Certificate c;
854                                 string const extra = c.read_string (dcp::file_to_string (wx_to_std (d->GetPath ())));
855                                 if (!extra.empty ()) {
856                                         message_dialog (
857                                                 this,
858                                                 _("This file contains other certificates (or other data) after its first certificate. "
859                                                   "Only the first certificate will be used.")
860                                                 );
861                                 }
862                                 _chain->add (c);
863                                 _set (_chain);
864                                 update_certificate_list ();
865                         } catch (dcp::MiscError& e) {
866                                 error_dialog (this, wxString::Format (_("Could not read certificate file (%s)"), e.what ()));
867                         }
868                 }
869
870                 d->Destroy ();
871
872                 update_sensitivity ();
873         }
874
875         void remove_certificate ()
876         {
877                 int i = _certificates->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
878                 if (i == -1) {
879                         return;
880                 }
881
882                 _certificates->DeleteItem (i);
883                 _chain->remove (i);
884                 _set (_chain);
885
886                 update_sensitivity ();
887         }
888
889         void export_certificate ()
890         {
891                 int i = _certificates->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
892                 if (i == -1) {
893                         return;
894                 }
895
896                 wxFileDialog* d = new wxFileDialog (
897                         this, _("Select Certificate File"), wxEmptyString, wxEmptyString, wxT ("PEM files (*.pem)|*.pem"),
898                         wxFD_SAVE | wxFD_OVERWRITE_PROMPT
899                         );
900
901                 dcp::CertificateChain::List all = _chain->root_to_leaf ();
902                 dcp::CertificateChain::List::iterator j = all.begin ();
903                 for (int k = 0; k < i; ++k) {
904                         ++j;
905                 }
906
907                 if (d->ShowModal () == wxID_OK) {
908                         FILE* f = fopen_boost (wx_to_std (d->GetPath ()), "w");
909                         if (!f) {
910                                 throw OpenFileError (wx_to_std (d->GetPath ()), errno, false);
911                         }
912
913                         string const s = j->certificate (true);
914                         fwrite (s.c_str(), 1, s.length(), f);
915                         fclose (f);
916                 }
917                 d->Destroy ();
918         }
919
920         void update_certificate_list ()
921         {
922                 _certificates->DeleteAllItems ();
923                 size_t n = 0;
924                 dcp::CertificateChain::List certs = _chain->root_to_leaf ();
925                 BOOST_FOREACH (dcp::Certificate const & i, certs) {
926                         wxListItem item;
927                         item.SetId (n);
928                         _certificates->InsertItem (item);
929                         _certificates->SetItem (n, 1, std_to_wx (i.thumbprint ()));
930
931                         if (n == 0) {
932                                 _certificates->SetItem (n, 0, _("Root"));
933                         } else if (n == (certs.size() - 1)) {
934                                 _certificates->SetItem (n, 0, _("Leaf"));
935                         } else {
936                                 _certificates->SetItem (n, 0, _("Intermediate"));
937                         }
938
939                         ++n;
940                 }
941         }
942
943         void remake_certificates ()
944         {
945                 shared_ptr<const dcp::CertificateChain> chain = _get ();
946
947                 string subject_organization_name;
948                 string subject_organizational_unit_name;
949                 string root_common_name;
950                 string intermediate_common_name;
951                 string leaf_common_name;
952
953                 dcp::CertificateChain::List all = chain->root_to_leaf ();
954
955                 if (all.size() >= 1) {
956                         /* Have a root */
957                         subject_organization_name = chain->root().subject_organization_name ();
958                         subject_organizational_unit_name = chain->root().subject_organizational_unit_name ();
959                         root_common_name = chain->root().subject_common_name ();
960                 }
961
962                 if (all.size() >= 2) {
963                         /* Have a leaf */
964                         leaf_common_name = chain->leaf().subject_common_name ();
965                 }
966
967                 if (all.size() >= 3) {
968                         /* Have an intermediate */
969                         dcp::CertificateChain::List::iterator i = all.begin ();
970                         ++i;
971                         intermediate_common_name = i->subject_common_name ();
972                 }
973
974                 MakeChainDialog* d = new MakeChainDialog (
975                         this,
976                         subject_organization_name,
977                         subject_organizational_unit_name,
978                         root_common_name,
979                         intermediate_common_name,
980                         leaf_common_name
981                         );
982
983                 if (d->ShowModal () == wxID_OK) {
984                         _chain.reset (
985                                 new dcp::CertificateChain (
986                                         openssl_path (),
987                                         d->organisation (),
988                                         d->organisational_unit (),
989                                         d->root_common_name (),
990                                         d->intermediate_common_name (),
991                                         d->leaf_common_name ()
992                                         )
993                                 );
994
995                         _set (_chain);
996                         update_certificate_list ();
997                         update_private_key ();
998                 }
999
1000                 d->Destroy ();
1001         }
1002
1003         void update_sensitivity ()
1004         {
1005                 _remove_certificate->Enable (_certificates->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) != -1);
1006                 _export_certificate->Enable (_certificates->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) != -1);
1007         }
1008
1009         void update_private_key ()
1010         {
1011                 checked_set (_private_key, dcp::private_key_fingerprint (_chain->key().get ()));
1012                 _sizer->Layout ();
1013         }
1014
1015         void load_private_key ()
1016         {
1017                 wxFileDialog* d = new wxFileDialog (this, _("Select Key File"));
1018
1019                 if (d->ShowModal() == wxID_OK) {
1020                         try {
1021                                 boost::filesystem::path p (wx_to_std (d->GetPath ()));
1022                                 if (boost::filesystem::file_size (p) > 8192) {
1023                                         error_dialog (
1024                                                 this,
1025                                                 wxString::Format (_("Could not read key file; file is too long (%s)"), std_to_wx (p.string ()))
1026                                                 );
1027                                         return;
1028                                 }
1029
1030                                 _chain->set_key (dcp::file_to_string (p));
1031                                 _set (_chain);
1032                                 update_private_key ();
1033                         } catch (dcp::MiscError& e) {
1034                                 error_dialog (this, wxString::Format (_("Could not read certificate file (%s)"), e.what ()));
1035                         }
1036                 }
1037
1038                 d->Destroy ();
1039
1040                 update_sensitivity ();
1041         }
1042
1043         void export_private_key ()
1044         {
1045                 optional<string> key = _chain->key ();
1046                 if (!key) {
1047                         return;
1048                 }
1049
1050                 wxFileDialog* d = new wxFileDialog (
1051                         this, _("Select Key File"), wxEmptyString, wxEmptyString, wxT ("PEM files (*.pem)|*.pem"),
1052                         wxFD_SAVE | wxFD_OVERWRITE_PROMPT
1053                         );
1054
1055                 if (d->ShowModal () == wxID_OK) {
1056                         FILE* f = fopen_boost (wx_to_std (d->GetPath ()), "w");
1057                         if (!f) {
1058                                 throw OpenFileError (wx_to_std (d->GetPath ()), errno, false);
1059                         }
1060
1061                         string const s = _chain->key().get ();
1062                         fwrite (s.c_str(), 1, s.length(), f);
1063                         fclose (f);
1064                 }
1065                 d->Destroy ();
1066         }
1067
1068         wxListCtrl* _certificates;
1069         wxButton* _add_certificate;
1070         wxButton* _export_certificate;
1071         wxButton* _remove_certificate;
1072         wxButton* _remake_certificates;
1073         wxStaticText* _private_key;
1074         wxButton* _load_private_key;
1075         wxButton* _export_private_key;
1076         wxSizer* _sizer;
1077         wxBoxSizer* _button_sizer;
1078         shared_ptr<dcp::CertificateChain> _chain;
1079         boost::function<void (shared_ptr<dcp::CertificateChain>)> _set;
1080         boost::function<shared_ptr<const dcp::CertificateChain> (void)> _get;
1081 };
1082
1083 class KeysPage : public StandardPage
1084 {
1085 public:
1086         KeysPage (wxSize panel_size, int border)
1087                 : StandardPage (panel_size, border)
1088         {}
1089
1090         wxString GetName () const
1091         {
1092                 return _("Keys");
1093         }
1094
1095 #ifdef DCPOMATIC_OSX
1096         wxBitmap GetLargeIcon () const
1097         {
1098                 return wxBitmap ("keys", wxBITMAP_TYPE_PNG_RESOURCE);
1099         }
1100 #endif
1101
1102 private:
1103
1104         void setup ()
1105         {
1106                 _signer = new CertificateChainEditor (
1107                         _panel, _("Signing DCPs and KDMs"), _border,
1108                         boost::bind (&Config::set_signer_chain, Config::instance (), _1),
1109                         boost::bind (&Config::signer_chain, Config::instance ())
1110                         );
1111
1112                 _panel->GetSizer()->Add (_signer);
1113
1114                 _decryption = new CertificateChainEditor (
1115                         _panel, _("Decrypting DCPs"), _border,
1116                         boost::bind (&Config::set_decryption_chain, Config::instance (), _1),
1117                         boost::bind (&Config::decryption_chain, Config::instance ())
1118                         );
1119
1120                 _panel->GetSizer()->Add (_decryption);
1121
1122                 _export_decryption_certificate = new wxButton (_decryption, wxID_ANY, _("Export DCP decryption\ncertificate..."));
1123                 _decryption->add_button (_export_decryption_certificate);
1124                 _export_decryption_chain = new wxButton (_decryption, wxID_ANY, _("Export DCP decryption\nchain..."));
1125                 _decryption->add_button (_export_decryption_chain);
1126
1127                 _export_decryption_certificate->Bind (wxEVT_BUTTON, boost::bind (&KeysPage::export_decryption_certificate, this));
1128                 _export_decryption_chain->Bind (wxEVT_BUTTON, boost::bind (&KeysPage::export_decryption_chain, this));
1129         }
1130
1131         void export_decryption_certificate ()
1132         {
1133                 wxFileDialog* d = new wxFileDialog (
1134                         _panel, _("Select Certificate File"), wxEmptyString, wxEmptyString, wxT ("PEM files (*.pem)|*.pem"),
1135                         wxFD_SAVE | wxFD_OVERWRITE_PROMPT
1136                         );
1137
1138                 if (d->ShowModal () == wxID_OK) {
1139                         FILE* f = fopen_boost (wx_to_std (d->GetPath ()), "w");
1140                         if (!f) {
1141                                 throw OpenFileError (wx_to_std (d->GetPath ()), errno, false);
1142                         }
1143
1144                         string const s = Config::instance()->decryption_chain()->leaf().certificate (true);
1145                         fwrite (s.c_str(), 1, s.length(), f);
1146                         fclose (f);
1147                 }
1148                 d->Destroy ();
1149         }
1150
1151         void export_decryption_chain ()
1152         {
1153                 wxFileDialog* d = new wxFileDialog (
1154                         _panel, _("Select Chain File"), wxEmptyString, wxEmptyString, wxT ("PEM files (*.pem)|*.pem"),
1155                         wxFD_SAVE | wxFD_OVERWRITE_PROMPT
1156                         );
1157
1158                 if (d->ShowModal () == wxID_OK) {
1159                         FILE* f = fopen_boost (wx_to_std (d->GetPath ()), "w");
1160                         if (!f) {
1161                                 throw OpenFileError (wx_to_std (d->GetPath ()), errno, false);
1162                         }
1163
1164                         string const s = Config::instance()->decryption_chain()->chain();
1165                         fwrite (s.c_str(), 1, s.length(), f);
1166                         fclose (f);
1167                 }
1168                 d->Destroy ();
1169         }
1170
1171         void config_changed ()
1172         {
1173                 _signer->config_changed ();
1174                 _decryption->config_changed ();
1175         }
1176
1177         CertificateChainEditor* _signer;
1178         CertificateChainEditor* _decryption;
1179         wxButton* _export_decryption_certificate;
1180         wxButton* _export_decryption_chain;
1181 };
1182
1183 class TMSPage : public StandardPage
1184 {
1185 public:
1186         TMSPage (wxSize panel_size, int border)
1187                 : StandardPage (panel_size, border)
1188         {}
1189
1190         wxString GetName () const
1191         {
1192                 return _("TMS");
1193         }
1194
1195 #ifdef DCPOMATIC_OSX
1196         wxBitmap GetLargeIcon () const
1197         {
1198                 return wxBitmap ("tms", wxBITMAP_TYPE_PNG_RESOURCE);
1199         }
1200 #endif
1201
1202 private:
1203         void setup ()
1204         {
1205                 wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
1206                 table->AddGrowableCol (1, 1);
1207                 _panel->GetSizer()->Add (table, 1, wxALL | wxEXPAND, _border);
1208
1209                 add_label_to_sizer (table, _panel, _("Protocol"), true);
1210                 _tms_protocol = new wxChoice (_panel, wxID_ANY);
1211                 table->Add (_tms_protocol, 1, wxEXPAND);
1212
1213                 add_label_to_sizer (table, _panel, _("IP address"), true);
1214                 _tms_ip = new wxTextCtrl (_panel, wxID_ANY);
1215                 table->Add (_tms_ip, 1, wxEXPAND);
1216
1217                 add_label_to_sizer (table, _panel, _("Target path"), true);
1218                 _tms_path = new wxTextCtrl (_panel, wxID_ANY);
1219                 table->Add (_tms_path, 1, wxEXPAND);
1220
1221                 add_label_to_sizer (table, _panel, _("User name"), true);
1222                 _tms_user = new wxTextCtrl (_panel, wxID_ANY);
1223                 table->Add (_tms_user, 1, wxEXPAND);
1224
1225                 add_label_to_sizer (table, _panel, _("Password"), true);
1226                 _tms_password = new wxTextCtrl (_panel, wxID_ANY);
1227                 table->Add (_tms_password, 1, wxEXPAND);
1228
1229                 _tms_protocol->Append (_("SCP (for AAM and Doremi)"));
1230                 _tms_protocol->Append (_("FTP (for Dolby)"));
1231
1232                 _tms_protocol->Bind (wxEVT_CHOICE, boost::bind (&TMSPage::tms_protocol_changed, this));
1233                 _tms_ip->Bind (wxEVT_TEXT, boost::bind (&TMSPage::tms_ip_changed, this));
1234                 _tms_path->Bind (wxEVT_TEXT, boost::bind (&TMSPage::tms_path_changed, this));
1235                 _tms_user->Bind (wxEVT_TEXT, boost::bind (&TMSPage::tms_user_changed, this));
1236                 _tms_password->Bind (wxEVT_TEXT, boost::bind (&TMSPage::tms_password_changed, this));
1237         }
1238
1239         void config_changed ()
1240         {
1241                 Config* config = Config::instance ();
1242
1243                 checked_set (_tms_protocol, config->tms_protocol ());
1244                 checked_set (_tms_ip, config->tms_ip ());
1245                 checked_set (_tms_path, config->tms_path ());
1246                 checked_set (_tms_user, config->tms_user ());
1247                 checked_set (_tms_password, config->tms_password ());
1248         }
1249
1250         void tms_protocol_changed ()
1251         {
1252                 Config::instance()->set_tms_protocol (static_cast<Protocol> (_tms_protocol->GetSelection ()));
1253         }
1254
1255         void tms_ip_changed ()
1256         {
1257                 Config::instance()->set_tms_ip (wx_to_std (_tms_ip->GetValue ()));
1258         }
1259
1260         void tms_path_changed ()
1261         {
1262                 Config::instance()->set_tms_path (wx_to_std (_tms_path->GetValue ()));
1263         }
1264
1265         void tms_user_changed ()
1266         {
1267                 Config::instance()->set_tms_user (wx_to_std (_tms_user->GetValue ()));
1268         }
1269
1270         void tms_password_changed ()
1271         {
1272                 Config::instance()->set_tms_password (wx_to_std (_tms_password->GetValue ()));
1273         }
1274
1275         wxChoice* _tms_protocol;
1276         wxTextCtrl* _tms_ip;
1277         wxTextCtrl* _tms_path;
1278         wxTextCtrl* _tms_user;
1279         wxTextCtrl* _tms_password;
1280 };
1281
1282 static string
1283 column (string s)
1284 {
1285         return s;
1286 }
1287
1288 class KDMEmailPage : public StandardPage
1289 {
1290 public:
1291
1292         KDMEmailPage (wxSize panel_size, int border)
1293 #ifdef DCPOMATIC_OSX
1294                 /* We have to force both width and height of this one */
1295                 : StandardPage (wxSize (480, 128), border)
1296 #else
1297                 : StandardPage (panel_size, border)
1298 #endif
1299         {}
1300
1301         wxString GetName () const
1302         {
1303                 return _("KDM Email");
1304         }
1305
1306 #ifdef DCPOMATIC_OSX
1307         wxBitmap GetLargeIcon () const
1308         {
1309                 return wxBitmap ("kdm_email", wxBITMAP_TYPE_PNG_RESOURCE);
1310         }
1311 #endif
1312
1313 private:
1314         void setup ()
1315         {
1316                 wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
1317                 table->AddGrowableCol (1, 1);
1318                 _panel->GetSizer()->Add (table, 1, wxEXPAND | wxALL, _border);
1319
1320                 add_label_to_sizer (table, _panel, _("Outgoing mail server"), true);
1321                 {
1322                         wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
1323                         _mail_server = new wxTextCtrl (_panel, wxID_ANY);
1324                         s->Add (_mail_server, 1, wxEXPAND | wxALL);
1325                         add_label_to_sizer (s, _panel, _("port"), false);
1326                         _mail_port = new wxSpinCtrl (_panel, wxID_ANY);
1327                         _mail_port->SetRange (0, 65535);
1328                         s->Add (_mail_port);
1329                         table->Add (s, 1, wxEXPAND | wxALL);
1330                 }
1331
1332                 add_label_to_sizer (table, _panel, _("Mail user name"), true);
1333                 _mail_user = new wxTextCtrl (_panel, wxID_ANY);
1334                 table->Add (_mail_user, 1, wxEXPAND | wxALL);
1335
1336                 add_label_to_sizer (table, _panel, _("Mail password"), true);
1337                 _mail_password = new wxTextCtrl (_panel, wxID_ANY);
1338                 table->Add (_mail_password, 1, wxEXPAND | wxALL);
1339
1340                 add_label_to_sizer (table, _panel, _("Subject"), true);
1341                 _kdm_subject = new wxTextCtrl (_panel, wxID_ANY);
1342                 table->Add (_kdm_subject, 1, wxEXPAND | wxALL);
1343
1344                 add_label_to_sizer (table, _panel, _("From address"), true);
1345                 _kdm_from = new wxTextCtrl (_panel, wxID_ANY);
1346                 table->Add (_kdm_from, 1, wxEXPAND | wxALL);
1347
1348                 vector<string> columns;
1349                 columns.push_back (wx_to_std (_("Address")));
1350                 add_label_to_sizer (table, _panel, _("CC addresses"), true);
1351                 _kdm_cc = new EditableList<string, EmailDialog> (
1352                         _panel,
1353                         columns,
1354                         bind (&Config::kdm_cc, Config::instance()),
1355                         bind (&Config::set_kdm_cc, Config::instance(), _1),
1356                         bind (&column, _1)
1357                         );
1358                 table->Add (_kdm_cc, 1, wxEXPAND | wxALL);
1359
1360                 add_label_to_sizer (table, _panel, _("BCC address"), true);
1361                 _kdm_bcc = new wxTextCtrl (_panel, wxID_ANY);
1362                 table->Add (_kdm_bcc, 1, wxEXPAND | wxALL);
1363
1364                 _kdm_email = new wxTextCtrl (_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (-1, 200), wxTE_MULTILINE);
1365                 _panel->GetSizer()->Add (_kdm_email, 0, wxEXPAND | wxALL, _border);
1366
1367                 _reset_kdm_email = new wxButton (_panel, wxID_ANY, _("Reset to default subject and text"));
1368                 _panel->GetSizer()->Add (_reset_kdm_email, 0, wxEXPAND | wxALL, _border);
1369
1370                 _kdm_cc->layout ();
1371
1372                 _mail_server->Bind (wxEVT_TEXT, boost::bind (&KDMEmailPage::mail_server_changed, this));
1373                 _mail_port->Bind (wxEVT_SPINCTRL, boost::bind (&KDMEmailPage::mail_port_changed, this));
1374                 _mail_user->Bind (wxEVT_TEXT, boost::bind (&KDMEmailPage::mail_user_changed, this));
1375                 _mail_password->Bind (wxEVT_TEXT, boost::bind (&KDMEmailPage::mail_password_changed, this));
1376                 _kdm_subject->Bind (wxEVT_TEXT, boost::bind (&KDMEmailPage::kdm_subject_changed, this));
1377                 _kdm_from->Bind (wxEVT_TEXT, boost::bind (&KDMEmailPage::kdm_from_changed, this));
1378                 _kdm_bcc->Bind (wxEVT_TEXT, boost::bind (&KDMEmailPage::kdm_bcc_changed, this));
1379                 _kdm_email->Bind (wxEVT_TEXT, boost::bind (&KDMEmailPage::kdm_email_changed, this));
1380                 _reset_kdm_email->Bind (wxEVT_BUTTON, boost::bind (&KDMEmailPage::reset_kdm_email, this));
1381         }
1382
1383         void config_changed ()
1384         {
1385                 Config* config = Config::instance ();
1386
1387                 checked_set (_mail_server, config->mail_server ());
1388                 checked_set (_mail_port, config->mail_port ());
1389                 checked_set (_mail_user, config->mail_user ());
1390                 checked_set (_mail_password, config->mail_password ());
1391                 checked_set (_kdm_subject, config->kdm_subject ());
1392                 checked_set (_kdm_from, config->kdm_from ());
1393                 checked_set (_kdm_bcc, config->kdm_bcc ());
1394                 checked_set (_kdm_email, Config::instance()->kdm_email ());
1395         }
1396
1397         void mail_server_changed ()
1398         {
1399                 Config::instance()->set_mail_server (wx_to_std (_mail_server->GetValue ()));
1400         }
1401
1402         void mail_port_changed ()
1403         {
1404                 Config::instance()->set_mail_port (_mail_port->GetValue ());
1405         }
1406
1407         void mail_user_changed ()
1408         {
1409                 Config::instance()->set_mail_user (wx_to_std (_mail_user->GetValue ()));
1410         }
1411
1412         void mail_password_changed ()
1413         {
1414                 Config::instance()->set_mail_password (wx_to_std (_mail_password->GetValue ()));
1415         }
1416
1417         void kdm_subject_changed ()
1418         {
1419                 Config::instance()->set_kdm_subject (wx_to_std (_kdm_subject->GetValue ()));
1420         }
1421
1422         void kdm_from_changed ()
1423         {
1424                 Config::instance()->set_kdm_from (wx_to_std (_kdm_from->GetValue ()));
1425         }
1426
1427         void kdm_bcc_changed ()
1428         {
1429                 Config::instance()->set_kdm_bcc (wx_to_std (_kdm_bcc->GetValue ()));
1430         }
1431
1432         void kdm_email_changed ()
1433         {
1434                 if (_kdm_email->GetValue().IsEmpty ()) {
1435                         /* Sometimes we get sent an erroneous notification that the email
1436                            is empty; I don't know why.
1437                         */
1438                         return;
1439                 }
1440                 Config::instance()->set_kdm_email (wx_to_std (_kdm_email->GetValue ()));
1441         }
1442
1443         void reset_kdm_email ()
1444         {
1445                 Config::instance()->reset_kdm_email ();
1446                 checked_set (_kdm_email, Config::instance()->kdm_email ());
1447         }
1448
1449         wxTextCtrl* _mail_server;
1450         wxSpinCtrl* _mail_port;
1451         wxTextCtrl* _mail_user;
1452         wxTextCtrl* _mail_password;
1453         wxTextCtrl* _kdm_subject;
1454         wxTextCtrl* _kdm_from;
1455         EditableList<string, EmailDialog>* _kdm_cc;
1456         wxTextCtrl* _kdm_bcc;
1457         wxTextCtrl* _kdm_email;
1458         wxButton* _reset_kdm_email;
1459 };
1460
1461 /** @class AdvancedPage
1462  *  @brief Advanced page of the preferences dialog.
1463  */
1464 class AdvancedPage : public StockPage
1465 {
1466 public:
1467         AdvancedPage (wxSize panel_size, int border)
1468                 : StockPage (Kind_Advanced, panel_size, border)
1469                 , _maximum_j2k_bandwidth (0)
1470                 , _allow_any_dcp_frame_rate (0)
1471                 , _only_servers_encode (0)
1472                 , _log_general (0)
1473                 , _log_warning (0)
1474                 , _log_error (0)
1475                 , _log_timing (0)
1476                 , _log_debug_decode (0)
1477                 , _log_debug_encode (0)
1478                 , _log_debug_email (0)
1479         {}
1480
1481 private:
1482         void add_top_aligned_label_to_sizer (wxSizer* table, wxWindow* parent, wxString text)
1483         {
1484                 int flags = wxALIGN_TOP | wxTOP | wxLEFT;
1485 #ifdef __WXOSX__
1486                 flags |= wxALIGN_RIGHT;
1487                 text += wxT (":");
1488 #endif
1489                 wxStaticText* m = new wxStaticText (parent, wxID_ANY, text);
1490                 table->Add (m, 0, flags, DCPOMATIC_SIZER_Y_GAP);
1491         }
1492
1493         void setup ()
1494         {
1495                 wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
1496                 table->AddGrowableCol (1, 1);
1497                 _panel->GetSizer()->Add (table, 1, wxALL | wxEXPAND, _border);
1498
1499                 {
1500                         add_label_to_sizer (table, _panel, _("Maximum JPEG2000 bandwidth"), true);
1501                         wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
1502                         _maximum_j2k_bandwidth = new wxSpinCtrl (_panel);
1503                         s->Add (_maximum_j2k_bandwidth, 1);
1504                         add_label_to_sizer (s, _panel, _("Mbit/s"), false);
1505                         table->Add (s, 1);
1506                 }
1507
1508                 _allow_any_dcp_frame_rate = new wxCheckBox (_panel, wxID_ANY, _("Allow any DCP frame rate"));
1509                 table->Add (_allow_any_dcp_frame_rate, 1, wxEXPAND | wxALL);
1510                 table->AddSpacer (0);
1511
1512                 _only_servers_encode = new wxCheckBox (_panel, wxID_ANY, _("Only servers encode"));
1513                 table->Add (_only_servers_encode, 1, wxEXPAND | wxALL);
1514                 table->AddSpacer (0);
1515
1516                 {
1517                         add_top_aligned_label_to_sizer (table, _panel, _("DCP metadata filename format"));
1518                         dcp::NameFormat::Map titles;
1519                         titles['t'] = "type (cpl/pkl)";
1520                         dcp::NameFormat::Map examples;
1521                         examples['t'] = "cpl";
1522                         _dcp_metadata_filename_format = new NameFormatEditor (
1523                                 _panel, Config::instance()->dcp_metadata_filename_format(), titles, examples, "_eb1c112c-ca3c-4ae6-9263-c6714ff05d64.xml"
1524                                 );
1525                         table->Add (_dcp_metadata_filename_format->panel(), 1, wxEXPAND | wxALL);
1526                 }
1527
1528                 {
1529                         add_top_aligned_label_to_sizer (table, _panel, _("DCP asset filename format"));
1530                         dcp::NameFormat::Map titles;
1531                         titles['t'] = "type (j2c/pcm/sub)";
1532                         titles['r'] = "reel number";
1533                         titles['n'] = "number of reels";
1534                         titles['c'] = "content filename";
1535                         dcp::NameFormat::Map examples;
1536                         examples['t'] = "j2c";
1537                         examples['r'] = "1";
1538                         examples['n'] = "4";
1539                         examples['c'] = "myfile.mp4";
1540                         _dcp_asset_filename_format = new NameFormatEditor (
1541                                 _panel, Config::instance()->dcp_asset_filename_format(), titles, examples, "_eb1c112c-ca3c-4ae6-9263-c6714ff05d64.mxf"
1542                                 );
1543                         table->Add (_dcp_asset_filename_format->panel(), 1, wxEXPAND | wxALL);
1544                 }
1545
1546                 {
1547                         add_top_aligned_label_to_sizer (table, _panel, _("Log"));
1548                         wxBoxSizer* t = new wxBoxSizer (wxVERTICAL);
1549                         _log_general = new wxCheckBox (_panel, wxID_ANY, _("General"));
1550                         t->Add (_log_general, 1, wxEXPAND | wxALL);
1551                         _log_warning = new wxCheckBox (_panel, wxID_ANY, _("Warnings"));
1552                         t->Add (_log_warning, 1, wxEXPAND | wxALL);
1553                         _log_error = new wxCheckBox (_panel, wxID_ANY, _("Errors"));
1554                         t->Add (_log_error, 1, wxEXPAND | wxALL);
1555                         /// TRANSLATORS: translate the word "Timing" here; do not include the "Config|" prefix
1556                         _log_timing = new wxCheckBox (_panel, wxID_ANY, S_("Config|Timing"));
1557                         t->Add (_log_timing, 1, wxEXPAND | wxALL);
1558                         _log_debug_decode = new wxCheckBox (_panel, wxID_ANY, _("Debug: decode"));
1559                         t->Add (_log_debug_decode, 1, wxEXPAND | wxALL);
1560                         _log_debug_encode = new wxCheckBox (_panel, wxID_ANY, _("Debug: encode"));
1561                         t->Add (_log_debug_encode, 1, wxEXPAND | wxALL);
1562                         _log_debug_email = new wxCheckBox (_panel, wxID_ANY, _("Debug: email sending"));
1563                         t->Add (_log_debug_email, 1, wxEXPAND | wxALL);
1564                         table->Add (t, 0, wxALL, 6);
1565                 }
1566
1567 #ifdef DCPOMATIC_WINDOWS
1568                 _win32_console = new wxCheckBox (_panel, wxID_ANY, _("Open console window"));
1569                 table->Add (_win32_console, 1, wxEXPAND | wxALL);
1570                 table->AddSpacer (0);
1571 #endif
1572
1573                 _maximum_j2k_bandwidth->SetRange (1, 1000);
1574                 _maximum_j2k_bandwidth->Bind (wxEVT_SPINCTRL, boost::bind (&AdvancedPage::maximum_j2k_bandwidth_changed, this));
1575                 _allow_any_dcp_frame_rate->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::allow_any_dcp_frame_rate_changed, this));
1576                 _only_servers_encode->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::only_servers_encode_changed, this));
1577                 _dcp_metadata_filename_format->Changed.connect (boost::bind (&AdvancedPage::dcp_metadata_filename_format_changed, this));
1578                 _dcp_asset_filename_format->Changed.connect (boost::bind (&AdvancedPage::dcp_asset_filename_format_changed, this));
1579                 _log_general->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::log_changed, this));
1580                 _log_warning->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::log_changed, this));
1581                 _log_error->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::log_changed, this));
1582                 _log_timing->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::log_changed, this));
1583                 _log_debug_decode->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::log_changed, this));
1584                 _log_debug_encode->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::log_changed, this));
1585                 _log_debug_email->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::log_changed, this));
1586 #ifdef DCPOMATIC_WINDOWS
1587                 _win32_console->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::win32_console_changed, this));
1588 #endif
1589         }
1590
1591         void config_changed ()
1592         {
1593                 Config* config = Config::instance ();
1594
1595                 checked_set (_maximum_j2k_bandwidth, config->maximum_j2k_bandwidth() / 1000000);
1596                 checked_set (_allow_any_dcp_frame_rate, config->allow_any_dcp_frame_rate ());
1597                 checked_set (_only_servers_encode, config->only_servers_encode ());
1598                 checked_set (_log_general, config->log_types() & LogEntry::TYPE_GENERAL);
1599                 checked_set (_log_warning, config->log_types() & LogEntry::TYPE_WARNING);
1600                 checked_set (_log_error, config->log_types() & LogEntry::TYPE_ERROR);
1601                 checked_set (_log_timing, config->log_types() & LogEntry::TYPE_TIMING);
1602                 checked_set (_log_debug_decode, config->log_types() & LogEntry::TYPE_DEBUG_DECODE);
1603                 checked_set (_log_debug_encode, config->log_types() & LogEntry::TYPE_DEBUG_ENCODE);
1604                 checked_set (_log_debug_email, config->log_types() & LogEntry::TYPE_DEBUG_EMAIL);
1605 #ifdef DCPOMATIC_WINDOWS
1606                 checked_set (_win32_console, config->win32_console());
1607 #endif
1608         }
1609
1610         void maximum_j2k_bandwidth_changed ()
1611         {
1612                 Config::instance()->set_maximum_j2k_bandwidth (_maximum_j2k_bandwidth->GetValue() * 1000000);
1613         }
1614
1615         void allow_any_dcp_frame_rate_changed ()
1616         {
1617                 Config::instance()->set_allow_any_dcp_frame_rate (_allow_any_dcp_frame_rate->GetValue ());
1618         }
1619
1620         void only_servers_encode_changed ()
1621         {
1622                 Config::instance()->set_only_servers_encode (_only_servers_encode->GetValue ());
1623         }
1624
1625         void dcp_metadata_filename_format_changed ()
1626         {
1627                 Config::instance()->set_dcp_metadata_filename_format (_dcp_metadata_filename_format->get ());
1628         }
1629
1630         void dcp_asset_filename_format_changed ()
1631         {
1632                 Config::instance()->set_dcp_asset_filename_format (_dcp_asset_filename_format->get ());
1633         }
1634
1635         void log_changed ()
1636         {
1637                 int types = 0;
1638                 if (_log_general->GetValue ()) {
1639                         types |= LogEntry::TYPE_GENERAL;
1640                 }
1641                 if (_log_warning->GetValue ()) {
1642                         types |= LogEntry::TYPE_WARNING;
1643                 }
1644                 if (_log_error->GetValue ())  {
1645                         types |= LogEntry::TYPE_ERROR;
1646                 }
1647                 if (_log_timing->GetValue ()) {
1648                         types |= LogEntry::TYPE_TIMING;
1649                 }
1650                 if (_log_debug_decode->GetValue ()) {
1651                         types |= LogEntry::TYPE_DEBUG_DECODE;
1652                 }
1653                 if (_log_debug_encode->GetValue ()) {
1654                         types |= LogEntry::TYPE_DEBUG_ENCODE;
1655                 }
1656                 if (_log_debug_email->GetValue ()) {
1657                         types |= LogEntry::TYPE_DEBUG_EMAIL;
1658                 }
1659                 Config::instance()->set_log_types (types);
1660         }
1661
1662 #ifdef DCPOMATIC_WINDOWS
1663         void win32_console_changed ()
1664         {
1665                 Config::instance()->set_win32_console (_win32_console->GetValue ());
1666         }
1667 #endif
1668
1669         wxSpinCtrl* _maximum_j2k_bandwidth;
1670         wxCheckBox* _allow_any_dcp_frame_rate;
1671         wxCheckBox* _only_servers_encode;
1672         NameFormatEditor* _dcp_metadata_filename_format;
1673         NameFormatEditor* _dcp_asset_filename_format;
1674         wxCheckBox* _log_general;
1675         wxCheckBox* _log_warning;
1676         wxCheckBox* _log_error;
1677         wxCheckBox* _log_timing;
1678         wxCheckBox* _log_debug_decode;
1679         wxCheckBox* _log_debug_encode;
1680         wxCheckBox* _log_debug_email;
1681 #ifdef DCPOMATIC_WINDOWS
1682         wxCheckBox* _win32_console;
1683 #endif
1684 };
1685
1686 wxPreferencesEditor*
1687 create_config_dialog ()
1688 {
1689         wxPreferencesEditor* e = new wxPreferencesEditor ();
1690
1691 #ifdef DCPOMATIC_OSX
1692         /* Width that we force some of the config panels to be on OSX so that
1693            the containing window doesn't shrink too much when we select those panels.
1694            This is obviously an unpleasant hack.
1695         */
1696         wxSize ps = wxSize (520, -1);
1697         int const border = 16;
1698 #else
1699         wxSize ps = wxSize (-1, -1);
1700         int const border = 8;
1701 #endif
1702
1703         e->AddPage (new GeneralPage (ps, border));
1704         e->AddPage (new DefaultsPage (ps, border));
1705         e->AddPage (new EncodingServersPage (ps, border));
1706         e->AddPage (new KeysPage (ps, border));
1707         e->AddPage (new TMSPage (ps, border));
1708         e->AddPage (new KDMEmailPage (ps, border));
1709         e->AddPage (new AdvancedPage (ps, border));
1710         return e;
1711 }