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