Remove GL context accessor and hence lock.
[dcpomatic.git] / src / wx / config_dialog.cc
index 3871f738c9711ba02e477730b53211fd2e217540..55d387528e9d42c55a0974d99fe094b2428bbf86 100644 (file)
@@ -1,5 +1,5 @@
 /*
-    Copyright (C) 2012-2018 Carl Hetherington <cth@carlh.net>
+    Copyright (C) 2012-2019 Carl Hetherington <cth@carlh.net>
 
     This file is part of DCP-o-matic.
 
 */
 
 #include "config_dialog.h"
+#include "static_text.h"
+#include "check_box.h"
 #include "nag_dialog.h"
+#include "dcpomatic_button.h"
+#include "audio_mapping_view.h"
+#include <dcp/raw_convert.h>
+#include <iostream>
 
 using std::string;
+using std::vector;
+using std::pair;
+using std::make_pair;
+using std::map;
 using boost::bind;
 using boost::optional;
 using boost::shared_ptr;
+using boost::function;
 
 static
-void
+bool
 do_nothing ()
 {
-
+       return false;
 }
 
 Page::Page (wxSize panel_size, int border)
@@ -39,7 +50,7 @@ Page::Page (wxSize panel_size, int border)
        , _panel_size (panel_size)
        , _window_exists (false)
 {
-       _config_connection = Config::instance()->Changed.connect (boost::bind (&Page::config_changed_wrapper, this));
+       _config_connection = Config::instance()->Changed.connect (bind (&Page::config_changed_wrapper, this));
 }
 
 wxWindow*
@@ -53,7 +64,7 @@ Page::create_window (wxWindow* parent)
        _window_exists = true;
        config_changed ();
 
-       _panel->Bind (wxEVT_DESTROY, boost::bind (&Page::window_destroyed, this));
+       _panel->Bind (wxEVT_DESTROY, bind (&Page::window_destroyed, this));
 
        return _panel;
 }
