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