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