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