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