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