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