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