Take the leaf of a certificate chain if one is provided
[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 <dcp/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 using dcp::locale_convert;
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, locale_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 (
540                                 locale_convert<int> (string_client_data (_dcp_audio_channels->GetClientObject (s)))
541                                 );
542                 }
543         }
544
545         void directory_changed ()
546         {
547                 Config::instance()->set_default_directory (wx_to_std (_directory->GetPath ()));
548         }
549
550         void edit_isdcf_metadata_clicked ()
551         {
552                 ISDCFMetadataDialog* d = new ISDCFMetadataDialog (_panel, Config::instance()->default_isdcf_metadata (), false);
553                 d->ShowModal ();
554                 Config::instance()->set_default_isdcf_metadata (d->isdcf_metadata ());
555                 d->Destroy ();
556         }
557
558         void still_length_changed ()
559         {
560                 Config::instance()->set_default_still_length (_still_length->GetValue ());
561         }
562
563         void container_changed ()
564         {
565                 vector<Ratio const *> ratio = Ratio::all ();
566                 Config::instance()->set_default_container (ratio[_container->GetSelection()]);
567         }
568
569         void dcp_content_type_changed ()
570         {
571                 vector<DCPContentType const *> ct = DCPContentType::all ();
572                 Config::instance()->set_default_dcp_content_type (ct[_dcp_content_type->GetSelection()]);
573         }
574
575         void standard_changed ()
576         {
577                 Config::instance()->set_default_interop (_standard->GetSelection() == 1);
578         }
579
580         wxSpinCtrl* _j2k_bandwidth;
581         wxSpinCtrl* _audio_delay;
582         wxButton* _isdcf_metadata_button;
583         wxSpinCtrl* _still_length;
584 #ifdef DCPOMATIC_USE_OWN_PICKER
585         DirPickerCtrl* _directory;
586 #else
587         wxDirPickerCtrl* _directory;
588 #endif
589         wxChoice* _container;
590         wxChoice* _dcp_content_type;
591         wxChoice* _dcp_audio_channels;
592         wxChoice* _standard;
593 };
594
595 class EncodingServersPage : public StandardPage
596 {
597 public:
598         EncodingServersPage (wxSize panel_size, int border)
599                 : StandardPage (panel_size, border)
600         {}
601
602         wxString GetName () const
603         {
604                 return _("Servers");
605         }
606
607 #ifdef DCPOMATIC_OSX
608         wxBitmap GetLargeIcon () const
609         {
610                 return wxBitmap ("servers", wxBITMAP_TYPE_PNG_RESOURCE);
611         }
612 #endif
613
614 private:
615         void setup ()
616         {
617                 _use_any_servers = new wxCheckBox (_panel, wxID_ANY, _("Search network for servers"));
618                 _panel->GetSizer()->Add (_use_any_servers, 0, wxALL, _border);
619
620                 vector<string> columns;
621                 columns.push_back (wx_to_std (_("IP address / host name")));
622                 _servers_list = new EditableList<string, ServerDialog> (
623                         _panel,
624                         columns,
625                         boost::bind (&Config::servers, Config::instance()),
626                         boost::bind (&Config::set_servers, Config::instance(), _1),
627                         boost::bind (&always_valid),
628                         boost::bind (&EncodingServersPage::server_column, this, _1)
629                         );
630
631                 _panel->GetSizer()->Add (_servers_list, 1, wxEXPAND | wxALL, _border);
632
633                 _use_any_servers->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&EncodingServersPage::use_any_servers_changed, this));
634         }
635
636         void config_changed ()
637         {
638                 checked_set (_use_any_servers, Config::instance()->use_any_servers ());
639                 _servers_list->refresh ();
640         }
641
642         void use_any_servers_changed ()
643         {
644                 Config::instance()->set_use_any_servers (_use_any_servers->GetValue ());
645         }
646
647         string server_column (string s)
648         {
649                 return s;
650         }
651
652         wxCheckBox* _use_any_servers;
653         EditableList<string, ServerDialog>* _servers_list;
654 };
655
656 class CertificateChainEditor : public wxPanel
657 {
658 public:
659         CertificateChainEditor (
660                 wxWindow* parent,
661                 wxString title,
662                 int border,
663                 function<void (shared_ptr<dcp::CertificateChain>)> set,
664                 function<shared_ptr<const dcp::CertificateChain> (void)> get
665                 )
666                 : wxPanel (parent)
667                 , _set (set)
668                 , _get (get)
669         {
670                 wxFont subheading_font (*wxNORMAL_FONT);
671                 subheading_font.SetWeight (wxFONTWEIGHT_BOLD);
672
673                 _sizer = new wxBoxSizer (wxVERTICAL);
674
675                 {
676                         wxStaticText* m = new wxStaticText (this, wxID_ANY, title);
677                         m->SetFont (subheading_font);
678                         _sizer->Add (m, 0, wxALL, border);
679                 }
680
681                 wxBoxSizer* certificates_sizer = new wxBoxSizer (wxHORIZONTAL);
682                 _sizer->Add (certificates_sizer, 0, wxLEFT | wxRIGHT, border);
683
684                 _certificates = new wxListCtrl (this, wxID_ANY, wxDefaultPosition, wxSize (440, 150), wxLC_REPORT | wxLC_SINGLE_SEL);
685
686                 {
687                         wxListItem ip;
688                         ip.SetId (0);
689                         ip.SetText (_("Type"));
690                         ip.SetWidth (100);
691                         _certificates->InsertColumn (0, ip);
692                 }
693
694                 {
695                         wxListItem ip;
696                         ip.SetId (1);
697                         ip.SetText (_("Thumbprint"));
698                         ip.SetWidth (340);
699
700                         wxFont font = ip.GetFont ();
701                         font.SetFamily (wxFONTFAMILY_TELETYPE);
702                         ip.SetFont (font);
703
704                         _certificates->InsertColumn (1, ip);
705                 }
706
707                 certificates_sizer->Add (_certificates, 1, wxEXPAND);
708
709                 {
710                         wxSizer* s = new wxBoxSizer (wxVERTICAL);
711                         _add_certificate = new wxButton (this, wxID_ANY, _("Add..."));
712                         s->Add (_add_certificate, 0, wxTOP | wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
713                         _remove_certificate = new wxButton (this, wxID_ANY, _("Remove"));
714                         s->Add (_remove_certificate, 0, wxTOP | wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
715                         _export_certificate = new wxButton (this, wxID_ANY, _("Export"));
716                         s->Add (_export_certificate, 0, wxTOP | wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
717                         certificates_sizer->Add (s, 0, wxLEFT, DCPOMATIC_SIZER_X_GAP);
718                 }
719
720                 wxGridBagSizer* table = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
721                 _sizer->Add (table, 1, wxALL | wxEXPAND, border);
722                 int r = 0;
723
724                 add_label_to_sizer (table, this, _("Leaf private key"), true, wxGBPosition (r, 0));
725                 _private_key = new wxStaticText (this, wxID_ANY, wxT (""));
726                 wxFont font = _private_key->GetFont ();
727                 font.SetFamily (wxFONTFAMILY_TELETYPE);
728                 _private_key->SetFont (font);
729                 table->Add (_private_key, wxGBPosition (r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
730                 _load_private_key = new wxButton (this, wxID_ANY, _("Load..."));
731                 table->Add (_load_private_key, wxGBPosition (r, 2));
732                 _export_private_key = new wxButton (this, wxID_ANY, _("Export..."));
733                 table->Add (_export_private_key, wxGBPosition (r, 3));
734                 ++r;
735
736                 _button_sizer = new wxBoxSizer (wxHORIZONTAL);
737                 _remake_certificates = new wxButton (this, wxID_ANY, _("Re-make certificates\nand key..."));
738                 _button_sizer->Add (_remake_certificates, 1, wxRIGHT, border);
739                 table->Add (_button_sizer, wxGBPosition (r, 0), wxGBSpan (1, 4));
740                 ++r;
741
742                 _add_certificate->Bind     (wxEVT_COMMAND_BUTTON_CLICKED,       boost::bind (&CertificateChainEditor::add_certificate, this));
743                 _remove_certificate->Bind  (wxEVT_COMMAND_BUTTON_CLICKED,       boost::bind (&CertificateChainEditor::remove_certificate, this));
744                 _export_certificate->Bind  (wxEVT_COMMAND_BUTTON_CLICKED,       boost::bind (&CertificateChainEditor::export_certificate, this));
745                 _certificates->Bind        (wxEVT_COMMAND_LIST_ITEM_SELECTED,   boost::bind (&CertificateChainEditor::update_sensitivity, this));
746                 _certificates->Bind        (wxEVT_COMMAND_LIST_ITEM_DESELECTED, boost::bind (&CertificateChainEditor::update_sensitivity, this));
747                 _remake_certificates->Bind (wxEVT_COMMAND_BUTTON_CLICKED,       boost::bind (&CertificateChainEditor::remake_certificates, this));
748                 _load_private_key->Bind    (wxEVT_COMMAND_BUTTON_CLICKED,       boost::bind (&CertificateChainEditor::load_private_key, this));
749                 _export_private_key->Bind  (wxEVT_COMMAND_BUTTON_CLICKED,       boost::bind (&CertificateChainEditor::export_private_key, this));
750
751                 SetSizerAndFit (_sizer);
752         }
753
754         void config_changed ()
755         {
756                 _chain.reset (new dcp::CertificateChain (*_get().get ()));
757
758                 update_certificate_list ();
759                 update_private_key ();
760                 update_sensitivity ();
761         }
762
763         void add_button (wxWindow* button)
764         {
765                 _button_sizer->Add (button, 0, wxLEFT | wxRIGHT, DCPOMATIC_SIZER_X_GAP);
766                 _sizer->Layout ();
767         }
768
769 private:
770         void add_certificate ()
771         {
772                 wxFileDialog* d = new wxFileDialog (this, _("Select Certificate File"));
773
774                 if (d->ShowModal() == wxID_OK) {
775                         try {
776                                 dcp::Certificate c;
777                                 string const extra = c.read_string (dcp::file_to_string (wx_to_std (d->GetPath ())));
778                                 if (!extra.empty ()) {
779                                         message_dialog (
780                                                 this,
781                                                 _("This file contains other certificates (or other data) after its first certificate. "
782                                                   "Only the first certificate will be used.")
783                                                 );
784                                 }
785                                 _chain->add (c);
786                                 _set (_chain);
787                                 update_certificate_list ();
788                         } catch (dcp::MiscError& e) {
789                                 error_dialog (this, wxString::Format (_("Could not read certificate file (%s)"), e.what ()));
790                         }
791                 }
792
793                 d->Destroy ();
794
795                 update_sensitivity ();
796         }
797
798         void remove_certificate ()
799         {
800                 int i = _certificates->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
801                 if (i == -1) {
802                         return;
803                 }
804
805                 _certificates->DeleteItem (i);
806                 _chain->remove (i);
807                 _set (_chain);
808
809                 update_sensitivity ();
810         }
811
812         void export_certificate ()
813         {
814                 int i = _certificates->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
815                 if (i == -1) {
816                         return;
817                 }
818
819                 wxFileDialog* d = new wxFileDialog (
820                         this, _("Select Certificate File"), wxEmptyString, wxEmptyString, wxT ("PEM files (*.pem)|*.pem"),
821                         wxFD_SAVE | wxFD_OVERWRITE_PROMPT
822                         );
823
824                 dcp::CertificateChain::List all = _chain->root_to_leaf ();
825                 dcp::CertificateChain::List::iterator j = all.begin ();
826                 for (int k = 0; k < i; ++k) {
827                         ++j;
828                 }
829
830                 if (d->ShowModal () == wxID_OK) {
831                         FILE* f = fopen_boost (wx_to_std (d->GetPath ()), "w");
832                         if (!f) {
833                                 throw OpenFileError (wx_to_std (d->GetPath ()), errno, false);
834                         }
835
836                         string const s = j->certificate (true);
837                         fwrite (s.c_str(), 1, s.length(), f);
838                         fclose (f);
839                 }
840                 d->Destroy ();
841         }
842
843         void update_certificate_list ()
844         {
845                 _certificates->DeleteAllItems ();
846                 size_t n = 0;
847                 dcp::CertificateChain::List certs = _chain->root_to_leaf ();
848                 BOOST_FOREACH (dcp::Certificate const & i, certs) {
849                         wxListItem item;
850                         item.SetId (n);
851                         _certificates->InsertItem (item);
852                         _certificates->SetItem (n, 1, std_to_wx (i.thumbprint ()));
853
854                         if (n == 0) {
855                                 _certificates->SetItem (n, 0, _("Root"));
856                         } else if (n == (certs.size() - 1)) {
857                                 _certificates->SetItem (n, 0, _("Leaf"));
858                         } else {
859                                 _certificates->SetItem (n, 0, _("Intermediate"));
860                         }
861
862                         ++n;
863                 }
864         }
865
866         void remake_certificates ()
867         {
868                 shared_ptr<const dcp::CertificateChain> chain = _get ();
869
870                 string subject_organization_name;
871                 string subject_organizational_unit_name;
872                 string root_common_name;
873                 string intermediate_common_name;
874                 string leaf_common_name;
875
876                 dcp::CertificateChain::List all = chain->root_to_leaf ();
877
878                 if (all.size() >= 1) {
879                         /* Have a root */
880                         subject_organization_name = chain->root().subject_organization_name ();
881                         subject_organizational_unit_name = chain->root().subject_organizational_unit_name ();
882                         root_common_name = chain->root().subject_common_name ();
883                 }
884
885                 if (all.size() >= 2) {
886                         /* Have a leaf */
887                         leaf_common_name = chain->leaf().subject_common_name ();
888                 }
889
890                 if (all.size() >= 3) {
891                         /* Have an intermediate */
892                         dcp::CertificateChain::List::iterator i = all.begin ();
893                         ++i;
894                         intermediate_common_name = i->subject_common_name ();
895                 }
896
897                 MakeChainDialog* d = new MakeChainDialog (
898                         this,
899                         subject_organization_name,
900                         subject_organizational_unit_name,
901                         root_common_name,
902                         intermediate_common_name,
903                         leaf_common_name
904                         );
905
906                 if (d->ShowModal () == wxID_OK) {
907                         _chain.reset (
908                                 new dcp::CertificateChain (
909                                         openssl_path (),
910                                         d->organisation (),
911                                         d->organisational_unit (),
912                                         d->root_common_name (),
913                                         d->intermediate_common_name (),
914                                         d->leaf_common_name ()
915                                         )
916                                 );
917
918                         _set (_chain);
919                         update_certificate_list ();
920                         update_private_key ();
921                 }
922
923                 d->Destroy ();
924         }
925
926         void update_sensitivity ()
927         {
928                 _remove_certificate->Enable (_certificates->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) != -1);
929                 _export_certificate->Enable (_certificates->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) != -1);
930         }
931
932         void update_private_key ()
933         {
934                 checked_set (_private_key, dcp::private_key_fingerprint (_chain->key().get ()));
935                 _sizer->Layout ();
936         }
937
938         void load_private_key ()
939         {
940                 wxFileDialog* d = new wxFileDialog (this, _("Select Key File"));
941
942                 if (d->ShowModal() == wxID_OK) {
943                         try {
944                                 boost::filesystem::path p (wx_to_std (d->GetPath ()));
945                                 if (boost::filesystem::file_size (p) > 8192) {
946                                         error_dialog (
947                                                 this,
948                                                 wxString::Format (_("Could not read key file; file is too long (%s)"), std_to_wx (p.string ()))
949                                                 );
950                                         return;
951                                 }
952
953                                 _chain->set_key (dcp::file_to_string (p));
954                                 _set (_chain);
955                                 update_private_key ();
956                         } catch (dcp::MiscError& e) {
957                                 error_dialog (this, wxString::Format (_("Could not read certificate file (%s)"), e.what ()));
958                         }
959                 }
960
961                 d->Destroy ();
962
963                 update_sensitivity ();
964         }
965
966         void export_private_key ()
967         {
968                 optional<string> key = _chain->key ();
969                 if (!key) {
970                         return;
971                 }
972
973                 wxFileDialog* d = new wxFileDialog (
974                         this, _("Select Key File"), wxEmptyString, wxEmptyString, wxT ("PEM files (*.pem)|*.pem"),
975                         wxFD_SAVE | wxFD_OVERWRITE_PROMPT
976                         );
977
978                 if (d->ShowModal () == wxID_OK) {
979                         FILE* f = fopen_boost (wx_to_std (d->GetPath ()), "w");
980                         if (!f) {
981                                 throw OpenFileError (wx_to_std (d->GetPath ()), errno, false);
982                         }
983
984                         string const s = _chain->key().get ();
985                         fwrite (s.c_str(), 1, s.length(), f);
986                         fclose (f);
987                 }
988                 d->Destroy ();
989         }
990
991         wxListCtrl* _certificates;
992         wxButton* _add_certificate;
993         wxButton* _export_certificate;
994         wxButton* _remove_certificate;
995         wxButton* _remake_certificates;
996         wxStaticText* _private_key;
997         wxButton* _load_private_key;
998         wxButton* _export_private_key;
999         wxSizer* _sizer;
1000         wxBoxSizer* _button_sizer;
1001         shared_ptr<dcp::CertificateChain> _chain;
1002         boost::function<void (shared_ptr<dcp::CertificateChain>)> _set;
1003         boost::function<shared_ptr<const dcp::CertificateChain> (void)> _get;
1004 };
1005
1006 class KeysPage : public StandardPage
1007 {
1008 public:
1009         KeysPage (wxSize panel_size, int border)
1010                 : StandardPage (panel_size, border)
1011         {}
1012
1013         wxString GetName () const
1014         {
1015                 return _("Keys");
1016         }
1017
1018 #ifdef DCPOMATIC_OSX
1019         wxBitmap GetLargeIcon () const
1020         {
1021                 return wxBitmap ("keys", wxBITMAP_TYPE_PNG_RESOURCE);
1022         }
1023 #endif
1024
1025 private:
1026
1027         void setup ()
1028         {
1029                 _signer = new CertificateChainEditor (
1030                         _panel, _("Signing DCPs and KDMs"), _border,
1031                         boost::bind (&Config::set_signer_chain, Config::instance (), _1),
1032                         boost::bind (&Config::signer_chain, Config::instance ())
1033                         );
1034
1035                 _panel->GetSizer()->Add (_signer);
1036
1037                 _decryption = new CertificateChainEditor (
1038                         _panel, _("Decrypting DCPs"), _border,
1039                         boost::bind (&Config::set_decryption_chain, Config::instance (), _1),
1040                         boost::bind (&Config::decryption_chain, Config::instance ())
1041                         );
1042
1043                 _panel->GetSizer()->Add (_decryption);
1044
1045                 _export_decryption_certificate = new wxButton (_decryption, wxID_ANY, _("Export DCP decryption\ncertificate..."));
1046                 _decryption->add_button (_export_decryption_certificate);
1047                 _export_decryption_chain = new wxButton (_decryption, wxID_ANY, _("Export DCP decryption\nchain..."));
1048                 _decryption->add_button (_export_decryption_chain);
1049
1050                 _export_decryption_certificate->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&KeysPage::export_decryption_certificate, this));
1051                 _export_decryption_chain->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&KeysPage::export_decryption_chain, this));
1052         }
1053
1054         void export_decryption_certificate ()
1055         {
1056                 wxFileDialog* d = new wxFileDialog (
1057                         _panel, _("Select Certificate File"), wxEmptyString, wxEmptyString, wxT ("PEM files (*.pem)|*.pem"),
1058                         wxFD_SAVE | wxFD_OVERWRITE_PROMPT
1059                         );
1060
1061                 if (d->ShowModal () == wxID_OK) {
1062                         FILE* f = fopen_boost (wx_to_std (d->GetPath ()), "w");
1063                         if (!f) {
1064                                 throw OpenFileError (wx_to_std (d->GetPath ()), errno, false);
1065                         }
1066
1067                         string const s = Config::instance()->decryption_chain()->leaf().certificate (true);
1068                         fwrite (s.c_str(), 1, s.length(), f);
1069                         fclose (f);
1070                 }
1071                 d->Destroy ();
1072         }
1073
1074         void export_decryption_chain ()
1075         {
1076                 wxFileDialog* d = new wxFileDialog (
1077                         _panel, _("Select Chain File"), wxEmptyString, wxEmptyString, wxT ("PEM files (*.pem)|*.pem"),
1078                         wxFD_SAVE | wxFD_OVERWRITE_PROMPT
1079                         );
1080
1081                 if (d->ShowModal () == wxID_OK) {
1082                         FILE* f = fopen_boost (wx_to_std (d->GetPath ()), "w");
1083                         if (!f) {
1084                                 throw OpenFileError (wx_to_std (d->GetPath ()), errno, false);
1085                         }
1086
1087                         string const s = Config::instance()->decryption_chain()->chain();
1088                         fwrite (s.c_str(), 1, s.length(), f);
1089                         fclose (f);
1090                 }
1091                 d->Destroy ();
1092         }
1093
1094         void config_changed ()
1095         {
1096                 _signer->config_changed ();
1097                 _decryption->config_changed ();
1098         }
1099
1100         CertificateChainEditor* _signer;
1101         CertificateChainEditor* _decryption;
1102         wxButton* _export_decryption_certificate;
1103         wxButton* _export_decryption_chain;
1104 };
1105
1106 class TMSPage : public StandardPage
1107 {
1108 public:
1109         TMSPage (wxSize panel_size, int border)
1110                 : StandardPage (panel_size, border)
1111         {}
1112
1113         wxString GetName () const
1114         {
1115                 return _("TMS");
1116         }
1117
1118 #ifdef DCPOMATIC_OSX
1119         wxBitmap GetLargeIcon () const
1120         {
1121                 return wxBitmap ("tms", wxBITMAP_TYPE_PNG_RESOURCE);
1122         }
1123 #endif
1124
1125 private:
1126         void setup ()
1127         {
1128                 wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
1129                 table->AddGrowableCol (1, 1);
1130                 _panel->GetSizer()->Add (table, 1, wxALL | wxEXPAND, _border);
1131
1132                 add_label_to_sizer (table, _panel, _("Protocol"), true);
1133                 _tms_protocol = new wxChoice (_panel, wxID_ANY);
1134                 table->Add (_tms_protocol, 1, wxEXPAND);
1135
1136                 add_label_to_sizer (table, _panel, _("IP address"), true);
1137                 _tms_ip = new wxTextCtrl (_panel, wxID_ANY);
1138                 table->Add (_tms_ip, 1, wxEXPAND);
1139
1140                 add_label_to_sizer (table, _panel, _("Target path"), true);
1141                 _tms_path = new wxTextCtrl (_panel, wxID_ANY);
1142                 table->Add (_tms_path, 1, wxEXPAND);
1143
1144                 add_label_to_sizer (table, _panel, _("User name"), true);
1145                 _tms_user = new wxTextCtrl (_panel, wxID_ANY);
1146                 table->Add (_tms_user, 1, wxEXPAND);
1147
1148                 add_label_to_sizer (table, _panel, _("Password"), true);
1149                 _tms_password = new wxTextCtrl (_panel, wxID_ANY);
1150                 table->Add (_tms_password, 1, wxEXPAND);
1151
1152                 _tms_protocol->Append (_("SCP (for AAM and Doremi)"));
1153                 _tms_protocol->Append (_("FTP (for Dolby)"));
1154
1155                 _tms_protocol->Bind (wxEVT_COMMAND_CHOICE_SELECTED, boost::bind (&TMSPage::tms_protocol_changed, this));
1156                 _tms_ip->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&TMSPage::tms_ip_changed, this));
1157                 _tms_path->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&TMSPage::tms_path_changed, this));
1158                 _tms_user->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&TMSPage::tms_user_changed, this));
1159                 _tms_password->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&TMSPage::tms_password_changed, this));
1160         }
1161
1162         void config_changed ()
1163         {
1164                 Config* config = Config::instance ();
1165
1166                 checked_set (_tms_protocol, config->tms_protocol ());
1167                 checked_set (_tms_ip, config->tms_ip ());
1168                 checked_set (_tms_path, config->tms_path ());
1169                 checked_set (_tms_user, config->tms_user ());
1170                 checked_set (_tms_password, config->tms_password ());
1171         }
1172
1173         void tms_protocol_changed ()
1174         {
1175                 Config::instance()->set_tms_protocol (static_cast<Protocol> (_tms_protocol->GetSelection ()));
1176         }
1177
1178         void tms_ip_changed ()
1179         {
1180                 Config::instance()->set_tms_ip (wx_to_std (_tms_ip->GetValue ()));
1181         }
1182
1183         void tms_path_changed ()
1184         {
1185                 Config::instance()->set_tms_path (wx_to_std (_tms_path->GetValue ()));
1186         }
1187
1188         void tms_user_changed ()
1189         {
1190                 Config::instance()->set_tms_user (wx_to_std (_tms_user->GetValue ()));
1191         }
1192
1193         void tms_password_changed ()
1194         {
1195                 Config::instance()->set_tms_password (wx_to_std (_tms_password->GetValue ()));
1196         }
1197
1198         wxChoice* _tms_protocol;
1199         wxTextCtrl* _tms_ip;
1200         wxTextCtrl* _tms_path;
1201         wxTextCtrl* _tms_user;
1202         wxTextCtrl* _tms_password;
1203 };
1204
1205 static string
1206 column (string s)
1207 {
1208         return s;
1209 }
1210
1211 class KDMEmailPage : public StandardPage
1212 {
1213 public:
1214
1215         KDMEmailPage (wxSize panel_size, int border)
1216 #ifdef DCPOMATIC_OSX
1217                 /* We have to force both width and height of this one */
1218                 : StandardPage (wxSize (480, 128), border)
1219 #else
1220                 : StandardPage (panel_size, border)
1221 #endif
1222         {}
1223
1224         wxString GetName () const
1225         {
1226                 return _("KDM Email");
1227         }
1228
1229 #ifdef DCPOMATIC_OSX
1230         wxBitmap GetLargeIcon () const
1231         {
1232                 return wxBitmap ("kdm_email", wxBITMAP_TYPE_PNG_RESOURCE);
1233         }
1234 #endif
1235
1236 private:
1237         void setup ()
1238         {
1239                 wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
1240                 table->AddGrowableCol (1, 1);
1241                 _panel->GetSizer()->Add (table, 1, wxEXPAND | wxALL, _border);
1242
1243                 add_label_to_sizer (table, _panel, _("Outgoing mail server"), true);
1244                 {
1245                         wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
1246                         _mail_server = new wxTextCtrl (_panel, wxID_ANY);
1247                         s->Add (_mail_server, 1, wxEXPAND | wxALL);
1248                         add_label_to_sizer (s, _panel, _("port"), false);
1249                         _mail_port = new wxSpinCtrl (_panel, wxID_ANY);
1250                         _mail_port->SetRange (0, 65535);
1251                         s->Add (_mail_port);
1252                         table->Add (s, 1, wxEXPAND | wxALL);
1253                 }
1254
1255                 add_label_to_sizer (table, _panel, _("Mail user name"), true);
1256                 _mail_user = new wxTextCtrl (_panel, wxID_ANY);
1257                 table->Add (_mail_user, 1, wxEXPAND | wxALL);
1258
1259                 add_label_to_sizer (table, _panel, _("Mail password"), true);
1260                 _mail_password = new wxTextCtrl (_panel, wxID_ANY);
1261                 table->Add (_mail_password, 1, wxEXPAND | wxALL);
1262
1263                 add_label_to_sizer (table, _panel, _("Subject"), true);
1264                 _kdm_subject = new wxTextCtrl (_panel, wxID_ANY);
1265                 table->Add (_kdm_subject, 1, wxEXPAND | wxALL);
1266
1267                 add_label_to_sizer (table, _panel, _("From address"), true);
1268                 _kdm_from = new wxTextCtrl (_panel, wxID_ANY);
1269                 table->Add (_kdm_from, 1, wxEXPAND | wxALL);
1270
1271                 vector<string> columns;
1272                 columns.push_back (wx_to_std (_("Address")));
1273                 add_label_to_sizer (table, _panel, _("CC addresses"), true);
1274                 _kdm_cc = new EditableList<string, EmailDialog> (
1275                         _panel,
1276                         columns,
1277                         bind (&Config::kdm_cc, Config::instance()),
1278                         bind (&Config::set_kdm_cc, Config::instance(), _1),
1279                         bind (&string_not_empty, _1),
1280                         bind (&column, _1)
1281                         );
1282                 table->Add (_kdm_cc, 1, wxEXPAND | wxALL);
1283
1284                 add_label_to_sizer (table, _panel, _("BCC address"), true);
1285                 _kdm_bcc = new wxTextCtrl (_panel, wxID_ANY);
1286                 table->Add (_kdm_bcc, 1, wxEXPAND | wxALL);
1287
1288                 _kdm_email = new wxTextCtrl (_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (-1, 200), wxTE_MULTILINE);
1289                 _panel->GetSizer()->Add (_kdm_email, 0, wxEXPAND | wxALL, _border);
1290
1291                 _reset_kdm_email = new wxButton (_panel, wxID_ANY, _("Reset to default subject and text"));
1292                 _panel->GetSizer()->Add (_reset_kdm_email, 0, wxEXPAND | wxALL, _border);
1293
1294                 _kdm_cc->layout ();
1295
1296                 _mail_server->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&KDMEmailPage::mail_server_changed, this));
1297                 _mail_port->Bind (wxEVT_COMMAND_SPINCTRL_UPDATED, boost::bind (&KDMEmailPage::mail_port_changed, this));
1298                 _mail_user->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&KDMEmailPage::mail_user_changed, this));
1299                 _mail_password->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&KDMEmailPage::mail_password_changed, this));
1300                 _kdm_subject->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&KDMEmailPage::kdm_subject_changed, this));
1301                 _kdm_from->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&KDMEmailPage::kdm_from_changed, this));
1302                 _kdm_bcc->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&KDMEmailPage::kdm_bcc_changed, this));
1303                 _kdm_email->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&KDMEmailPage::kdm_email_changed, this));
1304                 _reset_kdm_email->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&KDMEmailPage::reset_kdm_email, this));
1305         }
1306
1307         void config_changed ()
1308         {
1309                 Config* config = Config::instance ();
1310
1311                 checked_set (_mail_server, config->mail_server ());
1312                 checked_set (_mail_port, config->mail_port ());
1313                 checked_set (_mail_user, config->mail_user ());
1314                 checked_set (_mail_password, config->mail_password ());
1315                 checked_set (_kdm_subject, config->kdm_subject ());
1316                 checked_set (_kdm_from, config->kdm_from ());
1317                 checked_set (_kdm_bcc, config->kdm_bcc ());
1318                 checked_set (_kdm_email, Config::instance()->kdm_email ());
1319         }
1320
1321         void mail_server_changed ()
1322         {
1323                 Config::instance()->set_mail_server (wx_to_std (_mail_server->GetValue ()));
1324         }
1325
1326         void mail_port_changed ()
1327         {
1328                 Config::instance()->set_mail_port (_mail_port->GetValue ());
1329         }
1330
1331         void mail_user_changed ()
1332         {
1333                 Config::instance()->set_mail_user (wx_to_std (_mail_user->GetValue ()));
1334         }
1335
1336         void mail_password_changed ()
1337         {
1338                 Config::instance()->set_mail_password (wx_to_std (_mail_password->GetValue ()));
1339         }
1340
1341         void kdm_subject_changed ()
1342         {
1343                 Config::instance()->set_kdm_subject (wx_to_std (_kdm_subject->GetValue ()));
1344         }
1345
1346         void kdm_from_changed ()
1347         {
1348                 Config::instance()->set_kdm_from (wx_to_std (_kdm_from->GetValue ()));
1349         }
1350
1351         void kdm_bcc_changed ()
1352         {
1353                 Config::instance()->set_kdm_bcc (wx_to_std (_kdm_bcc->GetValue ()));
1354         }
1355
1356         void kdm_email_changed ()
1357         {
1358                 if (_kdm_email->GetValue().IsEmpty ()) {
1359                         /* Sometimes we get sent an erroneous notification that the email
1360                            is empty; I don't know why.
1361                         */
1362                         return;
1363                 }
1364                 Config::instance()->set_kdm_email (wx_to_std (_kdm_email->GetValue ()));
1365         }
1366
1367         void reset_kdm_email ()
1368         {
1369                 Config::instance()->reset_kdm_email ();
1370                 checked_set (_kdm_email, Config::instance()->kdm_email ());
1371         }
1372
1373         wxTextCtrl* _mail_server;
1374         wxSpinCtrl* _mail_port;
1375         wxTextCtrl* _mail_user;
1376         wxTextCtrl* _mail_password;
1377         wxTextCtrl* _kdm_subject;
1378         wxTextCtrl* _kdm_from;
1379         EditableList<string, EmailDialog>* _kdm_cc;
1380         wxTextCtrl* _kdm_bcc;
1381         wxTextCtrl* _kdm_email;
1382         wxButton* _reset_kdm_email;
1383 };
1384
1385 /** @class AdvancedPage
1386  *  @brief Advanced page of the preferences dialog.
1387  */
1388 class AdvancedPage : public StockPage
1389 {
1390 public:
1391         AdvancedPage (wxSize panel_size, int border)
1392                 : StockPage (Kind_Advanced, panel_size, border)
1393                 , _maximum_j2k_bandwidth (0)
1394                 , _allow_any_dcp_frame_rate (0)
1395                 , _only_servers_encode (0)
1396                 , _log_general (0)
1397                 , _log_warning (0)
1398                 , _log_error (0)
1399                 , _log_timing (0)
1400                 , _log_debug_decode (0)
1401                 , _log_debug_encode (0)
1402                 , _log_debug_email (0)
1403         {}
1404
1405 private:
1406         void add_top_aligned_label_to_sizer (wxSizer* table, wxWindow* parent, wxString text)
1407         {
1408                 int flags = wxALIGN_TOP | wxTOP | wxLEFT;
1409 #ifdef __WXOSX__
1410                 flags |= wxALIGN_RIGHT;
1411                 text += wxT (":");
1412 #endif
1413                 wxStaticText* m = new wxStaticText (parent, wxID_ANY, text);
1414                 table->Add (m, 0, flags, DCPOMATIC_SIZER_Y_GAP);
1415         }
1416
1417         void setup ()
1418         {
1419                 wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
1420                 table->AddGrowableCol (1, 1);
1421                 _panel->GetSizer()->Add (table, 1, wxALL | wxEXPAND, _border);
1422
1423                 {
1424                         add_label_to_sizer (table, _panel, _("Maximum JPEG2000 bandwidth"), true);
1425                         wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
1426                         _maximum_j2k_bandwidth = new wxSpinCtrl (_panel);
1427                         s->Add (_maximum_j2k_bandwidth, 1);
1428                         add_label_to_sizer (s, _panel, _("Mbit/s"), false);
1429                         table->Add (s, 1);
1430                 }
1431
1432                 _allow_any_dcp_frame_rate = new wxCheckBox (_panel, wxID_ANY, _("Allow any DCP frame rate"));
1433                 table->Add (_allow_any_dcp_frame_rate, 1, wxEXPAND | wxALL);
1434                 table->AddSpacer (0);
1435
1436                 _only_servers_encode = new wxCheckBox (_panel, wxID_ANY, _("Only servers encode"));
1437                 table->Add (_only_servers_encode, 1, wxEXPAND | wxALL);
1438                 table->AddSpacer (0);
1439
1440                 {
1441                         add_top_aligned_label_to_sizer (table, _panel, _("DCP metadata filename format"));
1442                         dcp::NameFormat::Map titles;
1443                         titles['t'] = "type (cpl/pkl)";
1444                         dcp::NameFormat::Map examples;
1445                         examples['t'] = "cpl";
1446                         _dcp_metadata_filename_format = new NameFormatEditor (
1447                                 _panel, Config::instance()->dcp_metadata_filename_format(), titles, examples, "_eb1c112c-ca3c-4ae6-9263-c6714ff05d64.xml"
1448                                 );
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['r'] = "reel number";
1457                         titles['n'] = "number of reels";
1458                         titles['c'] = "content filename";
1459                         dcp::NameFormat::Map examples;
1460                         examples['t'] = "j2c";
1461                         examples['r'] = "1";
1462                         examples['n'] = "4";
1463                         examples['c'] = "myfile.mp4";
1464                         _dcp_asset_filename_format = new NameFormatEditor (
1465                                 _panel, Config::instance()->dcp_asset_filename_format(), titles, examples, "_eb1c112c-ca3c-4ae6-9263-c6714ff05d64.mxf"
1466                                 );
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 }