2 Copyright (C) 2012-2019 Carl Hetherington <cth@carlh.net>
4 This file is part of DCP-o-matic.
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.
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.
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/>.
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>
36 using boost::optional;
37 using boost::shared_ptr;
38 using boost::function;
39 #if BOOST_VERSION >= 106100
40 using namespace boost::placeholders;
50 Page::Page (wxSize panel_size, int border)
53 , _panel_size (panel_size)
54 , _window_exists (false)
56 _config_connection = Config::instance()->Changed.connect (bind (&Page::config_changed_wrapper, this));
61 Page::CreateWindow (wxWindow* parent)
63 return create_window (parent);
68 Page::create_window (wxWindow* parent)
70 _panel = new wxPanel (parent, wxID_ANY, wxDefaultPosition, _panel_size);
71 wxBoxSizer* s = new wxBoxSizer (wxVERTICAL);
75 _window_exists = true;
78 _panel->Bind (wxEVT_DESTROY, bind (&Page::window_destroyed, this));
84 Page::config_changed_wrapper ()
92 Page::window_destroyed ()
94 _window_exists = false;
98 GeneralPage::GeneralPage (wxSize panel_size, int border)
99 : Page (panel_size, border)
106 GeneralPage::GetName () const
113 GeneralPage::add_language_controls (wxGridBagSizer* table, int& r)
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));
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)
143 wxFont font = restart->GetFont();
144 font.SetStyle (wxFONTSTYLE_ITALIC);
145 font.SetPointSize (font.GetPointSize() - 1);
146 restart->SetFont (font);
149 _set_language->Bind (wxEVT_CHECKBOX, bind (&GeneralPage::set_language_changed, this));
150 _language->Bind (wxEVT_CHOICE, bind (&GeneralPage::language_changed, this));
154 GeneralPage::add_update_controls (wxGridBagSizer* table, int& r)
156 _check_for_updates = new CheckBox (_panel, _("Check for updates on startup"));
157 table->Add (_check_for_updates, wxGBPosition (r, 0), wxGBSpan (1, 2));
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));
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));
169 GeneralPage::config_changed ()
171 Config* config = Config::instance ();
173 checked_set (_set_language, static_cast<bool>(config->language()));
175 /* Backwards compatibility of config file */
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";
192 string lang = config->language().get_value_or ("en_GB");
193 if (compat_map.find (lang) != compat_map.end ()) {
194 lang = compat_map[lang];
197 checked_set (_language, lang);
199 checked_set (_check_for_updates, config->check_for_updates ());
200 checked_set (_check_for_test_updates, config->check_for_test_updates ());
202 setup_sensitivity ();
206 GeneralPage::setup_sensitivity ()
208 _language->Enable (_set_language->GetValue ());
209 _check_for_test_updates->Enable (_check_for_updates->GetValue ());
213 GeneralPage::set_language_changed ()
215 setup_sensitivity ();
216 if (_set_language->GetValue ()) {
219 Config::instance()->unset_language ();
224 GeneralPage::language_changed ()
226 int const sel = _language->GetSelection ();
228 Config::instance()->set_language (string_client_data (_language->GetClientObject (sel)));
230 Config::instance()->unset_language ();
235 GeneralPage::check_for_updates_changed ()
237 Config::instance()->set_check_for_updates (_check_for_updates->GetValue ());
241 GeneralPage::check_for_test_updates_changed ()
243 Config::instance()->set_check_for_test_updates (_check_for_test_updates->GetValue ());
246 CertificateChainEditor::CertificateChainEditor (
250 function<void (shared_ptr<dcp::CertificateChain>)> set,
251 function<shared_ptr<const dcp::CertificateChain> (void)> get,
252 function<bool (void)> nag_alter
254 : wxDialog (parent, wxID_ANY, title)
257 , _nag_alter (nag_alter)
259 _sizer = new wxBoxSizer (wxVERTICAL);
261 wxBoxSizer* certificates_sizer = new wxBoxSizer (wxHORIZONTAL);
262 _sizer->Add (certificates_sizer, 0, wxALL, border);
264 _certificates = new wxListCtrl (this, wxID_ANY, wxDefaultPosition, wxSize (440, 150), wxLC_REPORT | wxLC_SINGLE_SEL);
269 ip.SetText (_("Type"));
271 _certificates->InsertColumn (0, ip);
277 ip.SetText (_("Thumbprint"));
280 wxFont font = ip.GetFont ();
281 font.SetFamily (wxFONTFAMILY_TELETYPE);
284 _certificates->InsertColumn (1, ip);
287 certificates_sizer->Add (_certificates, 1, wxEXPAND);
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);
302 wxGridBagSizer* table = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
303 _sizer->Add (table, 1, wxALL | wxEXPAND, border);
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));
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));
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));
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));
341 wxSizer* buttons = CreateSeparatedButtonSizer (wxCLOSE);
343 _sizer->Add (buttons, wxSizerFlags().Expand().DoubleBorder());
346 SetSizerAndFit (_sizer);
348 update_certificate_list ();
349 update_private_key ();
350 update_sensitivity ();
354 CertificateChainEditor::add_button (wxWindow* button)
356 _button_sizer->Add (button, 0, wxLEFT | wxRIGHT, DCPOMATIC_SIZER_X_GAP);
361 CertificateChainEditor::add_certificate ()
363 wxFileDialog* d = new wxFileDialog (this, _("Select Certificate File"));
365 if (d->ShowModal() == wxID_OK) {
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());
377 if (!extra.empty ()) {
380 _("This file contains other certificates (or other data) after its first certificate. "
381 "Only the first certificate will be used.")
384 shared_ptr<dcp::CertificateChain> chain(new dcp::CertificateChain(*_get().get()));
386 if (!chain->chain_valid ()) {
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.")
395 update_certificate_list ();
397 } catch (dcp::MiscError& e) {
398 error_dialog (this, _("Could not read certificate file."), std_to_wx(e.what()));
404 update_sensitivity ();
408 CertificateChainEditor::remove_certificate ()
411 /* Cancel was clicked */
415 int i = _certificates->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
420 _certificates->DeleteItem (i);
421 shared_ptr<dcp::CertificateChain> chain(new dcp::CertificateChain(*_get().get()));
425 update_sensitivity ();
426 update_certificate_list ();
430 CertificateChainEditor::export_certificate ()
432 int i = _certificates->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
437 wxFileDialog* d = new wxFileDialog (
438 this, _("Select Certificate File"), wxEmptyString, wxEmptyString, wxT ("PEM files (*.pem)|*.pem"),
439 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
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) {
448 if (d->ShowModal () == wxID_OK) {
449 boost::filesystem::path path (wx_to_std(d->GetPath()));
450 FILE* f = fopen_boost (path, "w");
452 throw OpenFileError (path, errno, OpenFileError::WRITE);
455 string const s = j->certificate (true);
456 checked_fwrite (s.c_str(), s.length(), f, path);
463 CertificateChainEditor::export_chain ()
465 wxFileDialog* d = new wxFileDialog (
466 this, _("Select Chain File"), wxEmptyString, wxEmptyString, wxT("PEM files (*.pem)|*.pem"),
467 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
470 if (d->ShowModal () == wxID_OK) {
471 boost::filesystem::path path (wx_to_std(d->GetPath()));
472 FILE* f = fopen_boost (path, "w");
474 throw OpenFileError (path, errno, OpenFileError::WRITE);
477 string const s = _get()->chain();
478 checked_fwrite (s.c_str(), s.length(), f, path);
486 CertificateChainEditor::update_certificate_list ()
488 _certificates->DeleteAllItems ();
490 dcp::CertificateChain::List certs = _get()->root_to_leaf ();
491 BOOST_FOREACH (dcp::Certificate const & i, certs) {
494 _certificates->InsertItem (item);
495 _certificates->SetItem (n, 1, std_to_wx (i.thumbprint ()));
498 _certificates->SetItem (n, 0, _("Root"));
499 } else if (n == (certs.size() - 1)) {
500 _certificates->SetItem (n, 0, _("Leaf"));
502 _certificates->SetItem (n, 0, _("Intermediate"));
508 static wxColour normal = _private_key_bad->GetForegroundColour ();
510 if (_get()->private_key_valid()) {
511 _private_key_bad->Hide ();
512 _private_key_bad->SetForegroundColour (normal);
514 _private_key_bad->Show ();
515 _private_key_bad->SetForegroundColour (wxColour (255, 0, 0));
520 CertificateChainEditor::remake_certificates ()
522 shared_ptr<const dcp::CertificateChain> chain = _get();
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;
530 dcp::CertificateChain::List all = chain->root_to_leaf ();
532 if (all.size() >= 1) {
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 ();
539 if (all.size() >= 2) {
541 leaf_common_name = chain->leaf().subject_common_name ();
544 if (all.size() >= 3) {
545 /* Have an intermediate */
546 dcp::CertificateChain::List::iterator i = all.begin ();
548 intermediate_common_name = i->subject_common_name ();
552 /* Cancel was clicked */
556 MakeChainDialog* d = new MakeChainDialog (
558 subject_organization_name,
559 subject_organizational_unit_name,
561 intermediate_common_name,
565 if (d->ShowModal () == wxID_OK) {
567 shared_ptr<dcp::CertificateChain> (
568 new dcp::CertificateChain (
571 d->organisational_unit (),
572 d->root_common_name (),
573 d->intermediate_common_name (),
574 d->leaf_common_name ()
579 update_certificate_list ();
580 update_private_key ();
587 CertificateChainEditor::update_sensitivity ()
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);
595 CertificateChainEditor::update_private_key ()
597 checked_set (_private_key, dcp::private_key_fingerprint (_get()->key().get()));
602 CertificateChainEditor::import_private_key ()
604 wxFileDialog* d = new wxFileDialog (this, _("Select Key File"));
606 if (d->ShowModal() == wxID_OK) {
608 boost::filesystem::path p (wx_to_std (d->GetPath ()));
609 if (boost::filesystem::file_size (p) > 8192) {
612 wxString::Format (_("Could not read key file; file is too long (%s)"), std_to_wx (p.string ()))
617 shared_ptr<dcp::CertificateChain> chain(new dcp::CertificateChain(*_get().get()));
618 chain->set_key (dcp::file_to_string (p));
620 update_private_key ();
621 } catch (dcp::MiscError& e) {
622 error_dialog (this, _("Could not read certificate file."), std_to_wx(e.what()));
628 update_sensitivity ();
632 CertificateChainEditor::export_private_key ()
634 optional<string> key = _get()->key();
639 wxFileDialog* d = new wxFileDialog (
640 this, _("Select Key File"), wxEmptyString, wxEmptyString, wxT ("PEM files (*.pem)|*.pem"),
641 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
644 if (d->ShowModal () == wxID_OK) {
645 boost::filesystem::path path (wx_to_std(d->GetPath()));
646 FILE* f = fopen_boost (path, "w");
648 throw OpenFileError (path, errno, OpenFileError::WRITE);
651 string const s = _get()->key().get ();
652 checked_fwrite (s.c_str(), s.length(), f, path);
659 KeysPage::GetName () const
667 wxFont subheading_font (*wxNORMAL_FONT);
668 subheading_font.SetWeight (wxFONTWEIGHT_BOLD);
670 wxSizer* sizer = _panel->GetSizer();
673 wxStaticText* m = new StaticText (_panel, _("Decrypting KDMs"));
674 m->SetFont (subheading_font);
675 sizer->Add (m, 0, wxALL, _border);
678 wxSizer* buttons = new wxBoxSizer (wxVERTICAL);
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);
689 sizer->Add (buttons, 0, wxLEFT, _border);
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));
697 wxStaticText* m = new StaticText (_panel, _("Signing DCPs and KDMs"));
698 m->SetFont (subheading_font);
699 sizer->Add (m, 0, wxALL, _border);
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));
708 KeysPage::decryption_advanced ()
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)
721 KeysPage::signing_advanced ()
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 ()),
734 KeysPage::export_decryption_chain_and_key ()
736 wxFileDialog* d = new wxFileDialog (
737 _panel, _("Select Export File"), wxEmptyString, wxEmptyString, wxT ("DOM files (*.dom)|*.dom"),
738 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
741 if (d->ShowModal () == wxID_OK) {
742 boost::filesystem::path path (wx_to_std(d->GetPath()));
743 FILE* f = fopen_boost (path, "w");
745 throw OpenFileError (path, errno, OpenFileError::WRITE);
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);
760 KeysPage::import_decryption_chain_and_key ()
762 if (NagDialog::maybe_nag (
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!"),
771 wxFileDialog* d = new wxFileDialog (
772 _panel, _("Select File To Import"), wxEmptyString, wxEmptyString, wxT ("DOM files (*.dom)|*.dom")
775 if (d->ShowModal () == wxID_OK) {
776 shared_ptr<dcp::CertificateChain> new_chain(new dcp::CertificateChain());
778 FILE* f = fopen_boost (wx_to_std (d->GetPath ()), "r");
780 throw OpenFileError (wx_to_std (d->GetPath ()), errno, OpenFileError::WRITE);
786 if (fgets (buffer, 128, f) == 0) {
790 if (strncmp (buffer, "-----END CERTIFICATE-----", 25) == 0) {
791 new_chain->add (dcp::Certificate (current));
793 } else if (strncmp (buffer, "-----END RSA PRIVATE KEY-----", 29) == 0) {
794 new_chain->set_key (current);
800 if (new_chain->chain_valid() && new_chain->private_key_valid()) {
801 Config::instance()->set_decryption_chain (new_chain);
803 error_dialog (_panel, _("Invalid DCP-o-matic export file"));
810 KeysPage::nag_alter_decryption_chain ()
812 return NagDialog::maybe_nag (
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!"),
821 KeysPage::export_decryption_certificate ()
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
828 if (d->ShowModal () == wxID_OK) {
829 boost::filesystem::path path (wx_to_std(d->GetPath()));
830 FILE* f = fopen_boost (path, "w");
832 throw OpenFileError (path, errno, OpenFileError::WRITE);
835 string const s = Config::instance()->decryption_chain()->leaf().certificate (true);
836 checked_fwrite (s.c_str(), s.length(), f, path);
844 SoundPage::GetName () const
852 wxGridBagSizer* table = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
853 _panel->GetSizer()->Add (table, 1, wxALL | wxEXPAND, _border);
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));
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);
873 _reset_to_default = new Button (_panel, _("Reset to default"));
874 table->Add (_reset_to_default, wxGBPosition(r, 1));
877 wxFont font = _sound_output_details->GetFont();
878 font.SetStyle (wxFONTSTYLE_ITALIC);
879 font.SetPointSize (font.GetPointSize() - 1);
880 _sound_output_details->SetFont (font);
882 RtAudio audio (DCPOMATIC_RTAUDIO_API);
883 for (unsigned int i = 0; i < audio.getDeviceCount(); ++i) {
885 RtAudio::DeviceInfo dev = audio.getDeviceInfo (i);
886 if (dev.probed && dev.outputChannels > 0) {
887 _sound_output->Append (std_to_wx (dev.name));
889 #ifdef DCPOMATIC_USE_RTERROR
892 } catch (RtAudioError&) {
894 /* Something went wrong so let's just ignore that device */
898 _sound->Bind (wxEVT_CHECKBOX, bind(&SoundPage::sound_changed, this));
899 _sound_output->Bind (wxEVT_CHOICE, bind(&SoundPage::sound_output_changed, this));
900 _map->Changed.connect (bind(&SoundPage::map_changed, this, _1));
901 _reset_to_default->Bind (wxEVT_BUTTON, bind(&SoundPage::reset_to_default, this));
905 SoundPage::reset_to_default ()
907 Config::instance()->set_audio_mapping_to_default ();
911 SoundPage::map_changed (AudioMapping m)
913 Config::instance()->set_audio_mapping (m);
917 SoundPage::sound_changed ()
919 Config::instance()->set_sound (_sound->GetValue ());
923 SoundPage::sound_output_changed ()
925 RtAudio audio (DCPOMATIC_RTAUDIO_API);
926 optional<string> const so = get_sound_output();
927 string default_device;
929 default_device = audio.getDeviceInfo(audio.getDefaultOutputDevice()).name;
930 #ifdef DCPOMATIC_USE_RTERROR
933 } catch (RtAudioError&) {
937 if (!so || *so == default_device) {
938 Config::instance()->unset_sound_output ();
940 Config::instance()->set_sound_output (*so);
945 SoundPage::config_changed ()
947 Config* config = Config::instance ();
949 checked_set (_sound, config->sound ());
951 optional<string> const current_so = get_sound_output ();
952 optional<string> configured_so;
954 if (config->sound_output()) {
955 configured_so = config->sound_output().get();
957 /* No configured output means we should use the default */
958 RtAudio audio (DCPOMATIC_RTAUDIO_API);
960 configured_so = audio.getDeviceInfo(audio.getDefaultOutputDevice()).name;
961 #ifdef DCPOMATIC_USE_RTERROR
964 } catch (RtAudioError&) {
966 /* Probably no audio devices at all */
970 if (configured_so && current_so != configured_so) {
971 /* Update _sound_output with the configured value */
973 while (i < _sound_output->GetCount()) {
974 if (_sound_output->GetString(i) == std_to_wx(*configured_so)) {
975 _sound_output->SetSelection (i);
982 RtAudio audio (DCPOMATIC_RTAUDIO_API);
984 map<int, wxString> apis;
985 apis[RtAudio::MACOSX_CORE] = _("CoreAudio");
986 apis[RtAudio::WINDOWS_ASIO] = _("ASIO");
987 apis[RtAudio::WINDOWS_DS] = _("Direct Sound");
988 apis[RtAudio::WINDOWS_WASAPI] = _("WASAPI");
989 apis[RtAudio::UNIX_JACK] = _("JACK");
990 apis[RtAudio::LINUX_ALSA] = _("ALSA");
991 apis[RtAudio::LINUX_PULSE] = _("PulseAudio");
992 apis[RtAudio::LINUX_OSS] = _("OSS");
993 apis[RtAudio::RTAUDIO_DUMMY] = _("Dummy");
997 for (unsigned int i = 0; i < audio.getDeviceCount(); ++i) {
999 RtAudio::DeviceInfo info = audio.getDeviceInfo(i);
1000 if (info.name == *configured_so && info.outputChannels > 0) {
1001 channels = info.outputChannels;
1003 #ifdef DCPOMATIC_USE_RTERROR
1004 } catch (RtError&) {
1006 } catch (RtAudioError&) {
1013 _sound_output_details->SetLabel (
1014 wxString::Format(_("%d channels on %s"), channels, apis[audio.getCurrentApi()])
1017 _map->set (Config::instance()->audio_mapping(channels));
1019 vector<NamedChannel> input;
1020 for (int i = 0; i < MAX_DCP_AUDIO_CHANNELS; ++i) {
1021 input.push_back (NamedChannel(short_audio_channel_name(i), i));
1023 _map->set_input_channels (input);
1025 vector<NamedChannel> output;
1026 for (int i = 0; i < channels; ++i) {
1027 output.push_back (NamedChannel(dcp::raw_convert<string>(i), i));
1029 _map->set_output_channels (output);
1031 setup_sensitivity ();
1035 SoundPage::setup_sensitivity ()
1037 _sound_output->Enable (_sound->GetValue());
1040 /** @return Currently-selected preview sound output in the dialogue */
1042 SoundPage::get_sound_output ()
1044 int const sel = _sound_output->GetSelection ();
1045 if (sel == wxNOT_FOUND) {
1046 return optional<string> ();
1049 return wx_to_std (_sound_output->GetString (sel));
1053 LocationsPage::LocationsPage (wxSize panel_size, int border)
1054 : Page (panel_size, border)
1060 LocationsPage::GetName () const
1062 return _("Locations");
1065 #ifdef DCPOMATIC_OSX
1067 LocationsPage::GetLargeIcon () const
1069 return wxBitmap ("locations", wxBITMAP_TYPE_PNG_RESOURCE);
1074 LocationsPage::setup ()
1078 wxGridBagSizer* table = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
1079 _panel->GetSizer()->Add (table, 1, wxALL | wxEXPAND, _border);
1081 add_label_to_sizer (table, _panel, _("Content directory"), true, wxGBPosition (r, 0));
1082 _content_directory = new wxDirPickerCtrl (_panel, wxID_ANY, wxEmptyString, wxDirSelectorPromptStr, wxDefaultPosition, wxSize (300, -1));
1083 table->Add (_content_directory, wxGBPosition (r, 1));
1086 add_label_to_sizer (table, _panel, _("Playlist directory"), true, wxGBPosition (r, 0));
1087 _playlist_directory = new wxDirPickerCtrl (_panel, wxID_ANY, wxEmptyString, wxDirSelectorPromptStr, wxDefaultPosition, wxSize (300, -1));
1088 table->Add (_playlist_directory, wxGBPosition (r, 1));
1091 add_label_to_sizer (table, _panel, _("KDM directory"), true, wxGBPosition (r, 0));
1092 _kdm_directory = new wxDirPickerCtrl (_panel, wxID_ANY, wxEmptyString, wxDirSelectorPromptStr, wxDefaultPosition, wxSize (300, -1));
1093 table->Add (_kdm_directory, wxGBPosition (r, 1));
1096 #ifdef DCPOMATIC_VARIANT_SWAROOP
1097 add_label_to_sizer (table, _panel, _("Background image"), true, wxGBPosition (r, 0));
1098 _background_image = new FilePickerCtrl (_panel, _("Select image file"), "*.png;*.jpg;*.jpeg;*.tif;*.tiff", true, false);
1099 table->Add (_background_image, wxGBPosition (r, 1));
1103 _content_directory->Bind (wxEVT_DIRPICKER_CHANGED, bind(&LocationsPage::content_directory_changed, this));
1104 _playlist_directory->Bind (wxEVT_DIRPICKER_CHANGED, bind(&LocationsPage::playlist_directory_changed, this));
1105 _kdm_directory->Bind (wxEVT_DIRPICKER_CHANGED, bind(&LocationsPage::kdm_directory_changed, this));
1106 #ifdef DCPOMATIC_VARIANT_SWAROOP
1107 _background_image->Bind (wxEVT_FILEPICKER_CHANGED, bind(&LocationsPage::background_image_changed, this));
1112 LocationsPage::config_changed ()
1114 Config* config = Config::instance ();
1116 if (config->player_content_directory()) {
1117 checked_set (_content_directory, *config->player_content_directory());
1119 if (config->player_playlist_directory()) {
1120 checked_set (_playlist_directory, *config->player_playlist_directory());
1122 if (config->player_kdm_directory()) {
1123 checked_set (_kdm_directory, *config->player_kdm_directory());
1125 #ifdef DCPOMATIC_VARIANT_SWAROOP
1126 if (config->player_background_image()) {
1127 checked_set (_background_image, *config->player_background_image());
1133 LocationsPage::content_directory_changed ()
1135 Config::instance()->set_player_content_directory(wx_to_std(_content_directory->GetPath()));
1139 LocationsPage::playlist_directory_changed ()
1141 Config::instance()->set_player_playlist_directory(wx_to_std(_playlist_directory->GetPath()));
1145 LocationsPage::kdm_directory_changed ()
1147 Config::instance()->set_player_kdm_directory(wx_to_std(_kdm_directory->GetPath()));
1150 #ifdef DCPOMATIC_VARIANT_SWAROOP
1152 LocationsPage::background_image_changed ()
1154 boost::filesystem::path const f = wx_to_std(_background_image->GetPath());
1155 if (!boost::filesystem::is_regular_file(f) || !wxImage::CanRead(std_to_wx(f.string()))) {
1156 error_dialog (0, _("Could not load image file."));
1157 if (Config::instance()->player_background_image()) {
1158 checked_set (_background_image, *Config::instance()->player_background_image());
1163 Config::instance()->set_player_background_image(f);