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