Add preference for default KDM target directory (#1013).
[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_CHECKBOX,   boost::bind (&GeneralPage::set_language_changed, this));
239                 _language->Bind     (wxEVT_CHOICE,    boost::bind (&GeneralPage::language_changed,     this));
240                 _cinemas_file->Bind (wxEVT_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_SPINCTRL, boost::bind (&GeneralPage::num_local_encoding_threads_changed, this));
244
245 #ifdef DCPOMATIC_HAVE_EBUR128_PATCHED_FFMPEG
246                 _analyse_ebur128->Bind (wxEVT_CHECKBOX, boost::bind (&GeneralPage::analyse_ebur128_changed, this));
247 #endif
248                 _automatic_audio_analysis->Bind (wxEVT_CHECKBOX, boost::bind (&GeneralPage::automatic_audio_analysis_changed, this));
249                 _check_for_updates->Bind (wxEVT_CHECKBOX, boost::bind (&GeneralPage::check_for_updates_changed, this));
250                 _check_for_test_updates->Bind (wxEVT_CHECKBOX, boost::bind (&GeneralPage::check_for_test_updates_changed, this));
251
252                 _issuer->Bind (wxEVT_TEXT, boost::bind (&GeneralPage::issuer_changed, this));
253                 _creator->Bind (wxEVT_TEXT, 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                 add_label_to_sizer (table, _panel, _("Default KDM directory"), true);
464 #ifdef DCPOMATIC_USE_OWN_PICKER
465                 _kdm_directory = new DirPickerCtrl (_panel);
466 #else
467                 _kdm_directory = new wxDirPickerCtrl (_panel, wxDD_DIR_MUST_EXIST);
468 #endif
469                 table->Add (_kdm_directory, 1, wxEXPAND);
470
471                 _still_length->SetRange (1, 3600);
472                 _still_length->Bind (wxEVT_SPINCTRL, boost::bind (&DefaultsPage::still_length_changed, this));
473
474                 _directory->Bind (wxEVT_DIRPICKER_CHANGED, boost::bind (&DefaultsPage::directory_changed, this));
475                 _kdm_directory->Bind (wxEVT_DIRPICKER_CHANGED, boost::bind (&DefaultsPage::kdm_directory_changed, this));
476
477                 _isdcf_metadata_button->Bind (wxEVT_BUTTON, boost::bind (&DefaultsPage::edit_isdcf_metadata_clicked, this));
478
479                 vector<Ratio const *> ratios = Ratio::all ();
480                 for (size_t i = 0; i < ratios.size(); ++i) {
481                         _container->Append (std_to_wx (ratios[i]->nickname ()));
482                 }
483
484                 _container->Bind (wxEVT_CHOICE, boost::bind (&DefaultsPage::container_changed, this));
485
486                 vector<DCPContentType const *> const ct = DCPContentType::all ();
487                 for (size_t i = 0; i < ct.size(); ++i) {
488                         _dcp_content_type->Append (std_to_wx (ct[i]->pretty_name ()));
489                 }
490
491                 setup_audio_channels_choice (_dcp_audio_channels, 2);
492
493                 _dcp_content_type->Bind (wxEVT_CHOICE, boost::bind (&DefaultsPage::dcp_content_type_changed, this));
494                 _dcp_audio_channels->Bind (wxEVT_CHOICE, boost::bind (&DefaultsPage::dcp_audio_channels_changed, this));
495
496                 _j2k_bandwidth->SetRange (50, 250);
497                 _j2k_bandwidth->Bind (wxEVT_SPINCTRL, boost::bind (&DefaultsPage::j2k_bandwidth_changed, this));
498
499                 _audio_delay->SetRange (-1000, 1000);
500                 _audio_delay->Bind (wxEVT_SPINCTRL, boost::bind (&DefaultsPage::audio_delay_changed, this));
501
502                 _standard->Append (_("SMPTE"));
503                 _standard->Append (_("Interop"));
504                 _standard->Bind (wxEVT_CHOICE, boost::bind (&DefaultsPage::standard_changed, this));
505         }
506
507         void config_changed ()
508         {
509                 Config* config = Config::instance ();
510
511                 vector<Ratio const *> ratios = Ratio::all ();
512                 for (size_t i = 0; i < ratios.size(); ++i) {
513                         if (ratios[i] == config->default_container ()) {
514                                 _container->SetSelection (i);
515                         }
516                 }
517
518                 vector<DCPContentType const *> const ct = DCPContentType::all ();
519                 for (size_t i = 0; i < ct.size(); ++i) {
520                         if (ct[i] == config->default_dcp_content_type ()) {
521                                 _dcp_content_type->SetSelection (i);
522                         }
523                 }
524
525                 checked_set (_still_length, config->default_still_length ());
526                 _directory->SetPath (std_to_wx (config->default_directory_or (wx_to_std (wxStandardPaths::Get().GetDocumentsDir())).string ()));
527                 _kdm_directory->SetPath (std_to_wx (config->default_kdm_directory_or (wx_to_std (wxStandardPaths::Get().GetDocumentsDir())).string ()));
528                 checked_set (_j2k_bandwidth, config->default_j2k_bandwidth() / 1000000);
529                 _j2k_bandwidth->SetRange (50, config->maximum_j2k_bandwidth() / 1000000);
530                 checked_set (_dcp_audio_channels, locale_convert<string> (config->default_dcp_audio_channels()));
531                 checked_set (_audio_delay, config->default_audio_delay ());
532                 checked_set (_standard, config->default_interop() ? 1 : 0);
533         }
534
535         void j2k_bandwidth_changed ()
536         {
537                 Config::instance()->set_default_j2k_bandwidth (_j2k_bandwidth->GetValue() * 1000000);
538         }
539
540         void audio_delay_changed ()
541         {
542                 Config::instance()->set_default_audio_delay (_audio_delay->GetValue());
543         }
544
545         void dcp_audio_channels_changed ()
546         {
547                 int const s = _dcp_audio_channels->GetSelection ();
548                 if (s != wxNOT_FOUND) {
549                         Config::instance()->set_default_dcp_audio_channels (
550                                 locale_convert<int> (string_client_data (_dcp_audio_channels->GetClientObject (s)))
551                                 );
552                 }
553         }
554
555         void directory_changed ()
556         {
557                 Config::instance()->set_default_directory (wx_to_std (_directory->GetPath ()));
558         }
559
560         void kdm_directory_changed ()
561         {
562                 Config::instance()->set_default_kdm_directory (wx_to_std (_kdm_directory->GetPath ()));
563         }
564
565         void edit_isdcf_metadata_clicked ()
566         {
567                 ISDCFMetadataDialog* d = new ISDCFMetadataDialog (_panel, Config::instance()->default_isdcf_metadata (), false);
568                 d->ShowModal ();
569                 Config::instance()->set_default_isdcf_metadata (d->isdcf_metadata ());
570                 d->Destroy ();
571         }
572
573         void still_length_changed ()
574         {
575                 Config::instance()->set_default_still_length (_still_length->GetValue ());
576         }
577
578         void container_changed ()
579         {
580                 vector<Ratio const *> ratio = Ratio::all ();
581                 Config::instance()->set_default_container (ratio[_container->GetSelection()]);
582         }
583
584         void dcp_content_type_changed ()
585         {
586                 vector<DCPContentType const *> ct = DCPContentType::all ();
587                 Config::instance()->set_default_dcp_content_type (ct[_dcp_content_type->GetSelection()]);
588         }
589
590         void standard_changed ()
591         {
592                 Config::instance()->set_default_interop (_standard->GetSelection() == 1);
593         }
594
595         wxSpinCtrl* _j2k_bandwidth;
596         wxSpinCtrl* _audio_delay;
597         wxButton* _isdcf_metadata_button;
598         wxSpinCtrl* _still_length;
599 #ifdef DCPOMATIC_USE_OWN_PICKER
600         DirPickerCtrl* _directory;
601         DirPickerCtrl* _kdm_directory;
602 #else
603         wxDirPickerCtrl* _directory;
604         wxDirPickerCtrl* _kdm_directory;
605 #endif
606         wxChoice* _container;
607         wxChoice* _dcp_content_type;
608         wxChoice* _dcp_audio_channels;
609         wxChoice* _standard;
610 };
611
612 class EncodingServersPage : public StandardPage
613 {
614 public:
615         EncodingServersPage (wxSize panel_size, int border)
616                 : StandardPage (panel_size, border)
617         {}
618
619         wxString GetName () const
620         {
621                 return _("Servers");
622         }
623
624 #ifdef DCPOMATIC_OSX
625         wxBitmap GetLargeIcon () const
626         {
627                 return wxBitmap ("servers", wxBITMAP_TYPE_PNG_RESOURCE);
628         }
629 #endif
630
631 private:
632         void setup ()
633         {
634                 _use_any_servers = new wxCheckBox (_panel, wxID_ANY, _("Search network for servers"));
635                 _panel->GetSizer()->Add (_use_any_servers, 0, wxALL, _border);
636
637                 vector<string> columns;
638                 columns.push_back (wx_to_std (_("IP address / host name")));
639                 _servers_list = new EditableList<string, ServerDialog> (
640                         _panel,
641                         columns,
642                         boost::bind (&Config::servers, Config::instance()),
643                         boost::bind (&Config::set_servers, Config::instance(), _1),
644                         boost::bind (&EncodingServersPage::server_column, this, _1)
645                         );
646
647                 _panel->GetSizer()->Add (_servers_list, 1, wxEXPAND | wxALL, _border);
648
649                 _use_any_servers->Bind (wxEVT_CHECKBOX, boost::bind (&EncodingServersPage::use_any_servers_changed, this));
650         }
651
652         void config_changed ()
653         {
654                 checked_set (_use_any_servers, Config::instance()->use_any_servers ());
655                 _servers_list->refresh ();
656         }
657
658         void use_any_servers_changed ()
659         {
660                 Config::instance()->set_use_any_servers (_use_any_servers->GetValue ());
661         }
662
663         string server_column (string s)
664         {
665                 return s;
666         }
667
668         wxCheckBox* _use_any_servers;
669         EditableList<string, ServerDialog>* _servers_list;
670 };
671
672 class CertificateChainEditor : public wxPanel
673 {
674 public:
675         CertificateChainEditor (
676                 wxWindow* parent,
677                 wxString title,
678                 int border,
679                 function<void (shared_ptr<dcp::CertificateChain>)> set,
680                 function<shared_ptr<const dcp::CertificateChain> (void)> get
681                 )
682                 : wxPanel (parent)
683                 , _set (set)
684                 , _get (get)
685         {
686                 wxFont subheading_font (*wxNORMAL_FONT);
687                 subheading_font.SetWeight (wxFONTWEIGHT_BOLD);
688
689                 _sizer = new wxBoxSizer (wxVERTICAL);
690
691                 {
692                         wxStaticText* m = new wxStaticText (this, wxID_ANY, title);
693                         m->SetFont (subheading_font);
694                         _sizer->Add (m, 0, wxALL, border);
695                 }
696
697                 wxBoxSizer* certificates_sizer = new wxBoxSizer (wxHORIZONTAL);
698                 _sizer->Add (certificates_sizer, 0, wxLEFT | wxRIGHT, border);
699
700                 _certificates = new wxListCtrl (this, wxID_ANY, wxDefaultPosition, wxSize (440, 150), wxLC_REPORT | wxLC_SINGLE_SEL);
701
702                 {
703                         wxListItem ip;
704                         ip.SetId (0);
705                         ip.SetText (_("Type"));
706                         ip.SetWidth (100);
707                         _certificates->InsertColumn (0, ip);
708                 }
709
710                 {
711                         wxListItem ip;
712                         ip.SetId (1);
713                         ip.SetText (_("Thumbprint"));
714                         ip.SetWidth (340);
715
716                         wxFont font = ip.GetFont ();
717                         font.SetFamily (wxFONTFAMILY_TELETYPE);
718                         ip.SetFont (font);
719
720                         _certificates->InsertColumn (1, ip);
721                 }
722
723                 certificates_sizer->Add (_certificates, 1, wxEXPAND);
724
725                 {
726                         wxSizer* s = new wxBoxSizer (wxVERTICAL);
727                         _add_certificate = new wxButton (this, wxID_ANY, _("Add..."));
728                         s->Add (_add_certificate, 0, wxTOP | wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
729                         _remove_certificate = new wxButton (this, wxID_ANY, _("Remove"));
730                         s->Add (_remove_certificate, 0, wxTOP | wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
731                         _export_certificate = new wxButton (this, wxID_ANY, _("Export"));
732                         s->Add (_export_certificate, 0, wxTOP | wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
733                         certificates_sizer->Add (s, 0, wxLEFT, DCPOMATIC_SIZER_X_GAP);
734                 }
735
736                 wxGridBagSizer* table = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
737                 _sizer->Add (table, 1, wxALL | wxEXPAND, border);
738                 int r = 0;
739
740                 add_label_to_sizer (table, this, _("Leaf private key"), true, wxGBPosition (r, 0));
741                 _private_key = new wxStaticText (this, wxID_ANY, wxT (""));
742                 wxFont font = _private_key->GetFont ();
743                 font.SetFamily (wxFONTFAMILY_TELETYPE);
744                 _private_key->SetFont (font);
745                 table->Add (_private_key, wxGBPosition (r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
746                 _load_private_key = new wxButton (this, wxID_ANY, _("Load..."));
747                 table->Add (_load_private_key, wxGBPosition (r, 2));
748                 _export_private_key = new wxButton (this, wxID_ANY, _("Export..."));
749                 table->Add (_export_private_key, wxGBPosition (r, 3));
750                 ++r;
751
752                 _button_sizer = new wxBoxSizer (wxHORIZONTAL);
753                 _remake_certificates = new wxButton (this, wxID_ANY, _("Re-make certificates\nand key..."));
754                 _button_sizer->Add (_remake_certificates, 1, wxRIGHT, border);
755                 table->Add (_button_sizer, wxGBPosition (r, 0), wxGBSpan (1, 4));
756                 ++r;
757
758                 _add_certificate->Bind     (wxEVT_BUTTON,       boost::bind (&CertificateChainEditor::add_certificate, this));
759                 _remove_certificate->Bind  (wxEVT_BUTTON,       boost::bind (&CertificateChainEditor::remove_certificate, this));
760                 _export_certificate->Bind  (wxEVT_BUTTON,       boost::bind (&CertificateChainEditor::export_certificate, this));
761                 _certificates->Bind        (wxEVT_LIST_ITEM_SELECTED,   boost::bind (&CertificateChainEditor::update_sensitivity, this));
762                 _certificates->Bind        (wxEVT_LIST_ITEM_DESELECTED, boost::bind (&CertificateChainEditor::update_sensitivity, this));
763                 _remake_certificates->Bind (wxEVT_BUTTON,       boost::bind (&CertificateChainEditor::remake_certificates, this));
764                 _load_private_key->Bind    (wxEVT_BUTTON,       boost::bind (&CertificateChainEditor::load_private_key, this));
765                 _export_private_key->Bind  (wxEVT_BUTTON,       boost::bind (&CertificateChainEditor::export_private_key, this));
766
767                 SetSizerAndFit (_sizer);
768         }
769
770         void config_changed ()
771         {
772                 _chain.reset (new dcp::CertificateChain (*_get().get ()));
773
774                 update_certificate_list ();
775                 update_private_key ();
776                 update_sensitivity ();
777         }
778
779         void add_button (wxWindow* button)
780         {
781                 _button_sizer->Add (button, 0, wxLEFT | wxRIGHT, DCPOMATIC_SIZER_X_GAP);
782                 _sizer->Layout ();
783         }
784
785 private:
786         void add_certificate ()
787         {
788                 wxFileDialog* d = new wxFileDialog (this, _("Select Certificate File"));
789
790                 if (d->ShowModal() == wxID_OK) {
791                         try {
792                                 dcp::Certificate c;
793                                 string const extra = c.read_string (dcp::file_to_string (wx_to_std (d->GetPath ())));
794                                 if (!extra.empty ()) {
795                                         message_dialog (
796                                                 this,
797                                                 _("This file contains other certificates (or other data) after its first certificate. "
798                                                   "Only the first certificate will be used.")
799                                                 );
800                                 }
801                                 _chain->add (c);
802                                 _set (_chain);
803                                 update_certificate_list ();
804                         } catch (dcp::MiscError& e) {
805                                 error_dialog (this, wxString::Format (_("Could not read certificate file (%s)"), e.what ()));
806                         }
807                 }
808
809                 d->Destroy ();
810
811                 update_sensitivity ();
812         }
813
814         void remove_certificate ()
815         {
816                 int i = _certificates->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
817                 if (i == -1) {
818                         return;
819                 }
820
821                 _certificates->DeleteItem (i);
822                 _chain->remove (i);
823                 _set (_chain);
824
825                 update_sensitivity ();
826         }
827
828         void export_certificate ()
829         {
830                 int i = _certificates->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
831                 if (i == -1) {
832                         return;
833                 }
834
835                 wxFileDialog* d = new wxFileDialog (
836                         this, _("Select Certificate File"), wxEmptyString, wxEmptyString, wxT ("PEM files (*.pem)|*.pem"),
837                         wxFD_SAVE | wxFD_OVERWRITE_PROMPT
838                         );
839
840                 dcp::CertificateChain::List all = _chain->root_to_leaf ();
841                 dcp::CertificateChain::List::iterator j = all.begin ();
842                 for (int k = 0; k < i; ++k) {
843                         ++j;
844                 }
845
846                 if (d->ShowModal () == wxID_OK) {
847                         FILE* f = fopen_boost (wx_to_std (d->GetPath ()), "w");
848                         if (!f) {
849                                 throw OpenFileError (wx_to_std (d->GetPath ()), errno, false);
850                         }
851
852                         string const s = j->certificate (true);
853                         fwrite (s.c_str(), 1, s.length(), f);
854                         fclose (f);
855                 }
856                 d->Destroy ();
857         }
858
859         void update_certificate_list ()
860         {
861                 _certificates->DeleteAllItems ();
862                 size_t n = 0;
863                 dcp::CertificateChain::List certs = _chain->root_to_leaf ();
864                 BOOST_FOREACH (dcp::Certificate const & i, certs) {
865                         wxListItem item;
866                         item.SetId (n);
867                         _certificates->InsertItem (item);
868                         _certificates->SetItem (n, 1, std_to_wx (i.thumbprint ()));
869
870                         if (n == 0) {
871                                 _certificates->SetItem (n, 0, _("Root"));
872                         } else if (n == (certs.size() - 1)) {
873                                 _certificates->SetItem (n, 0, _("Leaf"));
874                         } else {
875                                 _certificates->SetItem (n, 0, _("Intermediate"));
876                         }
877
878                         ++n;
879                 }
880         }
881
882         void remake_certificates ()
883         {
884                 shared_ptr<const dcp::CertificateChain> chain = _get ();
885
886                 string subject_organization_name;
887                 string subject_organizational_unit_name;
888                 string root_common_name;
889                 string intermediate_common_name;
890                 string leaf_common_name;
891
892                 dcp::CertificateChain::List all = chain->root_to_leaf ();
893
894                 if (all.size() >= 1) {
895                         /* Have a root */
896                         subject_organization_name = chain->root().subject_organization_name ();
897                         subject_organizational_unit_name = chain->root().subject_organizational_unit_name ();
898                         root_common_name = chain->root().subject_common_name ();
899                 }
900
901                 if (all.size() >= 2) {
902                         /* Have a leaf */
903                         leaf_common_name = chain->leaf().subject_common_name ();
904                 }
905
906                 if (all.size() >= 3) {
907                         /* Have an intermediate */
908                         dcp::CertificateChain::List::iterator i = all.begin ();
909                         ++i;
910                         intermediate_common_name = i->subject_common_name ();
911                 }
912
913                 MakeChainDialog* d = new MakeChainDialog (
914                         this,
915                         subject_organization_name,
916                         subject_organizational_unit_name,
917                         root_common_name,
918                         intermediate_common_name,
919                         leaf_common_name
920                         );
921
922                 if (d->ShowModal () == wxID_OK) {
923                         _chain.reset (
924                                 new dcp::CertificateChain (
925                                         openssl_path (),
926                                         d->organisation (),
927                                         d->organisational_unit (),
928                                         d->root_common_name (),
929                                         d->intermediate_common_name (),
930                                         d->leaf_common_name ()
931                                         )
932                                 );
933
934                         _set (_chain);
935                         update_certificate_list ();
936                         update_private_key ();
937                 }
938
939                 d->Destroy ();
940         }
941
942         void update_sensitivity ()
943         {
944                 _remove_certificate->Enable (_certificates->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) != -1);
945                 _export_certificate->Enable (_certificates->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) != -1);
946         }
947
948         void update_private_key ()
949         {
950                 checked_set (_private_key, dcp::private_key_fingerprint (_chain->key().get ()));
951                 _sizer->Layout ();
952         }
953
954         void load_private_key ()
955         {
956                 wxFileDialog* d = new wxFileDialog (this, _("Select Key File"));
957
958                 if (d->ShowModal() == wxID_OK) {
959                         try {
960                                 boost::filesystem::path p (wx_to_std (d->GetPath ()));
961                                 if (boost::filesystem::file_size (p) > 8192) {
962                                         error_dialog (
963                                                 this,
964                                                 wxString::Format (_("Could not read key file; file is too long (%s)"), std_to_wx (p.string ()))
965                                                 );
966                                         return;
967                                 }
968
969                                 _chain->set_key (dcp::file_to_string (p));
970                                 _set (_chain);
971                                 update_private_key ();
972                         } catch (dcp::MiscError& e) {
973                                 error_dialog (this, wxString::Format (_("Could not read certificate file (%s)"), e.what ()));
974                         }
975                 }
976
977                 d->Destroy ();
978
979                 update_sensitivity ();
980         }
981
982         void export_private_key ()
983         {
984                 optional<string> key = _chain->key ();
985                 if (!key) {
986                         return;
987                 }
988
989                 wxFileDialog* d = new wxFileDialog (
990                         this, _("Select Key File"), wxEmptyString, wxEmptyString, wxT ("PEM files (*.pem)|*.pem"),
991                         wxFD_SAVE | wxFD_OVERWRITE_PROMPT
992                         );
993
994                 if (d->ShowModal () == wxID_OK) {
995                         FILE* f = fopen_boost (wx_to_std (d->GetPath ()), "w");
996                         if (!f) {
997                                 throw OpenFileError (wx_to_std (d->GetPath ()), errno, false);
998                         }
999
1000                         string const s = _chain->key().get ();
1001                         fwrite (s.c_str(), 1, s.length(), f);
1002                         fclose (f);
1003                 }
1004                 d->Destroy ();
1005         }
1006
1007         wxListCtrl* _certificates;
1008         wxButton* _add_certificate;
1009         wxButton* _export_certificate;
1010         wxButton* _remove_certificate;
1011         wxButton* _remake_certificates;
1012         wxStaticText* _private_key;
1013         wxButton* _load_private_key;
1014         wxButton* _export_private_key;
1015         wxSizer* _sizer;
1016         wxBoxSizer* _button_sizer;
1017         shared_ptr<dcp::CertificateChain> _chain;
1018         boost::function<void (shared_ptr<dcp::CertificateChain>)> _set;
1019         boost::function<shared_ptr<const dcp::CertificateChain> (void)> _get;
1020 };
1021
1022 class KeysPage : public StandardPage
1023 {
1024 public:
1025         KeysPage (wxSize panel_size, int border)
1026                 : StandardPage (panel_size, border)
1027         {}
1028
1029         wxString GetName () const
1030         {
1031                 return _("Keys");
1032         }
1033
1034 #ifdef DCPOMATIC_OSX
1035         wxBitmap GetLargeIcon () const
1036         {
1037                 return wxBitmap ("keys", wxBITMAP_TYPE_PNG_RESOURCE);
1038         }
1039 #endif
1040
1041 private:
1042
1043         void setup ()
1044         {
1045                 _signer = new CertificateChainEditor (
1046                         _panel, _("Signing DCPs and KDMs"), _border,
1047                         boost::bind (&Config::set_signer_chain, Config::instance (), _1),
1048                         boost::bind (&Config::signer_chain, Config::instance ())
1049                         );
1050
1051                 _panel->GetSizer()->Add (_signer);
1052
1053                 _decryption = new CertificateChainEditor (
1054                         _panel, _("Decrypting DCPs"), _border,
1055                         boost::bind (&Config::set_decryption_chain, Config::instance (), _1),
1056                         boost::bind (&Config::decryption_chain, Config::instance ())
1057                         );
1058
1059                 _panel->GetSizer()->Add (_decryption);
1060
1061                 _export_decryption_certificate = new wxButton (_decryption, wxID_ANY, _("Export DCP decryption\ncertificate..."));
1062                 _decryption->add_button (_export_decryption_certificate);
1063                 _export_decryption_chain = new wxButton (_decryption, wxID_ANY, _("Export DCP decryption\nchain..."));
1064                 _decryption->add_button (_export_decryption_chain);
1065
1066                 _export_decryption_certificate->Bind (wxEVT_BUTTON, boost::bind (&KeysPage::export_decryption_certificate, this));
1067                 _export_decryption_chain->Bind (wxEVT_BUTTON, boost::bind (&KeysPage::export_decryption_chain, this));
1068         }
1069
1070         void export_decryption_certificate ()
1071         {
1072                 wxFileDialog* d = new wxFileDialog (
1073                         _panel, _("Select Certificate File"), wxEmptyString, wxEmptyString, wxT ("PEM files (*.pem)|*.pem"),
1074                         wxFD_SAVE | wxFD_OVERWRITE_PROMPT
1075                         );
1076
1077                 if (d->ShowModal () == wxID_OK) {
1078                         FILE* f = fopen_boost (wx_to_std (d->GetPath ()), "w");
1079                         if (!f) {
1080                                 throw OpenFileError (wx_to_std (d->GetPath ()), errno, false);
1081                         }
1082
1083                         string const s = Config::instance()->decryption_chain()->leaf().certificate (true);
1084                         fwrite (s.c_str(), 1, s.length(), f);
1085                         fclose (f);
1086                 }
1087                 d->Destroy ();
1088         }
1089
1090         void export_decryption_chain ()
1091         {
1092                 wxFileDialog* d = new wxFileDialog (
1093                         _panel, _("Select Chain File"), wxEmptyString, wxEmptyString, wxT ("PEM files (*.pem)|*.pem"),
1094                         wxFD_SAVE | wxFD_OVERWRITE_PROMPT
1095                         );
1096
1097                 if (d->ShowModal () == wxID_OK) {
1098                         FILE* f = fopen_boost (wx_to_std (d->GetPath ()), "w");
1099                         if (!f) {
1100                                 throw OpenFileError (wx_to_std (d->GetPath ()), errno, false);
1101                         }
1102
1103                         string const s = Config::instance()->decryption_chain()->chain();
1104                         fwrite (s.c_str(), 1, s.length(), f);
1105                         fclose (f);
1106                 }
1107                 d->Destroy ();
1108         }
1109
1110         void config_changed ()
1111         {
1112                 _signer->config_changed ();
1113                 _decryption->config_changed ();
1114         }
1115
1116         CertificateChainEditor* _signer;
1117         CertificateChainEditor* _decryption;
1118         wxButton* _export_decryption_certificate;
1119         wxButton* _export_decryption_chain;
1120 };
1121
1122 class TMSPage : public StandardPage
1123 {
1124 public:
1125         TMSPage (wxSize panel_size, int border)
1126                 : StandardPage (panel_size, border)
1127         {}
1128
1129         wxString GetName () const
1130         {
1131                 return _("TMS");
1132         }
1133
1134 #ifdef DCPOMATIC_OSX
1135         wxBitmap GetLargeIcon () const
1136         {
1137                 return wxBitmap ("tms", wxBITMAP_TYPE_PNG_RESOURCE);
1138         }
1139 #endif
1140
1141 private:
1142         void setup ()
1143         {
1144                 wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
1145                 table->AddGrowableCol (1, 1);
1146                 _panel->GetSizer()->Add (table, 1, wxALL | wxEXPAND, _border);
1147
1148                 add_label_to_sizer (table, _panel, _("Protocol"), true);
1149                 _tms_protocol = new wxChoice (_panel, wxID_ANY);
1150                 table->Add (_tms_protocol, 1, wxEXPAND);
1151
1152                 add_label_to_sizer (table, _panel, _("IP address"), true);
1153                 _tms_ip = new wxTextCtrl (_panel, wxID_ANY);
1154                 table->Add (_tms_ip, 1, wxEXPAND);
1155
1156                 add_label_to_sizer (table, _panel, _("Target path"), true);
1157                 _tms_path = new wxTextCtrl (_panel, wxID_ANY);
1158                 table->Add (_tms_path, 1, wxEXPAND);
1159
1160                 add_label_to_sizer (table, _panel, _("User name"), true);
1161                 _tms_user = new wxTextCtrl (_panel, wxID_ANY);
1162                 table->Add (_tms_user, 1, wxEXPAND);
1163
1164                 add_label_to_sizer (table, _panel, _("Password"), true);
1165                 _tms_password = new wxTextCtrl (_panel, wxID_ANY);
1166                 table->Add (_tms_password, 1, wxEXPAND);
1167
1168                 _tms_protocol->Append (_("SCP (for AAM and Doremi)"));
1169                 _tms_protocol->Append (_("FTP (for Dolby)"));
1170
1171                 _tms_protocol->Bind (wxEVT_CHOICE, boost::bind (&TMSPage::tms_protocol_changed, this));
1172                 _tms_ip->Bind (wxEVT_TEXT, boost::bind (&TMSPage::tms_ip_changed, this));
1173                 _tms_path->Bind (wxEVT_TEXT, boost::bind (&TMSPage::tms_path_changed, this));
1174                 _tms_user->Bind (wxEVT_TEXT, boost::bind (&TMSPage::tms_user_changed, this));
1175                 _tms_password->Bind (wxEVT_TEXT, boost::bind (&TMSPage::tms_password_changed, this));
1176         }
1177
1178         void config_changed ()
1179         {
1180                 Config* config = Config::instance ();
1181
1182                 checked_set (_tms_protocol, config->tms_protocol ());
1183                 checked_set (_tms_ip, config->tms_ip ());
1184                 checked_set (_tms_path, config->tms_path ());
1185                 checked_set (_tms_user, config->tms_user ());
1186                 checked_set (_tms_password, config->tms_password ());
1187         }
1188
1189         void tms_protocol_changed ()
1190         {
1191                 Config::instance()->set_tms_protocol (static_cast<Protocol> (_tms_protocol->GetSelection ()));
1192         }
1193
1194         void tms_ip_changed ()
1195         {
1196                 Config::instance()->set_tms_ip (wx_to_std (_tms_ip->GetValue ()));
1197         }
1198
1199         void tms_path_changed ()
1200         {
1201                 Config::instance()->set_tms_path (wx_to_std (_tms_path->GetValue ()));
1202         }
1203
1204         void tms_user_changed ()
1205         {
1206                 Config::instance()->set_tms_user (wx_to_std (_tms_user->GetValue ()));
1207         }
1208
1209         void tms_password_changed ()
1210         {
1211                 Config::instance()->set_tms_password (wx_to_std (_tms_password->GetValue ()));
1212         }
1213
1214         wxChoice* _tms_protocol;
1215         wxTextCtrl* _tms_ip;
1216         wxTextCtrl* _tms_path;
1217         wxTextCtrl* _tms_user;
1218         wxTextCtrl* _tms_password;
1219 };
1220
1221 static string
1222 column (string s)
1223 {
1224         return s;
1225 }
1226
1227 class KDMEmailPage : public StandardPage
1228 {
1229 public:
1230
1231         KDMEmailPage (wxSize panel_size, int border)
1232 #ifdef DCPOMATIC_OSX
1233                 /* We have to force both width and height of this one */
1234                 : StandardPage (wxSize (480, 128), border)
1235 #else
1236                 : StandardPage (panel_size, border)
1237 #endif
1238         {}
1239
1240         wxString GetName () const
1241         {
1242                 return _("KDM Email");
1243         }
1244
1245 #ifdef DCPOMATIC_OSX
1246         wxBitmap GetLargeIcon () const
1247         {
1248                 return wxBitmap ("kdm_email", wxBITMAP_TYPE_PNG_RESOURCE);
1249         }
1250 #endif
1251
1252 private:
1253         void setup ()
1254         {
1255                 wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
1256                 table->AddGrowableCol (1, 1);
1257                 _panel->GetSizer()->Add (table, 1, wxEXPAND | wxALL, _border);
1258
1259                 add_label_to_sizer (table, _panel, _("Outgoing mail server"), true);
1260                 {
1261                         wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
1262                         _mail_server = new wxTextCtrl (_panel, wxID_ANY);
1263                         s->Add (_mail_server, 1, wxEXPAND | wxALL);
1264                         add_label_to_sizer (s, _panel, _("port"), false);
1265                         _mail_port = new wxSpinCtrl (_panel, wxID_ANY);
1266                         _mail_port->SetRange (0, 65535);
1267                         s->Add (_mail_port);
1268                         table->Add (s, 1, wxEXPAND | wxALL);
1269                 }
1270
1271                 add_label_to_sizer (table, _panel, _("Mail user name"), true);
1272                 _mail_user = new wxTextCtrl (_panel, wxID_ANY);
1273                 table->Add (_mail_user, 1, wxEXPAND | wxALL);
1274
1275                 add_label_to_sizer (table, _panel, _("Mail password"), true);
1276                 _mail_password = new wxTextCtrl (_panel, wxID_ANY);
1277                 table->Add (_mail_password, 1, wxEXPAND | wxALL);
1278
1279                 add_label_to_sizer (table, _panel, _("Subject"), true);
1280                 _kdm_subject = new wxTextCtrl (_panel, wxID_ANY);
1281                 table->Add (_kdm_subject, 1, wxEXPAND | wxALL);
1282
1283                 add_label_to_sizer (table, _panel, _("From address"), true);
1284                 _kdm_from = new wxTextCtrl (_panel, wxID_ANY);
1285                 table->Add (_kdm_from, 1, wxEXPAND | wxALL);
1286
1287                 vector<string> columns;
1288                 columns.push_back (wx_to_std (_("Address")));
1289                 add_label_to_sizer (table, _panel, _("CC addresses"), true);
1290                 _kdm_cc = new EditableList<string, EmailDialog> (
1291                         _panel,
1292                         columns,
1293                         bind (&Config::kdm_cc, Config::instance()),
1294                         bind (&Config::set_kdm_cc, Config::instance(), _1),
1295                         bind (&column, _1)
1296                         );
1297                 table->Add (_kdm_cc, 1, wxEXPAND | wxALL);
1298
1299                 add_label_to_sizer (table, _panel, _("BCC address"), true);
1300                 _kdm_bcc = new wxTextCtrl (_panel, wxID_ANY);
1301                 table->Add (_kdm_bcc, 1, wxEXPAND | wxALL);
1302
1303                 _kdm_email = new wxTextCtrl (_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (-1, 200), wxTE_MULTILINE);
1304                 _panel->GetSizer()->Add (_kdm_email, 0, wxEXPAND | wxALL, _border);
1305
1306                 _reset_kdm_email = new wxButton (_panel, wxID_ANY, _("Reset to default subject and text"));
1307                 _panel->GetSizer()->Add (_reset_kdm_email, 0, wxEXPAND | wxALL, _border);
1308
1309                 _kdm_cc->layout ();
1310
1311                 _mail_server->Bind (wxEVT_TEXT, boost::bind (&KDMEmailPage::mail_server_changed, this));
1312                 _mail_port->Bind (wxEVT_SPINCTRL, boost::bind (&KDMEmailPage::mail_port_changed, this));
1313                 _mail_user->Bind (wxEVT_TEXT, boost::bind (&KDMEmailPage::mail_user_changed, this));
1314                 _mail_password->Bind (wxEVT_TEXT, boost::bind (&KDMEmailPage::mail_password_changed, this));
1315                 _kdm_subject->Bind (wxEVT_TEXT, boost::bind (&KDMEmailPage::kdm_subject_changed, this));
1316                 _kdm_from->Bind (wxEVT_TEXT, boost::bind (&KDMEmailPage::kdm_from_changed, this));
1317                 _kdm_bcc->Bind (wxEVT_TEXT, boost::bind (&KDMEmailPage::kdm_bcc_changed, this));
1318                 _kdm_email->Bind (wxEVT_TEXT, boost::bind (&KDMEmailPage::kdm_email_changed, this));
1319                 _reset_kdm_email->Bind (wxEVT_BUTTON, boost::bind (&KDMEmailPage::reset_kdm_email, this));
1320         }
1321
1322         void config_changed ()
1323         {
1324                 Config* config = Config::instance ();
1325
1326                 checked_set (_mail_server, config->mail_server ());
1327                 checked_set (_mail_port, config->mail_port ());
1328                 checked_set (_mail_user, config->mail_user ());
1329                 checked_set (_mail_password, config->mail_password ());
1330                 checked_set (_kdm_subject, config->kdm_subject ());
1331                 checked_set (_kdm_from, config->kdm_from ());
1332                 checked_set (_kdm_bcc, config->kdm_bcc ());
1333                 checked_set (_kdm_email, Config::instance()->kdm_email ());
1334         }
1335
1336         void mail_server_changed ()
1337         {
1338                 Config::instance()->set_mail_server (wx_to_std (_mail_server->GetValue ()));
1339         }
1340
1341         void mail_port_changed ()
1342         {
1343                 Config::instance()->set_mail_port (_mail_port->GetValue ());
1344         }
1345
1346         void mail_user_changed ()
1347         {
1348                 Config::instance()->set_mail_user (wx_to_std (_mail_user->GetValue ()));
1349         }
1350
1351         void mail_password_changed ()
1352         {
1353                 Config::instance()->set_mail_password (wx_to_std (_mail_password->GetValue ()));
1354         }
1355
1356         void kdm_subject_changed ()
1357         {
1358                 Config::instance()->set_kdm_subject (wx_to_std (_kdm_subject->GetValue ()));
1359         }
1360
1361         void kdm_from_changed ()
1362         {
1363                 Config::instance()->set_kdm_from (wx_to_std (_kdm_from->GetValue ()));
1364         }
1365
1366         void kdm_bcc_changed ()
1367         {
1368                 Config::instance()->set_kdm_bcc (wx_to_std (_kdm_bcc->GetValue ()));
1369         }
1370
1371         void kdm_email_changed ()
1372         {
1373                 if (_kdm_email->GetValue().IsEmpty ()) {
1374                         /* Sometimes we get sent an erroneous notification that the email
1375                            is empty; I don't know why.
1376                         */
1377                         return;
1378                 }
1379                 Config::instance()->set_kdm_email (wx_to_std (_kdm_email->GetValue ()));
1380         }
1381
1382         void reset_kdm_email ()
1383         {
1384                 Config::instance()->reset_kdm_email ();
1385                 checked_set (_kdm_email, Config::instance()->kdm_email ());
1386         }
1387
1388         wxTextCtrl* _mail_server;
1389         wxSpinCtrl* _mail_port;
1390         wxTextCtrl* _mail_user;
1391         wxTextCtrl* _mail_password;
1392         wxTextCtrl* _kdm_subject;
1393         wxTextCtrl* _kdm_from;
1394         EditableList<string, EmailDialog>* _kdm_cc;
1395         wxTextCtrl* _kdm_bcc;
1396         wxTextCtrl* _kdm_email;
1397         wxButton* _reset_kdm_email;
1398 };
1399
1400 /** @class AdvancedPage
1401  *  @brief Advanced page of the preferences dialog.
1402  */
1403 class AdvancedPage : public StockPage
1404 {
1405 public:
1406         AdvancedPage (wxSize panel_size, int border)
1407                 : StockPage (Kind_Advanced, panel_size, border)
1408                 , _maximum_j2k_bandwidth (0)
1409                 , _allow_any_dcp_frame_rate (0)
1410                 , _only_servers_encode (0)
1411                 , _log_general (0)
1412                 , _log_warning (0)
1413                 , _log_error (0)
1414                 , _log_timing (0)
1415                 , _log_debug_decode (0)
1416                 , _log_debug_encode (0)
1417                 , _log_debug_email (0)
1418         {}
1419
1420 private:
1421         void add_top_aligned_label_to_sizer (wxSizer* table, wxWindow* parent, wxString text)
1422         {
1423                 int flags = wxALIGN_TOP | wxTOP | wxLEFT;
1424 #ifdef __WXOSX__
1425                 flags |= wxALIGN_RIGHT;
1426                 text += wxT (":");
1427 #endif
1428                 wxStaticText* m = new wxStaticText (parent, wxID_ANY, text);
1429                 table->Add (m, 0, flags, DCPOMATIC_SIZER_Y_GAP);
1430         }
1431
1432         void setup ()
1433         {
1434                 wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
1435                 table->AddGrowableCol (1, 1);
1436                 _panel->GetSizer()->Add (table, 1, wxALL | wxEXPAND, _border);
1437
1438                 {
1439                         add_label_to_sizer (table, _panel, _("Maximum JPEG2000 bandwidth"), true);
1440                         wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
1441                         _maximum_j2k_bandwidth = new wxSpinCtrl (_panel);
1442                         s->Add (_maximum_j2k_bandwidth, 1);
1443                         add_label_to_sizer (s, _panel, _("Mbit/s"), false);
1444                         table->Add (s, 1);
1445                 }
1446
1447                 _allow_any_dcp_frame_rate = new wxCheckBox (_panel, wxID_ANY, _("Allow any DCP frame rate"));
1448                 table->Add (_allow_any_dcp_frame_rate, 1, wxEXPAND | wxALL);
1449                 table->AddSpacer (0);
1450
1451                 _only_servers_encode = new wxCheckBox (_panel, wxID_ANY, _("Only servers encode"));
1452                 table->Add (_only_servers_encode, 1, wxEXPAND | wxALL);
1453                 table->AddSpacer (0);
1454
1455                 {
1456                         add_top_aligned_label_to_sizer (table, _panel, _("DCP metadata filename format"));
1457                         dcp::NameFormat::Map titles;
1458                         titles['t'] = "type (cpl/pkl)";
1459                         dcp::NameFormat::Map examples;
1460                         examples['t'] = "cpl";
1461                         _dcp_metadata_filename_format = new NameFormatEditor (
1462                                 _panel, Config::instance()->dcp_metadata_filename_format(), titles, examples, "_eb1c112c-ca3c-4ae6-9263-c6714ff05d64.xml"
1463                                 );
1464                         table->Add (_dcp_metadata_filename_format->panel(), 1, wxEXPAND | wxALL);
1465                 }
1466
1467                 {
1468                         add_top_aligned_label_to_sizer (table, _panel, _("DCP asset filename format"));
1469                         dcp::NameFormat::Map titles;
1470                         titles['t'] = "type (j2c/pcm/sub)";
1471                         titles['r'] = "reel number";
1472                         titles['n'] = "number of reels";
1473                         titles['c'] = "content filename";
1474                         dcp::NameFormat::Map examples;
1475                         examples['t'] = "j2c";
1476                         examples['r'] = "1";
1477                         examples['n'] = "4";
1478                         examples['c'] = "myfile.mp4";
1479                         _dcp_asset_filename_format = new NameFormatEditor (
1480                                 _panel, Config::instance()->dcp_asset_filename_format(), titles, examples, "_eb1c112c-ca3c-4ae6-9263-c6714ff05d64.mxf"
1481                                 );
1482                         table->Add (_dcp_asset_filename_format->panel(), 1, wxEXPAND | wxALL);
1483                 }
1484
1485                 {
1486                         add_top_aligned_label_to_sizer (table, _panel, _("Log"));
1487                         wxBoxSizer* t = new wxBoxSizer (wxVERTICAL);
1488                         _log_general = new wxCheckBox (_panel, wxID_ANY, _("General"));
1489                         t->Add (_log_general, 1, wxEXPAND | wxALL);
1490                         _log_warning = new wxCheckBox (_panel, wxID_ANY, _("Warnings"));
1491                         t->Add (_log_warning, 1, wxEXPAND | wxALL);
1492                         _log_error = new wxCheckBox (_panel, wxID_ANY, _("Errors"));
1493                         t->Add (_log_error, 1, wxEXPAND | wxALL);
1494                         /// TRANSLATORS: translate the word "Timing" here; do not include the "Config|" prefix
1495                         _log_timing = new wxCheckBox (_panel, wxID_ANY, S_("Config|Timing"));
1496                         t->Add (_log_timing, 1, wxEXPAND | wxALL);
1497                         _log_debug_decode = new wxCheckBox (_panel, wxID_ANY, _("Debug: decode"));
1498                         t->Add (_log_debug_decode, 1, wxEXPAND | wxALL);
1499                         _log_debug_encode = new wxCheckBox (_panel, wxID_ANY, _("Debug: encode"));
1500                         t->Add (_log_debug_encode, 1, wxEXPAND | wxALL);
1501                         _log_debug_email = new wxCheckBox (_panel, wxID_ANY, _("Debug: email sending"));
1502                         t->Add (_log_debug_email, 1, wxEXPAND | wxALL);
1503                         table->Add (t, 0, wxALL, 6);
1504                 }
1505
1506 #ifdef DCPOMATIC_WINDOWS
1507                 _win32_console = new wxCheckBox (_panel, wxID_ANY, _("Open console window"));
1508                 table->Add (_win32_console, 1, wxEXPAND | wxALL);
1509                 table->AddSpacer (0);
1510 #endif
1511
1512                 _maximum_j2k_bandwidth->SetRange (1, 1000);
1513                 _maximum_j2k_bandwidth->Bind (wxEVT_SPINCTRL, boost::bind (&AdvancedPage::maximum_j2k_bandwidth_changed, this));
1514                 _allow_any_dcp_frame_rate->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::allow_any_dcp_frame_rate_changed, this));
1515                 _only_servers_encode->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::only_servers_encode_changed, this));
1516                 _dcp_metadata_filename_format->Changed.connect (boost::bind (&AdvancedPage::dcp_metadata_filename_format_changed, this));
1517                 _dcp_asset_filename_format->Changed.connect (boost::bind (&AdvancedPage::dcp_asset_filename_format_changed, this));
1518                 _log_general->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::log_changed, this));
1519                 _log_warning->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::log_changed, this));
1520                 _log_error->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::log_changed, this));
1521                 _log_timing->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::log_changed, this));
1522                 _log_debug_decode->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::log_changed, this));
1523                 _log_debug_encode->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::log_changed, this));
1524                 _log_debug_email->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::log_changed, this));
1525 #ifdef DCPOMATIC_WINDOWS
1526                 _win32_console->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::win32_console_changed, this));
1527 #endif
1528         }
1529
1530         void config_changed ()
1531         {
1532                 Config* config = Config::instance ();
1533
1534                 checked_set (_maximum_j2k_bandwidth, config->maximum_j2k_bandwidth() / 1000000);
1535                 checked_set (_allow_any_dcp_frame_rate, config->allow_any_dcp_frame_rate ());
1536                 checked_set (_only_servers_encode, config->only_servers_encode ());
1537                 checked_set (_log_general, config->log_types() & LogEntry::TYPE_GENERAL);
1538                 checked_set (_log_warning, config->log_types() & LogEntry::TYPE_WARNING);
1539                 checked_set (_log_error, config->log_types() & LogEntry::TYPE_ERROR);
1540                 checked_set (_log_timing, config->log_types() & LogEntry::TYPE_TIMING);
1541                 checked_set (_log_debug_decode, config->log_types() & LogEntry::TYPE_DEBUG_DECODE);
1542                 checked_set (_log_debug_encode, config->log_types() & LogEntry::TYPE_DEBUG_ENCODE);
1543                 checked_set (_log_debug_email, config->log_types() & LogEntry::TYPE_DEBUG_EMAIL);
1544 #ifdef DCPOMATIC_WINDOWS
1545                 checked_set (_win32_console, config->win32_console());
1546 #endif
1547         }
1548
1549         void maximum_j2k_bandwidth_changed ()
1550         {
1551                 Config::instance()->set_maximum_j2k_bandwidth (_maximum_j2k_bandwidth->GetValue() * 1000000);
1552         }
1553
1554         void allow_any_dcp_frame_rate_changed ()
1555         {
1556                 Config::instance()->set_allow_any_dcp_frame_rate (_allow_any_dcp_frame_rate->GetValue ());
1557         }
1558
1559         void only_servers_encode_changed ()
1560         {
1561                 Config::instance()->set_only_servers_encode (_only_servers_encode->GetValue ());
1562         }
1563
1564         void dcp_metadata_filename_format_changed ()
1565         {
1566                 Config::instance()->set_dcp_metadata_filename_format (_dcp_metadata_filename_format->get ());
1567         }
1568
1569         void dcp_asset_filename_format_changed ()
1570         {
1571                 Config::instance()->set_dcp_asset_filename_format (_dcp_asset_filename_format->get ());
1572         }
1573
1574         void log_changed ()
1575         {
1576                 int types = 0;
1577                 if (_log_general->GetValue ()) {
1578                         types |= LogEntry::TYPE_GENERAL;
1579                 }
1580                 if (_log_warning->GetValue ()) {
1581                         types |= LogEntry::TYPE_WARNING;
1582                 }
1583                 if (_log_error->GetValue ())  {
1584                         types |= LogEntry::TYPE_ERROR;
1585                 }
1586                 if (_log_timing->GetValue ()) {
1587                         types |= LogEntry::TYPE_TIMING;
1588                 }
1589                 if (_log_debug_decode->GetValue ()) {
1590                         types |= LogEntry::TYPE_DEBUG_DECODE;
1591                 }
1592                 if (_log_debug_encode->GetValue ()) {
1593                         types |= LogEntry::TYPE_DEBUG_ENCODE;
1594                 }
1595                 if (_log_debug_email->GetValue ()) {
1596                         types |= LogEntry::TYPE_DEBUG_EMAIL;
1597                 }
1598                 Config::instance()->set_log_types (types);
1599         }
1600
1601 #ifdef DCPOMATIC_WINDOWS
1602         void win32_console_changed ()
1603         {
1604                 Config::instance()->set_win32_console (_win32_console->GetValue ());
1605         }
1606 #endif
1607
1608         wxSpinCtrl* _maximum_j2k_bandwidth;
1609         wxCheckBox* _allow_any_dcp_frame_rate;
1610         wxCheckBox* _only_servers_encode;
1611         NameFormatEditor* _dcp_metadata_filename_format;
1612         NameFormatEditor* _dcp_asset_filename_format;
1613         wxCheckBox* _log_general;
1614         wxCheckBox* _log_warning;
1615         wxCheckBox* _log_error;
1616         wxCheckBox* _log_timing;
1617         wxCheckBox* _log_debug_decode;
1618         wxCheckBox* _log_debug_encode;
1619         wxCheckBox* _log_debug_email;
1620 #ifdef DCPOMATIC_WINDOWS
1621         wxCheckBox* _win32_console;
1622 #endif
1623 };
1624
1625 wxPreferencesEditor*
1626 create_config_dialog ()
1627 {
1628         wxPreferencesEditor* e = new wxPreferencesEditor ();
1629
1630 #ifdef DCPOMATIC_OSX
1631         /* Width that we force some of the config panels to be on OSX so that
1632            the containing window doesn't shrink too much when we select those panels.
1633            This is obviously an unpleasant hack.
1634         */
1635         wxSize ps = wxSize (520, -1);
1636         int const border = 16;
1637 #else
1638         wxSize ps = wxSize (-1, -1);
1639         int const border = 8;
1640 #endif
1641
1642         e->AddPage (new GeneralPage (ps, border));
1643         e->AddPage (new DefaultsPage (ps, border));
1644         e->AddPage (new EncodingServersPage (ps, border));
1645         e->AddPage (new KeysPage (ps, border));
1646         e->AddPage (new TMSPage (ps, border));
1647         e->AddPage (new KDMEmailPage (ps, border));
1648         e->AddPage (new AdvancedPage (ps, border));
1649         return e;
1650 }