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