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