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