@@ -107,26 +118,27 @@ GeneralPage::GeneralPage (wxSize panel_size, int border)
 void
 GeneralPage::add_language_controls (wxGridBagSizer* table, int& r)
 {
-       _set_language = new wxCheckBox (_panel, wxID_ANY, _("Set language"));
-       table->Add (_set_language, wxGBPosition (r, 0));
+       _set_language = new CheckBox (_panel, _("Set language"));
+       table->Add (_set_language, wxGBPosition (r, 0), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
        _language = new wxChoice (_panel, wxID_ANY);
-       std::vector<std::pair<std::string, std::string> > languages;
-       languages.push_back (std::make_pair ("Čeština", "cs_CZ"));
-       languages.push_back (std::make_pair ("汉语/漢語", "zh_CN"));
-       languages.push_back (std::make_pair ("Dansk", "da_DK"));
-       languages.push_back (std::make_pair ("Deutsch", "de_DE"));
-       languages.push_back (std::make_pair ("English", "en_GB"));
-       languages.push_back (std::make_pair ("Español", "es_ES"));
-       languages.push_back (std::make_pair ("Français", "fr_FR"));
-       languages.push_back (std::make_pair ("Italiano", "it_IT"));
-       languages.push_back (std::make_pair ("Nederlands", "nl_NL"));
-       languages.push_back (std::make_pair ("Русский", "ru_RU"));
-       languages.push_back (std::make_pair ("Polski", "pl_PL"));
-       languages.push_back (std::make_pair ("Português europeu", "pt_PT"));
-       languages.push_back (std::make_pair ("Português do Brasil", "pt_BR"));
-       languages.push_back (std::make_pair ("Svenska", "sv_SE"));
-       languages.push_back (std::make_pair ("Slovenský jazyk", "sk_SK"));
-       languages.push_back (std::make_pair ("українська мова", "uk_UA"));
+       vector<pair<string, string> > languages;
+       languages.push_back (make_pair ("Čeština", "cs_CZ"));
+       languages.push_back (make_pair ("汉语/漢語", "zh_CN"));
+       languages.push_back (make_pair ("Dansk", "da_DK"));
+       languages.push_back (make_pair ("Deutsch", "de_DE"));
+       languages.push_back (make_pair ("English", "en_GB"));
+       languages.push_back (make_pair ("Español", "es_ES"));
+       languages.push_back (make_pair ("Français", "fr_FR"));
+       languages.push_back (make_pair ("Italiano", "it_IT"));
+       languages.push_back (make_pair ("Nederlands", "nl_NL"));
+       languages.push_back (make_pair ("Русский", "ru_RU"));
+       languages.push_back (make_pair ("Polski", "pl_PL"));
+       languages.push_back (make_pair ("Português europeu", "pt_PT"));
+       languages.push_back (make_pair ("Português do Brasil", "pt_BR"));
+       languages.push_back (make_pair ("Svenska", "sv_SE"));
+       languages.push_back (make_pair ("Slovenský jazyk", "sk_SK"));
+       languages.push_back (make_pair ("Türkçe", "tr_TR"));
+       languages.push_back (make_pair ("українська мова", "uk_UA"));
        checked_set (_language, languages);
        table->Add (_language, wxGBPosition (r, 1));
        ++r;
@@ -140,44 +152,23 @@ GeneralPage::add_language_controls (wxGridBagSizer* table, int& r)
        restart->SetFont (font);
        ++r;
 
-       _set_language->Bind (wxEVT_CHECKBOX, boost::bind (&GeneralPage::set_language_changed, this));
-       _language->Bind     (wxEVT_CHOICE,   boost::bind (&GeneralPage::language_changed,     this));
-}
-
-void
-GeneralPage::add_play_sound_controls (wxGridBagSizer* table, int& r)
-{
-       _sound = new wxCheckBox (_panel, wxID_ANY, _("Play sound via"));
-       table->Add (_sound, wxGBPosition (r, 0));
-       _sound_output = new wxChoice (_panel, wxID_ANY);
-       table->Add (_sound_output, wxGBPosition (r, 1));
-       ++r;
-
-       RtAudio audio (DCPOMATIC_RTAUDIO_API);
-       for (unsigned int i = 0; i < audio.getDeviceCount(); ++i) {
-               RtAudio::DeviceInfo dev = audio.getDeviceInfo (i);
-               if (dev.probed && dev.outputChannels > 0) {
-                       _sound_output->Append (std_to_wx (dev.name));
-               }
-       }
-
-       _sound->Bind        (wxEVT_CHECKBOX, boost::bind (&GeneralPage::sound_changed, this));
-       _sound_output->Bind (wxEVT_CHOICE,   boost::bind (&GeneralPage::sound_output_changed, this));
+       _set_language->Bind (wxEVT_CHECKBOX, bind (&GeneralPage::set_language_changed, this));
+       _language->Bind     (wxEVT_CHOICE,   bind (&GeneralPage::language_changed,     this));
 }
 
 void
 GeneralPage::add_update_controls (wxGridBagSizer* table, int& r)
 {
-       _check_for_updates = new wxCheckBox (_panel, wxID_ANY, _("Check for updates on startup"));
+       _check_for_updates = new CheckBox (_panel, _("Check for updates on startup"));
        table->Add (_check_for_updates, wxGBPosition (r, 0), wxGBSpan (1, 2));
        ++r;
 
-       _check_for_test_updates = new wxCheckBox (_panel, wxID_ANY, _("Check for testing updates on startup"));
+       _check_for_test_updates = new CheckBox (_panel, _("Check for testing updates on startup"));
        table->Add (_check_for_test_updates, wxGBPosition (r, 0), wxGBSpan (1, 2));
        ++r;
 
-       _check_for_updates->Bind (wxEVT_CHECKBOX, boost::bind (&GeneralPage::check_for_updates_changed, this));
-       _check_for_test_updates->Bind (wxEVT_CHECKBOX, boost::bind (&GeneralPage::check_for_test_updates_changed, this));
+       _check_for_updates->Bind (wxEVT_CHECKBOX, bind (&GeneralPage::check_for_updates_changed, this));
+       _check_for_test_updates->Bind (wxEVT_CHECKBOX, bind (&GeneralPage::check_for_test_updates_changed, this));
 }
 
 void
@@ -189,7 +180,7 @@ GeneralPage::config_changed ()
 
        /* Backwards compatibility of config file */
 
-       std::map<std::string, std::string> compat_map;
+       map<string, string> compat_map;
        compat_map["fr"] = "fr_FR";
        compat_map["it"] = "it_IT";
        compat_map["es"] = "es_ES";
@@ -204,7 +195,7 @@ GeneralPage::config_changed ()
        compat_map["cs"] = "cs_CZ";
        compat_map["uk"] = "uk_UA";
 
-       std::string lang = config->language().get_value_or ("en_GB");
+       string lang = config->language().get_value_or ("en_GB");
        if (compat_map.find (lang) != compat_map.end ()) {
                lang = compat_map[lang];
        }
@@ -214,35 +205,6 @@ GeneralPage::config_changed ()
        checked_set (_check_for_updates, config->check_for_updates ());
        checked_set (_check_for_test_updates, config->check_for_test_updates ());
 
-       checked_set (_sound, config->sound ());
-
-       boost::optional<std::string> const current_so = get_sound_output ();
-       boost::optional<std::string> configured_so;
-
-       if (config->sound_output()) {
-               configured_so = config->sound_output().get();
-       } else {
-               /* No configured output means we should use the default */
-               RtAudio audio (DCPOMATIC_RTAUDIO_API);
-               try {
-                       configured_so = audio.getDeviceInfo(audio.getDefaultOutputDevice()).name;
-               } catch (RtAudioError& e) {
-                       /* Probably no audio devices at all */
-               }
-       }
-
-       if (configured_so && current_so != configured_so) {
-               /* Update _sound_output with the configured value */
-               unsigned int i = 0;
-               while (i < _sound_output->GetCount()) {
-                       if (_sound_output->GetString(i) == std_to_wx(*configured_so)) {
-                               _sound_output->SetSelection (i);
-                               break;
-                       }
-                       ++i;
-               }
-       }
-
        setup_sensitivity ();
 }
 
