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;
47 Page::Page (wxSize panel_size, int border)
50 , _panel_size (panel_size)
51 , _window_exists (false)
53 _config_connection = Config::instance()->Changed.connect (bind (&Page::config_changed_wrapper, this));
58 Page::CreateWindow (wxWindow* parent)
60 return create_window (parent);
65 Page::create_window (wxWindow* parent)
67 _panel = new wxPanel (parent, wxID_ANY, wxDefaultPosition, _panel_size);
68 wxBoxSizer* s = new wxBoxSizer (wxVERTICAL);
72 _window_exists = true;
75 _panel->Bind (wxEVT_DESTROY, bind (&Page::window_destroyed, this));
81 Page::config_changed_wrapper ()
89 Page::window_destroyed ()
91 _window_exists = false;
95 GeneralPage::GeneralPage (wxSize panel_size, int border)
96 : Page (panel_size, border)
103 GeneralPage::GetName () const
110 GeneralPage::add_language_controls (wxGridBagSizer* table, int& r)
112 _set_language = new CheckBox (_panel, _("Set language"));
113 table->Add (_set_language, wxGBPosition (r, 0), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
114 _language = new wxChoice (_panel, wxID_ANY);
115 vector<pair<string, string> > languages;
116 languages.push_back (make_pair ("Čeština", "cs_CZ"));
117 languages.push_back (make_pair ("汉语/漢語", "zh_CN"));
118 languages.push_back (make_pair ("Dansk", "da_DK"));
119 languages.push_back (make_pair ("Deutsch", "de_DE"));
120 languages.push_back (make_pair ("English", "en_GB"));
121 languages.push_back (make_pair ("Español", "es_ES"));
122 languages.push_back (make_pair ("Français", "fr_FR"));
123 languages.push_back (make_pair ("Italiano", "it_IT"));
124 languages.push_back (make_pair ("Nederlands", "nl_NL"));
125 languages.push_back (make_pair ("Русский", "ru_RU"));
126 languages.push_back (make_pair ("Polski", "pl_PL"));
127 languages.push_back (make_pair ("Português europeu", "pt_PT"));
128 languages.push_back (make_pair ("Português do Brasil", "pt_BR"));
129 languages.push_back (make_pair ("Svenska", "sv_SE"));
130 languages.push_back (make_pair ("Slovenský jazyk", "sk_SK"));
131 languages.push_back (make_pair ("Türkçe", "tr_TR"));
132 languages.push_back (make_pair ("українська мова", "uk_UA"));
133 checked_set (_language, languages);
134 table->Add (_language, wxGBPosition (r, 1));
137 wxStaticText* restart = add_label_to_sizer (
138 table, _panel, _("(restart DCP-o-matic to see language changes)"), false, wxGBPosition (r, 0), wxGBSpan (1, 2)
140 wxFont font = restart->GetFont();
141 font.SetStyle (wxFONTSTYLE_ITALIC);
142 font.SetPointSize (font.GetPointSize() - 1);
143 restart->SetFont (font);
146 _set_language->Bind (wxEVT_CHECKBOX, bind (&GeneralPage::set_language_changed, this));
147 _language->Bind (wxEVT_CHOICE, bind (&GeneralPage::language_changed, this));
151 GeneralPage::add_update_controls (wxGridBagSizer* table, int& r)
153 _check_for_updates = new CheckBox (_panel, _("Check for updates on startup"));
154 table->Add (_check_for_updates, wxGBPosition (r, 0), wxGBSpan (1, 2));
157 _check_for_test_updates = new CheckBox (_panel, _("Check for testing updates on startup"));
158 table->Add (_check_for_test_updates, wxGBPosition (r, 0), wxGBSpan (1, 2));
161 _check_for_updates->Bind (wxEVT_CHECKBOX, bind (&GeneralPage::check_for_updates_changed, this));
162 _check_for_test_updates->Bind (wxEVT_CHECKBOX, bind (&GeneralPage::check_for_test_updates_changed, this));
166 GeneralPage::config_changed ()
168 Config* config = Config::instance ();
170 checked_set (_set_language, static_cast<bool>(config->language()));
172 /* Backwards compatibility of config file */
174 map<string, string> compat_map;
175 compat_map["fr"] = "fr_FR";
176 compat_map["it"] = "it_IT";
177 compat_map["es"] = "es_ES";
178 compat_map["sv"] = "sv_SE";
179 compat_map["de"] = "de_DE";
180 compat_map["nl"] = "nl_NL";
181 compat_map["ru"] = "ru_RU";
182 compat_map["pl"] = "pl_PL";
183 compat_map["da"] = "da_DK";
184 compat_map["pt"] = "pt_PT";
185 compat_map["sk"] = "sk_SK";
186 compat_map["cs"] = "cs_CZ";
187 compat_map["uk"] = "uk_UA";
189 string lang = config->language().get_value_or ("en_GB");
190 if (compat_map.find (lang) != compat_map.end ()) {
191 lang = compat_map[lang];
194 checked_set (_language, lang);
196 checked_set (_check_for_updates, config->check_for_updates ());
197 checked_set (_check_for_test_updates, config->check_for_test_updates ());
199 setup_sensitivity ();
203 GeneralPage::setup_sensitivity ()
205 _language->Enable (_set_language->GetValue ());
206 _check_for_test_updates->Enable (_check_for_updates->GetValue ());
210 GeneralPage::set_language_changed ()
212 setup_sensitivity ();
213 if (_set_language->GetValue ()) {
216 Config::instance()->unset_language ();
221 GeneralPage::language_changed ()
223 int const sel = _language->GetSelection ();
225 Config::instance()->set_language (string_client_data (_language->GetClientObject (sel)));
227 Config::instance()->unset_language ();
232 GeneralPage::check_for_updates_changed ()
234 Config::instance()->set_check_for_updates (_check_for_updates->GetValue ());
238 GeneralPage::check_for_test_updates_changed ()
240 Config::instance()->set_check_for_test_updates (_check_for_test_updates->GetValue ());
243 CertificateChainEditor::CertificateChainEditor (
247 function<void (shared_ptr<dcp::CertificateChain>)> set,
248 function<shared_ptr<const dcp::CertificateChain> (void)> get,
249 function<bool (void)> nag_alter
251 : wxDialog (parent, wxID_ANY, title)
254 , _nag_alter (nag_alter)
256 _sizer = new wxBoxSizer (wxVERTICAL);
258 wxBoxSizer* certificates_sizer = new wxBoxSizer (wxHORIZONTAL);
259 _sizer->Add (certificates_sizer, 0, wxALL, border);
261 _certificates = new wxListCtrl (this, wxID_ANY, wxDefaultPosition, wxSize (440, 150), wxLC_REPORT | wxLC_SINGLE_SEL);
266 ip.SetText (_("Type"));
268 _certificates->InsertColumn (0, ip);
274 ip.SetText (_("Thumbprint"));
277 wxFont font = ip.GetFont ();
278 font.SetFamily (wxFONTFAMILY_TELETYPE);
281 _certificates->InsertColumn (1, ip);
284 certificates_sizer->Add (_certificates, 1, wxEXPAND);
287 wxSizer* s = new wxBoxSizer (wxVERTICAL);
288 _add_certificate = new Button (this, _("Add..."));
289 s->Add (_add_certificate, 1, wxTOP | wxBOTTOM | wxEXPAND, DCPOMATIC_BUTTON_STACK_GAP);
290 _remove_certificate = new Button (this, _("Remove"));
291 s->Add (_remove_certificate, 1, wxTOP | wxBOTTOM | wxEXPAND, DCPOMATIC_BUTTON_STACK_GAP);
292 _export_certificate = new Button (this, _("Export certificate..."));
293 s->Add (_export_certificate, 1, wxTOP | wxBOTTOM | wxEXPAND, DCPOMATIC_BUTTON_STACK_GAP);
294 _export_chain = new Button (this, _("Export chain..."));
295 s->Add (_export_chain, 1, wxTOP | wxBOTTOM | wxEXPAND, DCPOMATIC_BUTTON_STACK_GAP);
296 certificates_sizer->Add (s, 0, wxLEFT, DCPOMATIC_SIZER_X_GAP);
299 wxGridBagSizer* table = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
300 _sizer->Add (table, 1, wxALL | wxEXPAND, border);
303 add_label_to_sizer (table, this, _("Leaf private key"), true, wxGBPosition (r, 0));
304 _private_key = new StaticText (this, wxT(""));
305 wxFont font = _private_key->GetFont ();
306 font.SetFamily (wxFONTFAMILY_TELETYPE);
307 _private_key->SetFont (font);
308 table->Add (_private_key, wxGBPosition (r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
309 _import_private_key = new Button (this, _("Import..."));
310 table->Add (_import_private_key, wxGBPosition (r, 2));
311 _export_private_key = new Button (this, _("Export..."));
312 table->Add (_export_private_key, wxGBPosition (r, 3));
315 _button_sizer = new wxBoxSizer (wxHORIZONTAL);
316 _remake_certificates = new Button (this, _("Re-make certificates and key..."));
317 _button_sizer->Add (_remake_certificates, 1, wxRIGHT, border);
318 table->Add (_button_sizer, wxGBPosition (r, 0), wxGBSpan (1, 4));
321 _private_key_bad = new StaticText (this, _("Leaf private key does not match leaf certificate!"));
322 font = *wxSMALL_FONT;
323 font.SetWeight (wxFONTWEIGHT_BOLD);
324 _private_key_bad->SetFont (font);
325 table->Add (_private_key_bad, wxGBPosition (r, 0), wxGBSpan (1, 3));
328 _add_certificate->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::add_certificate, this));
329 _remove_certificate->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::remove_certificate, this));
330 _export_certificate->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::export_certificate, this));
331 _certificates->Bind (wxEVT_LIST_ITEM_SELECTED, bind (&CertificateChainEditor::update_sensitivity, this));
332 _certificates->Bind (wxEVT_LIST_ITEM_DESELECTED, bind (&CertificateChainEditor::update_sensitivity, this));
333 _remake_certificates->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::remake_certificates, this));
334 _export_chain->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::export_chain, this));
335 _import_private_key->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::import_private_key, this));
336 _export_private_key->Bind (wxEVT_BUTTON, bind (&CertificateChainEditor::export_private_key, this));
338 wxSizer* buttons = CreateSeparatedButtonSizer (wxCLOSE);
340 _sizer->Add (buttons, wxSizerFlags().Expand().DoubleBorder());
343 SetSizerAndFit (_sizer);
345 update_certificate_list ();
346 update_private_key ();
347 update_sensitivity ();
351 CertificateChainEditor::add_button (wxWindow* button)
353 _button_sizer->Add (button, 0, wxLEFT | wxRIGHT, DCPOMATIC_SIZER_X_GAP);
358 CertificateChainEditor::add_certificate ()
360 wxFileDialog* d = new wxFileDialog (this, _("Select Certificate File"));
362 if (d->ShowModal() == wxID_OK) {
367 extra = c.read_string (dcp::file_to_string (wx_to_std (d->GetPath ())));
368 } catch (boost::filesystem::filesystem_error& e) {
369 error_dialog (this, _("Could not import certificate (%s)"), d->GetPath());
374 if (!extra.empty ()) {
377 _("This file contains other certificates (or other data) after its first certificate. "
378 "Only the first certificate will be used.")
381 shared_ptr<dcp::CertificateChain> chain(new dcp::CertificateChain(*_get().get()));
383 if (!chain->chain_valid ()) {
386 _("Adding this certificate would make the chain inconsistent, so it will not be added. "
387 "Add certificates in order from root to intermediate to leaf.")
392 update_certificate_list ();
394 } catch (dcp::MiscError& e) {
395 error_dialog (this, _("Could not read certificate file."), std_to_wx(e.what()));
401 update_sensitivity ();
405 CertificateChainEditor::remove_certificate ()
408 /* Cancel was clicked */
412 int i = _certificates->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
417 _certificates->DeleteItem (i);
418 shared_ptr<dcp::CertificateChain> chain(new dcp::CertificateChain(*_get().get()));
422 update_sensitivity ();
423 update_certificate_list ();
427 CertificateChainEditor::export_certificate ()
429 int i = _certificates->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
434 wxFileDialog* d = new wxFileDialog (
435 this, _("Select Certificate File"), wxEmptyString, wxEmptyString, wxT ("PEM files (*.pem)|*.pem"),
436 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
439 dcp::CertificateChain::List all = _get()->root_to_leaf ();
440 dcp::CertificateChain::List::iterator j = all.begin ();
441 for (int k = 0; k < i; ++k) {
445 if (d->ShowModal () == wxID_OK) {
446 boost::filesystem::path path (wx_to_std(d->GetPath()));
447 FILE* f = fopen_boost (path, "w");
449 throw OpenFileError (path, errno, OpenFileError::WRITE);
452 string const s = j->certificate (true);
453 checked_fwrite (s.c_str(), s.length(), f, path);
460 CertificateChainEditor::export_chain ()
462 wxFileDialog* d = new wxFileDialog (
463 this, _("Select Chain File"), wxEmptyString, wxEmptyString, wxT("PEM files (*.pem)|*.pem"),
464 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
467 if (d->ShowModal () == wxID_OK) {
468 boost::filesystem::path path (wx_to_std(d->GetPath()));
469 FILE* f = fopen_boost (path, "w");
471 throw OpenFileError (path, errno, OpenFileError::WRITE);
474 string const s = _get()->chain();
475 checked_fwrite (s.c_str(), s.length(), f, path);
483 CertificateChainEditor::update_certificate_list ()
485 _certificates->DeleteAllItems ();
487 dcp::CertificateChain::List certs = _get()->root_to_leaf ();
488 BOOST_FOREACH (dcp::Certificate const & i, certs) {
491 _certificates->InsertItem (item);
492 _certificates->SetItem (n, 1, std_to_wx (i.thumbprint ()));
495 _certificates->SetItem (n, 0, _("Root"));
496 } else if (n == (certs.size() - 1)) {
497 _certificates->SetItem (n, 0, _("Leaf"));
499 _certificates->SetItem (n, 0, _("Intermediate"));
505 static wxColour normal = _private_key_bad->GetForegroundColour ();
507 if (_get()->private_key_valid()) {
508 _private_key_bad->Hide ();
509 _private_key_bad->SetForegroundColour (normal);
511 _private_key_bad->Show ();
512 _private_key_bad->SetForegroundColour (wxColour (255, 0, 0));
517 CertificateChainEditor::remake_certificates ()
519 shared_ptr<const dcp::CertificateChain> chain = _get();
521 string subject_organization_name;
522 string subject_organizational_unit_name;
523 string root_common_name;
524 string intermediate_common_name;
525 string leaf_common_name;
527 dcp::CertificateChain::List all = chain->root_to_leaf ();
529 if (all.size() >= 1) {
531 subject_organization_name = chain->root().subject_organization_name ();
532 subject_organizational_unit_name = chain->root().subject_organizational_unit_name ();
533 root_common_name = chain->root().subject_common_name ();
536 if (all.size() >= 2) {
538 leaf_common_name = chain->leaf().subject_common_name ();
541 if (all.size() >= 3) {
542 /* Have an intermediate */
543 dcp::CertificateChain::List::iterator i = all.begin ();
545 intermediate_common_name = i->subject_common_name ();
549 /* Cancel was clicked */
553 MakeChainDialog* d = new MakeChainDialog (
555 subject_organization_name,
556 subject_organizational_unit_name,
558 intermediate_common_name,
562 if (d->ShowModal () == wxID_OK) {
564 shared_ptr<dcp::CertificateChain> (
565 new dcp::CertificateChain (
568 d->organisational_unit (),
569 d->root_common_name (),
570 d->intermediate_common_name (),
571 d->leaf_common_name ()
576 update_certificate_list ();
577 update_private_key ();
584 CertificateChainEditor::update_sensitivity ()
586 /* We can only remove the leaf certificate */
587 _remove_certificate->Enable (_certificates->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) == (_certificates->GetItemCount() - 1));
588 _export_certificate->Enable (_certificates->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) != -1);
592 CertificateChainEditor::update_private_key ()
594 checked_set (_private_key, dcp::private_key_fingerprint (_get()->key().get()));
599 CertificateChainEditor::import_private_key ()
601 wxFileDialog* d = new wxFileDialog (this, _("Select Key File"));
603 if (d->ShowModal() == wxID_OK) {
605 boost::filesystem::path p (wx_to_std (d->GetPath ()));
606 if (boost::filesystem::file_size (p) > 8192) {
609 wxString::Format (_("Could not read key file; file is too long (%s)"), std_to_wx (p.string ()))
614 shared_ptr<dcp::CertificateChain> chain(new dcp::CertificateChain(*_get().get()));
615 chain->set_key (dcp::file_to_string (p));
617 update_private_key ();
618 } catch (dcp::MiscError& e) {
619 error_dialog (this, _("Could not read certificate file."), std_to_wx(e.what()));
625 update_sensitivity ();
629 CertificateChainEditor::export_private_key ()
631 optional<string> key = _get()->key();
636 wxFileDialog* d = new wxFileDialog (
637 this, _("Select Key File"), wxEmptyString, wxEmptyString, wxT ("PEM files (*.pem)|*.pem"),
638 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
641 if (d->ShowModal () == wxID_OK) {
642 boost::filesystem::path path (wx_to_std(d->GetPath()));
643 FILE* f = fopen_boost (path, "w");
645 throw OpenFileError (path, errno, OpenFileError::WRITE);
648 string const s = _get()->key().get ();
649 checked_fwrite (s.c_str(), s.length(), f, path);
656 KeysPage::GetName () const
664 wxFont subheading_font (*wxNORMAL_FONT);
665 subheading_font.SetWeight (wxFONTWEIGHT_BOLD);
667 wxSizer* sizer = _panel->GetSizer();
670 wxStaticText* m = new StaticText (_panel, _("Decrypting KDMs"));
671 m->SetFont (subheading_font);
672 sizer->Add (m, 0, wxALL, _border);
675 wxSizer* buttons = new wxBoxSizer (wxVERTICAL);
677 wxButton* export_decryption_certificate = new Button (_panel, _("Export KDM decryption certificate..."));
678 buttons->Add (export_decryption_certificate, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
679 wxButton* export_settings = new Button (_panel, _("Export all KDM decryption settings..."));
680 buttons->Add (export_settings, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
681 wxButton* import_settings = new Button (_panel, _("Import all KDM decryption settings..."));
682 buttons->Add (import_settings, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
683 wxButton* decryption_advanced = new Button (_panel, _("Advanced..."));
684 buttons->Add (decryption_advanced, 0);
686 sizer->Add (buttons, 0, wxLEFT, _border);
688 export_decryption_certificate->Bind (wxEVT_BUTTON, bind (&KeysPage::export_decryption_certificate, this));
689 export_settings->Bind (wxEVT_BUTTON, bind (&KeysPage::export_decryption_chain_and_key, this));
690 import_settings->Bind (wxEVT_BUTTON, bind (&KeysPage::import_decryption_chain_and_key, this));
691 decryption_advanced->Bind (wxEVT_BUTTON, bind (&KeysPage::decryption_advanced, this));
694 wxStaticText* m = new StaticText (_panel, _("Signing DCPs and KDMs"));
695 m->SetFont (subheading_font);
696 sizer->Add (m, 0, wxALL, _border);
699 wxButton* signing_advanced = new Button (_panel, _("Advanced..."));
700 sizer->Add (signing_advanced, 0, wxLEFT | wxBOTTOM, _border);
701 signing_advanced->Bind (wxEVT_BUTTON, bind (&KeysPage::signing_advanced, this));
705 KeysPage::decryption_advanced ()
707 CertificateChainEditor* c = new CertificateChainEditor (
708 _panel, _("Decrypting KDMs"), _border,
709 bind (&Config::set_decryption_chain, Config::instance (), _1),
710 bind (&Config::decryption_chain, Config::instance ()),
711 bind (&KeysPage::nag_alter_decryption_chain, this)
718 KeysPage::signing_advanced ()
720 CertificateChainEditor* c = new CertificateChainEditor (
721 _panel, _("Signing DCPs and KDMs"), _border,
722 bind (&Config::set_signer_chain, Config::instance (), _1),
723 bind (&Config::signer_chain, Config::instance ()),
731 KeysPage::export_decryption_chain_and_key ()
733 wxFileDialog* d = new wxFileDialog (
734 _panel, _("Select Export File"), wxEmptyString, wxEmptyString, wxT ("DOM files (*.dom)|*.dom"),
735 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
738 if (d->ShowModal () == wxID_OK) {
739 boost::filesystem::path path (wx_to_std(d->GetPath()));
740 FILE* f = fopen_boost (path, "w");
742 throw OpenFileError (path, errno, OpenFileError::WRITE);
745 string const chain = Config::instance()->decryption_chain()->chain();
746 checked_fwrite (chain.c_str(), chain.length(), f, path);
747 optional<string> const key = Config::instance()->decryption_chain()->key();
748 DCPOMATIC_ASSERT (key);
749 checked_fwrite (key->c_str(), key->length(), f, path);
757 KeysPage::import_decryption_chain_and_key ()
759 if (NagDialog::maybe_nag (
761 Config::NAG_IMPORT_DECRYPTION_CHAIN,
762 _("If you continue with this operation you will no longer be able to use any DKDMs that you have created with the current certificates and key. Also, any KDMs that have been sent to you for those certificates will become useless. Proceed with caution!"),
768 wxFileDialog* d = new wxFileDialog (
769 _panel, _("Select File To Import"), wxEmptyString, wxEmptyString, wxT ("DOM files (*.dom)|*.dom")
772 if (d->ShowModal () == wxID_OK) {
773 shared_ptr<dcp::CertificateChain> new_chain(new dcp::CertificateChain());
775 FILE* f = fopen_boost (wx_to_std (d->GetPath ()), "r");
777 throw OpenFileError (wx_to_std (d->GetPath ()), errno, OpenFileError::WRITE);
783 if (fgets (buffer, 128, f) == 0) {
787 if (strncmp (buffer, "-----END CERTIFICATE-----", 25) == 0) {
788 new_chain->add (dcp::Certificate (current));
790 } else if (strncmp (buffer, "-----END RSA PRIVATE KEY-----", 29) == 0) {
791 new_chain->set_key (current);
797 if (new_chain->chain_valid() && new_chain->private_key_valid()) {
798 Config::instance()->set_decryption_chain (new_chain);
800 error_dialog (_panel, _("Invalid DCP-o-matic export file"));
807 KeysPage::nag_alter_decryption_chain ()
809 return NagDialog::maybe_nag (
811 Config::NAG_ALTER_DECRYPTION_CHAIN,
812 _("If you continue with this operation you will no longer be able to use any DKDMs that you have created. Also, any KDMs that have been sent to you will become useless. Proceed with caution!"),
818 KeysPage::export_decryption_certificate ()
820 wxFileDialog* d = new wxFileDialog (
821 _panel, _("Select Certificate File"), wxEmptyString, _("dcpomatic_kdm_decryption_cert.pem"), wxT ("PEM files (*.pem)|*.pem"),
822 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
825 if (d->ShowModal () == wxID_OK) {
826 boost::filesystem::path path (wx_to_std(d->GetPath()));
827 FILE* f = fopen_boost (path, "w");
829 throw OpenFileError (path, errno, OpenFileError::WRITE);
832 string const s = Config::instance()->decryption_chain()->leaf().certificate (true);
833 checked_fwrite (s.c_str(), s.length(), f, path);
841 SoundPage::GetName () const
849 wxGridBagSizer* table = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
850 _panel->GetSizer()->Add (table, 1, wxALL | wxEXPAND, _border);
854 _sound = new CheckBox (_panel, _("Play sound via"));
855 table->Add (_sound, wxGBPosition (r, 0), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
856 wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
857 _sound_output = new wxChoice (_panel, wxID_ANY);
858 s->Add (_sound_output, 0);
859 _sound_output_details = new wxStaticText (_panel, wxID_ANY, wxT(""));
860 s->Add (_sound_output_details, 1, wxALIGN_CENTER_VERTICAL | wxLEFT, DCPOMATIC_SIZER_X_GAP);
861 table->Add (s, wxGBPosition(r, 1));
864 add_label_to_sizer (table, _panel, _("Mapping"), true, wxGBPosition(r, 0));
865 _map = new AudioMappingView (_panel, _("DCP"), _("DCP"), _("Output"), _("output"));
866 _map->SetSize (-1, 600);
867 table->Add (_map, wxGBPosition(r, 1), wxDefaultSpan, wxEXPAND);
870 _reset_to_default = new Button (_panel, _("Reset to default"));
871 table->Add (_reset_to_default, wxGBPosition(r, 1));
874 wxFont font = _sound_output_details->GetFont();
875 font.SetStyle (wxFONTSTYLE_ITALIC);
876 font.SetPointSize (font.GetPointSize() - 1);
877 _sound_output_details->SetFont (font);
879 RtAudio audio (DCPOMATIC_RTAUDIO_API);
880 for (unsigned int i = 0; i < audio.getDeviceCount(); ++i) {
882 RtAudio::DeviceInfo dev = audio.getDeviceInfo (i);
883 if (dev.probed && dev.outputChannels > 0) {
884 _sound_output->Append (std_to_wx (dev.name));
886 #ifdef DCPOMATIC_USE_RTERROR
889 } catch (RtAudioError&) {
891 /* Something went wrong so let's just ignore that device */
895 _sound->Bind (wxEVT_CHECKBOX, bind(&SoundPage::sound_changed, this));
896 _sound_output->Bind (wxEVT_CHOICE, bind(&SoundPage::sound_output_changed, this));
897 _map->Changed.connect (bind(&SoundPage::map_changed, this, _1));
898 _reset_to_default->Bind (wxEVT_BUTTON, bind(&SoundPage::reset_to_default, this));
902 SoundPage::reset_to_default ()
904 Config::instance()->set_audio_mapping_to_default ();
908 SoundPage::map_changed (AudioMapping m)
910 Config::instance()->set_audio_mapping (m);
914 SoundPage::sound_changed ()
916 Config::instance()->set_sound (_sound->GetValue ());
920 SoundPage::sound_output_changed ()
922 RtAudio audio (DCPOMATIC_RTAUDIO_API);
923 optional<string> const so = get_sound_output();
924 string default_device;
926 default_device = audio.getDeviceInfo(audio.getDefaultOutputDevice()).name;
927 } catch (RtAudioError&) {
930 if (!so || *so == default_device) {
931 Config::instance()->unset_sound_output ();
933 Config::instance()->set_sound_output (*so);
938 SoundPage::config_changed ()
940 Config* config = Config::instance ();
942 checked_set (_sound, config->sound ());
944 optional<string> const current_so = get_sound_output ();
945 optional<string> configured_so;
947 if (config->sound_output()) {
948 configured_so = config->sound_output().get();
950 /* No configured output means we should use the default */
951 RtAudio audio (DCPOMATIC_RTAUDIO_API);
953 configured_so = audio.getDeviceInfo(audio.getDefaultOutputDevice()).name;
954 } catch (RtAudioError& e) {
955 /* Probably no audio devices at all */
959 if (configured_so && current_so != configured_so) {
960 /* Update _sound_output with the configured value */
962 while (i < _sound_output->GetCount()) {
963 if (_sound_output->GetString(i) == std_to_wx(*configured_so)) {
964 _sound_output->SetSelection (i);
971 RtAudio audio (DCPOMATIC_RTAUDIO_API);
973 map<int, wxString> apis;
974 apis[RtAudio::MACOSX_CORE] = _("CoreAudio");
975 apis[RtAudio::WINDOWS_ASIO] = _("ASIO");
976 apis[RtAudio::WINDOWS_DS] = _("Direct Sound");
977 apis[RtAudio::WINDOWS_WASAPI] = _("WASAPI");
978 apis[RtAudio::UNIX_JACK] = _("JACK");
979 apis[RtAudio::LINUX_ALSA] = _("ALSA");
980 apis[RtAudio::LINUX_PULSE] = _("PulseAudio");
981 apis[RtAudio::LINUX_OSS] = _("OSS");
982 apis[RtAudio::RTAUDIO_DUMMY] = _("Dummy");
986 for (unsigned int i = 0; i < audio.getDeviceCount(); ++i) {
987 RtAudio::DeviceInfo info = audio.getDeviceInfo(i);
988 if (info.name == *configured_so && info.outputChannels > 0) {
989 channels = info.outputChannels;
994 _sound_output_details->SetLabel (
995 wxString::Format(_("%d channels on %s"), channels, apis[audio.getCurrentApi()])
998 _map->set (Config::instance()->audio_mapping(channels));
1000 vector<string> input;
1001 for (int i = 0; i < MAX_DCP_AUDIO_CHANNELS; ++i) {
1002 input.push_back (short_audio_channel_name(i));
1004 _map->set_input_channels (input);
1006 vector<string> output;
1007 for (int i = 0; i < channels; ++i) {
1008 output.push_back (dcp::raw_convert<string>(i));
1010 _map->set_output_channels (output);
1012 setup_sensitivity ();
1016 SoundPage::setup_sensitivity ()
1018 _sound_output->Enable (_sound->GetValue());
1021 /** @return Currently-selected preview sound output in the dialogue */
1023 SoundPage::get_sound_output ()
1025 int const sel = _sound_output->GetSelection ();
1026 if (sel == wxNOT_FOUND) {
1027 return optional<string> ();
1030 return wx_to_std (_sound_output->GetString (sel));
1034 LocationsPage::LocationsPage (wxSize panel_size, int border)
1035 : Page (panel_size, border)
1041 LocationsPage::GetName () const
1043 return _("Locations");
1046 #ifdef DCPOMATIC_OSX
1048 LocationsPage::GetLargeIcon () const
1050 return wxBitmap ("locations", wxBITMAP_TYPE_PNG_RESOURCE);
1055 LocationsPage::setup ()
1059 wxGridBagSizer* table = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
1060 _panel->GetSizer()->Add (table, 1, wxALL | wxEXPAND, _border);
1062 add_label_to_sizer (table, _panel, _("Content directory"), true, wxGBPosition (r, 0));
1063 _content_directory = new wxDirPickerCtrl (_panel, wxID_ANY, wxEmptyString, wxDirSelectorPromptStr, wxDefaultPosition, wxSize (300, -1));
1064 table->Add (_content_directory, wxGBPosition (r, 1));
1067 add_label_to_sizer (table, _panel, _("Playlist directory"), true, wxGBPosition (r, 0));
1068 _playlist_directory = new wxDirPickerCtrl (_panel, wxID_ANY, wxEmptyString, wxDirSelectorPromptStr, wxDefaultPosition, wxSize (300, -1));
1069 table->Add (_playlist_directory, wxGBPosition (r, 1));
1072 add_label_to_sizer (table, _panel, _("KDM directory"), true, wxGBPosition (r, 0));
1073 _kdm_directory = new wxDirPickerCtrl (_panel, wxID_ANY, wxEmptyString, wxDirSelectorPromptStr, wxDefaultPosition, wxSize (300, -1));
1074 table->Add (_kdm_directory, wxGBPosition (r, 1));
1077 #ifdef DCPOMATIC_VARIANT_SWAROOP
1078 add_label_to_sizer (table, _panel, _("Background image"), true, wxGBPosition (r, 0));
1079 _background_image = new FilePickerCtrl (_panel, _("Select image file"), "*.png;*.jpg;*.jpeg;*.tif;*.tiff", true, false);
1080 table->Add (_background_image, wxGBPosition (r, 1));
1084 _content_directory->Bind (wxEVT_DIRPICKER_CHANGED, bind(&LocationsPage::content_directory_changed, this));
1085 _playlist_directory->Bind (wxEVT_DIRPICKER_CHANGED, bind(&LocationsPage::playlist_directory_changed, this));
1086 _kdm_directory->Bind (wxEVT_DIRPICKER_CHANGED, bind(&LocationsPage::kdm_directory_changed, this));
1087 #ifdef DCPOMATIC_VARIANT_SWAROOP
1088 _background_image->Bind (wxEVT_FILEPICKER_CHANGED, bind(&LocationsPage::background_image_changed, this));
1093 LocationsPage::config_changed ()
1095 Config* config = Config::instance ();
1097 if (config->player_content_directory()) {
1098 checked_set (_content_directory, *config->player_content_directory());
1100 if (config->player_playlist_directory()) {
1101 checked_set (_playlist_directory, *config->player_playlist_directory());
1103 if (config->player_kdm_directory()) {
1104 checked_set (_kdm_directory, *config->player_kdm_directory());
1106 #ifdef DCPOMATIC_VARIANT_SWAROOP
1107 if (config->player_background_image()) {
1108 checked_set (_background_image, *config->player_background_image());
1114 LocationsPage::content_directory_changed ()
1116 Config::instance()->set_player_content_directory(wx_to_std(_content_directory->GetPath()));
1120 LocationsPage::playlist_directory_changed ()
1122 Config::instance()->set_player_playlist_directory(wx_to_std(_playlist_directory->GetPath()));
1126 LocationsPage::kdm_directory_changed ()
1128 Config::instance()->set_player_kdm_directory(wx_to_std(_kdm_directory->GetPath()));
1131 #ifdef DCPOMATIC_VARIANT_SWAROOP
1133 LocationsPage::background_image_changed ()
1135 boost::filesystem::path const f = wx_to_std(_background_image->GetPath());
1136 if (!boost::filesystem::is_regular_file(f) || !wxImage::CanRead(std_to_wx(f.string()))) {
1137 error_dialog (0, _("Could not load image file."));
1138 if (Config::instance()->player_background_image()) {
1139 checked_set (_background_image, *Config::instance()->player_background_image());
1144 Config::instance()->set_player_background_image(f);