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