Rename Encoder -> FilmEncoder, and subclasses.
[dcpomatic.git] / src / wx / config_dialog.cc
1 /*
2     Copyright (C) 2012-2021 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
22 #include "audio_mapping_view.h"
23 #include "check_box.h"
24 #include "config_dialog.h"
25 #include "dcpomatic_button.h"
26 #include "nag_dialog.h"
27 #include "static_text.h"
28 #include "wx_variant.h"
29 #include "lib/constants.h"
30 #include "lib/util.h"
31 #include <dcp/file.h>
32 #include <dcp/filesystem.h>
33 #include <dcp/raw_convert.h>
34
35
36 using std::function;
37 using std::make_pair;
38 using std::make_shared;
39 using std::map;
40 using std::pair;
41 using std::shared_ptr;
42 using std::string;
43 using std::vector;
44 using boost::bind;
45 using boost::optional;
46 #if BOOST_VERSION >= 106100
47 using namespace boost::placeholders;
48 #endif
49
50
51 static
52 bool
53 do_nothing ()
54 {
55         return false;
56 }
57
58 Page::Page (wxSize panel_size, int border)
59         : _border (border)
60         , _panel (0)
61         , _panel_size (panel_size)
62         , _window_exists (false)
63 {
64         _config_connection = Config::instance()->Changed.connect (bind (&Page::config_changed_wrapper, this));
65 }
66
67
68 wxWindow*
69 Page::CreateWindow (wxWindow* parent)
70 {
71         return create_window (parent);
72 }
73
74
75 wxWindow*
76 Page::create_window (wxWindow* parent)
77 {
78         _panel = new wxPanel (parent, wxID_ANY, wxDefaultPosition, _panel_size);
79         auto s = new wxBoxSizer (wxVERTICAL);
80         _panel->SetSizer (s);
81
82         setup ();
83         _window_exists = true;
84         config_changed ();
85
86         _panel->Bind (wxEVT_DESTROY, bind (&Page::window_destroyed, this));
87
88         return _panel;
89 }
90
91 void
92 Page::config_changed_wrapper ()
93 {
94         if (_window_exists) {
95                 config_changed ();
96         }
97 }
98
99 void
100 Page::window_destroyed ()
101 {
102         _window_exists = false;
103 }
104
105
106 GeneralPage::GeneralPage (wxSize panel_size, int border)
107         : Page (panel_size, border)
108 {
109
110 }
111
112
113 wxString
114 GeneralPage::GetName () const
115 {
116         return _("General");
117 }
118
119
120 void
121 GeneralPage::add_language_controls (wxGridBagSizer* table, int& r)
122 {
123         _set_language = new CheckBox (_panel, _("Set language"));
124         table->Add (_set_language, wxGBPosition (r, 0), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
125         _language = new wxChoice (_panel, wxID_ANY);
126         vector<pair<string, string>> languages;
127         languages.push_back (make_pair("Čeština", "cs_CZ"));
128         languages.push_back (make_pair("汉语/漢語", "zh_CN"));
129         languages.push_back (make_pair("Dansk", "da_DK"));
130         languages.push_back (make_pair("Deutsch", "de_DE"));
131         languages.push_back (make_pair("English", "en_GB"));
132         languages.push_back (make_pair("Español", "es_ES"));
133         languages.push_back (make_pair("فارسی", "fa_IR"));
134         languages.push_back (make_pair("Français", "fr_FR"));
135         languages.push_back (make_pair("Italiano", "it_IT"));
136         languages.push_back (make_pair("ქართული", "ka_KA"));
137         languages.push_back (make_pair("Nederlands", "nl_NL"));
138         languages.push_back (make_pair("Русский", "ru_RU"));
139         languages.push_back (make_pair("Polski", "pl_PL"));
140         languages.push_back (make_pair("Português europeu", "pt_PT"));
141         languages.push_back (make_pair("Português do Brasil", "pt_BR"));
142         languages.push_back (make_pair("Svenska", "sv_SE"));
143         languages.push_back (make_pair("Slovenščina", "sl_SI"));
144         languages.push_back (make_pair("Slovenský jazyk", "sk_SK"));
145         // languages.push_back (make_pair("Türkçe", "tr_TR"));
146         languages.push_back (make_pair("українська мова", "uk_UA"));
147         languages.push_back (make_pair("Magyar nyelv", "hu_HU"));
148         checked_set (_language, languages);
149         table->Add (_language, wxGBPosition (r, 1));
150         ++r;
151
152         auto restart = add_label_to_sizer (
153                 table, _panel, variant::wx::insert_dcpomatic(_("(restart %s to see language changes)")), false, wxGBPosition (r, 0), wxGBSpan (1, 2)
154                 );
155         wxFont font = restart->GetFont();
156         font.SetStyle (wxFONTSTYLE_ITALIC);
157         font.SetPointSize (font.GetPointSize() - 1);
158         restart->SetFont (font);
159         ++r;
160
161         _set_language->bind(&GeneralPage::set_language_changed, this);
162         _language->Bind     (wxEVT_CHOICE,   bind (&GeneralPage::language_changed,     this));
163 }
164
165 void
166 GeneralPage::add_update_controls (wxGridBagSizer* table, int& r)
167 {
168         _check_for_updates = new CheckBox (_panel, _("Check for updates on startup"));
169         table->Add (_check_for_updates, wxGBPosition (r, 0), wxGBSpan (1, 2));
170         ++r;
171
172         _check_for_test_updates = new CheckBox (_panel, _("Check for testing updates on startup"));
173         table->Add (_check_for_test_updates, wxGBPosition (r, 0), wxGBSpan (1, 2));
174         ++r;
175
176         _check_for_updates->bind(&GeneralPage::check_for_updates_changed, this);
177         _check_for_test_updates->bind(&GeneralPage::check_for_test_updates_changed, this);
178 }
179
180 void
181 GeneralPage::config_changed ()
182 {
183         auto config = Config::instance ();
184
185         checked_set (_set_language, static_cast<bool>(config->language()));
186
187         /* Backwards compatibility of config file */
188
189         map<string, string> compat_map;
190         compat_map["fr"] = "fr_FR";
191         compat_map["it"] = "it_IT";
192         compat_map["es"] = "es_ES";
193         compat_map["sv"] = "sv_SE";
194         compat_map["de"] = "de_DE";
195         compat_map["nl"] = "nl_NL";
196         compat_map["ru"] = "ru_RU";
197         compat_map["pl"] = "pl_PL";
198         compat_map["da"] = "da_DK";
199         compat_map["pt"] = "pt_PT";
200         compat_map["sk"] = "sk_SK";
201         compat_map["cs"] = "cs_CZ";
202         compat_map["uk"] = "uk_UA";
203
204         auto lang = config->language().get_value_or("en_GB");
205         if (compat_map.find(lang) != compat_map.end ()) {
206                 lang = compat_map[lang];
207         }
208
209         checked_set (_language, lang);
210
211         checked_set (_check_for_updates, config->check_for_updates ());
212         checked_set (_check_for_test_updates, config->check_for_test_updates ());
213
214         setup_sensitivity ();
215 }
216
217 void
218 GeneralPage::setup_sensitivity ()
219 {
220         _language->Enable (_set_language->GetValue ());
221         _check_for_test_updates->Enable (_check_for_updates->GetValue ());
222 }
223
224 void
225 GeneralPage::set_language_changed ()
226 {
227         setup_sensitivity ();
228         if (_set_language->GetValue ()) {
229                 language_changed ();
230         } else {
231                 Config::instance()->unset_language ();
232         }
233 }
234
235 void
236 GeneralPage::language_changed ()
237 {
238         int const sel = _language->GetSelection ();
239         if (sel != -1) {
240                 Config::instance()->set_language (string_client_data (_language->GetClientObject (sel)));
241         } else {
242                 Config::instance()->unset_language ();
243         }
244 }
245
246 void
247 GeneralPage::check_for_updates_changed ()
248 {
249         Config::instance()->set_check_for_updates (_check_for_updates->GetValue ());
250 }
251
252 void
253 GeneralPage::check_for_test_updates_changed ()
254 {
255         Config::instance()->set_check_for_test_updates (_check_for_test_updates->GetValue ());
256 }
257
258 CertificateChainEditor::CertificateChainEditor (
259         wxWindow* parent,
260         wxString title,
261         int border,
262         function<void (shared_ptr<dcp::CertificateChain>)> set,
263         function<shared_ptr<const dcp::CertificateChain> (void)> get,
264         function<bool (void)> nag_alter
265         )
266         : wxDialog (parent, wxID_ANY, title)
267         , _set (set)
268         , _get (get)
269         , _nag_alter (nag_alter)
270 {
271         _sizer = new wxBoxSizer (wxVERTICAL);
272
273         auto certificates_sizer = new wxBoxSizer (wxHORIZONTAL);
274         _sizer->Add (certificates_sizer, 0, wxALL, border);
275
276         _certificates = new wxListCtrl (this, wxID_ANY, wxDefaultPosition, wxSize (440, 150), wxLC_REPORT | wxLC_SINGLE_SEL);
277
278         {
279                 wxListItem ip;
280                 ip.SetId (0);
281                 ip.SetText (_("Type"));
282                 ip.SetWidth (100);
283                 _certificates->InsertColumn (0, ip);
284         }
285
286         {
287                 wxListItem ip;
288                 ip.SetId (1);
289                 ip.SetText (_("Thumbprint"));
290                 ip.SetWidth (340);
291
292                 wxFont font = ip.GetFont ();
293                 font.SetFamily (wxFONTFAMILY_TELETYPE);
294                 ip.SetFont (font);
295
296                 _certificates->InsertColumn (1, ip);
297         }
298
299         certificates_sizer->Add (_certificates, 1, wxEXPAND);
300
301         {
302                 auto s = new wxBoxSizer (wxVERTICAL);
303                 _add_certificate = new Button (this, _("Add..."));
304                 s->Add (_add_certificate, 1, wxTOP | wxBOTTOM | wxEXPAND, DCPOMATIC_BUTTON_STACK_GAP);
305                 _remove_certificate = new Button (this, _("Remove"));
306                 s->Add (_remove_certificate, 1, wxTOP | wxBOTTOM | wxEXPAND, DCPOMATIC_BUTTON_STACK_GAP);
307                 _export_certificate = new Button (this, _("Export certificate..."));
308                 s->Add (_export_certificate, 1, wxTOP | wxBOTTOM | wxEXPAND, DCPOMATIC_BUTTON_STACK_GAP);
309                 _export_chain = new Button (this, _("Export chain..."));
310                 s->Add (_export_chain, 1, wxTOP | wxBOTTOM | wxEXPAND, DCPOMATIC_BUTTON_STACK_GAP);
311                 certificates_sizer->Add (s, 0, wxLEFT, DCPOMATIC_SIZER_X_GAP);
312         }
313
314         auto table = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
315         _sizer->Add (table, 1, wxALL | wxEXPAND, border);
316         int r = 0;
317
318         add_label_to_sizer (table, this, _("Leaf private key"), true, wxGBPosition (r, 0));
319         _private_key = new StaticText (this, wxT(""));
320         wxFont font = _private_key->GetFont ();
321         font.SetFamily (wxFONTFAMILY_TELETYPE);
322         _private_key->SetFont (font);
323         table->Add (_private_key, wxGBPosition (r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
324         _import_private_key = new Button (this, _("Import..."));
325         table->Add (_import_private_key, wxGBPosition (r, 2));
326         _export_private_key = new Button (this, _("Export..."));
327         table->Add (_export_private_key, wxGBPosition (r, 3));
328         ++r;
329
330         _button_sizer = new wxBoxSizer (wxHORIZONTAL);
331         _remake_certificates = new Button (this, _("Re-make certificates and key..."));
332         _button_sizer->Add (_remake_certificates, 1, wxRIGHT, border);
333         table->Add (_button_sizer, wxGBPosition (r, 0), wxGBSpan (1, 4));
334         ++r;
335
336         _private_key_bad = new StaticText (this, _("Leaf private key does not match leaf certificate!"));
337         font = *wxSMALL_FONT;
338         font.SetWeight (wxFONTWEIGHT_BOLD);
339         _private_key_bad->SetFont (font);
340         table->Add (_private_key_bad, wxGBPosition (r, 0), wxGBSpan (1, 3));
341         ++r;
342
343         _add_certificate->Bind     (wxEVT_BUTTON,       bind (&CertificateChainEditor::add_certificate, this));
344         _remove_certificate->Bind  (wxEVT_BUTTON,       bind (&CertificateChainEditor::remove_certificate, this));
345         _export_certificate->Bind  (wxEVT_BUTTON,       bind (&CertificateChainEditor::export_certificate, this));
346         _certificates->Bind        (wxEVT_LIST_ITEM_SELECTED,   bind (&CertificateChainEditor::update_sensitivity, this));
347         _certificates->Bind        (wxEVT_LIST_ITEM_DESELECTED, bind (&CertificateChainEditor::update_sensitivity, this));
348         _remake_certificates->Bind (wxEVT_BUTTON,       bind (&CertificateChainEditor::remake_certificates, this));
349         _export_chain->Bind        (wxEVT_BUTTON,       bind (&CertificateChainEditor::export_chain, this));
350         _import_private_key->Bind  (wxEVT_BUTTON,       bind (&CertificateChainEditor::import_private_key, this));
351         _export_private_key->Bind  (wxEVT_BUTTON,       bind (&CertificateChainEditor::export_private_key, this));
352
353         auto buttons = CreateSeparatedButtonSizer (wxCLOSE);
354         if (buttons) {
355                 _sizer->Add (buttons, wxSizerFlags().Expand().DoubleBorder());
356         }
357
358         SetSizerAndFit (_sizer);
359
360         update_certificate_list ();
361         update_private_key ();
362         update_sensitivity ();
363 }
364
365 void
366 CertificateChainEditor::add_button (wxWindow* button)
367 {
368         _button_sizer->Add (button, 0, wxLEFT | wxRIGHT, DCPOMATIC_SIZER_X_GAP);
369         _sizer->Layout ();
370 }
371
372 void
373 CertificateChainEditor::add_certificate ()
374 {
375         auto d = make_wx<wxFileDialog>(this, _("Select Certificate File"));
376
377         if (d->ShowModal() == wxID_OK) {
378                 try {
379                         dcp::Certificate c;
380                         string extra;
381                         try {
382                                 extra = c.read_string (dcp::file_to_string (wx_to_std (d->GetPath ())));
383                         } catch (boost::filesystem::filesystem_error& e) {
384                                 error_dialog (this, _("Could not import certificate (%s)"), d->GetPath());
385                                 return;
386                         }
387
388                         if (!extra.empty ()) {
389                                 message_dialog (
390                                         this,
391                                         _("This file contains other certificates (or other data) after its first certificate. "
392                                           "Only the first certificate will be used.")
393                                         );
394                         }
395                         auto chain = make_shared<dcp::CertificateChain>(*_get().get());
396                         chain->add (c);
397                         if (!chain->chain_valid ()) {
398                                 error_dialog (
399                                         this,
400                                         _("Adding this certificate would make the chain inconsistent, so it will not be added. "
401                                           "Add certificates in order from root to intermediate to leaf.")
402                                         );
403                                 chain->remove (c);
404                         } else {
405                                 _set (chain);
406                                 update_certificate_list ();
407                         }
408                 } catch (dcp::MiscError& e) {
409                         error_dialog (this, _("Could not read certificate file."), std_to_wx(e.what()));
410                 }
411         }
412
413         update_sensitivity ();
414 }
415
416 void
417 CertificateChainEditor::remove_certificate ()
418 {
419         if (_nag_alter()) {
420                 /* Cancel was clicked */
421                 return;
422         }
423
424         int i = _certificates->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
425         if (i == -1) {
426                 return;
427         }
428
429         _certificates->DeleteItem (i);
430         auto chain = make_shared<dcp::CertificateChain>(*_get().get());
431         chain->remove (i);
432         _set (chain);
433
434         update_sensitivity ();
435         update_certificate_list ();
436 }
437
438 void
439 CertificateChainEditor::export_certificate ()
440 {
441         int i = _certificates->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
442         if (i == -1) {
443                 return;
444         }
445
446         auto all = _get()->root_to_leaf();
447
448         wxString default_name;
449         if (i == 0) {
450                 default_name = "root.pem";
451         } else if (i == static_cast<int>(all.size() - 1)) {
452                 default_name = "leaf.pem";
453         } else {
454                 default_name = "intermediate.pem";
455         }
456
457         auto d = make_wx<wxFileDialog>(
458                 this, _("Select Certificate File"), wxEmptyString, default_name, wxT ("PEM files (*.pem)|*.pem"),
459                 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
460                 );
461
462         auto j = all.begin ();
463         for (int k = 0; k < i; ++k) {
464                 ++j;
465         }
466
467         if (d->ShowModal() != wxID_OK) {
468                 return;
469         }
470
471         boost::filesystem::path path(wx_to_std(d->GetPath()));
472         if (path.extension() != ".pem") {
473                 path += ".pem";
474         }
475         dcp::File f(path, "w");
476         if (!f) {
477                 throw OpenFileError(path, errno, OpenFileError::WRITE);
478         }
479
480         string const s = j->certificate(true);
481         f.checked_write(s.c_str(), s.length());
482 }
483
484 void
485 CertificateChainEditor::export_chain ()
486 {
487         auto d = make_wx<wxFileDialog>(
488                 this, _("Select Chain File"), wxEmptyString, wxT("certificate_chain.pem"), wxT("PEM files (*.pem)|*.pem"),
489                 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
490                 );
491
492         if (d->ShowModal() != wxID_OK) {
493                 return;
494         }
495
496         boost::filesystem::path path(wx_to_std(d->GetPath()));
497         if (path.extension() != ".pem") {
498                 path += ".pem";
499         }
500         dcp::File f(path, "w");
501         if (!f) {
502                 throw OpenFileError(path, errno, OpenFileError::WRITE);
503         }
504
505         auto const s = _get()->chain();
506         f.checked_write(s.c_str(), s.length());
507 }
508
509 void
510 CertificateChainEditor::update_certificate_list ()
511 {
512         _certificates->DeleteAllItems ();
513         size_t n = 0;
514         auto certs = _get()->root_to_leaf();
515         for (auto const& i: certs) {
516                 wxListItem item;
517                 item.SetId (n);
518                 _certificates->InsertItem (item);
519                 _certificates->SetItem (n, 1, std_to_wx (i.thumbprint ()));
520
521                 if (n == 0) {
522                         _certificates->SetItem (n, 0, _("Root"));
523                 } else if (n == (certs.size() - 1)) {
524                         _certificates->SetItem (n, 0, _("Leaf"));
525                 } else {
526                         _certificates->SetItem (n, 0, _("Intermediate"));
527                 }
528
529                 ++n;
530         }
531
532         static wxColour normal = _private_key_bad->GetForegroundColour ();
533
534         if (_get()->private_key_valid()) {
535                 _private_key_bad->Hide ();
536                 _private_key_bad->SetForegroundColour (normal);
537         } else {
538                 _private_key_bad->Show ();
539                 _private_key_bad->SetForegroundColour (wxColour (255, 0, 0));
540         }
541 }
542
543 void
544 CertificateChainEditor::remake_certificates ()
545 {
546         if (_nag_alter()) {
547                 /* Cancel was clicked */
548                 return;
549         }
550
551         auto d = make_wx<MakeChainDialog>(this, _get());
552
553         if (d->ShowModal () == wxID_OK) {
554                 _set (d->get());
555                 update_certificate_list ();
556                 update_private_key ();
557         }
558 }
559
560 void
561 CertificateChainEditor::update_sensitivity ()
562 {
563         /* We can only remove the leaf certificate */
564         _remove_certificate->Enable (_certificates->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) == (_certificates->GetItemCount() - 1));
565         _export_certificate->Enable (_certificates->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) != -1);
566 }
567
568 void
569 CertificateChainEditor::update_private_key ()
570 {
571         checked_set (_private_key, dcp::private_key_fingerprint (_get()->key().get()));
572         _sizer->Layout ();
573 }
574
575 void
576 CertificateChainEditor::import_private_key ()
577 {
578         auto d = make_wx<wxFileDialog>(this, _("Select Key File"));
579
580         if (d->ShowModal() == wxID_OK) {
581                 try {
582                         boost::filesystem::path p (wx_to_std (d->GetPath ()));
583                         if (dcp::filesystem::file_size(p) > 8192) {
584                                 error_dialog (
585                                         this,
586                                         wxString::Format (_("Could not read key file; file is too long (%s)"), std_to_wx (p.string ()))
587                                         );
588                                 return;
589                         }
590
591                         auto chain = make_shared<dcp::CertificateChain>(*_get().get());
592                         chain->set_key (dcp::file_to_string (p));
593                         _set (chain);
594                         update_private_key ();
595                 } catch (std::exception& e) {
596                         error_dialog (this, _("Could not read certificate file."), std_to_wx(e.what()));
597                 }
598         }
599
600         update_sensitivity ();
601 }
602
603 void
604 CertificateChainEditor::export_private_key ()
605 {
606         auto key = _get()->key();
607         if (!key) {
608                 return;
609         }
610
611         auto d = make_wx<wxFileDialog>(
612                 this, _("Select Key File"), wxEmptyString, wxT("private_key.pem"), wxT("PEM files (*.pem)|*.pem"),
613                 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
614                 );
615
616         if (d->ShowModal () == wxID_OK) {
617                 boost::filesystem::path path (wx_to_std(d->GetPath()));
618                 if (path.extension() != ".pem") {
619                         path += ".pem";
620                 }
621                 dcp::File f(path, "w");
622                 if (!f) {
623                         throw OpenFileError (path, errno, OpenFileError::WRITE);
624                 }
625
626                 auto const s = _get()->key().get ();
627                 f.checked_write(s.c_str(), s.length());
628         }
629 }
630
631 wxString
632 KeysPage::GetName () const
633 {
634         return _("Keys");
635 }
636
637 void
638 KeysPage::setup ()
639 {
640         wxFont subheading_font (*wxNORMAL_FONT);
641         subheading_font.SetWeight (wxFONTWEIGHT_BOLD);
642
643         auto sizer = _panel->GetSizer();
644
645         {
646                 auto m = new StaticText (_panel, _("Decrypting KDMs"));
647                 m->SetFont (subheading_font);
648                 sizer->Add (m, 0, wxALL | wxEXPAND, _border);
649         }
650
651         auto kdm_buttons = new wxBoxSizer (wxVERTICAL);
652
653         auto export_decryption_certificate = new Button (_panel, _("Export KDM decryption leaf certificate..."));
654         kdm_buttons->Add (export_decryption_certificate, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
655         auto export_settings = new Button (_panel, _("Export all KDM decryption settings..."));
656         kdm_buttons->Add (export_settings, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
657         auto import_settings = new Button (_panel, _("Import all KDM decryption settings..."));
658         kdm_buttons->Add (import_settings, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
659         auto decryption_advanced = new Button (_panel, _("Advanced..."));
660         kdm_buttons->Add (decryption_advanced, 0);
661
662         sizer->Add (kdm_buttons, 0, wxLEFT, _border);
663
664         export_decryption_certificate->Bind (wxEVT_BUTTON, bind (&KeysPage::export_decryption_certificate, this));
665         export_settings->Bind (wxEVT_BUTTON, bind (&KeysPage::export_decryption_chain_and_key, this));
666         import_settings->Bind (wxEVT_BUTTON, bind (&KeysPage::import_decryption_chain_and_key, this));
667         decryption_advanced->Bind (wxEVT_BUTTON, bind (&KeysPage::decryption_advanced, this));
668
669         {
670                 auto m = new StaticText (_panel, _("Signing DCPs and KDMs"));
671                 m->SetFont (subheading_font);
672                 sizer->Add (m, 0, wxALL | wxEXPAND, _border);
673         }
674
675         auto signing_buttons = new wxBoxSizer (wxVERTICAL);
676
677         auto signing_advanced = new Button (_panel, _("Advanced..."));
678         signing_buttons->Add (signing_advanced, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
679         auto remake_signing = new Button (_panel, _("Re-make certificates and key..."));
680         signing_buttons->Add (remake_signing, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
681
682         sizer->Add (signing_buttons, 0, wxLEFT, _border);
683
684         signing_advanced->Bind (wxEVT_BUTTON, bind (&KeysPage::signing_advanced, this));
685         remake_signing->Bind (wxEVT_BUTTON, bind(&KeysPage::remake_signing, this));
686 }
687
688
689 void
690 KeysPage::remake_signing ()
691 {
692         auto d = new MakeChainDialog (_panel, Config::instance()->signer_chain());
693
694         if (d->ShowModal () == wxID_OK) {
695                 Config::instance()->set_signer_chain(d->get());
696         }
697 }
698
699
700 void
701 KeysPage::decryption_advanced ()
702 {
703         auto c = new CertificateChainEditor (
704                 _panel, _("Decrypting KDMs"), _border,
705                 bind(&Config::set_decryption_chain, Config::instance(), _1),
706                 bind(&Config::decryption_chain, Config::instance()),
707                 bind(&KeysPage::nag_alter_decryption_chain, this)
708                 );
709
710         c->ShowModal();
711 }
712
713 void
714 KeysPage::signing_advanced ()
715 {
716         auto c = new CertificateChainEditor (
717                 _panel, _("Signing DCPs and KDMs"), _border,
718                 bind(&Config::set_signer_chain, Config::instance(), _1),
719                 bind(&Config::signer_chain, Config::instance()),
720                 bind(&do_nothing)
721                 );
722
723         c->ShowModal();
724 }
725
726 void
727 KeysPage::export_decryption_chain_and_key ()
728 {
729         auto d = make_wx<wxFileDialog>(
730                 _panel, _("Select Export File"), wxEmptyString, wxEmptyString, wxT ("DOM files (*.dom)|*.dom"),
731                 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
732                 );
733
734         if (d->ShowModal() != wxID_OK) {
735                 return;
736         }
737
738         boost::filesystem::path path(wx_to_std(d->GetPath()));
739         dcp::File f(path, "w");
740         if (!f) {
741                 throw OpenFileError(path, errno, OpenFileError::WRITE);
742         }
743
744         auto const chain = Config::instance()->decryption_chain()->chain();
745         f.checked_write(chain.c_str(), chain.length());
746         auto const key = Config::instance()->decryption_chain()->key();
747         DCPOMATIC_ASSERT(key);
748         f.checked_write(key->c_str(), key->length());
749 }
750
751 void
752 KeysPage::import_decryption_chain_and_key ()
753 {
754         if (NagDialog::maybe_nag (
755                     _panel,
756                     Config::NAG_IMPORT_DECRYPTION_CHAIN,
757                     _("If you continue with this operation you will no longer be able to use any DKDMs that you have created with the current certificates and key.  Also, any KDMs that have been sent to you for those certificates will become useless.  Proceed with caution!"),
758                     true
759                     )) {
760                 return;
761         }
762
763         auto d = make_wx<wxFileDialog>(
764                 _panel, _("Select File To Import"), wxEmptyString, wxEmptyString, wxT ("DOM files (*.dom)|*.dom")
765                 );
766
767         if (d->ShowModal() != wxID_OK) {
768                 return;
769         }
770
771         auto new_chain = make_shared<dcp::CertificateChain>();
772
773         dcp::File f(wx_to_std(d->GetPath()), "r");
774         if (!f) {
775                 throw OpenFileError(f.path(), errno, OpenFileError::WRITE);
776         }
777
778         string current;
779         while (!f.eof()) {
780                 char buffer[128];
781                 if (f.gets(buffer, 128) == 0) {
782                         break;
783                 }
784                 current += buffer;
785                 if (strncmp (buffer, "-----END CERTIFICATE-----", 25) == 0) {
786                         new_chain->add(dcp::Certificate(current));
787                         current = "";
788                 } else if (strncmp (buffer, "-----END RSA PRIVATE KEY-----", 29) == 0) {
789                         new_chain->set_key(current);
790                         current = "";
791                 }
792         }
793
794         if (new_chain->chain_valid() && new_chain->private_key_valid()) {
795                 Config::instance()->set_decryption_chain(new_chain);
796         } else {
797                 error_dialog(_panel, variant::wx::insert_dcpomatic(_("Invalid %s export file")));
798         }
799 }
800
801 bool
802 KeysPage::nag_alter_decryption_chain ()
803 {
804         return NagDialog::maybe_nag (
805                 _panel,
806                 Config::NAG_ALTER_DECRYPTION_CHAIN,
807                 _("If you continue with this operation you will no longer be able to use any DKDMs that you have created.  Also, any KDMs that have been sent to you will become useless.  Proceed with caution!"),
808                 true
809                 );
810 }
811
812 void
813 KeysPage::export_decryption_certificate ()
814 {
815         auto config = Config::instance();
816         wxString default_name = "dcpomatic";
817         if (!config->dcp_creator().empty()) {
818                 default_name += "_" + std_to_wx(careful_string_filter(config->dcp_creator()));
819         }
820         if (!config->dcp_issuer().empty()) {
821                 default_name += "_" + std_to_wx(careful_string_filter(config->dcp_issuer()));
822         }
823         default_name += wxT("_kdm_decryption_cert.pem");
824
825         auto d = make_wx<wxFileDialog>(
826                 _panel, _("Select Certificate File"), wxEmptyString, default_name, wxT("PEM files (*.pem)|*.pem"),
827                 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
828                 );
829
830         if (d->ShowModal() != wxID_OK) {
831                 return;
832         }
833
834         boost::filesystem::path path(wx_to_std(d->GetPath()));
835         if (path.extension() != ".pem") {
836                 path += ".pem";
837         }
838         dcp::File f(path, "w");
839         if (!f) {
840                 throw OpenFileError(path, errno, OpenFileError::WRITE);
841         }
842
843         auto const s = Config::instance()->decryption_chain()->leaf().certificate (true);
844         f.checked_write(s.c_str(), s.length());
845 }
846
847 wxString
848 SoundPage::GetName () const
849 {
850         return _("Sound");
851 }
852
853 void
854 SoundPage::setup ()
855 {
856         auto table = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
857         _panel->GetSizer()->Add (table, 1, wxALL | wxEXPAND, _border);
858
859         int r = 0;
860
861         _sound = new CheckBox (_panel, _("Play sound via"));
862         table->Add (_sound, wxGBPosition (r, 0), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
863         wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
864         _sound_output = new wxChoice (_panel, wxID_ANY);
865         s->Add (_sound_output, 0);
866         _sound_output_details = new wxStaticText (_panel, wxID_ANY, wxT(""));
867         s->Add (_sound_output_details, 1, wxALIGN_CENTER_VERTICAL | wxLEFT, DCPOMATIC_SIZER_X_GAP);
868         table->Add (s, wxGBPosition(r, 1));
869         ++r;
870
871         add_label_to_sizer (table, _panel, _("Mapping"), true, wxGBPosition(r, 0));
872         _map = new AudioMappingView (_panel, _("DCP"), _("DCP"), _("Output"), _("output"));
873         table->Add (_map, wxGBPosition(r, 1), wxDefaultSpan, wxEXPAND);
874         ++r;
875
876         _reset_to_default = new Button (_panel, _("Reset to default"));
877         table->Add (_reset_to_default, wxGBPosition(r, 1));
878         ++r;
879
880         wxFont font = _sound_output_details->GetFont();
881         font.SetStyle (wxFONTSTYLE_ITALIC);
882         font.SetPointSize (font.GetPointSize() - 1);
883         _sound_output_details->SetFont (font);
884
885         RtAudio audio (DCPOMATIC_RTAUDIO_API);
886 #if (RTAUDIO_VERSION_MAJOR >= 6)
887         for (auto device_id: audio.getDeviceIds()) {
888                 auto dev = audio.getDeviceInfo(device_id);
889                 if (dev.outputChannels > 0) {
890                         _sound_output->Append(std_to_wx(dev.name));
891                 }
892         }
893 #else
894         for (unsigned int i = 0; i < audio.getDeviceCount(); ++i) {
895                 try {
896                         auto dev = audio.getDeviceInfo (i);
897                         if (dev.probed && dev.outputChannels > 0) {
898                                 _sound_output->Append (std_to_wx (dev.name));
899                         }
900                 } catch (RtAudioError&) {
901                         /* Something went wrong so let's just ignore that device */
902                 }
903         }
904 #endif
905
906         _sound->bind(&SoundPage::sound_changed, this);
907         _sound_output->Bind (wxEVT_CHOICE,   bind(&SoundPage::sound_output_changed, this));
908         _map->Changed.connect (bind(&SoundPage::map_changed, this, _1));
909         _reset_to_default->Bind (wxEVT_BUTTON,   bind(&SoundPage::reset_to_default, this));
910 }
911
912 void
913 SoundPage::reset_to_default ()
914 {
915         Config::instance()->set_audio_mapping_to_default ();
916 }
917
918 void
919 SoundPage::map_changed (AudioMapping m)
920 {
921         Config::instance()->set_audio_mapping (m);
922 }
923
924 void
925 SoundPage::sound_changed ()
926 {
927         Config::instance()->set_sound (_sound->GetValue ());
928 }
929
930 void
931 SoundPage::sound_output_changed ()
932 {
933         RtAudio audio (DCPOMATIC_RTAUDIO_API);
934         auto const so = get_sound_output();
935         string default_device;
936 #if (RTAUDIO_VERSION_MAJOR >= 6)
937         default_device = audio.getDeviceInfo(audio.getDefaultOutputDevice()).name;
938 #else
939         try {
940                 default_device = audio.getDeviceInfo(audio.getDefaultOutputDevice()).name;
941         } catch (RtAudioError&) {
942                 /* Never mind */
943         }
944 #endif
945         if (!so || *so == default_device) {
946                 Config::instance()->unset_sound_output ();
947         } else {
948                 Config::instance()->set_sound_output (*so);
949         }
950 }
951
952 void
953 SoundPage::config_changed ()
954 {
955         auto config = Config::instance ();
956
957         checked_set (_sound, config->sound ());
958
959         auto const current_so = get_sound_output ();
960         optional<string> configured_so;
961
962         if (config->sound_output()) {
963                 configured_so = config->sound_output().get();
964         } else {
965                 /* No configured output means we should use the default */
966                 RtAudio audio (DCPOMATIC_RTAUDIO_API);
967 #if (RTAUDIO_VERSION_MAJOR >= 6)
968                 configured_so = audio.getDeviceInfo(audio.getDefaultOutputDevice()).name;
969 #else
970                 try {
971                         configured_so = audio.getDeviceInfo(audio.getDefaultOutputDevice()).name;
972                 } catch (RtAudioError&) {
973                         /* Probably no audio devices at all */
974                 }
975 #endif
976         }
977
978         if (configured_so && current_so != configured_so) {
979                 /* Update _sound_output with the configured value */
980                 unsigned int i = 0;
981                 while (i < _sound_output->GetCount()) {
982                         if (_sound_output->GetString(i) == std_to_wx(*configured_so)) {
983                                 _sound_output->SetSelection (i);
984                                 break;
985                         }
986                         ++i;
987                 }
988         }
989
990         RtAudio audio (DCPOMATIC_RTAUDIO_API);
991
992         map<int, wxString> apis;
993         apis[RtAudio::MACOSX_CORE]    = _("CoreAudio");
994         apis[RtAudio::WINDOWS_ASIO]   = _("ASIO");
995         apis[RtAudio::WINDOWS_DS]     = _("Direct Sound");
996         apis[RtAudio::WINDOWS_WASAPI] = _("WASAPI");
997         apis[RtAudio::UNIX_JACK]      = _("JACK");
998         apis[RtAudio::LINUX_ALSA]     = _("ALSA");
999         apis[RtAudio::LINUX_PULSE]    = _("PulseAudio");
1000         apis[RtAudio::LINUX_OSS]      = _("OSS");
1001         apis[RtAudio::RTAUDIO_DUMMY]  = _("Dummy");
1002
1003         int channels = 0;
1004         if (configured_so) {
1005 #if (RTAUDIO_VERSION_MAJOR >= 6)
1006                 for (auto device_id: audio.getDeviceIds()) {
1007                         auto info = audio.getDeviceInfo(device_id);
1008                         if (info.name == *configured_so && info.outputChannels > 0) {
1009                                 channels = info.outputChannels;
1010                         }
1011                 }
1012 #else
1013                 for (unsigned int i = 0; i < audio.getDeviceCount(); ++i) {
1014                         try {
1015                                 auto info = audio.getDeviceInfo(i);
1016                                 if (info.name == *configured_so && info.outputChannels > 0) {
1017                                         channels = info.outputChannels;
1018                                 }
1019                         } catch (RtAudioError&) {
1020                                 /* Never mind */
1021                         }
1022                 }
1023 #endif
1024         }
1025
1026         _sound_output_details->SetLabel (
1027                 wxString::Format(_("%d channels on %s"), channels, apis[audio.getCurrentApi()])
1028                 );
1029
1030         _map->set (Config::instance()->audio_mapping(channels));
1031
1032         vector<NamedChannel> input;
1033         for (int i = 0; i < MAX_DCP_AUDIO_CHANNELS; ++i) {
1034                 input.push_back (NamedChannel(short_audio_channel_name(i), i));
1035         }
1036         _map->set_input_channels (input);
1037
1038         vector<NamedChannel> output;
1039         for (int i = 0; i < channels; ++i) {
1040                 output.push_back (NamedChannel(dcp::raw_convert<string>(i), i));
1041         }
1042         _map->set_output_channels (output);
1043
1044         setup_sensitivity ();
1045 }
1046
1047 void
1048 SoundPage::setup_sensitivity ()
1049 {
1050         _sound_output->Enable (_sound->GetValue());
1051 }
1052
1053 /** @return Currently-selected preview sound output in the dialogue */
1054 optional<string>
1055 SoundPage::get_sound_output ()
1056 {
1057         int const sel = _sound_output->GetSelection ();
1058         if (sel == wxNOT_FOUND) {
1059                 return optional<string> ();
1060         }
1061
1062         return wx_to_std (_sound_output->GetString (sel));
1063 }
1064
1065
1066 LocationsPage::LocationsPage (wxSize panel_size, int border)
1067         : Page (panel_size, border)
1068 {
1069
1070 }
1071
1072 wxString
1073 LocationsPage::GetName () const
1074 {
1075         return _("Locations");
1076 }
1077
1078 #ifdef DCPOMATIC_OSX
1079 wxBitmap
1080 LocationsPage::GetLargeIcon () const
1081 {
1082         return wxBitmap(icon_path("locations"), wxBITMAP_TYPE_PNG);
1083 }
1084 #endif
1085
1086 void
1087 LocationsPage::setup ()
1088 {
1089         int r = 0;
1090
1091         auto table = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
1092         _panel->GetSizer()->Add (table, 1, wxALL | wxEXPAND, _border);
1093
1094         add_label_to_sizer (table, _panel, _("Content directory"), true, wxGBPosition (r, 0));
1095         _content_directory = new wxDirPickerCtrl (_panel, wxID_ANY, wxEmptyString, wxDirSelectorPromptStr, wxDefaultPosition, wxSize (300, -1));
1096         table->Add (_content_directory, wxGBPosition (r, 1));
1097         ++r;
1098
1099         add_label_to_sizer (table, _panel, _("Playlist directory"), true, wxGBPosition (r, 0));
1100         _playlist_directory = new wxDirPickerCtrl (_panel, wxID_ANY, wxEmptyString, wxDirSelectorPromptStr, wxDefaultPosition, wxSize (300, -1));
1101         table->Add (_playlist_directory, wxGBPosition (r, 1));
1102         ++r;
1103
1104         add_label_to_sizer (table, _panel, _("KDM directory"), true, wxGBPosition (r, 0));
1105         _kdm_directory = new wxDirPickerCtrl (_panel, wxID_ANY, wxEmptyString, wxDirSelectorPromptStr, wxDefaultPosition, wxSize (300, -1));
1106         table->Add (_kdm_directory, wxGBPosition (r, 1));
1107         ++r;
1108
1109         _content_directory->Bind (wxEVT_DIRPICKER_CHANGED, bind(&LocationsPage::content_directory_changed, this));
1110         _playlist_directory->Bind (wxEVT_DIRPICKER_CHANGED, bind(&LocationsPage::playlist_directory_changed, this));
1111         _kdm_directory->Bind (wxEVT_DIRPICKER_CHANGED, bind(&LocationsPage::kdm_directory_changed, this));
1112 }
1113
1114 void
1115 LocationsPage::config_changed ()
1116 {
1117         auto config = Config::instance ();
1118
1119         if (config->player_content_directory()) {
1120                 checked_set (_content_directory, *config->player_content_directory());
1121         }
1122         if (config->player_playlist_directory()) {
1123                 checked_set (_playlist_directory, *config->player_playlist_directory());
1124         }
1125         if (config->player_kdm_directory()) {
1126                 checked_set (_kdm_directory, *config->player_kdm_directory());
1127         }
1128 }
1129
1130 void
1131 LocationsPage::content_directory_changed ()
1132 {
1133         Config::instance()->set_player_content_directory(wx_to_std(_content_directory->GetPath()));
1134 }
1135
1136 void
1137 LocationsPage::playlist_directory_changed ()
1138 {
1139         Config::instance()->set_player_playlist_directory(wx_to_std(_playlist_directory->GetPath()));
1140 }
1141
1142 void
1143 LocationsPage::kdm_directory_changed ()
1144 {
1145         Config::instance()->set_player_kdm_directory(wx_to_std(_kdm_directory->GetPath()));
1146 }