Revert overwrite changes; remove our replacement of file extension (#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"),
519                 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
520                 );
521
522         dcp::CertificateChain::List all = _get()->root_to_leaf ();
523         dcp::CertificateChain::List::iterator j = all.begin ();
524         for (int k = 0; k < i; ++k) {
525                 ++j;
526         }
527
528         if (d->ShowModal () == wxID_OK) {
529                 boost::filesystem::path path (wx_to_std(d->GetPath()));
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         d->Destroy ();
540 }
541
542 void
543 CertificateChainEditor::update_certificate_list ()
544 {
545         _certificates->DeleteAllItems ();
546         size_t n = 0;
547         dcp::CertificateChain::List certs = _get()->root_to_leaf ();
548         BOOST_FOREACH (dcp::Certificate const & i, certs) {
549                 wxListItem item;
550                 item.SetId (n);
551                 _certificates->InsertItem (item);
552                 _certificates->SetItem (n, 1, std_to_wx (i.thumbprint ()));
553
554                 if (n == 0) {
555                         _certificates->SetItem (n, 0, _("Root"));
556                 } else if (n == (certs.size() - 1)) {
557                         _certificates->SetItem (n, 0, _("Leaf"));
558                 } else {
559                         _certificates->SetItem (n, 0, _("Intermediate"));
560                 }
561
562                 ++n;
563         }
564
565         static wxColour normal = _private_key_bad->GetForegroundColour ();
566
567         if (_get()->private_key_valid()) {
568                 _private_key_bad->Hide ();
569                 _private_key_bad->SetForegroundColour (normal);
570         } else {
571                 _private_key_bad->Show ();
572                 _private_key_bad->SetForegroundColour (wxColour (255, 0, 0));
573         }
574 }
575
576 void
577 CertificateChainEditor::remake_certificates ()
578 {
579         shared_ptr<const dcp::CertificateChain> chain = _get();
580
581         string subject_organization_name;
582         string subject_organizational_unit_name;
583         string root_common_name;
584         string intermediate_common_name;
585         string leaf_common_name;
586
587         dcp::CertificateChain::List all = chain->root_to_leaf ();
588
589         if (all.size() >= 1) {
590                 /* Have a root */
591                 subject_organization_name = chain->root().subject_organization_name ();
592                 subject_organizational_unit_name = chain->root().subject_organizational_unit_name ();
593                 root_common_name = chain->root().subject_common_name ();
594         }
595
596         if (all.size() >= 2) {
597                 /* Have a leaf */
598                 leaf_common_name = chain->leaf().subject_common_name ();
599         }
600
601         if (all.size() >= 3) {
602                 /* Have an intermediate */
603                 dcp::CertificateChain::List::iterator i = all.begin ();
604                 ++i;
605                 intermediate_common_name = i->subject_common_name ();
606         }
607
608         if (_nag_remake()) {
609                 /* Cancel was clicked */
610                 return;
611         }
612
613         MakeChainDialog* d = new MakeChainDialog (
614                 this,
615                 subject_organization_name,
616                 subject_organizational_unit_name,
617                 root_common_name,
618                 intermediate_common_name,
619                 leaf_common_name
620                 );
621
622         if (d->ShowModal () == wxID_OK) {
623                 _set (
624                         shared_ptr<dcp::CertificateChain> (
625                                 new dcp::CertificateChain (
626                                         openssl_path (),
627                                         d->organisation (),
628                                         d->organisational_unit (),
629                                         d->root_common_name (),
630                                         d->intermediate_common_name (),
631                                         d->leaf_common_name ()
632                                         )
633                                 )
634                         );
635
636                 update_certificate_list ();
637                 update_private_key ();
638         }
639
640         d->Destroy ();
641 }
642
643 void
644 CertificateChainEditor::update_sensitivity ()
645 {
646         /* We can only remove the leaf certificate */
647         _remove_certificate->Enable (_certificates->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) == (_certificates->GetItemCount() - 1));
648         _export_certificate->Enable (_certificates->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) != -1);
649 }
650
651 void
652 CertificateChainEditor::update_private_key ()
653 {
654         checked_set (_private_key, dcp::private_key_fingerprint (_get()->key().get()));
655         _sizer->Layout ();
656 }
657
658 void
659 CertificateChainEditor::import_private_key ()
660 {
661         wxFileDialog* d = new wxFileDialog (this, _("Select Key File"));
662
663         if (d->ShowModal() == wxID_OK) {
664                 try {
665                         boost::filesystem::path p (wx_to_std (d->GetPath ()));
666                         if (boost::filesystem::file_size (p) > 8192) {
667                                 error_dialog (
668                                         this,
669                                         wxString::Format (_("Could not read key file; file is too long (%s)"), std_to_wx (p.string ()))
670                                         );
671                                 return;
672                         }
673
674                         shared_ptr<dcp::CertificateChain> chain(new dcp::CertificateChain(*_get().get()));
675                         chain->set_key (dcp::file_to_string (p));
676                         _set (chain);
677                         update_private_key ();
678                 } catch (dcp::MiscError& e) {
679                         error_dialog (this, _("Could not read certificate file."), std_to_wx(e.what()));
680                 }
681         }
682
683         d->Destroy ();
684
685         update_sensitivity ();
686 }
687
688 void
689 CertificateChainEditor::export_private_key ()
690 {
691         optional<string> key = _get()->key();
692         if (!key) {
693                 return;
694         }
695
696         wxFileDialog* d = new wxFileDialog (
697                 this, _("Select Key File"), wxEmptyString, wxEmptyString, wxT ("PEM files (*.pem)|*.pem"),
698                 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
699                 );
700
701         if (d->ShowModal () == wxID_OK) {
702                 boost::filesystem::path path (wx_to_std(d->GetPath()));
703                 FILE* f = fopen_boost (path, "w");
704                 if (!f) {
705                         throw OpenFileError (path, errno, false);
706                 }
707
708                 string const s = _get()->key().get ();
709                 fwrite (s.c_str(), 1, s.length(), f);
710                 fclose (f);
711         }
712         d->Destroy ();
713 }
714
715 wxString
716 KeysPage::GetName () const
717 {
718         return _("Keys");
719 }
720
721 void
722 KeysPage::setup ()
723 {
724         wxFont subheading_font (*wxNORMAL_FONT);
725         subheading_font.SetWeight (wxFONTWEIGHT_BOLD);
726
727         wxSizer* sizer = _panel->GetSizer();
728
729         {
730                 wxStaticText* m = new wxStaticText (_panel, wxID_ANY, _("Decrypting KDMs"));
731                 m->SetFont (subheading_font);
732                 sizer->Add (m, 0, wxALL, _border);
733         }
734
735         wxButton* export_decryption_certificate = new wxButton (_panel, wxID_ANY, _("Export KDM decryption certificate..."));
736         sizer->Add (export_decryption_certificate, 0, wxLEFT, _border);
737         wxButton* export_decryption_chain = new wxButton (_panel, wxID_ANY, _("Export KDM decryption chain..."));
738         sizer->Add (export_decryption_chain, 0, wxLEFT, _border);
739         wxButton* export_settings = new wxButton (_panel, wxID_ANY, _("Export all KDM decryption settings..."));
740         sizer->Add (export_settings, 0, wxLEFT, _border);
741         wxButton* import_settings = new wxButton (_panel, wxID_ANY, _("Import all KDM decryption settings..."));
742         sizer->Add (import_settings, 0, wxLEFT, _border);
743         wxButton* decryption_advanced = new wxButton (_panel, wxID_ANY, _("Advanced..."));
744         sizer->Add (decryption_advanced, 0, wxALL, _border);
745
746         export_decryption_certificate->Bind (wxEVT_BUTTON, bind (&KeysPage::export_decryption_certificate, this));
747         export_decryption_chain->Bind (wxEVT_BUTTON, bind (&KeysPage::export_decryption_chain, this));
748         export_settings->Bind (wxEVT_BUTTON, bind (&KeysPage::export_decryption_chain_and_key, this));
749         import_settings->Bind (wxEVT_BUTTON, bind (&KeysPage::import_decryption_chain_and_key, this));
750         decryption_advanced->Bind (wxEVT_BUTTON, bind (&KeysPage::decryption_advanced, this));
751
752         {
753                 wxStaticText* m = new wxStaticText (_panel, wxID_ANY, _("Signing DCPs and KDMs"));
754                 m->SetFont (subheading_font);
755                 sizer->Add (m, 0, wxALL, _border);
756         }
757
758         wxButton* signing_advanced = new wxButton (_panel, wxID_ANY, _("Advanced..."));
759         sizer->Add (signing_advanced, 0, wxLEFT, _border);
760         signing_advanced->Bind (wxEVT_BUTTON, bind (&KeysPage::signing_advanced, this));
761 }
762
763 void
764 KeysPage::decryption_advanced ()
765 {
766         CertificateChainEditor* c = new CertificateChainEditor (
767                 _panel, _("Decrypting KDMs"), _border,
768                 bind (&Config::set_decryption_chain, Config::instance (), _1),
769                 bind (&Config::decryption_chain, Config::instance ()),
770                 bind (&KeysPage::nag_remake_decryption_chain, this)
771                 );
772
773         c->ShowModal();
774 }
775
776 void
777 KeysPage::signing_advanced ()
778 {
779         CertificateChainEditor* c = new CertificateChainEditor (
780                 _panel, _("Signing DCPs and KDMs"), _border,
781                 bind (&Config::set_signer_chain, Config::instance (), _1),
782                 bind (&Config::signer_chain, Config::instance ()),
783                 bind (&do_nothing)
784                 );
785
786         c->ShowModal();
787 }
788
789 void
790 KeysPage::export_decryption_chain_and_key ()
791 {
792         wxFileDialog* d = new wxFileDialog (
793                 _panel, _("Select Export File"), wxEmptyString, wxEmptyString, wxT ("DOM files (*.dom)|*.dom"),
794                 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
795                 );
796
797         if (d->ShowModal () == wxID_OK) {
798                 boost::filesystem::path path (wx_to_std(d->GetPath()));
799                 FILE* f = fopen_boost (path, "w");
800                 if (!f) {
801                         throw OpenFileError (path, errno, false);
802                 }
803
804                 string const chain = Config::instance()->decryption_chain()->chain();
805                 fwrite (chain.c_str(), 1, chain.length(), f);
806                 optional<string> const key = Config::instance()->decryption_chain()->key();
807                 DCPOMATIC_ASSERT (key);
808                 fwrite (key->c_str(), 1, key->length(), f);
809                 fclose (f);
810         }
811         d->Destroy ();
812
813 }
814
815 void
816 KeysPage::import_decryption_chain_and_key ()
817 {
818         wxFileDialog* d = new wxFileDialog (
819                 _panel, _("Select File To Import"), wxEmptyString, wxEmptyString, wxT ("DOM files (*.dom)|*.dom")
820                 );
821
822         if (d->ShowModal () == wxID_OK) {
823                 shared_ptr<dcp::CertificateChain> new_chain(new dcp::CertificateChain());
824
825                 FILE* f = fopen_boost (wx_to_std (d->GetPath ()), "r");
826                 if (!f) {
827                         throw OpenFileError (wx_to_std (d->GetPath ()), errno, false);
828                 }
829
830                 string current;
831                 while (!feof (f)) {
832                         char buffer[128];
833                         if (fgets (buffer, 128, f) == 0) {
834                                 break;
835                         }
836                         current += buffer;
837                         if (strncmp (buffer, "-----END CERTIFICATE-----", 25) == 0) {
838                                 new_chain->add (dcp::Certificate (current));
839                                 current = "";
840                         } else if (strncmp (buffer, "-----END RSA PRIVATE KEY-----", 29) == 0) {
841                                 new_chain->set_key (current);
842                                 current = "";
843                         }
844                 }
845                 fclose (f);
846
847                 if (new_chain->chain_valid() && new_chain->private_key_valid()) {
848                         Config::instance()->set_decryption_chain (new_chain);
849                 } else {
850                         error_dialog (_panel, _("Invalid DCP-o-matic export file"));
851                 }
852         }
853         d->Destroy ();
854 }
855
856 bool
857 KeysPage::nag_remake_decryption_chain ()
858 {
859         return NagDialog::maybe_nag (
860                 _panel,
861                 Config::NAG_REMAKE_DECRYPTION_CHAIN,
862                 _("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!"),
863                 true
864                 );
865 }
866
867 void
868 KeysPage::export_decryption_chain ()
869 {
870         wxFileDialog* d = new wxFileDialog (
871                 _panel, _("Select Chain File"), wxEmptyString, _("dcpomatic_kdm_decryption_chain.pem"), wxT ("PEM files (*.pem)|*.pem"),
872                 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
873                 );
874
875         if (d->ShowModal () == wxID_OK) {
876                 boost::filesystem::path path (wx_to_std(d->GetPath()));
877                 FILE* f = fopen_boost (path, "w");
878                 if (!f) {
879                         throw OpenFileError (path, errno, false);
880                 }
881
882                 string const s = Config::instance()->decryption_chain()->chain();
883                 fwrite (s.c_str(), 1, s.length(), f);
884                 fclose (f);
885         }
886         d->Destroy ();
887 }
888
889 void
890 KeysPage::export_decryption_certificate ()
891 {
892         wxFileDialog* d = new wxFileDialog (
893                 _panel, _("Select Certificate File"), wxEmptyString, _("dcpomatic_kdm_decryption_cert.pem"), wxT ("PEM files (*.pem)|*.pem"),
894                 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
895                 );
896
897         if (d->ShowModal () == wxID_OK) {
898                 boost::filesystem::path path (wx_to_std(d->GetPath()));
899                 FILE* f = fopen_boost (path, "w");
900                 if (!f) {
901                         throw OpenFileError (path, errno, false);
902                 }
903
904                 string const s = Config::instance()->decryption_chain()->leaf().certificate (true);
905                 fwrite (s.c_str(), 1, s.length(), f);
906                 fclose (f);
907         }
908
909         d->Destroy ();
910 }