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 std::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 } catch (RtAudioError&) {
890 /* Something went wrong so let's just ignore that device */
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));
901 SoundPage::reset_to_default ()
903 Config::instance()->set_audio_mapping_to_default ();
907 SoundPage::map_changed (AudioMapping m)
909 Config::instance()->set_audio_mapping (m);
913 SoundPage::sound_changed ()
915 Config::instance()->set_sound (_sound->GetValue ());
919 SoundPage::sound_output_changed ()
921 RtAudio audio (DCPOMATIC_RTAUDIO_API);
922 optional<string> const so = get_sound_output();
923 string default_device;
925 default_device = audio.getDeviceInfo(audio.getDefaultOutputDevice()).name;
926 } catch (RtAudioError&) {
929 if (!so || *so == default_device) {
930 Config::instance()->unset_sound_output ();
932 Config::instance()->set_sound_output (*so);
937 SoundPage::config_changed ()
939 Config* config = Config::instance ();
941 checked_set (_sound, config->sound ());
943 optional<string> const current_so = get_sound_output ();
944 optional<string> configured_so;
946 if (config->sound_output()) {
947 configured_so = config->sound_output().get();
949 /* No configured output means we should use the default */
950 RtAudio audio (DCPOMATIC_RTAUDIO_API);
952 configured_so = audio.getDeviceInfo(audio.getDefaultOutputDevice()).name;
953 } catch (RtAudioError&) {
954 /* Probably no audio devices at all */
958 if (configured_so && current_so != configured_so) {
959 /* Update _sound_output with the configured value */
961 while (i < _sound_output->GetCount()) {
962 if (_sound_output->GetString(i) == std_to_wx(*configured_so)) {
963 _sound_output->SetSelection (i);
970 RtAudio audio (DCPOMATIC_RTAUDIO_API);
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");
985 for (unsigned int i = 0; i < audio.getDeviceCount(); ++i) {
987 RtAudio::DeviceInfo info = audio.getDeviceInfo(i);
988 if (info.name == *configured_so && info.outputChannels > 0) {
989 channels = info.outputChannels;
991 } catch (RtAudioError&) {
997 _sound_output_details->SetLabel (
998 wxString::Format(_("%d channels on %s"), channels, apis[audio.getCurrentApi()])
1001 _map->set (Config::instance()->audio_mapping(channels));
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));
1007 _map->set_input_channels (input);
1009 vector<NamedChannel> output;
1010 for (int i = 0; i < channels; ++i) {
1011 output.push_back (NamedChannel(dcp::raw_convert<string>(i), i));
1013 _map->set_output_channels (output);
1015 setup_sensitivity ();
1019 SoundPage::setup_sensitivity ()
1021 _sound_output->Enable (_sound->GetValue());
1024 /** @return Currently-selected preview sound output in the dialogue */
1026 SoundPage::get_sound_output ()
1028 int const sel = _sound_output->GetSelection ();
1029 if (sel == wxNOT_FOUND) {
1030 return optional<string> ();
1033 return wx_to_std (_sound_output->GetString (sel));
1037 LocationsPage::LocationsPage (wxSize panel_size, int border)
1038 : Page (panel_size, border)
1044 LocationsPage::GetName () const
1046 return _("Locations");
1049 #ifdef DCPOMATIC_OSX
1051 LocationsPage::GetLargeIcon () const
1053 return wxBitmap ("locations", wxBITMAP_TYPE_PNG_RESOURCE);
1058 LocationsPage::setup ()
1062 wxGridBagSizer* table = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
1063 _panel->GetSizer()->Add (table, 1, wxALL | wxEXPAND, _border);
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));
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));
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));
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));
1086 LocationsPage::config_changed ()
1088 Config* config = Config::instance ();
1090 if (config->player_content_directory()) {
1091 checked_set (_content_directory, *config->player_content_directory());
1093 if (config->player_playlist_directory()) {
1094 checked_set (_playlist_directory, *config->player_playlist_directory());
1096 if (config->player_kdm_directory()) {
1097 checked_set (_kdm_directory, *config->player_kdm_directory());
1102 LocationsPage::content_directory_changed ()
1104 Config::instance()->set_player_content_directory(wx_to_std(_content_directory->GetPath()));
1108 LocationsPage::playlist_directory_changed ()
1110 Config::instance()->set_player_playlist_directory(wx_to_std(_playlist_directory->GetPath()));
1114 LocationsPage::kdm_directory_changed ()
1116 Config::instance()->set_player_kdm_directory(wx_to_std(_kdm_directory->GetPath()));