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