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