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