@@ -251,19 +213,6 @@ GeneralPage::setup_sensitivity ()
 {
        _language->Enable (_set_language->GetValue ());
        _check_for_test_updates->Enable (_check_for_updates->GetValue ());
-       _sound_output->Enable (_sound->GetValue ());
-}
-
-/** @return Currently-selected preview sound output in the dialogue */
-boost::optional<std::string>
-GeneralPage::get_sound_output ()
-{
-       int const sel = _sound_output->GetSelection ();
-       if (sel == wxNOT_FOUND) {
-               return boost::optional<std::string> ();
-       }
-
-       return wx_to_std (_sound_output->GetString (sel));
 }
 
 void
@@ -300,36 +249,18 @@ GeneralPage::check_for_test_updates_changed ()
        Config::instance()->set_check_for_test_updates (_check_for_test_updates->GetValue ());
 }
 
-void
-GeneralPage::sound_changed ()
-{
-       Config::instance()->set_sound (_sound->GetValue ());
-}
-
-void
-GeneralPage::sound_output_changed ()
-{
-       RtAudio audio (DCPOMATIC_RTAUDIO_API);
-       boost::optional<std::string> const so = get_sound_output();
-       if (!so || *so == audio.getDeviceInfo(audio.getDefaultOutputDevice()).name) {
-               Config::instance()->unset_sound_output ();
-       } else {
-               Config::instance()->set_sound_output (*so);
-       }
-}
-
 CertificateChainEditor::CertificateChainEditor (
        wxWindow* parent,
        wxString title,
        int border,
-       boost::function<void (boost::shared_ptr<dcp::CertificateChain>)> set,
-       boost::function<boost::shared_ptr<const dcp::CertificateChain> (void)> get,
-       boost::function<void (void)> nag_remake
+       function<void (shared_ptr<dcp::CertificateChain>)> set,
+       function<shared_ptr<const dcp::CertificateChain> (void)> get,
+       function<bool (void)> nag_alter
        )
        : wxDialog (parent, wxID_ANY, title)
        , _set (set)
        , _get (get)
