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