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