Decryption keys are really for decrypting KDMs, not DCPs.
[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", true);
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 scale-to"), true);
519                 _scale_to = new wxChoice (_panel, wxID_ANY);
520                 table->Add (_scale_to);
521
522                 add_label_to_sizer (table, _panel, _("Default content type"), true);
523                 _dcp_content_type = new wxChoice (_panel, wxID_ANY);
524                 table->Add (_dcp_content_type);
525
526                 add_label_to_sizer (table, _panel, _("Default DCP audio channels"), true);
527                 _dcp_audio_channels = new wxChoice (_panel, wxID_ANY);
528                 table->Add (_dcp_audio_channels);
529
530                 {
531                         add_label_to_sizer (table, _panel, _("Default JPEG2000 bandwidth"), true);
532                         wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
533                         _j2k_bandwidth = new wxSpinCtrl (_panel);
534                         s->Add (_j2k_bandwidth);
535                         add_label_to_sizer (s, _panel, _("Mbit/s"), false);
536                         table->Add (s, 1);
537                 }
538
539                 {
540                         add_label_to_sizer (table, _panel, _("Default audio delay"), true);
541                         wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
542                         _audio_delay = new wxSpinCtrl (_panel);
543                         s->Add (_audio_delay);
544                         add_label_to_sizer (s, _panel, _("ms"), false);
545                         table->Add (s, 1);
546                 }
547
548                 add_label_to_sizer (table, _panel, _("Default standard"), true);
549                 _standard = new wxChoice (_panel, wxID_ANY);
550                 table->Add (_standard);
551
552                 add_label_to_sizer (table, _panel, _("Default KDM directory"), true);
553 #ifdef DCPOMATIC_USE_OWN_PICKER
554                 _kdm_directory = new DirPickerCtrl (_panel);
555 #else
556                 _kdm_directory = new wxDirPickerCtrl (_panel, wxDD_DIR_MUST_EXIST);
557 #endif
558                 table->Add (_kdm_directory, 1, wxEXPAND);
559
560                 _still_length->SetRange (1, 3600);
561                 _still_length->Bind (wxEVT_SPINCTRL, boost::bind (&DefaultsPage::still_length_changed, this));
562
563                 _directory->Bind (wxEVT_DIRPICKER_CHANGED, boost::bind (&DefaultsPage::directory_changed, this));
564                 _kdm_directory->Bind (wxEVT_DIRPICKER_CHANGED, boost::bind (&DefaultsPage::kdm_directory_changed, this));
565
566                 _isdcf_metadata_button->Bind (wxEVT_BUTTON, boost::bind (&DefaultsPage::edit_isdcf_metadata_clicked, this));
567
568                 vector<Ratio const *> ratios = Ratio::all ();
569                 for (size_t i = 0; i < ratios.size(); ++i) {
570                         _container->Append (std_to_wx (ratios[i]->nickname ()));
571                 }
572
573                 _container->Bind (wxEVT_CHOICE, boost::bind (&DefaultsPage::container_changed, this));
574
575                 _scale_to->Append (_("Guess from content"));
576
577                 for (size_t i = 0; i < ratios.size(); ++i) {
578                         _scale_to->Append (std_to_wx (ratios[i]->nickname ()));
579                 }
580
581                 _scale_to->Bind (wxEVT_CHOICE, boost::bind (&DefaultsPage::scale_to_changed, this));
582
583                 vector<DCPContentType const *> const ct = DCPContentType::all ();
584                 for (size_t i = 0; i < ct.size(); ++i) {
585                         _dcp_content_type->Append (std_to_wx (ct[i]->pretty_name ()));
586                 }
587
588                 setup_audio_channels_choice (_dcp_audio_channels, 2);
589
590                 _dcp_content_type->Bind (wxEVT_CHOICE, boost::bind (&DefaultsPage::dcp_content_type_changed, this));
591                 _dcp_audio_channels->Bind (wxEVT_CHOICE, boost::bind (&DefaultsPage::dcp_audio_channels_changed, this));
592
593                 _j2k_bandwidth->SetRange (50, 250);
594                 _j2k_bandwidth->Bind (wxEVT_SPINCTRL, boost::bind (&DefaultsPage::j2k_bandwidth_changed, this));
595
596                 _audio_delay->SetRange (-1000, 1000);
597                 _audio_delay->Bind (wxEVT_SPINCTRL, boost::bind (&DefaultsPage::audio_delay_changed, this));
598
599                 _standard->Append (_("SMPTE"));
600                 _standard->Append (_("Interop"));
601                 _standard->Bind (wxEVT_CHOICE, boost::bind (&DefaultsPage::standard_changed, this));
602         }
603
604         void config_changed ()
605         {
606                 Config* config = Config::instance ();
607
608                 vector<Ratio const *> ratios = Ratio::all ();
609                 for (size_t i = 0; i < ratios.size(); ++i) {
610                         if (ratios[i] == config->default_container ()) {
611                                 _container->SetSelection (i);
612                         }
613                         if (ratios[i] == config->default_scale_to ()) {
614                                 _scale_to->SetSelection (i + 1);
615                         }
616                 }
617
618                 if (!config->default_scale_to()) {
619                         _scale_to->SetSelection (0);
620                 }
621
622                 vector<DCPContentType const *> const ct = DCPContentType::all ();
623                 for (size_t i = 0; i < ct.size(); ++i) {
624                         if (ct[i] == config->default_dcp_content_type ()) {
625                                 _dcp_content_type->SetSelection (i);
626                         }
627                 }
628
629                 checked_set (_still_length, config->default_still_length ());
630                 _directory->SetPath (std_to_wx (config->default_directory_or (wx_to_std (wxStandardPaths::Get().GetDocumentsDir())).string ()));
631                 _kdm_directory->SetPath (std_to_wx (config->default_kdm_directory_or (wx_to_std (wxStandardPaths::Get().GetDocumentsDir())).string ()));
632                 checked_set (_j2k_bandwidth, config->default_j2k_bandwidth() / 1000000);
633                 _j2k_bandwidth->SetRange (50, config->maximum_j2k_bandwidth() / 1000000);
634                 checked_set (_dcp_audio_channels, locale_convert<string> (config->default_dcp_audio_channels()));
635                 checked_set (_audio_delay, config->default_audio_delay ());
636                 checked_set (_standard, config->default_interop() ? 1 : 0);
637         }
638
639         void j2k_bandwidth_changed ()
640         {
641                 Config::instance()->set_default_j2k_bandwidth (_j2k_bandwidth->GetValue() * 1000000);
642         }
643
644         void audio_delay_changed ()
645         {
646                 Config::instance()->set_default_audio_delay (_audio_delay->GetValue());
647         }
648
649         void dcp_audio_channels_changed ()
650         {
651                 int const s = _dcp_audio_channels->GetSelection ();
652                 if (s != wxNOT_FOUND) {
653                         Config::instance()->set_default_dcp_audio_channels (
654                                 locale_convert<int> (string_client_data (_dcp_audio_channels->GetClientObject (s)))
655                                 );
656                 }
657         }
658
659         void directory_changed ()
660         {
661                 Config::instance()->set_default_directory (wx_to_std (_directory->GetPath ()));
662         }
663
664         void kdm_directory_changed ()
665         {
666                 Config::instance()->set_default_kdm_directory (wx_to_std (_kdm_directory->GetPath ()));
667         }
668
669         void edit_isdcf_metadata_clicked ()
670         {
671                 ISDCFMetadataDialog* d = new ISDCFMetadataDialog (_panel, Config::instance()->default_isdcf_metadata (), false);
672                 d->ShowModal ();
673                 Config::instance()->set_default_isdcf_metadata (d->isdcf_metadata ());
674                 d->Destroy ();
675         }
676
677         void still_length_changed ()
678         {
679                 Config::instance()->set_default_still_length (_still_length->GetValue ());
680         }
681
682         void container_changed ()
683         {
684                 vector<Ratio const *> ratio = Ratio::all ();
685                 Config::instance()->set_default_container (ratio[_container->GetSelection()]);
686         }
687
688         void scale_to_changed ()
689         {
690                 int const s = _scale_to->GetSelection ();
691                 if (s == 0) {
692                         Config::instance()->set_default_scale_to (0);
693                 } else {
694                         vector<Ratio const *> ratio = Ratio::all ();
695                         Config::instance()->set_default_scale_to (ratio[s - 1]);
696                 }
697         }
698
699         void dcp_content_type_changed ()
700         {
701                 vector<DCPContentType const *> ct = DCPContentType::all ();
702                 Config::instance()->set_default_dcp_content_type (ct[_dcp_content_type->GetSelection()]);
703         }
704
705         void standard_changed ()
706         {
707                 Config::instance()->set_default_interop (_standard->GetSelection() == 1);
708         }
709
710         wxSpinCtrl* _j2k_bandwidth;
711         wxSpinCtrl* _audio_delay;
712         wxButton* _isdcf_metadata_button;
713         wxSpinCtrl* _still_length;
714 #ifdef DCPOMATIC_USE_OWN_PICKER
715         DirPickerCtrl* _directory;
716         DirPickerCtrl* _kdm_directory;
717 #else
718         wxDirPickerCtrl* _directory;
719         wxDirPickerCtrl* _kdm_directory;
720 #endif
721         wxChoice* _container;
722         wxChoice* _scale_to;
723         wxChoice* _dcp_content_type;
724         wxChoice* _dcp_audio_channels;
725         wxChoice* _standard;
726 };
727
728 class EncodingServersPage : public StandardPage
729 {
730 public:
731         EncodingServersPage (wxSize panel_size, int border)
732                 : StandardPage (panel_size, border)
733         {}
734
735         wxString GetName () const
736         {
737                 return _("Servers");
738         }
739
740 #ifdef DCPOMATIC_OSX
741         wxBitmap GetLargeIcon () const
742         {
743                 return wxBitmap ("servers", wxBITMAP_TYPE_PNG_RESOURCE);
744         }
745 #endif
746
747 private:
748         void setup ()
749         {
750                 _use_any_servers = new wxCheckBox (_panel, wxID_ANY, _("Search network for servers"));
751                 _panel->GetSizer()->Add (_use_any_servers, 0, wxALL, _border);
752
753                 vector<string> columns;
754                 columns.push_back (wx_to_std (_("IP address / host name")));
755                 _servers_list = new EditableList<string, ServerDialog> (
756                         _panel,
757                         columns,
758                         boost::bind (&Config::servers, Config::instance()),
759                         boost::bind (&Config::set_servers, Config::instance(), _1),
760                         boost::bind (&EncodingServersPage::server_column, this, _1)
761                         );
762
763                 _panel->GetSizer()->Add (_servers_list, 1, wxEXPAND | wxALL, _border);
764
765                 _use_any_servers->Bind (wxEVT_CHECKBOX, boost::bind (&EncodingServersPage::use_any_servers_changed, this));
766         }
767
768         void config_changed ()
769         {
770                 checked_set (_use_any_servers, Config::instance()->use_any_servers ());
771                 _servers_list->refresh ();
772         }
773
774         void use_any_servers_changed ()
775         {
776                 Config::instance()->set_use_any_servers (_use_any_servers->GetValue ());
777         }
778
779         string server_column (string s)
780         {
781                 return s;
782         }
783
784         wxCheckBox* _use_any_servers;
785         EditableList<string, ServerDialog>* _servers_list;
786 };
787
788 class CertificateChainEditor : public wxPanel
789 {
790 public:
791         CertificateChainEditor (
792                 wxWindow* parent,
793                 wxString title,
794                 int border,
795                 function<void (shared_ptr<dcp::CertificateChain>)> set,
796                 function<shared_ptr<const dcp::CertificateChain> (void)> get
797                 )
798                 : wxPanel (parent)
799                 , _set (set)
800                 , _get (get)
801         {
802                 wxFont subheading_font (*wxNORMAL_FONT);
803                 subheading_font.SetWeight (wxFONTWEIGHT_BOLD);
804
805                 _sizer = new wxBoxSizer (wxVERTICAL);
806
807                 {
808                         wxStaticText* m = new wxStaticText (this, wxID_ANY, title);
809                         m->SetFont (subheading_font);
810                         _sizer->Add (m, 0, wxALL, border);
811                 }
812
813                 wxBoxSizer* certificates_sizer = new wxBoxSizer (wxHORIZONTAL);
814                 _sizer->Add (certificates_sizer, 0, wxLEFT | wxRIGHT, border);
815
816                 _certificates = new wxListCtrl (this, wxID_ANY, wxDefaultPosition, wxSize (440, 150), wxLC_REPORT | wxLC_SINGLE_SEL);
817
818                 {
819                         wxListItem ip;
820                         ip.SetId (0);
821                         ip.SetText (_("Type"));
822                         ip.SetWidth (100);
823                         _certificates->InsertColumn (0, ip);
824                 }
825
826                 {
827                         wxListItem ip;
828                         ip.SetId (1);
829                         ip.SetText (_("Thumbprint"));
830                         ip.SetWidth (340);
831
832                         wxFont font = ip.GetFont ();
833                         font.SetFamily (wxFONTFAMILY_TELETYPE);
834                         ip.SetFont (font);
835
836                         _certificates->InsertColumn (1, ip);
837                 }
838
839                 certificates_sizer->Add (_certificates, 1, wxEXPAND);
840
841                 {
842                         wxSizer* s = new wxBoxSizer (wxVERTICAL);
843                         _add_certificate = new wxButton (this, wxID_ANY, _("Add..."));
844                         s->Add (_add_certificate, 0, wxTOP | wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
845                         _remove_certificate = new wxButton (this, wxID_ANY, _("Remove"));
846                         s->Add (_remove_certificate, 0, wxTOP | wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
847                         _export_certificate = new wxButton (this, wxID_ANY, _("Export"));
848                         s->Add (_export_certificate, 0, wxTOP | wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
849                         certificates_sizer->Add (s, 0, wxLEFT, DCPOMATIC_SIZER_X_GAP);
850                 }
851
852                 wxGridBagSizer* table = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
853                 _sizer->Add (table, 1, wxALL | wxEXPAND, border);
854                 int r = 0;
855
856                 add_label_to_sizer (table, this, _("Leaf private key"), true, wxGBPosition (r, 0));
857                 _private_key = new wxStaticText (this, wxID_ANY, wxT (""));
858                 wxFont font = _private_key->GetFont ();
859                 font.SetFamily (wxFONTFAMILY_TELETYPE);
860                 _private_key->SetFont (font);
861                 table->Add (_private_key, wxGBPosition (r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
862                 _load_private_key = new wxButton (this, wxID_ANY, _("Load..."));
863                 table->Add (_load_private_key, wxGBPosition (r, 2));
864                 _export_private_key = new wxButton (this, wxID_ANY, _("Export..."));
865                 table->Add (_export_private_key, wxGBPosition (r, 3));
866                 ++r;
867
868                 _button_sizer = new wxBoxSizer (wxHORIZONTAL);
869                 _remake_certificates = new wxButton (this, wxID_ANY, _("Re-make certificates\nand key..."));
870                 _button_sizer->Add (_remake_certificates, 1, wxRIGHT, border);
871                 table->Add (_button_sizer, wxGBPosition (r, 0), wxGBSpan (1, 4));
872                 ++r;
873
874                 _add_certificate->Bind     (wxEVT_BUTTON,       boost::bind (&CertificateChainEditor::add_certificate, this));
875                 _remove_certificate->Bind  (wxEVT_BUTTON,       boost::bind (&CertificateChainEditor::remove_certificate, this));
876                 _export_certificate->Bind  (wxEVT_BUTTON,       boost::bind (&CertificateChainEditor::export_certificate, this));
877                 _certificates->Bind        (wxEVT_LIST_ITEM_SELECTED,   boost::bind (&CertificateChainEditor::update_sensitivity, this));
878                 _certificates->Bind        (wxEVT_LIST_ITEM_DESELECTED, boost::bind (&CertificateChainEditor::update_sensitivity, this));
879                 _remake_certificates->Bind (wxEVT_BUTTON,       boost::bind (&CertificateChainEditor::remake_certificates, this));
880                 _load_private_key->Bind    (wxEVT_BUTTON,       boost::bind (&CertificateChainEditor::load_private_key, this));
881                 _export_private_key->Bind  (wxEVT_BUTTON,       boost::bind (&CertificateChainEditor::export_private_key, this));
882
883                 SetSizerAndFit (_sizer);
884         }
885
886         void config_changed ()
887         {
888                 _chain.reset (new dcp::CertificateChain (*_get().get ()));
889
890                 update_certificate_list ();
891                 update_private_key ();
892                 update_sensitivity ();
893         }
894
895         void add_button (wxWindow* button)
896         {
897                 _button_sizer->Add (button, 0, wxLEFT | wxRIGHT, DCPOMATIC_SIZER_X_GAP);
898                 _sizer->Layout ();
899         }
900
901 private:
902         void add_certificate ()
903         {
904                 wxFileDialog* d = new wxFileDialog (this, _("Select Certificate File"));
905
906                 if (d->ShowModal() == wxID_OK) {
907                         try {
908                                 dcp::Certificate c;
909                                 string const extra = c.read_string (dcp::file_to_string (wx_to_std (d->GetPath ())));
910                                 if (!extra.empty ()) {
911                                         message_dialog (
912                                                 this,
913                                                 _("This file contains other certificates (or other data) after its first certificate. "
914                                                   "Only the first certificate will be used.")
915                                                 );
916                                 }
917                                 _chain->add (c);
918                                 _set (_chain);
919                                 update_certificate_list ();
920                         } catch (dcp::MiscError& e) {
921                                 error_dialog (this, wxString::Format (_("Could not read certificate file (%s)"), e.what ()));
922                         }
923                 }
924
925                 d->Destroy ();
926
927                 update_sensitivity ();
928         }
929
930         void remove_certificate ()
931         {
932                 int i = _certificates->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
933                 if (i == -1) {
934                         return;
935                 }
936
937                 _certificates->DeleteItem (i);
938                 _chain->remove (i);
939                 _set (_chain);
940
941                 update_sensitivity ();
942         }
943
944         void export_certificate ()
945         {
946                 int i = _certificates->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
947                 if (i == -1) {
948                         return;
949                 }
950
951                 wxFileDialog* d = new wxFileDialog (
952                         this, _("Select Certificate File"), wxEmptyString, wxEmptyString, wxT ("PEM files (*.pem)|*.pem"),
953                         wxFD_SAVE | wxFD_OVERWRITE_PROMPT
954                         );
955
956                 dcp::CertificateChain::List all = _chain->root_to_leaf ();
957                 dcp::CertificateChain::List::iterator j = all.begin ();
958                 for (int k = 0; k < i; ++k) {
959                         ++j;
960                 }
961
962                 if (d->ShowModal () == wxID_OK) {
963                         FILE* f = fopen_boost (wx_to_std (d->GetPath ()), "w");
964                         if (!f) {
965                                 throw OpenFileError (wx_to_std (d->GetPath ()), errno, false);
966                         }
967
968                         string const s = j->certificate (true);
969                         fwrite (s.c_str(), 1, s.length(), f);
970                         fclose (f);
971                 }
972                 d->Destroy ();
973         }
974
975         void update_certificate_list ()
976         {
977                 _certificates->DeleteAllItems ();
978                 size_t n = 0;
979                 dcp::CertificateChain::List certs = _chain->root_to_leaf ();
980                 BOOST_FOREACH (dcp::Certificate const & i, certs) {
981                         wxListItem item;
982                         item.SetId (n);
983                         _certificates->InsertItem (item);
984                         _certificates->SetItem (n, 1, std_to_wx (i.thumbprint ()));
985
986                         if (n == 0) {
987                                 _certificates->SetItem (n, 0, _("Root"));
988                         } else if (n == (certs.size() - 1)) {
989                                 _certificates->SetItem (n, 0, _("Leaf"));
990                         } else {
991                                 _certificates->SetItem (n, 0, _("Intermediate"));
992                         }
993
994                         ++n;
995                 }
996         }
997
998         void remake_certificates ()
999         {
1000                 shared_ptr<const dcp::CertificateChain> chain = _get ();
1001
1002                 string subject_organization_name;
1003                 string subject_organizational_unit_name;
1004                 string root_common_name;
1005                 string intermediate_common_name;
1006                 string leaf_common_name;
1007
1008                 dcp::CertificateChain::List all = chain->root_to_leaf ();
1009
1010                 if (all.size() >= 1) {
1011                         /* Have a root */
1012                         subject_organization_name = chain->root().subject_organization_name ();
1013                         subject_organizational_unit_name = chain->root().subject_organizational_unit_name ();
1014                         root_common_name = chain->root().subject_common_name ();
1015                 }
1016
1017                 if (all.size() >= 2) {
1018                         /* Have a leaf */
1019                         leaf_common_name = chain->leaf().subject_common_name ();
1020                 }
1021
1022                 if (all.size() >= 3) {
1023                         /* Have an intermediate */
1024                         dcp::CertificateChain::List::iterator i = all.begin ();
1025                         ++i;
1026                         intermediate_common_name = i->subject_common_name ();
1027                 }
1028
1029                 MakeChainDialog* d = new MakeChainDialog (
1030                         this,
1031                         subject_organization_name,
1032                         subject_organizational_unit_name,
1033                         root_common_name,
1034                         intermediate_common_name,
1035                         leaf_common_name
1036                         );
1037
1038                 if (d->ShowModal () == wxID_OK) {
1039                         _chain.reset (
1040                                 new dcp::CertificateChain (
1041                                         openssl_path (),
1042                                         d->organisation (),
1043                                         d->organisational_unit (),
1044                                         d->root_common_name (),
1045                                         d->intermediate_common_name (),
1046                                         d->leaf_common_name ()
1047                                         )
1048                                 );
1049
1050                         _set (_chain);
1051                         update_certificate_list ();
1052                         update_private_key ();
1053                 }
1054
1055                 d->Destroy ();
1056         }
1057
1058         void update_sensitivity ()
1059         {
1060                 _remove_certificate->Enable (_certificates->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) != -1);
1061                 _export_certificate->Enable (_certificates->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) != -1);
1062         }
1063
1064         void update_private_key ()
1065         {
1066                 checked_set (_private_key, dcp::private_key_fingerprint (_chain->key().get ()));
1067                 _sizer->Layout ();
1068         }
1069
1070         void load_private_key ()
1071         {
1072                 wxFileDialog* d = new wxFileDialog (this, _("Select Key File"));
1073
1074                 if (d->ShowModal() == wxID_OK) {
1075                         try {
1076                                 boost::filesystem::path p (wx_to_std (d->GetPath ()));
1077                                 if (boost::filesystem::file_size (p) > 8192) {
1078                                         error_dialog (
1079                                                 this,
1080                                                 wxString::Format (_("Could not read key file; file is too long (%s)"), std_to_wx (p.string ()))
1081                                                 );
1082                                         return;
1083                                 }
1084
1085                                 _chain->set_key (dcp::file_to_string (p));
1086                                 _set (_chain);
1087                                 update_private_key ();
1088                         } catch (dcp::MiscError& e) {
1089                                 error_dialog (this, wxString::Format (_("Could not read certificate file (%s)"), e.what ()));
1090                         }
1091                 }
1092
1093                 d->Destroy ();
1094
1095                 update_sensitivity ();
1096         }
1097
1098         void export_private_key ()
1099         {
1100                 optional<string> key = _chain->key ();
1101                 if (!key) {
1102                         return;
1103                 }
1104
1105                 wxFileDialog* d = new wxFileDialog (
1106                         this, _("Select Key File"), wxEmptyString, wxEmptyString, wxT ("PEM files (*.pem)|*.pem"),
1107                         wxFD_SAVE | wxFD_OVERWRITE_PROMPT
1108                         );
1109
1110                 if (d->ShowModal () == wxID_OK) {
1111                         FILE* f = fopen_boost (wx_to_std (d->GetPath ()), "w");
1112                         if (!f) {
1113                                 throw OpenFileError (wx_to_std (d->GetPath ()), errno, false);
1114                         }
1115
1116                         string const s = _chain->key().get ();
1117                         fwrite (s.c_str(), 1, s.length(), f);
1118                         fclose (f);
1119                 }
1120                 d->Destroy ();
1121         }
1122
1123         wxListCtrl* _certificates;
1124         wxButton* _add_certificate;
1125         wxButton* _export_certificate;
1126         wxButton* _remove_certificate;
1127         wxButton* _remake_certificates;
1128         wxStaticText* _private_key;
1129         wxButton* _load_private_key;
1130         wxButton* _export_private_key;
1131         wxSizer* _sizer;
1132         wxBoxSizer* _button_sizer;
1133         shared_ptr<dcp::CertificateChain> _chain;
1134         boost::function<void (shared_ptr<dcp::CertificateChain>)> _set;
1135         boost::function<shared_ptr<const dcp::CertificateChain> (void)> _get;
1136 };
1137
1138 class KeysPage : public StandardPage
1139 {
1140 public:
1141         KeysPage (wxSize panel_size, int border)
1142                 : StandardPage (panel_size, border)
1143         {}
1144
1145         wxString GetName () const
1146         {
1147                 return _("Keys");
1148         }
1149
1150 #ifdef DCPOMATIC_OSX
1151         wxBitmap GetLargeIcon () const
1152         {
1153                 return wxBitmap ("keys", wxBITMAP_TYPE_PNG_RESOURCE);
1154         }
1155 #endif
1156
1157 private:
1158
1159         void setup ()
1160         {
1161                 _signer = new CertificateChainEditor (
1162                         _panel, _("Signing DCPs and KDMs"), _border,
1163                         boost::bind (&Config::set_signer_chain, Config::instance (), _1),
1164                         boost::bind (&Config::signer_chain, Config::instance ())
1165                         );
1166
1167                 _panel->GetSizer()->Add (_signer);
1168
1169                 _decryption = new CertificateChainEditor (
1170                         _panel, _("Decrypting KDMs"), _border,
1171                         boost::bind (&Config::set_decryption_chain, Config::instance (), _1),
1172                         boost::bind (&Config::decryption_chain, Config::instance ())
1173                         );
1174
1175                 _panel->GetSizer()->Add (_decryption);
1176
1177                 _export_decryption_certificate = new wxButton (_decryption, wxID_ANY, _("Export KDM decryption\ncertificate..."));
1178                 _decryption->add_button (_export_decryption_certificate);
1179                 _export_decryption_chain = new wxButton (_decryption, wxID_ANY, _("Export KDM decryption\nchain..."));
1180                 _decryption->add_button (_export_decryption_chain);
1181
1182                 _export_decryption_certificate->Bind (wxEVT_BUTTON, boost::bind (&KeysPage::export_decryption_certificate, this));
1183                 _export_decryption_chain->Bind (wxEVT_BUTTON, boost::bind (&KeysPage::export_decryption_chain, this));
1184         }
1185
1186         void export_decryption_certificate ()
1187         {
1188                 wxFileDialog* d = new wxFileDialog (
1189                         _panel, _("Select Certificate File"), wxEmptyString, wxEmptyString, wxT ("PEM files (*.pem)|*.pem"),
1190                         wxFD_SAVE | wxFD_OVERWRITE_PROMPT
1191                         );
1192
1193                 if (d->ShowModal () == wxID_OK) {
1194                         FILE* f = fopen_boost (wx_to_std (d->GetPath ()), "w");
1195                         if (!f) {
1196                                 throw OpenFileError (wx_to_std (d->GetPath ()), errno, false);
1197                         }
1198
1199                         string const s = Config::instance()->decryption_chain()->leaf().certificate (true);
1200                         fwrite (s.c_str(), 1, s.length(), f);
1201                         fclose (f);
1202                 }
1203                 d->Destroy ();
1204         }
1205
1206         void export_decryption_chain ()
1207         {
1208                 wxFileDialog* d = new wxFileDialog (
1209                         _panel, _("Select Chain File"), wxEmptyString, wxEmptyString, wxT ("PEM files (*.pem)|*.pem"),
1210                         wxFD_SAVE | wxFD_OVERWRITE_PROMPT
1211                         );
1212
1213                 if (d->ShowModal () == wxID_OK) {
1214                         FILE* f = fopen_boost (wx_to_std (d->GetPath ()), "w");
1215                         if (!f) {
1216                                 throw OpenFileError (wx_to_std (d->GetPath ()), errno, false);
1217                         }
1218
1219                         string const s = Config::instance()->decryption_chain()->chain();
1220                         fwrite (s.c_str(), 1, s.length(), f);
1221                         fclose (f);
1222                 }
1223                 d->Destroy ();
1224         }
1225
1226         void config_changed ()
1227         {
1228                 _signer->config_changed ();
1229                 _decryption->config_changed ();
1230         }
1231
1232         CertificateChainEditor* _signer;
1233         CertificateChainEditor* _decryption;
1234         wxButton* _export_decryption_certificate;
1235         wxButton* _export_decryption_chain;
1236 };
1237
1238 class TMSPage : public StandardPage
1239 {
1240 public:
1241         TMSPage (wxSize panel_size, int border)
1242                 : StandardPage (panel_size, border)
1243         {}
1244
1245         wxString GetName () const
1246         {
1247                 return _("TMS");
1248         }
1249
1250 #ifdef DCPOMATIC_OSX
1251         wxBitmap GetLargeIcon () const
1252         {
1253                 return wxBitmap ("tms", wxBITMAP_TYPE_PNG_RESOURCE);
1254         }
1255 #endif
1256
1257 private:
1258         void setup ()
1259         {
1260                 wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
1261                 table->AddGrowableCol (1, 1);
1262                 _panel->GetSizer()->Add (table, 1, wxALL | wxEXPAND, _border);
1263
1264                 add_label_to_sizer (table, _panel, _("Protocol"), true);
1265                 _tms_protocol = new wxChoice (_panel, wxID_ANY);
1266                 table->Add (_tms_protocol, 1, wxEXPAND);
1267
1268                 add_label_to_sizer (table, _panel, _("IP address"), true);
1269                 _tms_ip = new wxTextCtrl (_panel, wxID_ANY);
1270                 table->Add (_tms_ip, 1, wxEXPAND);
1271
1272                 add_label_to_sizer (table, _panel, _("Target path"), true);
1273                 _tms_path = new wxTextCtrl (_panel, wxID_ANY);
1274                 table->Add (_tms_path, 1, wxEXPAND);
1275
1276                 add_label_to_sizer (table, _panel, _("User name"), true);
1277                 _tms_user = new wxTextCtrl (_panel, wxID_ANY);
1278                 table->Add (_tms_user, 1, wxEXPAND);
1279
1280                 add_label_to_sizer (table, _panel, _("Password"), true);
1281                 _tms_password = new wxTextCtrl (_panel, wxID_ANY);
1282                 table->Add (_tms_password, 1, wxEXPAND);
1283
1284                 _tms_protocol->Append (_("SCP (for AAM and Doremi)"));
1285                 _tms_protocol->Append (_("FTP (for Dolby)"));
1286
1287                 _tms_protocol->Bind (wxEVT_CHOICE, boost::bind (&TMSPage::tms_protocol_changed, this));
1288                 _tms_ip->Bind (wxEVT_TEXT, boost::bind (&TMSPage::tms_ip_changed, this));
1289                 _tms_path->Bind (wxEVT_TEXT, boost::bind (&TMSPage::tms_path_changed, this));
1290                 _tms_user->Bind (wxEVT_TEXT, boost::bind (&TMSPage::tms_user_changed, this));
1291                 _tms_password->Bind (wxEVT_TEXT, boost::bind (&TMSPage::tms_password_changed, this));
1292         }
1293
1294         void config_changed ()
1295         {
1296                 Config* config = Config::instance ();
1297
1298                 checked_set (_tms_protocol, config->tms_protocol ());
1299                 checked_set (_tms_ip, config->tms_ip ());
1300                 checked_set (_tms_path, config->tms_path ());
1301                 checked_set (_tms_user, config->tms_user ());
1302                 checked_set (_tms_password, config->tms_password ());
1303         }
1304
1305         void tms_protocol_changed ()
1306         {
1307                 Config::instance()->set_tms_protocol (static_cast<Protocol> (_tms_protocol->GetSelection ()));
1308         }
1309
1310         void tms_ip_changed ()
1311         {
1312                 Config::instance()->set_tms_ip (wx_to_std (_tms_ip->GetValue ()));
1313         }
1314
1315         void tms_path_changed ()
1316         {
1317                 Config::instance()->set_tms_path (wx_to_std (_tms_path->GetValue ()));
1318         }
1319
1320         void tms_user_changed ()
1321         {
1322                 Config::instance()->set_tms_user (wx_to_std (_tms_user->GetValue ()));
1323         }
1324
1325         void tms_password_changed ()
1326         {
1327                 Config::instance()->set_tms_password (wx_to_std (_tms_password->GetValue ()));
1328         }
1329
1330         wxChoice* _tms_protocol;
1331         wxTextCtrl* _tms_ip;
1332         wxTextCtrl* _tms_path;
1333         wxTextCtrl* _tms_user;
1334         wxTextCtrl* _tms_password;
1335 };
1336
1337 static string
1338 column (string s)
1339 {
1340         return s;
1341 }
1342
1343 class KDMEmailPage : public StandardPage
1344 {
1345 public:
1346
1347         KDMEmailPage (wxSize panel_size, int border)
1348 #ifdef DCPOMATIC_OSX
1349                 /* We have to force both width and height of this one */
1350                 : StandardPage (wxSize (480, 128), border)
1351 #else
1352                 : StandardPage (panel_size, border)
1353 #endif
1354         {}
1355
1356         wxString GetName () const
1357         {
1358                 return _("KDM Email");
1359         }
1360
1361 #ifdef DCPOMATIC_OSX
1362         wxBitmap GetLargeIcon () const
1363         {
1364                 return wxBitmap ("kdm_email", wxBITMAP_TYPE_PNG_RESOURCE);
1365         }
1366 #endif
1367
1368 private:
1369         void setup ()
1370         {
1371                 wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
1372                 table->AddGrowableCol (1, 1);
1373                 _panel->GetSizer()->Add (table, 1, wxEXPAND | wxALL, _border);
1374
1375                 add_label_to_sizer (table, _panel, _("Outgoing mail server"), true);
1376                 {
1377                         wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
1378                         _mail_server = new wxTextCtrl (_panel, wxID_ANY);
1379                         s->Add (_mail_server, 1, wxEXPAND | wxALL);
1380                         add_label_to_sizer (s, _panel, _("port"), false);
1381                         _mail_port = new wxSpinCtrl (_panel, wxID_ANY);
1382                         _mail_port->SetRange (0, 65535);
1383                         s->Add (_mail_port);
1384                         table->Add (s, 1, wxEXPAND | wxALL);
1385                 }
1386
1387                 add_label_to_sizer (table, _panel, _("Mail user name"), true);
1388                 _mail_user = new wxTextCtrl (_panel, wxID_ANY);
1389                 table->Add (_mail_user, 1, wxEXPAND | wxALL);
1390
1391                 add_label_to_sizer (table, _panel, _("Mail password"), true);
1392                 _mail_password = new wxTextCtrl (_panel, wxID_ANY);
1393                 table->Add (_mail_password, 1, wxEXPAND | wxALL);
1394
1395                 add_label_to_sizer (table, _panel, _("Subject"), true);
1396                 _kdm_subject = new wxTextCtrl (_panel, wxID_ANY);
1397                 table->Add (_kdm_subject, 1, wxEXPAND | wxALL);
1398
1399                 add_label_to_sizer (table, _panel, _("From address"), true);
1400                 _kdm_from = new wxTextCtrl (_panel, wxID_ANY);
1401                 table->Add (_kdm_from, 1, wxEXPAND | wxALL);
1402
1403                 vector<string> columns;
1404                 columns.push_back (wx_to_std (_("Address")));
1405                 add_label_to_sizer (table, _panel, _("CC addresses"), true);
1406                 _kdm_cc = new EditableList<string, EmailDialog> (
1407                         _panel,
1408                         columns,
1409                         bind (&Config::kdm_cc, Config::instance()),
1410                         bind (&Config::set_kdm_cc, Config::instance(), _1),
1411                         bind (&column, _1)
1412                         );
1413                 table->Add (_kdm_cc, 1, wxEXPAND | wxALL);
1414
1415                 add_label_to_sizer (table, _panel, _("BCC address"), true);
1416                 _kdm_bcc = new wxTextCtrl (_panel, wxID_ANY);
1417                 table->Add (_kdm_bcc, 1, wxEXPAND | wxALL);
1418
1419                 _kdm_email = new wxTextCtrl (_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (-1, 200), wxTE_MULTILINE);
1420                 _panel->GetSizer()->Add (_kdm_email, 0, wxEXPAND | wxALL, _border);
1421
1422                 _reset_kdm_email = new wxButton (_panel, wxID_ANY, _("Reset to default subject and text"));
1423                 _panel->GetSizer()->Add (_reset_kdm_email, 0, wxEXPAND | wxALL, _border);
1424
1425                 _kdm_cc->layout ();
1426
1427                 _mail_server->Bind (wxEVT_TEXT, boost::bind (&KDMEmailPage::mail_server_changed, this));
1428                 _mail_port->Bind (wxEVT_SPINCTRL, boost::bind (&KDMEmailPage::mail_port_changed, this));
1429                 _mail_user->Bind (wxEVT_TEXT, boost::bind (&KDMEmailPage::mail_user_changed, this));
1430                 _mail_password->Bind (wxEVT_TEXT, boost::bind (&KDMEmailPage::mail_password_changed, this));
1431                 _kdm_subject->Bind (wxEVT_TEXT, boost::bind (&KDMEmailPage::kdm_subject_changed, this));
1432                 _kdm_from->Bind (wxEVT_TEXT, boost::bind (&KDMEmailPage::kdm_from_changed, this));
1433                 _kdm_bcc->Bind (wxEVT_TEXT, boost::bind (&KDMEmailPage::kdm_bcc_changed, this));
1434                 _kdm_email->Bind (wxEVT_TEXT, boost::bind (&KDMEmailPage::kdm_email_changed, this));
1435                 _reset_kdm_email->Bind (wxEVT_BUTTON, boost::bind (&KDMEmailPage::reset_kdm_email, this));
1436         }
1437
1438         void config_changed ()
1439         {
1440                 Config* config = Config::instance ();
1441
1442                 checked_set (_mail_server, config->mail_server ());
1443                 checked_set (_mail_port, config->mail_port ());
1444                 checked_set (_mail_user, config->mail_user ());
1445                 checked_set (_mail_password, config->mail_password ());
1446                 checked_set (_kdm_subject, config->kdm_subject ());
1447                 checked_set (_kdm_from, config->kdm_from ());
1448                 checked_set (_kdm_bcc, config->kdm_bcc ());
1449                 checked_set (_kdm_email, Config::instance()->kdm_email ());
1450         }
1451
1452         void mail_server_changed ()
1453         {
1454                 Config::instance()->set_mail_server (wx_to_std (_mail_server->GetValue ()));
1455         }
1456
1457         void mail_port_changed ()
1458         {
1459                 Config::instance()->set_mail_port (_mail_port->GetValue ());
1460         }
1461
1462         void mail_user_changed ()
1463         {
1464                 Config::instance()->set_mail_user (wx_to_std (_mail_user->GetValue ()));
1465         }
1466
1467         void mail_password_changed ()
1468         {
1469                 Config::instance()->set_mail_password (wx_to_std (_mail_password->GetValue ()));
1470         }
1471
1472         void kdm_subject_changed ()
1473         {
1474                 Config::instance()->set_kdm_subject (wx_to_std (_kdm_subject->GetValue ()));
1475         }
1476
1477         void kdm_from_changed ()
1478         {
1479                 Config::instance()->set_kdm_from (wx_to_std (_kdm_from->GetValue ()));
1480         }
1481
1482         void kdm_bcc_changed ()
1483         {
1484                 Config::instance()->set_kdm_bcc (wx_to_std (_kdm_bcc->GetValue ()));
1485         }
1486
1487         void kdm_email_changed ()
1488         {
1489                 if (_kdm_email->GetValue().IsEmpty ()) {
1490                         /* Sometimes we get sent an erroneous notification that the email
1491                            is empty; I don't know why.
1492                         */
1493                         return;
1494                 }
1495                 Config::instance()->set_kdm_email (wx_to_std (_kdm_email->GetValue ()));
1496         }
1497
1498         void reset_kdm_email ()
1499         {
1500                 Config::instance()->reset_kdm_email ();
1501                 checked_set (_kdm_email, Config::instance()->kdm_email ());
1502         }
1503
1504         wxTextCtrl* _mail_server;
1505         wxSpinCtrl* _mail_port;
1506         wxTextCtrl* _mail_user;
1507         wxTextCtrl* _mail_password;
1508         wxTextCtrl* _kdm_subject;
1509         wxTextCtrl* _kdm_from;
1510         EditableList<string, EmailDialog>* _kdm_cc;
1511         wxTextCtrl* _kdm_bcc;
1512         wxTextCtrl* _kdm_email;
1513         wxButton* _reset_kdm_email;
1514 };
1515
1516 class CoverSheetPage : public StandardPage
1517 {
1518 public:
1519
1520         CoverSheetPage (wxSize panel_size, int border)
1521 #ifdef DCPOMATIC_OSX
1522                 /* We have to force both width and height of this one */
1523                 : StandardPage (wxSize (480, 128), border)
1524 #else
1525                 : StandardPage (panel_size, border)
1526 #endif
1527         {}
1528
1529         wxString GetName () const
1530         {
1531                 return _("Cover Sheet");
1532         }
1533
1534 #ifdef DCPOMATIC_OSX
1535         wxBitmap GetLargeIcon () const
1536         {
1537                 return wxBitmap ("cover_sheet", wxBITMAP_TYPE_PNG_RESOURCE);
1538         }
1539 #endif
1540
1541 private:
1542         void setup ()
1543         {
1544                 _cover_sheet = new wxTextCtrl (_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (-1, 200), wxTE_MULTILINE);
1545                 _panel->GetSizer()->Add (_cover_sheet, 0, wxEXPAND | wxALL, _border);
1546
1547                 _reset_cover_sheet = new wxButton (_panel, wxID_ANY, _("Reset to default text"));
1548                 _panel->GetSizer()->Add (_reset_cover_sheet, 0, wxEXPAND | wxALL, _border);
1549
1550                 _cover_sheet->Bind (wxEVT_TEXT, boost::bind (&CoverSheetPage::cover_sheet_changed, this));
1551                 _reset_cover_sheet->Bind (wxEVT_BUTTON, boost::bind (&CoverSheetPage::reset_cover_sheet, this));
1552         }
1553
1554         void config_changed ()
1555         {
1556                 checked_set (_cover_sheet, Config::instance()->cover_sheet ());
1557         }
1558
1559         void cover_sheet_changed ()
1560         {
1561                 if (_cover_sheet->GetValue().IsEmpty ()) {
1562                         /* Sometimes we get sent an erroneous notification that the cover sheet
1563                            is empty; I don't know why.
1564                         */
1565                         return;
1566                 }
1567                 Config::instance()->set_cover_sheet (wx_to_std (_cover_sheet->GetValue ()));
1568         }
1569
1570         void reset_cover_sheet ()
1571         {
1572                 Config::instance()->reset_cover_sheet ();
1573                 checked_set (_cover_sheet, Config::instance()->cover_sheet ());
1574         }
1575
1576         wxTextCtrl* _cover_sheet;
1577         wxButton* _reset_cover_sheet;
1578 };
1579
1580
1581 /** @class AdvancedPage
1582  *  @brief Advanced page of the preferences dialog.
1583  */
1584 class AdvancedPage : public StockPage
1585 {
1586 public:
1587         AdvancedPage (wxSize panel_size, int border)
1588                 : StockPage (Kind_Advanced, panel_size, border)
1589                 , _maximum_j2k_bandwidth (0)
1590                 , _allow_any_dcp_frame_rate (0)
1591                 , _only_servers_encode (0)
1592                 , _log_general (0)
1593                 , _log_warning (0)
1594                 , _log_error (0)
1595                 , _log_timing (0)
1596                 , _log_debug_decode (0)
1597                 , _log_debug_encode (0)
1598                 , _log_debug_email (0)
1599         {}
1600
1601 private:
1602         void add_top_aligned_label_to_sizer (wxSizer* table, wxWindow* parent, wxString text)
1603         {
1604                 int flags = wxALIGN_TOP | wxTOP | wxLEFT;
1605 #ifdef __WXOSX__
1606                 flags |= wxALIGN_RIGHT;
1607                 text += wxT (":");
1608 #endif
1609                 wxStaticText* m = new wxStaticText (parent, wxID_ANY, text);
1610                 table->Add (m, 0, flags, DCPOMATIC_SIZER_Y_GAP);
1611         }
1612
1613         void setup ()
1614         {
1615                 wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
1616                 table->AddGrowableCol (1, 1);
1617                 _panel->GetSizer()->Add (table, 1, wxALL | wxEXPAND, _border);
1618
1619                 {
1620                         add_label_to_sizer (table, _panel, _("Maximum JPEG2000 bandwidth"), true);
1621                         wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
1622                         _maximum_j2k_bandwidth = new wxSpinCtrl (_panel);
1623                         s->Add (_maximum_j2k_bandwidth, 1);
1624                         add_label_to_sizer (s, _panel, _("Mbit/s"), false);
1625                         table->Add (s, 1);
1626                 }
1627
1628                 _allow_any_dcp_frame_rate = new wxCheckBox (_panel, wxID_ANY, _("Allow any DCP frame rate"));
1629                 table->Add (_allow_any_dcp_frame_rate, 1, wxEXPAND | wxALL);
1630                 table->AddSpacer (0);
1631
1632                 _only_servers_encode = new wxCheckBox (_panel, wxID_ANY, _("Only servers encode"));
1633                 table->Add (_only_servers_encode, 1, wxEXPAND | wxALL);
1634                 table->AddSpacer (0);
1635
1636                 {
1637                         add_top_aligned_label_to_sizer (table, _panel, _("DCP metadata filename format"));
1638                         dcp::NameFormat::Map titles;
1639                         titles['t'] = "type (cpl/pkl)";
1640                         dcp::NameFormat::Map examples;
1641                         examples['t'] = "cpl";
1642                         _dcp_metadata_filename_format = new NameFormatEditor (
1643                                 _panel, Config::instance()->dcp_metadata_filename_format(), titles, examples, "_eb1c112c-ca3c-4ae6-9263-c6714ff05d64.xml"
1644                                 );
1645                         table->Add (_dcp_metadata_filename_format->panel(), 1, wxEXPAND | wxALL);
1646                 }
1647
1648                 {
1649                         add_top_aligned_label_to_sizer (table, _panel, _("DCP asset filename format"));
1650                         dcp::NameFormat::Map titles;
1651                         titles['t'] = "type (j2c/pcm/sub)";
1652                         titles['r'] = "reel number";
1653                         titles['n'] = "number of reels";
1654                         titles['c'] = "content filename";
1655                         dcp::NameFormat::Map examples;
1656                         examples['t'] = "j2c";
1657                         examples['r'] = "1";
1658                         examples['n'] = "4";
1659                         examples['c'] = "myfile.mp4";
1660                         _dcp_asset_filename_format = new NameFormatEditor (
1661                                 _panel, Config::instance()->dcp_asset_filename_format(), titles, examples, "_eb1c112c-ca3c-4ae6-9263-c6714ff05d64.mxf"
1662                                 );
1663                         table->Add (_dcp_asset_filename_format->panel(), 1, wxEXPAND | wxALL);
1664                 }
1665
1666                 {
1667                         add_top_aligned_label_to_sizer (table, _panel, _("Log"));
1668                         wxBoxSizer* t = new wxBoxSizer (wxVERTICAL);
1669                         _log_general = new wxCheckBox (_panel, wxID_ANY, _("General"));
1670                         t->Add (_log_general, 1, wxEXPAND | wxALL);
1671                         _log_warning = new wxCheckBox (_panel, wxID_ANY, _("Warnings"));
1672                         t->Add (_log_warning, 1, wxEXPAND | wxALL);
1673                         _log_error = new wxCheckBox (_panel, wxID_ANY, _("Errors"));
1674                         t->Add (_log_error, 1, wxEXPAND | wxALL);
1675                         /// TRANSLATORS: translate the word "Timing" here; do not include the "Config|" prefix
1676                         _log_timing = new wxCheckBox (_panel, wxID_ANY, S_("Config|Timing"));
1677                         t->Add (_log_timing, 1, wxEXPAND | wxALL);
1678                         _log_debug_decode = new wxCheckBox (_panel, wxID_ANY, _("Debug: decode"));
1679                         t->Add (_log_debug_decode, 1, wxEXPAND | wxALL);
1680                         _log_debug_encode = new wxCheckBox (_panel, wxID_ANY, _("Debug: encode"));
1681                         t->Add (_log_debug_encode, 1, wxEXPAND | wxALL);
1682                         _log_debug_email = new wxCheckBox (_panel, wxID_ANY, _("Debug: email sending"));
1683                         t->Add (_log_debug_email, 1, wxEXPAND | wxALL);
1684                         table->Add (t, 0, wxALL, 6);
1685                 }
1686
1687 #ifdef DCPOMATIC_WINDOWS
1688                 _win32_console = new wxCheckBox (_panel, wxID_ANY, _("Open console window"));
1689                 table->Add (_win32_console, 1, wxEXPAND | wxALL);
1690                 table->AddSpacer (0);
1691 #endif
1692
1693                 _maximum_j2k_bandwidth->SetRange (1, 1000);
1694                 _maximum_j2k_bandwidth->Bind (wxEVT_SPINCTRL, boost::bind (&AdvancedPage::maximum_j2k_bandwidth_changed, this));
1695                 _allow_any_dcp_frame_rate->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::allow_any_dcp_frame_rate_changed, this));
1696                 _only_servers_encode->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::only_servers_encode_changed, this));
1697                 _dcp_metadata_filename_format->Changed.connect (boost::bind (&AdvancedPage::dcp_metadata_filename_format_changed, this));
1698                 _dcp_asset_filename_format->Changed.connect (boost::bind (&AdvancedPage::dcp_asset_filename_format_changed, this));
1699                 _log_general->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::log_changed, this));
1700                 _log_warning->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::log_changed, this));
1701                 _log_error->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::log_changed, this));
1702                 _log_timing->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::log_changed, this));
1703                 _log_debug_decode->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::log_changed, this));
1704                 _log_debug_encode->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::log_changed, this));
1705                 _log_debug_email->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::log_changed, this));
1706 #ifdef DCPOMATIC_WINDOWS
1707                 _win32_console->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::win32_console_changed, this));
1708 #endif
1709         }
1710
1711         void config_changed ()
1712         {
1713                 Config* config = Config::instance ();
1714
1715                 checked_set (_maximum_j2k_bandwidth, config->maximum_j2k_bandwidth() / 1000000);
1716                 checked_set (_allow_any_dcp_frame_rate, config->allow_any_dcp_frame_rate ());
1717                 checked_set (_only_servers_encode, config->only_servers_encode ());
1718                 checked_set (_log_general, config->log_types() & LogEntry::TYPE_GENERAL);
1719                 checked_set (_log_warning, config->log_types() & LogEntry::TYPE_WARNING);
1720                 checked_set (_log_error, config->log_types() & LogEntry::TYPE_ERROR);
1721                 checked_set (_log_timing, config->log_types() & LogEntry::TYPE_TIMING);
1722                 checked_set (_log_debug_decode, config->log_types() & LogEntry::TYPE_DEBUG_DECODE);
1723                 checked_set (_log_debug_encode, config->log_types() & LogEntry::TYPE_DEBUG_ENCODE);
1724                 checked_set (_log_debug_email, config->log_types() & LogEntry::TYPE_DEBUG_EMAIL);
1725 #ifdef DCPOMATIC_WINDOWS
1726                 checked_set (_win32_console, config->win32_console());
1727 #endif
1728         }
1729
1730         void maximum_j2k_bandwidth_changed ()
1731         {
1732                 Config::instance()->set_maximum_j2k_bandwidth (_maximum_j2k_bandwidth->GetValue() * 1000000);
1733         }
1734
1735         void allow_any_dcp_frame_rate_changed ()
1736         {
1737                 Config::instance()->set_allow_any_dcp_frame_rate (_allow_any_dcp_frame_rate->GetValue ());
1738         }
1739
1740         void only_servers_encode_changed ()
1741         {
1742                 Config::instance()->set_only_servers_encode (_only_servers_encode->GetValue ());
1743         }
1744
1745         void dcp_metadata_filename_format_changed ()
1746         {
1747                 Config::instance()->set_dcp_metadata_filename_format (_dcp_metadata_filename_format->get ());
1748         }
1749
1750         void dcp_asset_filename_format_changed ()
1751         {
1752                 Config::instance()->set_dcp_asset_filename_format (_dcp_asset_filename_format->get ());
1753         }
1754
1755         void log_changed ()
1756         {
1757                 int types = 0;
1758                 if (_log_general->GetValue ()) {
1759                         types |= LogEntry::TYPE_GENERAL;
1760                 }
1761                 if (_log_warning->GetValue ()) {
1762                         types |= LogEntry::TYPE_WARNING;
1763                 }
1764                 if (_log_error->GetValue ())  {
1765                         types |= LogEntry::TYPE_ERROR;
1766                 }
1767                 if (_log_timing->GetValue ()) {
1768                         types |= LogEntry::TYPE_TIMING;
1769                 }
1770                 if (_log_debug_decode->GetValue ()) {
1771                         types |= LogEntry::TYPE_DEBUG_DECODE;
1772                 }
1773                 if (_log_debug_encode->GetValue ()) {
1774                         types |= LogEntry::TYPE_DEBUG_ENCODE;
1775                 }
1776                 if (_log_debug_email->GetValue ()) {
1777                         types |= LogEntry::TYPE_DEBUG_EMAIL;
1778                 }
1779                 Config::instance()->set_log_types (types);
1780         }
1781
1782 #ifdef DCPOMATIC_WINDOWS
1783         void win32_console_changed ()
1784         {
1785                 Config::instance()->set_win32_console (_win32_console->GetValue ());
1786         }
1787 #endif
1788
1789         wxSpinCtrl* _maximum_j2k_bandwidth;
1790         wxCheckBox* _allow_any_dcp_frame_rate;
1791         wxCheckBox* _only_servers_encode;
1792         NameFormatEditor* _dcp_metadata_filename_format;
1793         NameFormatEditor* _dcp_asset_filename_format;
1794         wxCheckBox* _log_general;
1795         wxCheckBox* _log_warning;
1796         wxCheckBox* _log_error;
1797         wxCheckBox* _log_timing;
1798         wxCheckBox* _log_debug_decode;
1799         wxCheckBox* _log_debug_encode;
1800         wxCheckBox* _log_debug_email;
1801 #ifdef DCPOMATIC_WINDOWS
1802         wxCheckBox* _win32_console;
1803 #endif
1804 };
1805
1806 wxPreferencesEditor*
1807 create_config_dialog ()
1808 {
1809         wxPreferencesEditor* e = new wxPreferencesEditor ();
1810
1811 #ifdef DCPOMATIC_OSX
1812         /* Width that we force some of the config panels to be on OSX so that
1813            the containing window doesn't shrink too much when we select those panels.
1814            This is obviously an unpleasant hack.
1815         */
1816         wxSize ps = wxSize (520, -1);
1817         int const border = 16;
1818 #else
1819         wxSize ps = wxSize (-1, -1);
1820         int const border = 8;
1821 #endif
1822
1823         e->AddPage (new GeneralPage (ps, border));
1824         e->AddPage (new DefaultsPage (ps, border));
1825         e->AddPage (new EncodingServersPage (ps, border));
1826         e->AddPage (new KeysPage (ps, border));
1827         e->AddPage (new TMSPage (ps, border));
1828         e->AddPage (new KDMEmailPage (ps, border));
1829         e->AddPage (new CoverSheetPage (ps, border));
1830         e->AddPage (new AdvancedPage (ps, border));
1831         return e;
1832 }