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