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