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