-       , _nag_remake (nag_remake)
+       , _nag_alter (nag_alter)
 {
        wxFont subheading_font (*wxNORMAL_FONT);
        subheading_font.SetWeight (wxFONTWEIGHT_BOLD);
@@ -337,7 +268,7 @@ CertificateChainEditor::CertificateChainEditor (
        _sizer = new wxBoxSizer (wxVERTICAL);
 
        {
-               wxStaticText* m = new wxStaticText (this, wxID_ANY, title);
+               wxStaticText* m = new StaticText (this, title);
                m->SetFont (subheading_font);
                _sizer->Add (m, 0, wxALL, border);
        }
@@ -372,12 +303,14 @@ CertificateChainEditor::CertificateChainEditor (
 
        {
                wxSizer* s = new wxBoxSizer (wxVERTICAL);
-               _add_certificate = new wxButton (this, wxID_ANY, _("Add..."));
-               s->Add (_add_certificate, 0, wxTOP | wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
-               _remove_certificate = new wxButton (this, wxID_ANY, _("Remove"));
-               s->Add (_remove_certificate, 0, wxTOP | wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
-               _export_certificate = new wxButton (this, wxID_ANY, _("Export"));
-               s->Add (_export_certificate, 0, wxTOP | wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
+               _add_certificate = new Button (this, _("Add..."));
+               s->Add (_add_certificate, 1, wxTOP | wxBOTTOM | wxEXPAND, DCPOMATIC_BUTTON_STACK_GAP);
+               _remove_certificate = new Button (this, _("Remove"));
+               s->Add (_remove_certificate, 1, wxTOP | wxBOTTOM | wxEXPAND, DCPOMATIC_BUTTON_STACK_GAP);
+               _export_certificate = new Button (this, _("Export certificate..."));
+               s->Add (_export_certificate, 1, wxTOP | wxBOTTOM | wxEXPAND, DCPOMATIC_BUTTON_STACK_GAP);
+               _export_chain = new Button (this, _("Export chain..."));
+               s->Add (_export_chain, 1, wxTOP | wxBOTTOM | wxEXPAND, DCPOMATIC_BUTTON_STACK_GAP);
                certificates_sizer->Add (s, 0, wxLEFT, DCPOMATIC_SIZER_X_GAP);
        }
 
@@ -386,38 +319,39 @@ CertificateChainEditor::CertificateChainEditor (
        int r = 0;
 
        add_label_to_sizer (table, this, _("Leaf private key"), true, wxGBPosition (r, 0));
-       _private_key = new wxStaticText (this, wxID_ANY, wxT (""));
+       _private_key = new StaticText (this, wxT(""));
        wxFont font = _private_key->GetFont ();
        font.SetFamily (wxFONTFAMILY_TELETYPE);
        _private_key->SetFont (font);
        table->Add (_private_key, wxGBPosition (r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
-       _import_private_key = new wxButton (this, wxID_ANY, _("Import..."));
+       _import_private_key = new Button (this, _("Import..."));
        table->Add (_import_private_key, wxGBPosition (r, 2));
-       _export_private_key = new wxButton (this, wxID_ANY, _("Export..."));
+       _export_private_key = new Button (this, _("Export..."));
        table->Add (_export_private_key, wxGBPosition (r, 3));
        ++r;
 
        _button_sizer = new wxBoxSizer (wxHORIZONTAL);
-       _remake_certificates = new wxButton (this, wxID_ANY, _("Re-make certificates and key..."));
+       _remake_certificates = new Button (this, _("Re-make certificates and key..."));
        _button_sizer->Add (_remake_certificates, 1, wxRIGHT, border);
        table->Add (_button_sizer, wxGBPosition (r, 0), wxGBSpan (1, 4));
        ++r;
 
-       _private_key_bad = new wxStaticText (this, wxID_ANY, _("Leaf private key does not match leaf certificate!"));
+       _private_key_bad = new StaticText (this, _("Leaf private key does not match leaf certificate!"));
        font = *wxSMALL_FONT;
        font.SetWeight (wxFONTWEIGHT_BOLD);
        _private_key_bad->SetFont (font);
        table->Add (_private_key_bad, wxGBPosition (r, 0), wxGBSpan (1, 3));
        ++r;
 
-       _add_certificate->Bind     (wxEVT_BUTTON,       boost::bind (&CertificateChainEditor::add_certificate, this));
-       _remove_certificate->Bind  (wxEVT_BUTTON,       boost::bind (&CertificateChainEditor::remove_certificate, this));
-       _export_certificate->Bind  (wxEVT_BUTTON,       boost::bind (&CertificateChainEditor::export_certificate, this));
-       _certificates->Bind        (wxEVT_LIST_ITEM_SELECTED,   boost::bind (&CertificateChainEditor::update_sensitivity, this));
-       _certificates->Bind        (wxEVT_LIST_ITEM_DESELECTED, boost::bind (&CertificateChainEditor::update_sensitivity, this));
-       _remake_certificates->Bind (wxEVT_BUTTON,       boost::bind (&CertificateChainEditor::remake_certificates, this));
-       _import_private_key->Bind  (wxEVT_BUTTON,       boost::bind (&CertificateChainEditor::import_private_key, this));
-       _export_private_key->Bind  (wxEVT_BUTTON,       boost::bind (&CertificateChainEditor::export_private_key, this));
+       _add_certificate->Bind     (wxEVT_BUTTON,       bind (&CertificateChainEditor::add_certificate, this));
+       _remove_certificate->Bind  (wxEVT_BUTTON,       bind (&CertificateChainEditor::remove_certificate, this));
+       _export_certificate->Bind  (wxEVT_BUTTON,       bind (&CertificateChainEditor::export_certificate, this));
+       _certificates->Bind        (wxEVT_LIST_ITEM_SELECTED,   bind (&CertificateChainEditor::update_sensitivity, this));
+       _certificates->Bind        (wxEVT_LIST_ITEM_DESELECTED, bind (&CertificateChainEditor::update_sensitivity, this));
+       _remake_certificates->Bind (wxEVT_BUTTON,       bind (&CertificateChainEditor::remake_certificates, this));
+       _export_chain->Bind        (wxEVT_BUTTON,       bind (&CertificateChainEditor::export_chain, this));
+       _import_private_key->Bind  (wxEVT_BUTTON,       bind (&CertificateChainEditor::import_private_key, this));
+       _export_private_key->Bind  (wxEVT_BUTTON,       bind (&CertificateChainEditor::export_private_key, this));
 
        wxSizer* buttons = CreateSeparatedButtonSizer (wxCLOSE);
        if (buttons) {
@@ -446,11 +380,11 @@ CertificateChainEditor::add_certificate ()
        if (d->ShowModal() == wxID_OK) {
                try {
                        dcp::Certificate c;
-                       std::string extra;
+                       string extra;
                        try {
                                extra = c.read_string (dcp::file_to_string (wx_to_std (d->GetPath ())));
                        } catch (boost::filesystem::filesystem_error& e) {
-                               error_dialog (this, wxString::Format (_("Could not import certificate (%s)"), d->GetPath().data()));
+                               error_dialog (this, _("Could not import certificate (%s)"), d->GetPath());
                                d->Destroy ();
                                return;
                        }
@@ -476,7 +410,7 @@ CertificateChainEditor::add_certificate ()
                                update_certificate_list ();
                        }
                } catch (dcp::MiscError& e) {
-                       error_dialog (this, wxString::Format (_("Could not read certificate file (%s)"), e.what ()));
+                       error_dialog (this, _("Could not read certificate file."), std_to_wx(e.what()));
                }
        }
 
@@ -488,6 +422,11 @@ CertificateChainEditor::add_certificate ()
 void
 CertificateChainEditor::remove_certificate ()
 {
+       if (_nag_alter()) {
+               /* Cancel was clicked */
+               return;
+       }
+
        int i = _certificates->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
        if (i == -1) {
                return;
@@ -499,6 +438,7 @@ CertificateChainEditor::remove_certificate ()
        _set (chain);
 
        update_sensitivity ();
+       update_certificate_list ();
 }
 
 void
@@ -521,15 +461,39 @@ CertificateChainEditor::export_certificate ()
        }
 
        if (d->ShowModal () == wxID_OK) {
-               FILE* f = fopen_boost (path_from_file_dialog (d, "pem"), "w");
+               boost::filesystem::path path (wx_to_std(d->GetPath()));
+               FILE* f = fopen_boost (path, "w");
+               if (!f) {
+                       throw OpenFileError (path, errno, OpenFileError::WRITE);
+               }
+
+               string const s = j->certificate (true);
+               checked_fwrite (s.c_str(), s.length(), f, path);
+               fclose (f);
+       }
+       d->Destroy ();
+}
+
+void
+CertificateChainEditor::export_chain ()
+{
+       wxFileDialog* d = new wxFileDialog (
+               this, _("Select Chain File"), wxEmptyString, wxEmptyString, wxT("PEM files (*.pem)|*.pem"),
+               wxFD_SAVE | wxFD_OVERWRITE_PROMPT
+               );
+
+       if (d->ShowModal () == wxID_OK) {
+               boost::filesystem::path path (wx_to_std(d->GetPath()));
+               FILE* f = fopen_boost (path, "w");
                if (!f) {
-                       throw OpenFileError (wx_to_std (d->GetPath ()), errno, false);
+                       throw OpenFileError (path, errno, OpenFileError::WRITE);
                }
 
-               std::string const s = j->certificate (true);
-               fwrite (s.c_str(), 1, s.length(), f);
+               string const s = _get()->chain();
+               checked_fwrite (s.c_str(), s.length(), f, path);
                fclose (f);
        }
+
        d->Destroy ();
 }
 
@@ -570,13 +534,13 @@ CertificateChainEditor::update_certificate_list ()
 void
 CertificateChainEditor::remake_certificates ()
 {
-       boost::shared_ptr<const dcp::CertificateChain> chain = _get();
+       shared_ptr<const dcp::CertificateChain> chain = _get();
 
-       std::string subject_organization_name;
-       std::string subject_organizational_unit_name;
-       std::string root_common_name;
-       std::string intermediate_common_name;
-       std::string leaf_common_name;
+       string subject_organization_name;
+       string subject_organizational_unit_name;
+       string root_common_name;
+       string intermediate_common_name;
+       string leaf_common_name;
 
        dcp::CertificateChain::List all = chain->root_to_leaf ();
 
@@ -599,7 +563,10 @@ CertificateChainEditor::remake_certificates ()
                intermediate_common_name = i->subject_common_name ();
        }
 
-       _nag_remake ();
+       if (_nag_alter()) {
+               /* Cancel was clicked */
+               return;
+       }
 
        MakeChainDialog* d = new MakeChainDialog (
                this,
@@ -667,7 +634,7 @@ CertificateChainEditor::import_private_key ()
                        _set (chain);
                        update_private_key ();
                } catch (dcp::MiscError& e) {
-                       error_dialog (this, wxString::Format (_("Could not read certificate file (%s)"), e.what ()));
+                       error_dialog (this, _("Could not read certificate file."), std_to_wx(e.what()));
                }
        }
 
@@ -679,7 +646,7 @@ CertificateChainEditor::import_private_key ()
 void
 CertificateChainEditor::export_private_key ()
 {
-       boost::optional<std::string> key = _get()->key();
+       optional<string> key = _get()->key();
        if (!key) {
                return;
        }
@@ -690,13 +657,14 @@ CertificateChainEditor::export_private_key ()
                );
 
        if (d->ShowModal () == wxID_OK) {
-               FILE* f = fopen_boost (path_from_file_dialog (d, "pem"), "w");
+               boost::filesystem::path path (wx_to_std(d->GetPath()));
+               FILE* f = fopen_boost (path, "w");
                if (!f) {
-                       throw OpenFileError (wx_to_std (d->GetPath ()), errno, false);
+                       throw OpenFileError (path, errno, OpenFileError::WRITE);
                }
 
-               std::string const s = _get()->key().get ();
-               fwrite (s.c_str(), 1, s.length(), f);
+               string const s = _get()->key().get ();
+               checked_fwrite (s.c_str(), s.length(), f, path);
                fclose (f);
        }
        d->Destroy ();
@@ -717,35 +685,32 @@ KeysPage::setup ()
        wxSizer* sizer = _panel->GetSizer();
 
        {
-               wxStaticText* m = new wxStaticText (_panel, wxID_ANY, _("Decrypting KDMs"));
+               wxStaticText* m = new StaticText (_panel, _("Decrypting KDMs"));
                m->SetFont (subheading_font);
                sizer->Add (m, 0, wxALL, _border);
        }
 
-       wxButton* export_decryption_certificate = new wxButton (_panel, wxID_ANY, _("Export KDM decryption certificate..."));
+       wxButton* export_decryption_certificate = new Button (_panel, _("Export KDM decryption certificate..."));
        sizer->Add (export_decryption_certificate, 0, wxLEFT, _border);
-       wxButton* export_decryption_chain = new wxButton (_panel, wxID_ANY, _("Export KDM decryption chain..."));
-       sizer->Add (export_decryption_chain, 0, wxLEFT, _border);
-       wxButton* export_settings = new wxButton (_panel, wxID_ANY, _("Export all KDM decryption settings..."));
+       wxButton* export_settings = new Button (_panel, _("Export all KDM decryption settings..."));
        sizer->Add (export_settings, 0, wxLEFT, _border);
-       wxButton* import_settings = new wxButton (_panel, wxID_ANY, _("Import all KDM decryption settings..."));
+       wxButton* import_settings = new Button (_panel, _("Import all KDM decryption settings..."));
        sizer->Add (import_settings, 0, wxLEFT, _border);
-       wxButton* decryption_advanced = new wxButton (_panel, wxID_ANY, _("Advanced..."));
+       wxButton* decryption_advanced = new Button (_panel, _("Advanced..."));
        sizer->Add (decryption_advanced, 0, wxALL, _border);
 
        export_decryption_certificate->Bind (wxEVT_BUTTON, bind (&KeysPage::export_decryption_certificate, this));
-       export_decryption_chain->Bind (wxEVT_BUTTON, bind (&KeysPage::export_decryption_chain, this));
        export_settings->Bind (wxEVT_BUTTON, bind (&KeysPage::export_decryption_chain_and_key, this));
        import_settings->Bind (wxEVT_BUTTON, bind (&KeysPage::import_decryption_chain_and_key, this));
        decryption_advanced->Bind (wxEVT_BUTTON, bind (&KeysPage::decryption_advanced, this));
 
        {
-               wxStaticText* m = new wxStaticText (_panel, wxID_ANY, _("Signing DCPs and KDMs"));
+               wxStaticText* m = new StaticText (_panel, _("Signing DCPs and KDMs"));
                m->SetFont (subheading_font);
                sizer->Add (m, 0, wxALL, _border);
        }
 
-       wxButton* signing_advanced = new wxButton (_panel, wxID_ANY, _("Advanced..."));
+       wxButton* signing_advanced = new Button (_panel, _("Advanced..."));
        sizer->Add (signing_advanced, 0, wxLEFT, _border);
        signing_advanced->Bind (wxEVT_BUTTON, bind (&KeysPage::signing_advanced, this));
 }
@@ -757,7 +722,7 @@ KeysPage::decryption_advanced ()
                _panel, _("Decrypting KDMs"), _border,
                bind (&Config::set_decryption_chain, Config::instance (), _1),
                bind (&Config::decryption_chain, Config::instance ()),
-               bind (&KeysPage::nag_remake_decryption_chain, this)
+               bind (&KeysPage::nag_alter_decryption_chain, this)
                );
 
        c->ShowModal();
@@ -785,16 +750,17 @@ KeysPage::export_decryption_chain_and_key ()
                );
 
        if (d->ShowModal () == wxID_OK) {
-               FILE* f = fopen_boost (path_from_file_dialog (d, "dom"), "w");
+               boost::filesystem::path path (wx_to_std(d->GetPath()));
+               FILE* f = fopen_boost (path, "w");
                if (!f) {
-                       throw OpenFileError (wx_to_std (d->GetPath ()), errno, false);
+                       throw OpenFileError (path, errno, OpenFileError::WRITE);
                }
 
                string const chain = Config::instance()->decryption_chain()->chain();
-               fwrite (chain.c_str(), 1, chain.length(), f);
+               checked_fwrite (chain.c_str(), chain.length(), f, path);
                optional<string> const key = Config::instance()->decryption_chain()->key();
                DCPOMATIC_ASSERT (key);
-               fwrite (key->c_str(), 1, key->length(), f);
+               checked_fwrite (key->c_str(), key->length(), f, path);
                fclose (f);
        }
        d->Destroy ();
@@ -804,6 +770,15 @@ KeysPage::export_decryption_chain_and_key ()
 void
 KeysPage::import_decryption_chain_and_key ()
 {
+       if (NagDialog::maybe_nag (
+                   _panel,
+                   Config::NAG_IMPORT_DECRYPTION_CHAIN,
+                   _("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!"),
+                   true
+                   )) {
+               return;
+       }
+
        wxFileDialog* d = new wxFileDialog (
                _panel, _("Select File To Import"), wxEmptyString, wxEmptyString, wxT ("DOM files (*.dom)|*.dom")
                );
@@ -813,7 +788,7 @@ KeysPage::import_decryption_chain_and_key ()
 
                FILE* f = fopen_boost (wx_to_std (d->GetPath ()), "r");
                if (!f) {
-                       throw OpenFileError (wx_to_std (d->GetPath ()), errno, false);
+                       throw OpenFileError (wx_to_std (d->GetPath ()), errno, OpenFileError::WRITE);
                }
 
                string current;
@@ -827,7 +802,6 @@ KeysPage::import_decryption_chain_and_key ()
                                new_chain->add (dcp::Certificate (current));
                                current = "";
                        } else if (strncmp (buffer, "-----END RSA PRIVATE KEY-----", 29) == 0) {
-                               std::cout << "the key is " << current << "\n";
                                new_chain->set_key (current);
                                current = "";
                        }
@@ -843,54 +817,215 @@ KeysPage::import_decryption_chain_and_key ()
        d->Destroy ();
 }
 
-void
-KeysPage::nag_remake_decryption_chain ()
+bool
+KeysPage::nag_alter_decryption_chain ()
 {
-       NagDialog::maybe_nag (
+       return NagDialog::maybe_nag (
                _panel,
-               Config::NAG_REMAKE_DECRYPTION_CHAIN,
-               _("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!")
+               Config::NAG_ALTER_DECRYPTION_CHAIN,
+               _("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!"),
+               true
                );
 }
 
 void
-KeysPage::export_decryption_chain ()
+KeysPage::export_decryption_certificate ()
 {
        wxFileDialog* d = new wxFileDialog (
-               _panel, _("Select Chain File"), wxEmptyString, wxEmptyString, wxT ("PEM files (*.pem)|*.pem"),
+               _panel, _("Select Certificate File"), wxEmptyString, _("dcpomatic_kdm_decryption_cert.pem"), wxT ("PEM files (*.pem)|*.pem"),
                wxFD_SAVE | wxFD_OVERWRITE_PROMPT
                );
 
        if (d->ShowModal () == wxID_OK) {
-               FILE* f = fopen_boost (path_from_file_dialog (d, "pem"), "w");
+               boost::filesystem::path path (wx_to_std(d->GetPath()));
+               FILE* f = fopen_boost (path, "w");
                if (!f) {
-                       throw OpenFileError (wx_to_std (d->GetPath ()), errno, false);
+                       throw OpenFileError (path, errno, OpenFileError::WRITE);
                }
 
-               std::string const s = Config::instance()->decryption_chain()->chain();
-               fwrite (s.c_str(), 1, s.length(), f);
+               string const s = Config::instance()->decryption_chain()->leaf().certificate (true);
+               checked_fwrite (s.c_str(), s.length(), f, path);
                fclose (f);
        }
+
        d->Destroy ();
 }
 
+wxString
+SoundPage::GetName () const
+{
+       return _("Sound");
+}
+
 void
-KeysPage::export_decryption_certificate ()
+SoundPage::setup ()
 {
-       wxFileDialog* d = new wxFileDialog (
-               _panel, _("Select Certificate File"), wxEmptyString, wxEmptyString, wxT ("PEM files (*.pem)|*.pem"),
-               wxFD_SAVE | wxFD_OVERWRITE_PROMPT
-               );
+       wxGridBagSizer* table = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
+       _panel->GetSizer()->Add (table, 1, wxALL | wxEXPAND, _border);
 
-       if (d->ShowModal () == wxID_OK) {
-               FILE* f = fopen_boost (path_from_file_dialog (d, "pem"), "w");
-               if (!f) {
-                       throw OpenFileError (wx_to_std (d->GetPath ()), errno, false);
+       int r = 0;
+
+       _sound = new CheckBox (_panel, _("Play sound via"));
+       table->Add (_sound, wxGBPosition (r, 0), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
+       wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
+       _sound_output = new wxChoice (_panel, wxID_ANY);
+       s->Add (_sound_output, 0);
+       _sound_output_details = new wxStaticText (_panel, wxID_ANY, wxT(""));
+       s->Add (_sound_output_details, 1, wxALIGN_CENTER_VERTICAL | wxLEFT, DCPOMATIC_SIZER_X_GAP);
+       table->Add (s, wxGBPosition(r, 1));
+       ++r;
+
+       add_label_to_sizer (table, _panel, _("Mapping"), true, wxGBPosition(r, 0));
+       _map = new AudioMappingView (_panel, _("DCP"), _("DCP"), _("Output"), _("output"));
+       _map->SetSize (-1, 600);
+       table->Add (_map, wxGBPosition(r, 1), wxDefaultSpan, wxEXPAND);
+       ++r;
+
+       _reset_to_default = new Button (_panel, _("Reset to default"));
+       table->Add (_reset_to_default, wxGBPosition(r, 1));
+       ++r;
+
+       wxFont font = _sound_output_details->GetFont();
+       font.SetStyle (wxFONTSTYLE_ITALIC);
+       font.SetPointSize (font.GetPointSize() - 1);
+       _sound_output_details->SetFont (font);
+
+       RtAudio audio (DCPOMATIC_RTAUDIO_API);
+       for (unsigned int i = 0; i < audio.getDeviceCount(); ++i) {
+               RtAudio::DeviceInfo dev = audio.getDeviceInfo (i);
+               if (dev.probed && dev.outputChannels > 0) {
+                       _sound_output->Append (std_to_wx (dev.name));
                }
+       }
 
-               std::string const s = Config::instance()->decryption_chain()->leaf().certificate (true);
-               fwrite (s.c_str(), 1, s.length(), f);
-               fclose (f);
+       _sound->Bind        (wxEVT_CHECKBOX, bind(&SoundPage::sound_changed, this));
+       _sound_output->Bind (wxEVT_CHOICE,   bind(&SoundPage::sound_output_changed, this));
+       _map->Changed.connect (bind(&SoundPage::map_changed, this, _1));
+       _reset_to_default->Bind (wxEVT_BUTTON,   bind(&SoundPage::reset_to_default, this));
+}
+
+void
+SoundPage::reset_to_default ()
+{
+       Config::instance()->set_audio_mapping_to_default ();
+}
+
+void
+SoundPage::map_changed (AudioMapping m)
+{
+       Config::instance()->set_audio_mapping (m);
+}
+
+void
+SoundPage::sound_changed ()
+{
+       Config::instance()->set_sound (_sound->GetValue ());
+}
+
+void
+SoundPage::sound_output_changed ()
+{
+       RtAudio audio (DCPOMATIC_RTAUDIO_API);
+       optional<string> const so = get_sound_output();
+       if (!so || *so == audio.getDeviceInfo(audio.getDefaultOutputDevice()).name) {
+               Config::instance()->unset_sound_output ();
+       } else {
+               Config::instance()->set_sound_output (*so);
        }
-       d->Destroy ();
+}
+
+void
+SoundPage::config_changed ()
+{
+       Config* config = Config::instance ();
+
+       checked_set (_sound, config->sound ());
+
+       optional<string> const current_so = get_sound_output ();
+       optional<string> configured_so;
+
+       if (config->sound_output()) {
+               configured_so = config->sound_output().get();
+       } else {
+               /* No configured output means we should use the default */
+               RtAudio audio (DCPOMATIC_RTAUDIO_API);
+               try {
+                       configured_so = audio.getDeviceInfo(audio.getDefaultOutputDevice()).name;
+               } catch (RtAudioError& e) {
+                       /* Probably no audio devices at all */
+               }
+       }
+
+       if (configured_so && current_so != configured_so) {
+               /* Update _sound_output with the configured value */
+               unsigned int i = 0;
+               while (i < _sound_output->GetCount()) {
+                       if (_sound_output->GetString(i) == std_to_wx(*configured_so)) {
+                               _sound_output->SetSelection (i);
+                               break;
+                       }
+                       ++i;
+               }
+       }
+
+       RtAudio audio (DCPOMATIC_RTAUDIO_API);
+
+       map<int, wxString> apis;
+       apis[RtAudio::MACOSX_CORE]    = _("CoreAudio");
+       apis[RtAudio::WINDOWS_ASIO]   = _("ASIO");
+       apis[RtAudio::WINDOWS_DS]     = _("Direct Sound");
+       apis[RtAudio::WINDOWS_WASAPI] = _("WASAPI");
+       apis[RtAudio::UNIX_JACK]      = _("JACK");
+       apis[RtAudio::LINUX_ALSA]     = _("ALSA");
+       apis[RtAudio::LINUX_PULSE]    = _("PulseAudio");
+       apis[RtAudio::LINUX_OSS]      = _("OSS");
+       apis[RtAudio::RTAUDIO_DUMMY]  = _("Dummy");
+
+       int channels = 0;
+       if (configured_so) {
+               for (unsigned int i = 0; i < audio.getDeviceCount(); ++i) {
+                       RtAudio::DeviceInfo info = audio.getDeviceInfo(i);
+                       if (info.name == *configured_so && info.outputChannels > 0) {
+                               channels = info.outputChannels;
+                       }
+               }
+       }
+
+       _sound_output_details->SetLabel (
+               wxString::Format(_("%d channels on %s"), channels, apis[audio.getCurrentApi()])
+               );
+
+       _map->set (Config::instance()->audio_mapping(channels));
+
+       vector<string> input;
+       for (int i = 0; i < MAX_DCP_AUDIO_CHANNELS; ++i) {
+               input.push_back (short_audio_channel_name(i));
+       }
+       _map->set_input_channels (input);
+
+       vector<string> output;
+       for (int i = 0; i < channels; ++i) {
+               output.push_back (dcp::raw_convert<string>(i));
+       }
+       _map->set_output_channels (output);
+
+       setup_sensitivity ();
+}
+
+void
+SoundPage::setup_sensitivity ()
+{
+       _sound_output->Enable (_sound->GetValue());
+}
+
+/** @return Currently-selected preview sound output in the dialogue */
+optional<string>
+SoundPage::get_sound_output ()
+{
+       int const sel = _sound_output->GetSelection ();
+       if (sel == wxNOT_FOUND) {
+               return optional<string> ();
+       }
+
+       return wx_to_std (_sound_output->GetString (sel));
 }