Be a little more careful to handle exceptions from boost::filesystem::file_size
[dcpomatic.git] / src / wx / config_dialog.cc
index 0a7e45221c83e3e9a232f6020afecd6954589af9..e0effec53eafb90a25d2ff2e966f9d92f90406d7 100644 (file)
@@ -1,5 +1,5 @@
 /*
-    Copyright (C) 2012-2019 Carl Hetherington <cth@carlh.net>
+    Copyright (C) 2012-2021 Carl Hetherington <cth@carlh.net>
 
     This file is part of DCP-o-matic.
 
 #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 std::make_shared;
 using boost::bind;
 using boost::optional;
-using boost::shared_ptr;
-using boost::function;
+using std::shared_ptr;
+using std::function;
+#if BOOST_VERSION >= 106100
+using namespace boost::placeholders;
+#endif
 
 static
 bool
@@ -50,11 +57,19 @@ Page::Page (wxSize panel_size, int border)
        _config_connection = Config::instance()->Changed.connect (bind (&Page::config_changed_wrapper, this));
 }
 
+
+wxWindow*
+Page::CreateWindow (wxWindow* parent)
+{
+       return create_window (parent);
+}
+
+
 wxWindow*
 Page::create_window (wxWindow* parent)
 {
        _panel = new wxPanel (parent, wxID_ANY, wxDefaultPosition, _panel_size);
-       wxBoxSizer* s = new wxBoxSizer (wxVERTICAL);
+       auto s = new wxBoxSizer (wxVERTICAL);
        _panel->SetSizer (s);
 
        setup ();
@@ -81,65 +96,49 @@ Page::window_destroyed ()
 }
 
 
-StockPage::StockPage (Kind kind, wxSize panel_size, int border)
-       : wxStockPreferencesPage (kind)
-       , Page (panel_size, border)
-{
-
-}
-
-wxWindow*
-StockPage::CreateWindow (wxWindow* parent)
-{
-       return create_window (parent);
-}
-
-StandardPage::StandardPage (wxSize panel_size, int border)
+GeneralPage::GeneralPage (wxSize panel_size, int border)
        : Page (panel_size, border)
 {
 
 }
 
-wxWindow*
-StandardPage::CreateWindow (wxWindow* parent)
-{
-       return create_window (parent);
-}
 
-GeneralPage::GeneralPage (wxSize panel_size, int border)
-       : StockPage (Kind_General, panel_size, border)
+wxString
+GeneralPage::GetName () const
 {
-
+       return _("General");
 }
 
+
 void
 GeneralPage::add_language_controls (wxGridBagSizer* table, int& r)
 {
        _set_language = new CheckBox (_panel, _("Set language"));
        table->Add (_set_language, wxGBPosition (r, 0), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
        _language = new wxChoice (_panel, wxID_ANY);
-       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 ("українська мова", "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;
 
-       wxStaticText* restart = add_label_to_sizer (
+       auto restart = add_label_to_sizer (
                table, _panel, _("(restart DCP-o-matic to see language changes)"), false, wxGBPosition (r, 0), wxGBSpan (1, 2)
                );
        wxFont font = restart->GetFont();
@@ -152,27 +151,6 @@ GeneralPage::add_language_controls (wxGridBagSizer* table, int& r)
        _language->Bind     (wxEVT_CHOICE,   bind (&GeneralPage::language_changed,     this));
 }
 
-void
-GeneralPage::add_play_sound_controls (wxGridBagSizer* table, int& r)
-{
-       _sound = new CheckBox (_panel, _("Play sound via"));
-       table->Add (_sound, wxGBPosition (r, 0), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
-       _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, bind (&GeneralPage::sound_changed, this));
-       _sound_output->Bind (wxEVT_CHOICE,   bind (&GeneralPage::sound_output_changed, this));
-}
-
 void
 GeneralPage::add_update_controls (wxGridBagSizer* table, int& r)
 {
@@ -191,7 +169,7 @@ GeneralPage::add_update_controls (wxGridBagSizer* table, int& r)
 void
 GeneralPage::config_changed ()
 {
-       Config* config = Config::instance ();
+       auto config = Config::instance ();
 
        checked_set (_set_language, static_cast<bool>(config->language()));
 
@@ -212,8 +190,8 @@ GeneralPage::config_changed ()
        compat_map["cs"] = "cs_CZ";
        compat_map["uk"] = "uk_UA";
 
-       string lang = config->language().get_value_or ("en_GB");
-       if (compat_map.find (lang) != compat_map.end ()) {
+       auto lang = config->language().get_value_or("en_GB");
+       if (compat_map.find(lang) != compat_map.end ()) {
                lang = compat_map[lang];
        }
 
@@ -222,35 +200,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 ());
-
-       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;
-               }
-       }
-
        setup_sensitivity ();
 }
 
@@ -259,19 +208,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 */
-optional<string>
-GeneralPage::get_sound_output ()
-{
-       int const sel = _sound_output->GetSelection ();
-       if (sel == wxNOT_FOUND) {
-               return optional<string> ();
-       }
-
-       return wx_to_std (_sound_output->GetString (sel));
 }
 
 void
