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