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