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