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