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