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