155472a38edd82f5f0c3e019533aaef67d16fe65
[dcpomatic.git] / src / wx / full_config_dialog.cc
1 /*
2     Copyright (C) 2012-2019 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/full_config_dialog.cc
22  *  @brief A dialogue to edit all DCP-o-matic configuration.
23  */
24
25 #include "full_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 "server_dialog.h"
32 #include "make_chain_dialog.h"
33 #include "email_dialog.h"
34 #include "name_format_editor.h"
35 #include "nag_dialog.h"
36 #include "config_move_dialog.h"
37 #include "config_dialog.h"
38 #include "static_text.h"
39 #include "check_box.h"
40 #include "dcpomatic_button.h"
41 #include "password_entry.h"
42 #include "lib/config.h"
43 #include "lib/ratio.h"
44 #include "lib/filter.h"
45 #include "lib/dcp_content_type.h"
46 #include "lib/log.h"
47 #include "lib/util.h"
48 #include "lib/cross.h"
49 #include "lib/exceptions.h"
50 #include <dcp/locale_convert.h>
51 #include <dcp/exceptions.h>
52 #include <dcp/certificate_chain.h>
53 #include <wx/stdpaths.h>
54 #include <wx/preferences.h>
55 #include <wx/spinctrl.h>
56 #include <wx/filepicker.h>
57 #include <RtAudio.h>
58 #include <boost/filesystem.hpp>
59 #include <iostream>
60
61 using std::vector;
62 using std::string;
63 using std::list;
64 using std::cout;
65 using std::pair;
66 using std::make_pair;
67 using std::map;
68 using boost::bind;
69 using std::shared_ptr;
70 using boost::function;
71 using boost::optional;
72 #if BOOST_VERSION >= 106100
73 using namespace boost::placeholders;
74 #endif
75 using dcp::locale_convert;
76
77 class FullGeneralPage : public GeneralPage
78 {
79 public:
80         FullGeneralPage (wxSize panel_size, int border)
81                 : GeneralPage (panel_size, border)
82         {}
83
84 private:
85         void setup ()
86         {
87                 wxGridBagSizer* table = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
88                 _panel->GetSizer()->Add (table, 1, wxALL | wxEXPAND, _border);
89
90                 int r = 0;
91                 add_language_controls (table, r);
92
93                 add_label_to_sizer (table, _panel, _("Number of threads DCP-o-matic should use"), true, wxGBPosition (r, 0));
94                 _master_encoding_threads = new wxSpinCtrl (_panel);
95                 table->Add (_master_encoding_threads, wxGBPosition (r, 1));
96                 ++r;
97
98                 add_label_to_sizer (table, _panel, _("Number of threads DCP-o-matic encode server should use"), true, wxGBPosition (r, 0));
99                 _server_encoding_threads = new wxSpinCtrl (_panel);
100                 table->Add (_server_encoding_threads, wxGBPosition (r, 1));
101                 ++r;
102
103                 add_label_to_sizer (table, _panel, _("Configuration file"), true, wxGBPosition (r, 0));
104                 _config_file = new FilePickerCtrl (_panel, _("Select configuration file"), "*.xml", true, false);
105                 table->Add (_config_file, wxGBPosition (r, 1));
106                 ++r;
107
108                 add_label_to_sizer (table, _panel, _("Cinema and screen database file"), true, wxGBPosition (r, 0));
109                 _cinemas_file = new FilePickerCtrl (_panel, _("Select cinema and screen database file"), "*.xml", true, false);
110                 table->Add (_cinemas_file, wxGBPosition (r, 1));
111                 Button* export_cinemas = new Button (_panel, _("Export..."));
112                 table->Add (export_cinemas, wxGBPosition (r, 2));
113                 ++r;
114
115 #ifdef DCPOMATIC_HAVE_EBUR128_PATCHED_FFMPEG
116                 _analyse_ebur128 = new CheckBox (_panel, _("Find integrated loudness, true peak and loudness range when analysing audio"));
117                 table->Add (_analyse_ebur128, wxGBPosition (r, 0), wxGBSpan (1, 2));
118                 ++r;
119 #endif
120
121                 _automatic_audio_analysis = new CheckBox (_panel, _("Automatically analyse content audio"));
122                 table->Add (_automatic_audio_analysis, wxGBPosition (r, 0), wxGBSpan (1, 2));
123                 ++r;
124
125                 add_update_controls (table, r);
126
127                 _config_file->Bind  (wxEVT_FILEPICKER_CHANGED, boost::bind (&FullGeneralPage::config_file_changed,   this));
128                 _cinemas_file->Bind (wxEVT_FILEPICKER_CHANGED, boost::bind (&FullGeneralPage::cinemas_file_changed,  this));
129
130                 _master_encoding_threads->SetRange (1, 128);
131                 _master_encoding_threads->Bind (wxEVT_SPINCTRL, boost::bind (&FullGeneralPage::master_encoding_threads_changed, this));
132                 _server_encoding_threads->SetRange (1, 128);
133                 _server_encoding_threads->Bind (wxEVT_SPINCTRL, boost::bind (&FullGeneralPage::server_encoding_threads_changed, this));
134                 export_cinemas->Bind (wxEVT_BUTTON, boost::bind (&FullGeneralPage::export_cinemas_file, this));
135
136 #ifdef DCPOMATIC_HAVE_EBUR128_PATCHED_FFMPEG
137                 _analyse_ebur128->Bind (wxEVT_CHECKBOX, boost::bind (&FullGeneralPage::analyse_ebur128_changed, this));
138 #endif
139                 _automatic_audio_analysis->Bind (wxEVT_CHECKBOX, boost::bind (&FullGeneralPage::automatic_audio_analysis_changed, this));
140         }
141
142         void config_changed ()
143         {
144                 Config* config = Config::instance ();
145
146                 checked_set (_master_encoding_threads, config->master_encoding_threads ());
147                 checked_set (_server_encoding_threads, config->server_encoding_threads ());
148 #ifdef DCPOMATIC_HAVE_EBUR128_PATCHED_FFMPEG
149                 checked_set (_analyse_ebur128, config->analyse_ebur128 ());
150 #endif
151                 checked_set (_automatic_audio_analysis, config->automatic_audio_analysis ());
152                 checked_set (_config_file, config->config_file());
153                 checked_set (_cinemas_file, config->cinemas_file());
154
155                 GeneralPage::config_changed ();
156         }
157
158         void export_cinemas_file ()
159         {
160                 wxFileDialog* d = new wxFileDialog (
161                         _panel, _("Select Cinemas File"), wxEmptyString, wxEmptyString, wxT ("XML files (*.xml)|*.xml"),
162                         wxFD_SAVE | wxFD_OVERWRITE_PROMPT
163                 );
164
165                 if (d->ShowModal () == wxID_OK) {
166                         boost::filesystem::copy_file (Config::instance()->cinemas_file(), wx_to_std(d->GetPath()));
167                 }
168                 d->Destroy ();
169         }
170
171 #ifdef DCPOMATIC_HAVE_EBUR128_PATCHED_FFMPEG
172         void analyse_ebur128_changed ()
173         {
174                 Config::instance()->set_analyse_ebur128 (_analyse_ebur128->GetValue ());
175         }
176 #endif
177
178         void automatic_audio_analysis_changed ()
179         {
180                 Config::instance()->set_automatic_audio_analysis (_automatic_audio_analysis->GetValue ());
181         }
182
183         void master_encoding_threads_changed ()
184         {
185                 Config::instance()->set_master_encoding_threads (_master_encoding_threads->GetValue ());
186         }
187
188         void server_encoding_threads_changed ()
189         {
190                 Config::instance()->set_server_encoding_threads (_server_encoding_threads->GetValue ());
191         }
192
193         void config_file_changed ()
194         {
195                 Config* config = Config::instance();
196                 boost::filesystem::path new_file = wx_to_std(_config_file->GetPath());
197                 if (new_file == config->config_file()) {
198                         return;
199                 }
200                 bool copy_and_link = true;
201                 if (boost::filesystem::exists(new_file)) {
202                         ConfigMoveDialog* d = new ConfigMoveDialog (_panel, new_file);
203                         if (d->ShowModal() == wxID_OK) {
204                                 copy_and_link = false;
205                         }
206                         d->Destroy ();
207                 }
208
209                 if (copy_and_link) {
210                         config->write ();
211                         if (new_file != config->config_file()) {
212                                 config->copy_and_link (new_file);
213                         }
214                 } else {
215                         config->link (new_file);
216                 }
217         }
218
219         void cinemas_file_changed ()
220         {
221                 Config::instance()->set_cinemas_file (wx_to_std (_cinemas_file->GetPath ()));
222         }
223
224         wxSpinCtrl* _master_encoding_threads;
225         wxSpinCtrl* _server_encoding_threads;
226         FilePickerCtrl* _config_file;
227         FilePickerCtrl* _cinemas_file;
228 #ifdef DCPOMATIC_HAVE_EBUR128_PATCHED_FFMPEG
229         wxCheckBox* _analyse_ebur128;
230 #endif
231         wxCheckBox* _automatic_audio_analysis;
232 };
233
234 class DefaultsPage : public Page
235 {
236 public:
237         DefaultsPage (wxSize panel_size, int border)
238                 : Page (panel_size, border)
239         {}
240
241         wxString GetName () const
242         {
243                 return _("Defaults");
244         }
245
246 #ifdef DCPOMATIC_OSX
247         wxBitmap GetLargeIcon () const
248         {
249                 return wxBitmap ("defaults", wxBITMAP_TYPE_PNG_RESOURCE);
250         }
251 #endif
252
253 private:
254         void setup ()
255         {
256                 wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
257                 table->AddGrowableCol (1, 1);
258                 _panel->GetSizer()->Add (table, 1, wxALL | wxEXPAND, _border);
259
260                 {
261                         add_label_to_sizer (table, _panel, _("Default duration of still images"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
262                         wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
263                         _still_length = new wxSpinCtrl (_panel);
264                         s->Add (_still_length);
265                         add_label_to_sizer (s, _panel, _("s"), false, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
266                         table->Add (s, 1);
267                 }
268
269                 add_label_to_sizer (table, _panel, _("Default directory for new films"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
270 #ifdef DCPOMATIC_USE_OWN_PICKER
271                 _directory = new DirPickerCtrl (_panel);
272 #else
273                 _directory = new wxDirPickerCtrl (_panel, wxDD_DIR_MUST_EXIST);
274 #endif
275                 table->Add (_directory, 1, wxEXPAND);
276
277                 add_label_to_sizer (table, _panel, _("Default container"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
278                 _container = new wxChoice (_panel, wxID_ANY);
279                 table->Add (_container);
280
281                 add_label_to_sizer (table, _panel, _("Default content type"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
282                 _dcp_content_type = new wxChoice (_panel, wxID_ANY);
283                 table->Add (_dcp_content_type);
284
285                 add_label_to_sizer (table, _panel, _("Default DCP audio channels"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
286                 _dcp_audio_channels = new wxChoice (_panel, wxID_ANY);
287                 table->Add (_dcp_audio_channels);
288
289                 {
290                         add_label_to_sizer (table, _panel, _("Default JPEG2000 bandwidth"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
291                         wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
292                         _j2k_bandwidth = new wxSpinCtrl (_panel);
293                         s->Add (_j2k_bandwidth);
294                         add_label_to_sizer (s, _panel, _("Mbit/s"), false, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
295                         table->Add (s, 1);
296                 }
297
298                 {
299                         add_label_to_sizer (table, _panel, _("Default audio delay"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
300                         wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
301                         _audio_delay = new wxSpinCtrl (_panel);
302                         s->Add (_audio_delay);
303                         add_label_to_sizer (s, _panel, _("ms"), false, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
304                         table->Add (s, 1);
305                 }
306
307                 add_label_to_sizer (table, _panel, _("Default standard"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
308                 _standard = new wxChoice (_panel, wxID_ANY);
309                 table->Add (_standard);
310
311                 add_label_to_sizer (table, _panel, _("Default KDM directory"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
312 #ifdef DCPOMATIC_USE_OWN_PICKER
313                 _kdm_directory = new DirPickerCtrl (_panel);
314 #else
315                 _kdm_directory = new wxDirPickerCtrl (_panel, wxDD_DIR_MUST_EXIST);
316 #endif
317
318                 table->Add (_kdm_directory, 1, wxEXPAND);
319
320                 _still_length->SetRange (1, 3600);
321                 _still_length->Bind (wxEVT_SPINCTRL, boost::bind (&DefaultsPage::still_length_changed, this));
322
323                 _directory->Bind (wxEVT_DIRPICKER_CHANGED, boost::bind (&DefaultsPage::directory_changed, this));
324                 _kdm_directory->Bind (wxEVT_DIRPICKER_CHANGED, boost::bind (&DefaultsPage::kdm_directory_changed, this));
325
326                 for (auto i: Ratio::containers()) {
327                         _container->Append (std_to_wx(i->container_nickname()));
328                 }
329
330                 _container->Bind (wxEVT_CHOICE, boost::bind (&DefaultsPage::container_changed, this));
331
332                 for (auto i: DCPContentType::all()) {
333                         _dcp_content_type->Append (std_to_wx (i->pretty_name ()));
334                 }
335
336                 setup_audio_channels_choice (_dcp_audio_channels, 2);
337
338                 _dcp_content_type->Bind (wxEVT_CHOICE, boost::bind (&DefaultsPage::dcp_content_type_changed, this));
339                 _dcp_audio_channels->Bind (wxEVT_CHOICE, boost::bind (&DefaultsPage::dcp_audio_channels_changed, this));
340
341                 _j2k_bandwidth->SetRange (50, 250);
342                 _j2k_bandwidth->Bind (wxEVT_SPINCTRL, boost::bind (&DefaultsPage::j2k_bandwidth_changed, this));
343
344                 _audio_delay->SetRange (-1000, 1000);
345                 _audio_delay->Bind (wxEVT_SPINCTRL, boost::bind (&DefaultsPage::audio_delay_changed, this));
346
347                 _standard->Append (_("SMPTE"));
348                 _standard->Append (_("Interop"));
349                 _standard->Bind (wxEVT_CHOICE, boost::bind (&DefaultsPage::standard_changed, this));
350         }
351
352         void config_changed ()
353         {
354                 Config* config = Config::instance ();
355
356                 vector<Ratio const *> containers = Ratio::containers ();
357                 for (size_t i = 0; i < containers.size(); ++i) {
358                         if (containers[i] == config->default_container ()) {
359                                 _container->SetSelection (i);
360                         }
361                 }
362
363                 vector<DCPContentType const *> const ct = DCPContentType::all ();
364                 for (size_t i = 0; i < ct.size(); ++i) {
365                         if (ct[i] == config->default_dcp_content_type ()) {
366                                 _dcp_content_type->SetSelection (i);
367                         }
368                 }
369
370                 checked_set (_still_length, config->default_still_length ());
371                 _directory->SetPath (std_to_wx (config->default_directory_or (wx_to_std (wxStandardPaths::Get().GetDocumentsDir())).string ()));
372                 _kdm_directory->SetPath (std_to_wx (config->default_kdm_directory_or (wx_to_std (wxStandardPaths::Get().GetDocumentsDir())).string ()));
373                 checked_set (_j2k_bandwidth, config->default_j2k_bandwidth() / 1000000);
374                 _j2k_bandwidth->SetRange (50, config->maximum_j2k_bandwidth() / 1000000);
375                 checked_set (_dcp_audio_channels, locale_convert<string> (config->default_dcp_audio_channels()));
376                 checked_set (_audio_delay, config->default_audio_delay ());
377                 checked_set (_standard, config->default_interop() ? 1 : 0);
378         }
379
380         void j2k_bandwidth_changed ()
381         {
382                 Config::instance()->set_default_j2k_bandwidth (_j2k_bandwidth->GetValue() * 1000000);
383         }
384
385         void audio_delay_changed ()
386         {
387                 Config::instance()->set_default_audio_delay (_audio_delay->GetValue());
388         }
389
390         void dcp_audio_channels_changed ()
391         {
392                 int const s = _dcp_audio_channels->GetSelection ();
393                 if (s != wxNOT_FOUND) {
394                         Config::instance()->set_default_dcp_audio_channels (
395                                 locale_convert<int> (string_client_data (_dcp_audio_channels->GetClientObject (s)))
396                                 );
397                 }
398         }
399
400         void directory_changed ()
401         {
402                 Config::instance()->set_default_directory (wx_to_std (_directory->GetPath ()));
403         }
404
405         void kdm_directory_changed ()
406         {
407                 Config::instance()->set_default_kdm_directory (wx_to_std (_kdm_directory->GetPath ()));
408         }
409
410         void still_length_changed ()
411         {
412                 Config::instance()->set_default_still_length (_still_length->GetValue ());
413         }
414
415         void container_changed ()
416         {
417                 vector<Ratio const *> ratio = Ratio::containers ();
418                 Config::instance()->set_default_container (ratio[_container->GetSelection()]);
419         }
420
421         void dcp_content_type_changed ()
422         {
423                 vector<DCPContentType const *> ct = DCPContentType::all ();
424                 Config::instance()->set_default_dcp_content_type (ct[_dcp_content_type->GetSelection()]);
425         }
426
427         void standard_changed ()
428         {
429                 Config::instance()->set_default_interop (_standard->GetSelection() == 1);
430         }
431
432         wxSpinCtrl* _j2k_bandwidth;
433         wxSpinCtrl* _audio_delay;
434         wxSpinCtrl* _still_length;
435 #ifdef DCPOMATIC_USE_OWN_PICKER
436         DirPickerCtrl* _directory;
437         DirPickerCtrl* _kdm_directory;
438 #else
439         wxDirPickerCtrl* _directory;
440         wxDirPickerCtrl* _kdm_directory;
441 #endif
442         wxChoice* _container;
443         wxChoice* _dcp_content_type;
444         wxChoice* _dcp_audio_channels;
445         wxChoice* _standard;
446 };
447
448 class EncodingServersPage : public Page
449 {
450 public:
451         EncodingServersPage (wxSize panel_size, int border)
452                 : Page (panel_size, border)
453         {}
454
455         wxString GetName () const
456         {
457                 return _("Servers");
458         }
459
460 #ifdef DCPOMATIC_OSX
461         wxBitmap GetLargeIcon () const
462         {
463                 return wxBitmap ("servers", wxBITMAP_TYPE_PNG_RESOURCE);
464         }
465 #endif
466
467 private:
468         void setup ()
469         {
470                 _use_any_servers = new CheckBox (_panel, _("Search network for servers"));
471                 _panel->GetSizer()->Add (_use_any_servers, 0, wxALL, _border);
472
473                 vector<EditableListColumn> columns;
474                 columns.push_back (EditableListColumn(_("IP address / host name")));
475                 _servers_list = new EditableList<string, ServerDialog> (
476                         _panel,
477                         columns,
478                         boost::bind (&Config::servers, Config::instance()),
479                         boost::bind (&Config::set_servers, Config::instance(), _1),
480                         boost::bind (&EncodingServersPage::server_column, this, _1)
481                         );
482
483                 _panel->GetSizer()->Add (_servers_list, 1, wxEXPAND | wxALL, _border);
484
485                 _use_any_servers->Bind (wxEVT_CHECKBOX, boost::bind (&EncodingServersPage::use_any_servers_changed, this));
486         }
487
488         void config_changed ()
489         {
490                 checked_set (_use_any_servers, Config::instance()->use_any_servers ());
491                 _servers_list->refresh ();
492         }
493
494         void use_any_servers_changed ()
495         {
496                 Config::instance()->set_use_any_servers (_use_any_servers->GetValue ());
497         }
498
499         string server_column (string s)
500         {
501                 return s;
502         }
503
504         wxCheckBox* _use_any_servers;
505         EditableList<string, ServerDialog>* _servers_list;
506 };
507
508 class TMSPage : public Page
509 {
510 public:
511         TMSPage (wxSize panel_size, int border)
512                 : Page (panel_size, border)
513         {}
514
515         wxString GetName () const
516         {
517                 return _("TMS");
518         }
519
520 #ifdef DCPOMATIC_OSX
521         wxBitmap GetLargeIcon () const
522         {
523                 return wxBitmap ("tms", wxBITMAP_TYPE_PNG_RESOURCE);
524         }
525 #endif
526
527 private:
528         void setup ()
529         {
530                 _upload = new CheckBox (_panel, _("Upload DCP to TMS after creation"));
531                 _panel->GetSizer()->Add (_upload, 0, wxALL | wxEXPAND, _border);
532
533                 wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
534                 table->AddGrowableCol (1, 1);
535                 _panel->GetSizer()->Add (table, 1, wxALL | wxEXPAND, _border);
536
537                 add_label_to_sizer (table, _panel, _("Protocol"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
538                 _tms_protocol = new wxChoice (_panel, wxID_ANY);
539                 table->Add (_tms_protocol, 1, wxEXPAND);
540
541                 add_label_to_sizer (table, _panel, _("IP address"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
542                 _tms_ip = new wxTextCtrl (_panel, wxID_ANY);
543                 table->Add (_tms_ip, 1, wxEXPAND);
544
545                 add_label_to_sizer (table, _panel, _("Target path"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
546                 _tms_path = new wxTextCtrl (_panel, wxID_ANY);
547                 table->Add (_tms_path, 1, wxEXPAND);
548
549                 add_label_to_sizer (table, _panel, _("User name"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
550                 _tms_user = new wxTextCtrl (_panel, wxID_ANY);
551                 table->Add (_tms_user, 1, wxEXPAND);
552
553                 add_label_to_sizer (table, _panel, _("Password"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
554                 _tms_password = new PasswordEntry (_panel);
555                 table->Add (_tms_password->get_panel(), 1, wxEXPAND);
556
557                 _tms_protocol->Append (_("SCP (for AAM and Doremi)"));
558                 _tms_protocol->Append (_("FTP (for Dolby)"));
559
560                 _upload->Bind (wxEVT_CHECKBOX, boost::bind(&TMSPage::upload_changed, this));
561                 _tms_protocol->Bind (wxEVT_CHOICE, boost::bind (&TMSPage::tms_protocol_changed, this));
562                 _tms_ip->Bind (wxEVT_TEXT, boost::bind (&TMSPage::tms_ip_changed, this));
563                 _tms_path->Bind (wxEVT_TEXT, boost::bind (&TMSPage::tms_path_changed, this));
564                 _tms_user->Bind (wxEVT_TEXT, boost::bind (&TMSPage::tms_user_changed, this));
565                 _tms_password->Changed.connect (boost::bind (&TMSPage::tms_password_changed, this));
566         }
567
568         void config_changed ()
569         {
570                 Config* config = Config::instance ();
571
572                 checked_set (_upload, config->upload_after_make_dcp());
573                 checked_set (_tms_protocol, static_cast<int>(config->tms_protocol()));
574                 checked_set (_tms_ip, config->tms_ip ());
575                 checked_set (_tms_path, config->tms_path ());
576                 checked_set (_tms_user, config->tms_user ());
577                 checked_set (_tms_password, config->tms_password ());
578         }
579
580         void upload_changed ()
581         {
582                 Config::instance()->set_upload_after_make_dcp (_upload->GetValue());
583         }
584
585         void tms_protocol_changed ()
586         {
587                 Config::instance()->set_tms_protocol(static_cast<FileTransferProtocol>(_tms_protocol->GetSelection()));
588         }
589
590         void tms_ip_changed ()
591         {
592                 Config::instance()->set_tms_ip (wx_to_std (_tms_ip->GetValue ()));
593         }
594
595         void tms_path_changed ()
596         {
597                 Config::instance()->set_tms_path (wx_to_std (_tms_path->GetValue ()));
598         }
599
600         void tms_user_changed ()
601         {
602                 Config::instance()->set_tms_user (wx_to_std (_tms_user->GetValue ()));
603         }
604
605         void tms_password_changed ()
606         {
607                 Config::instance()->set_tms_password (_tms_password->get());
608         }
609
610         CheckBox* _upload;
611         wxChoice* _tms_protocol;
612         wxTextCtrl* _tms_ip;
613         wxTextCtrl* _tms_path;
614         wxTextCtrl* _tms_user;
615         PasswordEntry* _tms_password;
616 };
617
618 static string
619 column (string s)
620 {
621         return s;
622 }
623
624 class EmailPage : public Page
625 {
626 public:
627         EmailPage (wxSize panel_size, int border)
628                 : Page (panel_size, border)
629         {}
630
631         wxString GetName () const
632         {
633                 return _("Email");
634         }
635
636 #ifdef DCPOMATIC_OSX
637         wxBitmap GetLargeIcon () const
638         {
639                 return wxBitmap ("email", wxBITMAP_TYPE_PNG_RESOURCE);
640         }
641 #endif
642
643 private:
644         void setup ()
645         {
646                 wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
647                 table->AddGrowableCol (1, 1);
648                 _panel->GetSizer()->Add (table, 1, wxEXPAND | wxALL, _border);
649
650                 add_label_to_sizer (table, _panel, _("Outgoing mail server"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
651                 {
652                         wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
653                         _server = new wxTextCtrl (_panel, wxID_ANY);
654                         s->Add (_server, 1, wxEXPAND | wxALL);
655                         add_label_to_sizer (s, _panel, _("port"), false, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
656                         _port = new wxSpinCtrl (_panel, wxID_ANY);
657                         _port->SetRange (0, 65535);
658                         s->Add (_port);
659                         add_label_to_sizer (s, _panel, _("protocol"), false, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
660                         _protocol = new wxChoice (_panel, wxID_ANY);
661                         /* Make sure this matches the switches in config_changed and port_changed below */
662                         _protocol->Append (_("Auto"));
663                         _protocol->Append (_("Plain"));
664                         _protocol->Append (_("STARTTLS"));
665                         _protocol->Append (_("SSL"));
666                         s->Add (_protocol);
667                         table->Add (s, 1, wxEXPAND | wxALL);
668                 }
669
670                 add_label_to_sizer (table, _panel, _("User name"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
671                 _user = new wxTextCtrl (_panel, wxID_ANY);
672                 table->Add (_user, 1, wxEXPAND | wxALL);
673
674                 add_label_to_sizer (table, _panel, _("Password"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
675                 _password = new PasswordEntry (_panel);
676                 table->Add (_password->get_panel(), 1, wxEXPAND | wxALL);
677
678                 _server->Bind (wxEVT_TEXT, boost::bind (&EmailPage::server_changed, this));
679                 _port->Bind (wxEVT_SPINCTRL, boost::bind (&EmailPage::port_changed, this));
680                 _protocol->Bind (wxEVT_CHOICE, boost::bind (&EmailPage::protocol_changed, this));
681                 _user->Bind (wxEVT_TEXT, boost::bind (&EmailPage::user_changed, this));
682                 _password->Changed.connect (boost::bind (&EmailPage::password_changed, this));
683         }
684
685         void config_changed ()
686         {
687                 auto config = Config::instance ();
688
689                 checked_set (_server, config->mail_server ());
690                 checked_set (_port, config->mail_port ());
691                 switch (config->mail_protocol()) {
692                 case EmailProtocol::AUTO:
693                         checked_set (_protocol, 0);
694                         break;
695                 case EmailProtocol::PLAIN:
696                         checked_set (_protocol, 1);
697                         break;
698                 case EmailProtocol::STARTTLS:
699                         checked_set (_protocol, 2);
700                         break;
701                 case EmailProtocol::SSL:
702                         checked_set (_protocol, 3);
703                         break;
704                 }
705                 checked_set (_user, config->mail_user ());
706                 checked_set (_password, config->mail_password());
707         }
708
709         void server_changed ()
710         {
711                 Config::instance()->set_mail_server (wx_to_std (_server->GetValue ()));
712         }
713
714         void port_changed ()
715         {
716                 Config::instance()->set_mail_port (_port->GetValue ());
717         }
718
719         void protocol_changed ()
720         {
721                 switch (_protocol->GetSelection()) {
722                 case 0:
723                         Config::instance()->set_mail_protocol(EmailProtocol::AUTO);
724                         break;
725                 case 1:
726                         Config::instance()->set_mail_protocol(EmailProtocol::PLAIN);
727                         break;
728                 case 2:
729                         Config::instance()->set_mail_protocol(EmailProtocol::STARTTLS);
730                         break;
731                 case 3:
732                         Config::instance()->set_mail_protocol(EmailProtocol::SSL);
733                         break;
734                 }
735         }
736
737         void user_changed ()
738         {
739                 Config::instance()->set_mail_user (wx_to_std (_user->GetValue ()));
740         }
741
742         void password_changed ()
743         {
744                 Config::instance()->set_mail_password(_password->get());
745         }
746
747         wxTextCtrl* _server;
748         wxSpinCtrl* _port;
749         wxChoice* _protocol;
750         wxTextCtrl* _user;
751         PasswordEntry* _password;
752 };
753
754 class KDMEmailPage : public Page
755 {
756 public:
757
758         KDMEmailPage (wxSize panel_size, int border)
759 #ifdef DCPOMATIC_OSX
760                 /* We have to force both width and height of this one */
761                 : Page (wxSize (panel_size.GetWidth(), 128), border)
762 #else
763                 : Page (panel_size, border)
764 #endif
765         {}
766
767         wxString GetName () const
768         {
769                 return _("KDM Email");
770         }
771
772 #ifdef DCPOMATIC_OSX
773         wxBitmap GetLargeIcon () const
774         {
775                 return wxBitmap ("kdm_email", wxBITMAP_TYPE_PNG_RESOURCE);
776         }
777 #endif
778
779 private:
780         void setup ()
781         {
782                 wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
783                 table->AddGrowableCol (1, 1);
784                 _panel->GetSizer()->Add (table, 1, wxEXPAND | wxALL, _border);
785
786                 add_label_to_sizer (table, _panel, _("Subject"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
787                 _subject = new wxTextCtrl (_panel, wxID_ANY);
788                 table->Add (_subject, 1, wxEXPAND | wxALL);
789
790                 add_label_to_sizer (table, _panel, _("From address"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
791                 _from = new wxTextCtrl (_panel, wxID_ANY);
792                 table->Add (_from, 1, wxEXPAND | wxALL);
793
794                 vector<EditableListColumn> columns;
795                 columns.push_back (EditableListColumn(_("Address")));
796                 add_label_to_sizer (table, _panel, _("CC addresses"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
797                 _cc = new EditableList<string, EmailDialog> (
798                         _panel,
799                         columns,
800                         bind (&Config::kdm_cc, Config::instance()),
801                         bind (&Config::set_kdm_cc, Config::instance(), _1),
802                         bind (&column, _1)
803                         );
804                 table->Add (_cc, 1, wxEXPAND | wxALL);
805
806                 add_label_to_sizer (table, _panel, _("BCC address"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
807                 _bcc = new wxTextCtrl (_panel, wxID_ANY);
808                 table->Add (_bcc, 1, wxEXPAND | wxALL);
809
810                 _email = new wxTextCtrl (_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (-1, 200), wxTE_MULTILINE);
811                 _panel->GetSizer()->Add (_email, 0, wxEXPAND | wxALL, _border);
812
813                 _reset_email = new Button (_panel, _("Reset to default subject and text"));
814                 _panel->GetSizer()->Add (_reset_email, 0, wxEXPAND | wxALL, _border);
815
816                 _cc->layout ();
817
818                 _subject->Bind (wxEVT_TEXT, boost::bind (&KDMEmailPage::kdm_subject_changed, this));
819                 _from->Bind (wxEVT_TEXT, boost::bind (&KDMEmailPage::kdm_from_changed, this));
820                 _bcc->Bind (wxEVT_TEXT, boost::bind (&KDMEmailPage::kdm_bcc_changed, this));
821                 _email->Bind (wxEVT_TEXT, boost::bind (&KDMEmailPage::kdm_email_changed, this));
822                 _reset_email->Bind (wxEVT_BUTTON, boost::bind (&KDMEmailPage::reset_email, this));
823         }
824
825         void config_changed ()
826         {
827                 Config* config = Config::instance ();
828
829                 checked_set (_subject, config->kdm_subject ());
830                 checked_set (_from, config->kdm_from ());
831                 checked_set (_bcc, config->kdm_bcc ());
832                 checked_set (_email, Config::instance()->kdm_email ());
833         }
834
835         void kdm_subject_changed ()
836         {
837                 Config::instance()->set_kdm_subject (wx_to_std (_subject->GetValue ()));
838         }
839
840         void kdm_from_changed ()
841         {
842                 Config::instance()->set_kdm_from (wx_to_std (_from->GetValue ()));
843         }
844
845         void kdm_bcc_changed ()
846         {
847                 Config::instance()->set_kdm_bcc (wx_to_std (_bcc->GetValue ()));
848         }
849
850         void kdm_email_changed ()
851         {
852                 if (_email->GetValue().IsEmpty ()) {
853                         /* Sometimes we get sent an erroneous notification that the email
854                            is empty; I don't know why.
855                         */
856                         return;
857                 }
858                 Config::instance()->set_kdm_email (wx_to_std (_email->GetValue ()));
859         }
860
861         void reset_email ()
862         {
863                 Config::instance()->reset_kdm_email ();
864                 checked_set (_email, Config::instance()->kdm_email ());
865         }
866
867         wxTextCtrl* _subject;
868         wxTextCtrl* _from;
869         EditableList<string, EmailDialog>* _cc;
870         wxTextCtrl* _bcc;
871         wxTextCtrl* _email;
872         wxButton* _reset_email;
873 };
874
875 class NotificationsPage : public Page
876 {
877 public:
878         NotificationsPage (wxSize panel_size, int border)
879 #ifdef DCPOMATIC_OSX
880                 /* We have to force both width and height of this one */
881                 : Page (wxSize (panel_size.GetWidth(), 128), border)
882 #else
883                 : Page (panel_size, border)
884 #endif
885         {}
886
887         wxString GetName () const
888         {
889                 return _("Notifications");
890         }
891
892 #ifdef DCPOMATIC_OSX
893         wxBitmap GetLargeIcon () const
894         {
895                 return wxBitmap ("notifications", wxBITMAP_TYPE_PNG_RESOURCE);
896         }
897 #endif
898
899 private:
900         void setup ()
901         {
902                 wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
903                 table->AddGrowableCol (1, 1);
904                 _panel->GetSizer()->Add (table, 1, wxEXPAND | wxALL, _border);
905
906                 _enable_message_box = new CheckBox (_panel, _("Message box"));
907                 table->Add (_enable_message_box, 1, wxEXPAND | wxALL);
908                 table->AddSpacer (0);
909
910                 _enable_email = new CheckBox (_panel, _("Email"));
911                 table->Add (_enable_email, 1, wxEXPAND | wxALL);
912                 table->AddSpacer (0);
913
914                 add_label_to_sizer (table, _panel, _("Subject"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
915                 _subject = new wxTextCtrl (_panel, wxID_ANY);
916                 table->Add (_subject, 1, wxEXPAND | wxALL);
917
918                 add_label_to_sizer (table, _panel, _("From address"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
919                 _from = new wxTextCtrl (_panel, wxID_ANY);
920                 table->Add (_from, 1, wxEXPAND | wxALL);
921
922                 add_label_to_sizer (table, _panel, _("To address"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
923                 _to = new wxTextCtrl (_panel, wxID_ANY);
924                 table->Add (_to, 1, wxEXPAND | wxALL);
925
926                 vector<EditableListColumn> columns;
927                 columns.push_back (EditableListColumn(_("Address")));
928                 add_label_to_sizer (table, _panel, _("CC addresses"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
929                 _cc = new EditableList<string, EmailDialog> (
930                         _panel,
931                         columns,
932                         bind (&Config::notification_cc, Config::instance()),
933                         bind (&Config::set_notification_cc, Config::instance(), _1),
934                         bind (&column, _1)
935                         );
936                 table->Add (_cc, 1, wxEXPAND | wxALL);
937
938                 add_label_to_sizer (table, _panel, _("BCC address"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
939                 _bcc = new wxTextCtrl (_panel, wxID_ANY);
940                 table->Add (_bcc, 1, wxEXPAND | wxALL);
941
942                 _email = new wxTextCtrl (_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (-1, 200), wxTE_MULTILINE);
943                 _panel->GetSizer()->Add (_email, 0, wxEXPAND | wxALL, _border);
944
945                 _reset_email = new Button (_panel, _("Reset to default subject and text"));
946                 _panel->GetSizer()->Add (_reset_email, 0, wxEXPAND | wxALL, _border);
947
948                 _cc->layout ();
949
950                 _enable_message_box->Bind (wxEVT_CHECKBOX, boost::bind (&NotificationsPage::type_changed, this, _enable_message_box, Config::MESSAGE_BOX));
951                 _enable_email->Bind (wxEVT_CHECKBOX, boost::bind (&NotificationsPage::type_changed, this, _enable_email, Config::EMAIL));
952
953                 _subject->Bind (wxEVT_TEXT, boost::bind (&NotificationsPage::notification_subject_changed, this));
954                 _from->Bind (wxEVT_TEXT, boost::bind (&NotificationsPage::notification_from_changed, this));
955                 _to->Bind (wxEVT_TEXT, boost::bind (&NotificationsPage::notification_to_changed, this));
956                 _bcc->Bind (wxEVT_TEXT, boost::bind (&NotificationsPage::notification_bcc_changed, this));
957                 _email->Bind (wxEVT_TEXT, boost::bind (&NotificationsPage::notification_email_changed, this));
958                 _reset_email->Bind (wxEVT_BUTTON, boost::bind (&NotificationsPage::reset_email, this));
959
960                 update_sensitivity ();
961         }
962
963         void update_sensitivity ()
964         {
965                 bool const s = _enable_email->GetValue();
966                 _subject->Enable(s);
967                 _from->Enable(s);
968                 _to->Enable(s);
969                 _cc->Enable(s);
970                 _bcc->Enable(s);
971                 _email->Enable(s);
972                 _reset_email->Enable(s);
973         }
974
975         void config_changed ()
976         {
977                 Config* config = Config::instance ();
978
979                 checked_set (_enable_message_box, config->notification(Config::MESSAGE_BOX));
980                 checked_set (_enable_email, config->notification(Config::EMAIL));
981                 checked_set (_subject, config->notification_subject ());
982                 checked_set (_from, config->notification_from ());
983                 checked_set (_to, config->notification_to ());
984                 checked_set (_bcc, config->notification_bcc ());
985                 checked_set (_email, Config::instance()->notification_email ());
986
987                 update_sensitivity ();
988         }
989
990         void notification_subject_changed ()
991         {
992                 Config::instance()->set_notification_subject (wx_to_std (_subject->GetValue ()));
993         }
994
995         void notification_from_changed ()
996         {
997                 Config::instance()->set_notification_from (wx_to_std (_from->GetValue ()));
998         }
999
1000         void notification_to_changed ()
1001         {
1002                 Config::instance()->set_notification_to (wx_to_std (_to->GetValue ()));
1003         }
1004
1005         void notification_bcc_changed ()
1006         {
1007                 Config::instance()->set_notification_bcc (wx_to_std (_bcc->GetValue ()));
1008         }
1009
1010         void notification_email_changed ()
1011         {
1012                 if (_email->GetValue().IsEmpty ()) {
1013                         /* Sometimes we get sent an erroneous notification that the email
1014                            is empty; I don't know why.
1015                         */
1016                         return;
1017                 }
1018                 Config::instance()->set_notification_email (wx_to_std (_email->GetValue ()));
1019         }
1020
1021         void reset_email ()
1022         {
1023                 Config::instance()->reset_notification_email ();
1024                 checked_set (_email, Config::instance()->notification_email ());
1025         }
1026
1027         void type_changed (wxCheckBox* b, Config::Notification n)
1028         {
1029                 Config::instance()->set_notification(n, b->GetValue());
1030                 update_sensitivity ();
1031         }
1032
1033         wxCheckBox* _enable_message_box;
1034         wxCheckBox* _enable_email;
1035
1036         wxTextCtrl* _subject;
1037         wxTextCtrl* _from;
1038         wxTextCtrl* _to;
1039         EditableList<string, EmailDialog>* _cc;
1040         wxTextCtrl* _bcc;
1041         wxTextCtrl* _email;
1042         wxButton* _reset_email;
1043 };
1044
1045 class CoverSheetPage : public Page
1046 {
1047 public:
1048
1049         CoverSheetPage (wxSize panel_size, int border)
1050 #ifdef DCPOMATIC_OSX
1051                 /* We have to force both width and height of this one */
1052                 : Page (wxSize (panel_size.GetWidth(), 128), border)
1053 #else
1054                 : Page (panel_size, border)
1055 #endif
1056         {}
1057
1058         wxString GetName () const
1059         {
1060                 return _("Cover Sheet");
1061         }
1062
1063 #ifdef DCPOMATIC_OSX
1064         wxBitmap GetLargeIcon () const
1065         {
1066                 return wxBitmap ("cover_sheet", wxBITMAP_TYPE_PNG_RESOURCE);
1067         }
1068 #endif
1069
1070 private:
1071         void setup ()
1072         {
1073                 _cover_sheet = new wxTextCtrl (_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (-1, 200), wxTE_MULTILINE);
1074                 _panel->GetSizer()->Add (_cover_sheet, 0, wxEXPAND | wxALL, _border);
1075
1076                 _reset_cover_sheet = new Button (_panel, _("Reset to default text"));
1077                 _panel->GetSizer()->Add (_reset_cover_sheet, 0, wxEXPAND | wxALL, _border);
1078
1079                 _cover_sheet->Bind (wxEVT_TEXT, boost::bind (&CoverSheetPage::cover_sheet_changed, this));
1080                 _reset_cover_sheet->Bind (wxEVT_BUTTON, boost::bind (&CoverSheetPage::reset_cover_sheet, this));
1081         }
1082
1083         void config_changed ()
1084         {
1085                 checked_set (_cover_sheet, Config::instance()->cover_sheet ());
1086         }
1087
1088         void cover_sheet_changed ()
1089         {
1090                 if (_cover_sheet->GetValue().IsEmpty ()) {
1091                         /* Sometimes we get sent an erroneous notification that the cover sheet
1092                            is empty; I don't know why.
1093                         */
1094                         return;
1095                 }
1096                 Config::instance()->set_cover_sheet (wx_to_std (_cover_sheet->GetValue ()));
1097         }
1098
1099         void reset_cover_sheet ()
1100         {
1101                 Config::instance()->reset_cover_sheet ();
1102                 checked_set (_cover_sheet, Config::instance()->cover_sheet ());
1103         }
1104
1105         wxTextCtrl* _cover_sheet;
1106         wxButton* _reset_cover_sheet;
1107 };
1108
1109
1110 class IdentifiersPage : public Page
1111 {
1112 public:
1113         IdentifiersPage (wxSize panel_size, int border)
1114                 : Page (panel_size, border)
1115         {}
1116
1117         wxString GetName () const
1118         {
1119                 return _("Identifiers");
1120         }
1121
1122 #ifdef DCPOMATIC_OSX
1123         wxBitmap GetLargeIcon () const
1124         {
1125                 return wxBitmap ("identifiers", wxBITMAP_TYPE_PNG_RESOURCE);
1126         }
1127 #endif
1128
1129 private:
1130         void setup ()
1131         {
1132                 wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
1133                 table->AddGrowableCol (1, 1);
1134
1135                 add_label_to_sizer (table, _panel, _("Issuer"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
1136                 _issuer = new wxTextCtrl (_panel, wxID_ANY);
1137                 _issuer->SetToolTip (_("This will be written to the DCP's XML files as the <Issuer>.  If it is blank, a default value mentioning DCP-o-matic will be used."));
1138                 table->Add (_issuer, 1, wxALL | wxEXPAND);
1139
1140                 add_label_to_sizer (table, _panel, _("Creator"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
1141                 _creator = new wxTextCtrl (_panel, wxID_ANY);
1142                 _creator->SetToolTip (_("This will be written to the DCP's XML files as the <Creator>.  If it is blank, a default value mentioning DCP-o-matic will be used."));
1143                 table->Add (_creator, 1, wxALL | wxEXPAND);
1144
1145                 add_label_to_sizer (table, _panel, _("Company name"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
1146                 _company_name = new wxTextCtrl (_panel, wxID_ANY);
1147                 _company_name->SetToolTip (_("This will be written to the DCP's MXF files as the 'company name'.  If it is blank, a default value mentioning libdcp (an internal DCP-o-matic library) will be used."));
1148                 table->Add (_company_name, 1, wxALL | wxEXPAND);
1149
1150                 add_label_to_sizer (table, _panel, _("Product name"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
1151                 _product_name = new wxTextCtrl (_panel, wxID_ANY);
1152                 _product_name->SetToolTip (_("This will be written to the DCP's MXF files as the 'product name'.  If it is blank, a default value mentioning libdcp (an internal DCP-o-matic library) will be used."));
1153                 table->Add (_product_name, 1, wxALL | wxEXPAND);
1154
1155                 add_label_to_sizer (table, _panel, _("Product version"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
1156                 _product_version = new wxTextCtrl (_panel, wxID_ANY);
1157                 _product_version->SetToolTip (_("This will be written to the DCP's MXF files as the 'product version'.  If it is blank, a default value mentioning libdcp (an internal DCP-o-matic library) will be used."));
1158                 table->Add (_product_version, 1, wxALL | wxEXPAND);
1159
1160                 add_label_to_sizer (table, _panel, _("JPEG2000 comment"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
1161                 _j2k_comment = new wxTextCtrl (_panel, wxID_ANY);
1162                 _j2k_comment->SetToolTip (_("This will be written to the DCP's JPEG2000 data as a comment.  If it is blank, a default value mentioning libdcp (an internal DCP-o-matic library) will be used."));
1163                 table->Add (_j2k_comment, 1, wxALL | wxEXPAND);
1164
1165                 _panel->GetSizer()->Add (table, 0, wxEXPAND | wxALL, _border);
1166
1167                 _issuer->Bind (wxEVT_TEXT, boost::bind(&IdentifiersPage::issuer_changed, this));
1168                 _creator->Bind (wxEVT_TEXT, boost::bind(&IdentifiersPage::creator_changed, this));
1169                 _company_name->Bind (wxEVT_TEXT, boost::bind(&IdentifiersPage::company_name_changed, this));
1170                 _product_name->Bind (wxEVT_TEXT, boost::bind(&IdentifiersPage::product_name_changed, this));
1171                 _product_version->Bind (wxEVT_TEXT, boost::bind(&IdentifiersPage::product_version_changed, this));
1172                 _j2k_comment->Bind (wxEVT_TEXT, boost::bind(&IdentifiersPage::j2k_comment_changed, this));
1173         }
1174
1175         void config_changed ()
1176         {
1177                 Config* config = Config::instance ();
1178                 checked_set (_issuer, config->dcp_issuer ());
1179                 checked_set (_creator, config->dcp_creator ());
1180                 checked_set (_company_name, config->dcp_company_name ());
1181                 checked_set (_product_name, config->dcp_product_name ());
1182                 checked_set (_product_version, config->dcp_product_version ());
1183                 checked_set (_j2k_comment, config->dcp_j2k_comment ());
1184         }
1185
1186         void issuer_changed ()
1187         {
1188                 Config::instance()->set_dcp_issuer (wx_to_std (_issuer->GetValue ()));
1189         }
1190
1191         void creator_changed ()
1192         {
1193                 Config::instance()->set_dcp_creator (wx_to_std (_creator->GetValue ()));
1194         }
1195
1196         void company_name_changed ()
1197         {
1198                 Config::instance()->set_dcp_company_name (wx_to_std(_company_name->GetValue()));
1199         }
1200
1201         void product_name_changed ()
1202         {
1203                 Config::instance()->set_dcp_product_name (wx_to_std(_product_name->GetValue()));
1204         }
1205
1206         void product_version_changed ()
1207         {
1208                 Config::instance()->set_dcp_product_version (wx_to_std(_product_version->GetValue()));
1209         }
1210
1211         void j2k_comment_changed ()
1212         {
1213                 Config::instance()->set_dcp_j2k_comment (wx_to_std(_j2k_comment->GetValue()));
1214         }
1215
1216         wxTextCtrl* _issuer;
1217         wxTextCtrl* _creator;
1218         wxTextCtrl* _company_name;
1219         wxTextCtrl* _product_name;
1220         wxTextCtrl* _product_version;
1221         wxTextCtrl* _j2k_comment;
1222 };
1223
1224
1225 /** @class AdvancedPage
1226  *  @brief Advanced page of the preferences dialog.
1227  */
1228 class AdvancedPage : public Page
1229 {
1230 public:
1231         AdvancedPage (wxSize panel_size, int border)
1232                 : Page (panel_size, border)
1233                 , _maximum_j2k_bandwidth (0)
1234                 , _allow_any_dcp_frame_rate (0)
1235                 , _allow_any_container (0)
1236                 , _show_experimental_audio_processors (0)
1237                 , _only_servers_encode (0)
1238                 , _log_general (0)
1239                 , _log_warning (0)
1240                 , _log_error (0)
1241                 , _log_timing (0)
1242                 , _log_debug_threed (0)
1243                 , _log_debug_encode (0)
1244                 , _log_debug_email (0)
1245                 , _log_debug_video_view (0)
1246                 , _log_debug_player (0)
1247                 , _log_debug_audio_analysis (0)
1248         {}
1249
1250         wxString GetName () const
1251         {
1252                 return _("Advanced");
1253         }
1254
1255 #ifdef DCPOMATIC_OSX
1256         wxBitmap GetLargeIcon () const
1257         {
1258                 return wxBitmap ("advanced", wxBITMAP_TYPE_PNG_RESOURCE);
1259         }
1260 #endif
1261
1262 private:
1263         void add_top_aligned_label_to_sizer (wxSizer* table, wxWindow* parent, wxString text)
1264         {
1265                 int flags = wxALIGN_TOP | wxTOP | wxLEFT;
1266 #ifdef __WXOSX__
1267                 flags |= wxALIGN_RIGHT;
1268                 text += wxT (":");
1269 #endif
1270                 wxStaticText* m = new StaticText (parent, text);
1271                 table->Add (m, 0, flags, DCPOMATIC_SIZER_Y_GAP);
1272         }
1273
1274         void setup ()
1275         {
1276                 wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
1277                 table->AddGrowableCol (1, 1);
1278                 _panel->GetSizer()->Add (table, 1, wxALL | wxEXPAND, _border);
1279
1280                 {
1281                         add_label_to_sizer (table, _panel, _("Maximum JPEG2000 bandwidth"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
1282                         wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
1283                         _maximum_j2k_bandwidth = new wxSpinCtrl (_panel);
1284                         s->Add (_maximum_j2k_bandwidth, 1);
1285                         add_label_to_sizer (s, _panel, _("Mbit/s"), false);
1286                         table->Add (s, 1);
1287                 }
1288
1289                 add_label_to_sizer (table, _panel, _("Video display mode"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
1290                 _video_display_mode = new wxChoice (_panel, wxID_ANY);
1291                 table->Add (_video_display_mode);
1292
1293                 wxStaticText* restart = add_label_to_sizer (table, _panel, _("(restart DCP-o-matic to change display mode)"), false);
1294                 wxFont font = restart->GetFont();
1295                 font.SetStyle (wxFONTSTYLE_ITALIC);
1296                 font.SetPointSize (font.GetPointSize() - 1);
1297                 restart->SetFont (font);
1298                 table->AddSpacer (0);
1299
1300                 _allow_any_dcp_frame_rate = new CheckBox (_panel, _("Allow any DCP frame rate"));
1301                 table->Add (_allow_any_dcp_frame_rate, 1, wxEXPAND | wxALL);
1302                 table->AddSpacer (0);
1303
1304                 _allow_any_container = new CheckBox (_panel, _("Allow full-frame and non-standard container ratios"));
1305                 table->Add (_allow_any_container, 1, wxEXPAND | wxALL);
1306                 table->AddSpacer (0);
1307
1308                 restart = add_label_to_sizer (table, _panel, _("(restart DCP-o-matic to see all ratios)"), false);
1309                 restart->SetFont (font);
1310                 table->AddSpacer (0);
1311
1312                 _show_experimental_audio_processors = new CheckBox (_panel, _("Show experimental audio processors"));
1313                 table->Add (_show_experimental_audio_processors, 1, wxEXPAND | wxALL);
1314                 table->AddSpacer (0);
1315
1316                 _only_servers_encode = new CheckBox (_panel, _("Only servers encode"));
1317                 table->Add (_only_servers_encode, 1, wxEXPAND | wxALL);
1318                 table->AddSpacer (0);
1319
1320                 {
1321                         add_label_to_sizer (table, _panel, _("Maximum number of frames to store per thread"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
1322                         wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
1323                         _frames_in_memory_multiplier = new wxSpinCtrl (_panel);
1324                         s->Add (_frames_in_memory_multiplier, 1);
1325                         table->Add (s, 1);
1326                 }
1327
1328                 {
1329                         add_top_aligned_label_to_sizer (table, _panel, _("DCP metadata filename format"));
1330                         dcp::NameFormat::Map titles;
1331                         titles['t'] = wx_to_std (_("type (cpl/pkl)"));
1332                         dcp::NameFormat::Map examples;
1333                         examples['t'] = "cpl";
1334                         _dcp_metadata_filename_format = new NameFormatEditor (
1335                                 _panel, Config::instance()->dcp_metadata_filename_format(), titles, examples, "_eb1c112c-ca3c-4ae6-9263-c6714ff05d64.xml"
1336                                 );
1337                         table->Add (_dcp_metadata_filename_format->panel(), 1, wxEXPAND | wxALL);
1338                 }
1339
1340                 {
1341                         add_top_aligned_label_to_sizer (table, _panel, _("DCP asset filename format"));
1342                         dcp::NameFormat::Map titles;
1343                         titles['t'] = wx_to_std (_("type (j2c/pcm/sub)"));
1344                         titles['r'] = wx_to_std (_("reel number"));
1345                         titles['n'] = wx_to_std (_("number of reels"));
1346                         titles['c'] = wx_to_std (_("content filename"));
1347                         dcp::NameFormat::Map examples;
1348                         examples['t'] = "j2c";
1349                         examples['r'] = "1";
1350                         examples['n'] = "4";
1351                         examples['c'] = "myfile.mp4";
1352                         _dcp_asset_filename_format = new NameFormatEditor (
1353                                 _panel, Config::instance()->dcp_asset_filename_format(), titles, examples, "_eb1c112c-ca3c-4ae6-9263-c6714ff05d64.mxf"
1354                                 );
1355                         table->Add (_dcp_asset_filename_format->panel(), 1, wxEXPAND | wxALL);
1356                 }
1357
1358                 {
1359                         add_top_aligned_label_to_sizer (table, _panel, _("Log"));
1360                         wxBoxSizer* t = new wxBoxSizer (wxVERTICAL);
1361                         _log_general = new CheckBox (_panel, _("General"));
1362                         t->Add (_log_general, 1, wxEXPAND | wxALL);
1363                         _log_warning = new CheckBox (_panel, _("Warnings"));
1364                         t->Add (_log_warning, 1, wxEXPAND | wxALL);
1365                         _log_error = new CheckBox (_panel, _("Errors"));
1366                         t->Add (_log_error, 1, wxEXPAND | wxALL);
1367                         /// TRANSLATORS: translate the word "Timing" here; do not include the "Config|" prefix
1368                         _log_timing = new CheckBox (_panel, S_("Config|Timing"));
1369                         t->Add (_log_timing, 1, wxEXPAND | wxALL);
1370                         _log_debug_threed = new CheckBox (_panel, _("Debug: 3D"));
1371                         t->Add (_log_debug_threed, 1, wxEXPAND | wxALL);
1372                         _log_debug_encode = new CheckBox (_panel, _("Debug: encode"));
1373                         t->Add (_log_debug_encode, 1, wxEXPAND | wxALL);
1374                         _log_debug_email = new CheckBox (_panel, _("Debug: email sending"));
1375                         t->Add (_log_debug_email, 1, wxEXPAND | wxALL);
1376                         _log_debug_video_view = new CheckBox (_panel, _("Debug: video view"));
1377                         t->Add (_log_debug_video_view, 1, wxEXPAND | wxALL);
1378                         _log_debug_player = new CheckBox (_panel, _("Debug: player"));
1379                         t->Add (_log_debug_player, 1, wxEXPAND | wxALL);
1380                         _log_debug_audio_analysis = new CheckBox (_panel, _("Debug: audio analysis"));
1381                         t->Add (_log_debug_audio_analysis, 1, wxEXPAND | wxALL);
1382                         table->Add (t, 0, wxALL, 6);
1383                 }
1384
1385 #ifdef DCPOMATIC_WINDOWS
1386                 _win32_console = new CheckBox (_panel, _("Open console window"));
1387                 table->Add (_win32_console, 1, wxEXPAND | wxALL);
1388                 table->AddSpacer (0);
1389 #endif
1390
1391                 _maximum_j2k_bandwidth->SetRange (1, 1000);
1392                 _maximum_j2k_bandwidth->Bind (wxEVT_SPINCTRL, boost::bind (&AdvancedPage::maximum_j2k_bandwidth_changed, this));
1393                 _video_display_mode->Append (_("Simple (safer)"));
1394                 _video_display_mode->Append (_("OpenGL (faster)"));
1395                 _video_display_mode->Bind (wxEVT_CHOICE, boost::bind(&AdvancedPage::video_display_mode_changed, this));
1396                 _allow_any_dcp_frame_rate->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::allow_any_dcp_frame_rate_changed, this));
1397                 _allow_any_container->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::allow_any_container_changed, this));
1398                 _show_experimental_audio_processors->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::show_experimental_audio_processors_changed, this));
1399                 _only_servers_encode->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::only_servers_encode_changed, this));
1400                 _frames_in_memory_multiplier->Bind (wxEVT_SPINCTRL, boost::bind(&AdvancedPage::frames_in_memory_multiplier_changed, this));
1401                 _dcp_metadata_filename_format->Changed.connect (boost::bind (&AdvancedPage::dcp_metadata_filename_format_changed, this));
1402                 _dcp_asset_filename_format->Changed.connect (boost::bind (&AdvancedPage::dcp_asset_filename_format_changed, this));
1403                 _log_general->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::log_changed, this));
1404                 _log_warning->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::log_changed, this));
1405                 _log_error->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::log_changed, this));
1406                 _log_timing->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::log_changed, this));
1407                 _log_debug_threed->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::log_changed, this));
1408                 _log_debug_encode->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::log_changed, this));
1409                 _log_debug_email->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::log_changed, this));
1410                 _log_debug_video_view->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::log_changed, this));
1411                 _log_debug_player->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::log_changed, this));
1412                 _log_debug_audio_analysis->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::log_changed, this));
1413 #ifdef DCPOMATIC_WINDOWS
1414                 _win32_console->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::win32_console_changed, this));
1415 #endif
1416         }
1417
1418         void config_changed ()
1419         {
1420                 Config* config = Config::instance ();
1421
1422                 checked_set (_maximum_j2k_bandwidth, config->maximum_j2k_bandwidth() / 1000000);
1423                 switch (config->video_view_type()) {
1424                 case Config::VIDEO_VIEW_SIMPLE:
1425                         checked_set (_video_display_mode, 0);
1426                         break;
1427                 case Config::VIDEO_VIEW_OPENGL:
1428                         checked_set (_video_display_mode, 1);
1429                         break;
1430                 }
1431                 checked_set (_allow_any_dcp_frame_rate, config->allow_any_dcp_frame_rate ());
1432                 checked_set (_allow_any_container, config->allow_any_container ());
1433                 checked_set (_show_experimental_audio_processors, config->show_experimental_audio_processors ());
1434                 checked_set (_only_servers_encode, config->only_servers_encode ());
1435                 checked_set (_log_general, config->log_types() & LogEntry::TYPE_GENERAL);
1436                 checked_set (_log_warning, config->log_types() & LogEntry::TYPE_WARNING);
1437                 checked_set (_log_error, config->log_types() & LogEntry::TYPE_ERROR);
1438                 checked_set (_log_timing, config->log_types() & LogEntry::TYPE_TIMING);
1439                 checked_set (_log_debug_threed, config->log_types() & LogEntry::TYPE_DEBUG_THREE_D);
1440                 checked_set (_log_debug_encode, config->log_types() & LogEntry::TYPE_DEBUG_ENCODE);
1441                 checked_set (_log_debug_email, config->log_types() & LogEntry::TYPE_DEBUG_EMAIL);
1442                 checked_set (_log_debug_video_view, config->log_types() & LogEntry::TYPE_DEBUG_VIDEO_VIEW);
1443                 checked_set (_log_debug_player, config->log_types() & LogEntry::TYPE_DEBUG_PLAYER);
1444                 checked_set (_log_debug_audio_analysis, config->log_types() & LogEntry::TYPE_DEBUG_AUDIO_ANALYSIS);
1445                 checked_set (_frames_in_memory_multiplier, config->frames_in_memory_multiplier());
1446 #ifdef DCPOMATIC_WINDOWS
1447                 checked_set (_win32_console, config->win32_console());
1448 #endif
1449         }
1450
1451         void maximum_j2k_bandwidth_changed ()
1452         {
1453                 Config::instance()->set_maximum_j2k_bandwidth (_maximum_j2k_bandwidth->GetValue() * 1000000);
1454         }
1455
1456         void video_display_mode_changed ()
1457         {
1458                 if (_video_display_mode->GetSelection() == 0) {
1459                         Config::instance()->set_video_view_type (Config::VIDEO_VIEW_SIMPLE);
1460                 } else {
1461                         Config::instance()->set_video_view_type (Config::VIDEO_VIEW_OPENGL);
1462                 }
1463         }
1464
1465         void frames_in_memory_multiplier_changed ()
1466         {
1467                 Config::instance()->set_frames_in_memory_multiplier (_frames_in_memory_multiplier->GetValue());
1468         }
1469
1470         void allow_any_dcp_frame_rate_changed ()
1471         {
1472                 Config::instance()->set_allow_any_dcp_frame_rate (_allow_any_dcp_frame_rate->GetValue ());
1473         }
1474
1475         void allow_any_container_changed ()
1476         {
1477                 Config::instance()->set_allow_any_container (_allow_any_container->GetValue ());
1478         }
1479
1480         void show_experimental_audio_processors_changed ()
1481         {
1482                 Config::instance()->set_show_experimental_audio_processors (_show_experimental_audio_processors->GetValue ());
1483         }
1484
1485         void only_servers_encode_changed ()
1486         {
1487                 Config::instance()->set_only_servers_encode (_only_servers_encode->GetValue ());
1488         }
1489
1490         void dcp_metadata_filename_format_changed ()
1491         {
1492                 Config::instance()->set_dcp_metadata_filename_format (_dcp_metadata_filename_format->get ());
1493         }
1494
1495         void dcp_asset_filename_format_changed ()
1496         {
1497                 Config::instance()->set_dcp_asset_filename_format (_dcp_asset_filename_format->get ());
1498         }
1499
1500         void log_changed ()
1501         {
1502                 int types = 0;
1503                 if (_log_general->GetValue ()) {
1504                         types |= LogEntry::TYPE_GENERAL;
1505                 }
1506                 if (_log_warning->GetValue ()) {
1507                         types |= LogEntry::TYPE_WARNING;
1508                 }
1509                 if (_log_error->GetValue ())  {
1510                         types |= LogEntry::TYPE_ERROR;
1511                 }
1512                 if (_log_timing->GetValue ()) {
1513                         types |= LogEntry::TYPE_TIMING;
1514                 }
1515                 if (_log_debug_threed->GetValue ()) {
1516                         types |= LogEntry::TYPE_DEBUG_THREE_D;
1517                 }
1518                 if (_log_debug_encode->GetValue ()) {
1519                         types |= LogEntry::TYPE_DEBUG_ENCODE;
1520                 }
1521                 if (_log_debug_email->GetValue ()) {
1522                         types |= LogEntry::TYPE_DEBUG_EMAIL;
1523                 }
1524                 if (_log_debug_video_view->GetValue()) {
1525                         types |= LogEntry::TYPE_DEBUG_VIDEO_VIEW;
1526                 }
1527                 if (_log_debug_player->GetValue()) {
1528                         types |= LogEntry::TYPE_DEBUG_PLAYER;
1529                 }
1530                 if (_log_debug_audio_analysis->GetValue()) {
1531                         types |= LogEntry::TYPE_DEBUG_AUDIO_ANALYSIS;
1532                 }
1533                 Config::instance()->set_log_types (types);
1534         }
1535
1536 #ifdef DCPOMATIC_WINDOWS
1537         void win32_console_changed ()
1538         {
1539                 Config::instance()->set_win32_console (_win32_console->GetValue ());
1540         }
1541 #endif
1542
1543         wxSpinCtrl* _maximum_j2k_bandwidth;
1544         wxChoice* _video_display_mode;
1545         wxSpinCtrl* _frames_in_memory_multiplier;
1546         wxCheckBox* _allow_any_dcp_frame_rate;
1547         wxCheckBox* _allow_any_container;
1548         wxCheckBox* _show_experimental_audio_processors;
1549         wxCheckBox* _only_servers_encode;
1550         NameFormatEditor* _dcp_metadata_filename_format;
1551         NameFormatEditor* _dcp_asset_filename_format;
1552         wxCheckBox* _log_general;
1553         wxCheckBox* _log_warning;
1554         wxCheckBox* _log_error;
1555         wxCheckBox* _log_timing;
1556         wxCheckBox* _log_debug_threed;
1557         wxCheckBox* _log_debug_encode;
1558         wxCheckBox* _log_debug_email;
1559         wxCheckBox* _log_debug_video_view;
1560         wxCheckBox* _log_debug_player;
1561         wxCheckBox* _log_debug_audio_analysis;
1562 #ifdef DCPOMATIC_WINDOWS
1563         wxCheckBox* _win32_console;
1564 #endif
1565 };
1566
1567 wxPreferencesEditor*
1568 create_full_config_dialog ()
1569 {
1570         wxPreferencesEditor* e = new wxPreferencesEditor ();
1571
1572 #ifdef DCPOMATIC_OSX
1573         /* Width that we force some of the config panels to be on OSX so that
1574            the containing window doesn't shrink too much when we select those panels.
1575            This is obviously an unpleasant hack.
1576         */
1577         wxSize ps = wxSize (700, -1);
1578         int const border = 16;
1579 #else
1580         wxSize ps = wxSize (-1, -1);
1581         int const border = 8;
1582 #endif
1583
1584         e->AddPage (new FullGeneralPage (ps, border));
1585         e->AddPage (new SoundPage (ps, border));
1586         e->AddPage (new DefaultsPage (ps, border));
1587         e->AddPage (new EncodingServersPage (ps, border));
1588         e->AddPage (new KeysPage (ps, border));
1589         e->AddPage (new TMSPage (ps, border));
1590         e->AddPage (new EmailPage (ps, border));
1591         e->AddPage (new KDMEmailPage (ps, border));
1592         e->AddPage (new NotificationsPage (ps, border));
1593         e->AddPage (new CoverSheetPage (ps, border));
1594         e->AddPage (new IdentifiersPage (ps, border));
1595         e->AddPage (new AdvancedPage (ps, border));
1596         return e;
1597 }