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