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