@@ -308,24 +244,6 @@ 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);
-       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);
-       }
-}
-
 CertificateChainEditor::CertificateChainEditor (
        wxWindow* parent,
        wxString title,
@@ -339,19 +257,10 @@ CertificateChainEditor::CertificateChainEditor (
        , _get (get)
        , _nag_alter (nag_alter)
 {
-       wxFont subheading_font (*wxNORMAL_FONT);
-       subheading_font.SetWeight (wxFONTWEIGHT_BOLD);
-
        _sizer = new wxBoxSizer (wxVERTICAL);
 
-       {
-               wxStaticText* m = new StaticText (this, title);
-               m->SetFont (subheading_font);
-               _sizer->Add (m, 0, wxALL, border);
-       }
-
-       wxBoxSizer* certificates_sizer = new wxBoxSizer (wxHORIZONTAL);
-       _sizer->Add (certificates_sizer, 0, wxLEFT | wxRIGHT, border);
+       auto certificates_sizer = new wxBoxSizer (wxHORIZONTAL);
+       _sizer->Add (certificates_sizer, 0, wxALL, border);
 
        _certificates = new wxListCtrl (this, wxID_ANY, wxDefaultPosition, wxSize (440, 150), wxLC_REPORT | wxLC_SINGLE_SEL);
 
@@ -379,17 +288,19 @@ CertificateChainEditor::CertificateChainEditor (
        certificates_sizer->Add (_certificates, 1, wxEXPAND);
 
        {
-               wxSizer* s = new wxBoxSizer (wxVERTICAL);
+               auto s = new wxBoxSizer (wxVERTICAL);
                _add_certificate = new Button (this, _("Add..."));
-               s->Add (_add_certificate, 0, wxTOP | wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
+               s->Add (_add_certificate, 1, wxTOP | wxBOTTOM | wxEXPAND, DCPOMATIC_BUTTON_STACK_GAP);
                _remove_certificate = new Button (this, _("Remove"));
-               s->Add (_remove_certificate, 0, wxTOP | wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
-               _export_certificate = new Button (this, _("Export"));
-               s->Add (_export_certificate, 0, wxTOP | wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
+               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);
        }
 
-       wxGridBagSizer* table = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
+       auto table = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
        _sizer->Add (table, 1, wxALL | wxEXPAND, border);
        int r = 0;
 
@@ -408,8 +319,6 @@ CertificateChainEditor::CertificateChainEditor (
        _button_sizer = new wxBoxSizer (wxHORIZONTAL);
        _remake_certificates = new Button (this, _("Re-make certificates and key..."));
        _button_sizer->Add (_remake_certificates, 1, wxRIGHT, border);
-       _export_chain = new Button (this, _("Export chain..."));
-       _button_sizer->Add (_export_chain, 1, wxRIGHT, border);
        table->Add (_button_sizer, wxGBPosition (r, 0), wxGBSpan (1, 4));
        ++r;
 
@@ -430,7 +339,7 @@ CertificateChainEditor::CertificateChainEditor (
        _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);
+       auto buttons = CreateSeparatedButtonSizer (wxCLOSE);
        if (buttons) {
                _sizer->Add (buttons, wxSizerFlags().Expand().DoubleBorder());
        }
@@ -452,7 +361,7 @@ CertificateChainEditor::add_button (wxWindow* button)
 void
 CertificateChainEditor::add_certificate ()
 {
-       wxFileDialog* d = new wxFileDialog (this, _("Select Certificate File"));
+       auto d = new wxFileDialog (this, _("Select Certificate File"));
 
        if (d->ShowModal() == wxID_OK) {
                try {
@@ -473,7 +382,7 @@ CertificateChainEditor::add_certificate ()
                                          "Only the first certificate will be used.")
                                        );
                        }
-                       shared_ptr<dcp::CertificateChain> chain(new dcp::CertificateChain(*_get().get()));
+                       auto chain = make_shared<dcp::CertificateChain>(*_get().get());
                        chain->add (c);
                        if (!chain->chain_valid ()) {
                                error_dialog (
@@ -510,11 +419,12 @@ CertificateChainEditor::remove_certificate ()
        }
 
        _certificates->DeleteItem (i);
-       shared_ptr<dcp::CertificateChain> chain(new dcp::CertificateChain(*_get().get()));
+       auto chain = make_shared<dcp::CertificateChain>(*_get().get());
        chain->remove (i);
        _set (chain);
 
        update_sensitivity ();
+       update_certificate_list ();
 }
 
 void
@@ -525,22 +435,35 @@ CertificateChainEditor::export_certificate ()
                return;
        }
 
-       wxFileDialog* d = new wxFileDialog (
-               this, _("Select Certificate File"), wxEmptyString, wxEmptyString, wxT ("PEM files (*.pem)|*.pem"),
+       auto all = _get()->root_to_leaf();
+
+       wxString default_name;
+       if (i == 0) {
+               default_name = "root.pem";
+       } else if (i == static_cast<int>(all.size() - 1)) {
+               default_name = "leaf.pem";
+       } else {
+               default_name = "intermediate.pem";
+       }
+
+       auto d = new wxFileDialog(
+               this, _("Select Certificate File"), wxEmptyString, default_name, wxT ("PEM files (*.pem)|*.pem"),
                wxFD_SAVE | wxFD_OVERWRITE_PROMPT
                );
 
-       dcp::CertificateChain::List all = _get()->root_to_leaf ();
-       dcp::CertificateChain::List::iterator j = all.begin ();
+       auto j = all.begin ();
        for (int k = 0; k < i; ++k) {
                ++j;
        }
 
        if (d->ShowModal () == wxID_OK) {
                boost::filesystem::path path (wx_to_std(d->GetPath()));
-               FILE* f = fopen_boost (path, "w");
+               if (path.extension() != ".pem") {
+                       path += ".pem";
+               }
+               auto f = fopen_boost (path, "w");
                if (!f) {
-                       throw OpenFileError (path, errno, false);
+                       throw OpenFileError (path, errno, OpenFileError::WRITE);
                }
 
                string const s = j->certificate (true);
@@ -553,19 +476,22 @@ CertificateChainEditor::export_certificate ()
 void
 CertificateChainEditor::export_chain ()
 {
-       wxFileDialog* d = new wxFileDialog (
-               this, _("Select Chain File"), wxEmptyString, wxEmptyString, wxT("PEM files (*.pem)|*.pem"),
+       auto d = new wxFileDialog (
+               this, _("Select Chain File"), wxEmptyString, wxT("certificate_chain.pem"), 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 (path.extension() != ".pem") {
+                       path += ".pem";
+               }
+               auto f = fopen_boost (path, "w");
                if (!f) {
-                       throw OpenFileError (path, errno, false);
+                       throw OpenFileError (path, errno, OpenFileError::WRITE);
                }
 
-               string const s = _get()->chain();
+               auto const s = _get()->chain();
                checked_fwrite (s.c_str(), s.length(), f, path);
                fclose (f);
        }
@@ -578,8 +504,8 @@ CertificateChainEditor::update_certificate_list ()
 {
        _certificates->DeleteAllItems ();
        size_t n = 0;
-       dcp::CertificateChain::List certs = _get()->root_to_leaf ();
-       BOOST_FOREACH (dcp::Certificate const & i, certs) {
+       auto certs = _get()->root_to_leaf();
+       for (auto const& i: certs) {
                wxListItem item;
                item.SetId (n);
                _certificates->InsertItem (item);
@@ -610,7 +536,7 @@ CertificateChainEditor::update_certificate_list ()
 void
 CertificateChainEditor::remake_certificates ()
 {
-       shared_ptr<const dcp::CertificateChain> chain = _get();
+       auto chain = _get();
 
        string subject_organization_name;
        string subject_organizational_unit_name;
@@ -618,7 +544,7 @@ CertificateChainEditor::remake_certificates ()
        string intermediate_common_name;
        string leaf_common_name;
 
-       dcp::CertificateChain::List all = chain->root_to_leaf ();
+       auto all = chain->root_to_leaf ();
 
        if (all.size() >= 1) {
                /* Have a root */
@@ -644,7 +570,7 @@ CertificateChainEditor::remake_certificates ()
                return;
        }
 
-       MakeChainDialog* d = new MakeChainDialog (
+       auto d = new MakeChainDialog (
                this,
                subject_organization_name,
                subject_organizational_unit_name,
@@ -655,15 +581,13 @@ CertificateChainEditor::remake_certificates ()
 
        if (d->ShowModal () == wxID_OK) {
                _set (
-                       shared_ptr<dcp::CertificateChain> (
-                               new dcp::CertificateChain (
-                                       openssl_path (),
-                                       d->organisation (),
-                                       d->organisational_unit (),
-                                       d->root_common_name (),
-                                       d->intermediate_common_name (),
-                                       d->leaf_common_name ()
-                                       )
+                       make_shared<dcp::CertificateChain> (
+                               openssl_path (),
+                               d->organisation (),
+                               d->organisational_unit (),
+                               d->root_common_name (),
+                               d->intermediate_common_name (),
+                               d->leaf_common_name ()
                                )
                        );
 
@@ -678,8 +602,8 @@ void
 CertificateChainEditor::update_sensitivity ()
 {
        /* We can only remove the leaf certificate */
-       _remove_certificate->Enable (_certificates->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) == (_certificates->GetItemCount() - 1));
-       _export_certificate->Enable (_certificates->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) != -1);
+       _remove_certificate->Enable (_certificates->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) == (_certificates->GetItemCount() - 1));
+       _export_certificate->Enable (_certificates->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) != -1);
 }
 
 void
@@ -692,7 +616,7 @@ CertificateChainEditor::update_private_key ()
 void
 CertificateChainEditor::import_private_key ()
 {
-       wxFileDialog* d = new wxFileDialog (this, _("Select Key File"));
+       auto d = new wxFileDialog (this, _("Select Key File"));
 
        if (d->ShowModal() == wxID_OK) {
                try {
@@ -705,11 +629,11 @@ CertificateChainEditor::import_private_key ()
                                return;
                        }
 
-                       shared_ptr<dcp::CertificateChain> chain(new dcp::CertificateChain(*_get().get()));
+                       auto chain = make_shared<dcp::CertificateChain>(*_get().get());
                        chain->set_key (dcp::file_to_string (p));
                        _set (chain);
                        update_private_key ();
-               } catch (dcp::MiscError& e) {
+               } catch (std::exception& e) {
                        error_dialog (this, _("Could not read certificate file."), std_to_wx(e.what()));
                }
        }
@@ -722,24 +646,27 @@ CertificateChainEditor::import_private_key ()
 void
 CertificateChainEditor::export_private_key ()
 {
-       optional<string> key = _get()->key();
+       auto key = _get()->key();
        if (!key) {
                return;
        }
 
-       wxFileDialog* d = new wxFileDialog (
-               this, _("Select Key File"), wxEmptyString, wxEmptyString, wxT ("PEM files (*.pem)|*.pem"),
+       auto d = new wxFileDialog (
+               this, _("Select Key File"), wxEmptyString, wxT("private_key.pem"), 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 (path.extension() != ".pem") {
+                       path += ".pem";
+               }
+               auto f = fopen_boost (path, "w");
                if (!f) {
-                       throw OpenFileError (path, errno, false);
+                       throw OpenFileError (path, errno, OpenFileError::WRITE);
                }
 
-               string const s = _get()->key().get ();
+               auto const s = _get()->key().get ();
                checked_fwrite (s.c_str(), s.length(), f, path);
                fclose (f);
        }
@@ -758,22 +685,26 @@ KeysPage::setup ()
        wxFont subheading_font (*wxNORMAL_FONT);
        subheading_font.SetWeight (wxFONTWEIGHT_BOLD);
 
-       wxSizer* sizer = _panel->GetSizer();
+       auto sizer = _panel->GetSizer();
 
        {
-               wxStaticText* m = new StaticText (_panel, _("Decrypting KDMs"));
+               auto m = new StaticText (_panel, _("Decrypting KDMs"));
                m->SetFont (subheading_font);
-               sizer->Add (m, 0, wxALL, _border);
+               sizer->Add (m, 0, wxALL | wxEXPAND, _border);
        }
 
-       wxButton* export_decryption_certificate = new Button (_panel, _("Export KDM decryption certificate..."));
-       sizer->Add (export_decryption_certificate, 0, wxLEFT, _border);
-       wxButton* export_settings = new Button (_panel, _("Export all KDM decryption settings..."));
-       sizer->Add (export_settings, 0, wxLEFT, _border);
-       wxButton* import_settings = new Button (_panel, _("Import all KDM decryption settings..."));
-       sizer->Add (import_settings, 0, wxLEFT, _border);
-       wxButton* decryption_advanced = new Button (_panel, _("Advanced..."));
-       sizer->Add (decryption_advanced, 0, wxALL, _border);
+       auto buttons = new wxBoxSizer (wxVERTICAL);
+
+       auto export_decryption_certificate = new Button (_panel, _("Export KDM decryption leaf certificate..."));
+       buttons->Add (export_decryption_certificate, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
+       auto export_settings = new Button (_panel, _("Export all KDM decryption settings..."));
+       buttons->Add (export_settings, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
+       auto import_settings = new Button (_panel, _("Import all KDM decryption settings..."));
+       buttons->Add (import_settings, 0, wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
+       auto decryption_advanced = new Button (_panel, _("Advanced..."));
+       buttons->Add (decryption_advanced, 0);
+
+       sizer->Add (buttons, 0, wxLEFT, _border);
 
        export_decryption_certificate->Bind (wxEVT_BUTTON, bind (&KeysPage::export_decryption_certificate, this));
        export_settings->Bind (wxEVT_BUTTON, bind (&KeysPage::export_decryption_chain_and_key, this));
@@ -781,24 +712,24 @@ KeysPage::setup ()
        decryption_advanced->Bind (wxEVT_BUTTON, bind (&KeysPage::decryption_advanced, this));
 
        {
-               wxStaticText* m = new StaticText (_panel, _("Signing DCPs and KDMs"));
+               auto m = new StaticText (_panel, _("Signing DCPs and KDMs"));
                m->SetFont (subheading_font);
-               sizer->Add (m, 0, wxALL, _border);
+               sizer->Add (m, 0, wxALL | wxEXPAND, _border);
        }
 
-       wxButton* signing_advanced = new Button (_panel, _("Advanced..."));
-       sizer->Add (signing_advanced, 0, wxLEFT, _border);
+       auto signing_advanced = new Button (_panel, _("Advanced..."));
+       sizer->Add (signing_advanced, 0, wxLEFT | wxBOTTOM, _border);
        signing_advanced->Bind (wxEVT_BUTTON, bind (&KeysPage::signing_advanced, this));
 }
 
 void
 KeysPage::decryption_advanced ()
 {
-       CertificateChainEditor* c = new CertificateChainEditor (
+       auto c = new CertificateChainEditor (
                _panel, _("Decrypting KDMs"), _border,
-               bind (&Config::set_decryption_chain, Config::instance (), _1),
-               bind (&Config::decryption_chain, Config::instance ()),
-               bind (&KeysPage::nag_alter_decryption_chain, this)
+               bind(&Config::set_decryption_chain, Config::instance(), _1),
+               bind(&Config::decryption_chain, Config::instance()),
+               bind(&KeysPage::nag_alter_decryption_chain, this)
                );
 
        c->ShowModal();
@@ -807,11 +738,11 @@ KeysPage::decryption_advanced ()
 void
 KeysPage::signing_advanced ()
 {
-       CertificateChainEditor* c = new CertificateChainEditor (
+       auto c = new CertificateChainEditor (
                _panel, _("Signing DCPs and KDMs"), _border,
-               bind (&Config::set_signer_chain, Config::instance (), _1),
-               bind (&Config::signer_chain, Config::instance ()),
-               bind (&do_nothing)
+               bind(&Config::set_signer_chain, Config::instance(), _1),
+               bind(&Config::signer_chain, Config::instance()),
+               bind(&do_nothing)
                );
 
        c->ShowModal();
@@ -820,19 +751,19 @@ KeysPage::signing_advanced ()
 void
 KeysPage::export_decryption_chain_and_key ()
 {
-       wxFileDialog* d = new wxFileDialog (
+       auto d = new wxFileDialog (
                _panel, _("Select Export File"), wxEmptyString, wxEmptyString, wxT ("DOM files (*.dom)|*.dom"),
                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");
+               auto f = fopen_boost (path, "w");
                if (!f) {
-                       throw OpenFileError (path, errno, false);
+                       throw OpenFileError (path, errno, OpenFileError::WRITE);
                }
 
-               string const chain = Config::instance()->decryption_chain()->chain();
+               auto const chain = Config::instance()->decryption_chain()->chain();
                checked_fwrite (chain.c_str(), chain.length(), f, path);
                optional<string> const key = Config::instance()->decryption_chain()->key();
                DCPOMATIC_ASSERT (key);
@@ -855,16 +786,16 @@ KeysPage::import_decryption_chain_and_key ()
                return;
        }
 
-       wxFileDialog* d = new wxFileDialog (
+       auto d = new wxFileDialog (
                _panel, _("Select File To Import"), wxEmptyString, wxEmptyString, wxT ("DOM files (*.dom)|*.dom")
                );
 
        if (d->ShowModal () == wxID_OK) {
-               shared_ptr<dcp::CertificateChain> new_chain(new dcp::CertificateChain());
+               auto new_chain = make_shared<dcp::CertificateChain>();
 
                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;
@@ -907,22 +838,311 @@ KeysPage::nag_alter_decryption_chain ()
 void
 KeysPage::export_decryption_certificate ()
 {
-       wxFileDialog* d = new wxFileDialog (
-               _panel, _("Select Certificate File"), wxEmptyString, _("dcpomatic_kdm_decryption_cert.pem"), wxT ("PEM files (*.pem)|*.pem"),
+       auto config = Config::instance();
+       wxString default_name = "dcpomatic";
+       if (!config->dcp_creator().empty()) {
+               default_name += "_" + std_to_wx(careful_string_filter(config->dcp_creator()));
+       }
+       if (!config->dcp_issuer().empty()) {
+               default_name += "_" + std_to_wx(careful_string_filter(config->dcp_issuer()));
+       }
+       default_name += wxT("_kdm_decryption_cert.pem");
+
+       auto d = new wxFileDialog (
+               _panel, _("Select Certificate File"), wxEmptyString, default_name, 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 (path.extension() != ".pem") {
+                       path += ".pem";
+               }
+               auto f = fopen_boost (path, "w");
                if (!f) {
-                       throw OpenFileError (path, errno, false);
+                       throw OpenFileError (path, errno, OpenFileError::WRITE);
                }
 
-               string const s = Config::instance()->decryption_chain()->leaf().certificate (true);
+               auto 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
+SoundPage::setup ()
+{
+       auto table = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
+       _panel->GetSizer()->Add (table, 1, wxALL | wxEXPAND, _border);
+
+       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) {
+               try {
+                       auto dev = audio.getDeviceInfo (i);
+                       if (dev.probed && dev.outputChannels > 0) {
+                               _sound_output->Append (std_to_wx (dev.name));
+                       }
+               } catch (RtAudioError&) {
+                       /* Something went wrong so let's just ignore that device */
+               }
+       }
+
+       _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);
+       auto const so = get_sound_output();
+       string default_device;
+       try {
+               default_device = audio.getDeviceInfo(audio.getDefaultOutputDevice()).name;
+       } catch (RtAudioError&) {
+               /* Never mind */
+       }
+       if (!so || *so == default_device) {
+               Config::instance()->unset_sound_output ();
+       } else {
+               Config::instance()->set_sound_output (*so);
+       }
+}
+
+void
+SoundPage::config_changed ()
+{
+       auto config = Config::instance ();
+
+       checked_set (_sound, config->sound ());
+
+       auto 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&) {
+                       /* 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) {
+                       try {
+                               auto info = audio.getDeviceInfo(i);
+                               if (info.name == *configured_so && info.outputChannels > 0) {
+                                       channels = info.outputChannels;
+                               }
+                       } catch (RtAudioError&) {
+                               /* Never mind */
+                       }
+               }
+       }
+
+       _sound_output_details->SetLabel (
+               wxString::Format(_("%d channels on %s"), channels, apis[audio.getCurrentApi()])
+               );
+
+       _map->set (Config::instance()->audio_mapping(channels));
+
+       vector<NamedChannel> input;
+       for (int i = 0; i < MAX_DCP_AUDIO_CHANNELS; ++i) {
+               input.push_back (NamedChannel(short_audio_channel_name(i), i));
+       }
+       _map->set_input_channels (input);
+
+       vector<NamedChannel> output;
+       for (int i = 0; i < channels; ++i) {
+               output.push_back (NamedChannel(dcp::raw_convert<string>(i), 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));
+}
+
+
+LocationsPage::LocationsPage (wxSize panel_size, int border)
+       : Page (panel_size, border)
+{
+
+}
+
+wxString
+LocationsPage::GetName () const
+{
+       return _("Locations");
+}
+
+#ifdef DCPOMATIC_OSX
+wxBitmap
+LocationsPage::GetLargeIcon () const
+{
+       return wxBitmap(bitmap_path("locations"), wxBITMAP_TYPE_PNG);
+}
+#endif
+
+void
+LocationsPage::setup ()
+{
+       int r = 0;
+
+       auto table = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
+       _panel->GetSizer()->Add (table, 1, wxALL | wxEXPAND, _border);
+
+       add_label_to_sizer (table, _panel, _("Content directory"), true, wxGBPosition (r, 0));
+       _content_directory = new wxDirPickerCtrl (_panel, wxID_ANY, wxEmptyString, wxDirSelectorPromptStr, wxDefaultPosition, wxSize (300, -1));
+       table->Add (_content_directory, wxGBPosition (r, 1));
+       ++r;
+
+       add_label_to_sizer (table, _panel, _("Playlist directory"), true, wxGBPosition (r, 0));
+       _playlist_directory = new wxDirPickerCtrl (_panel, wxID_ANY, wxEmptyString, wxDirSelectorPromptStr, wxDefaultPosition, wxSize (300, -1));
+       table->Add (_playlist_directory, wxGBPosition (r, 1));
+       ++r;
+
+       add_label_to_sizer (table, _panel, _("KDM directory"), true, wxGBPosition (r, 0));
+       _kdm_directory = new wxDirPickerCtrl (_panel, wxID_ANY, wxEmptyString, wxDirSelectorPromptStr, wxDefaultPosition, wxSize (300, -1));
+       table->Add (_kdm_directory, wxGBPosition (r, 1));
+       ++r;
+
+       _content_directory->Bind (wxEVT_DIRPICKER_CHANGED, bind(&LocationsPage::content_directory_changed, this));
+       _playlist_directory->Bind (wxEVT_DIRPICKER_CHANGED, bind(&LocationsPage::playlist_directory_changed, this));
+       _kdm_directory->Bind (wxEVT_DIRPICKER_CHANGED, bind(&LocationsPage::kdm_directory_changed, this));
+}
+
+void
+LocationsPage::config_changed ()
+{
+       auto config = Config::instance ();
+
+       if (config->player_content_directory()) {
+               checked_set (_content_directory, *config->player_content_directory());
+       }
+       if (config->player_playlist_directory()) {
+               checked_set (_playlist_directory, *config->player_playlist_directory());
+       }
+       if (config->player_kdm_directory()) {
+               checked_set (_kdm_directory, *config->player_kdm_directory());
+       }
+}
+
+void
+LocationsPage::content_directory_changed ()
+{
+       Config::instance()->set_player_content_directory(wx_to_std(_content_directory->GetPath()));
+}
+
+void
+LocationsPage::playlist_directory_changed ()
+{
+       Config::instance()->set_player_playlist_directory(wx_to_std(_playlist_directory->GetPath()));
+}
+
+void
+LocationsPage::kdm_directory_changed ()
+{
+       Config::instance()->set_player_kdm_directory(wx_to_std(_kdm_directory->GetPath()));
+}