Fix seek during slider drag.
[dcpomatic.git] / src / wx / config_dialog.cc
index c1eee3c7b1cec0026370d61072f73f3fa5db543a..f41d5146cb503d928143853f50d12751f28d297c 100644 (file)
@@ -1,19 +1,20 @@
 /*
-    Copyright (C) 2012-2014 Carl Hetherington <cth@carlh.net>
+    Copyright (C) 2012-2017 Carl Hetherington <cth@carlh.net>
 
-    This program is free software; you can redistribute it and/or modify
+    This file is part of DCP-o-matic.
+
+    DCP-o-matic is free software; you can redistribute it and/or modify
     it under the terms of the GNU General Public License as published by
     the Free Software Foundation; either version 2 of the License, or
     (at your option) any later version.
 
-    This program is distributed in the hope that it will be useful,
+    DCP-o-matic is distributed in the hope that it will be useful,
     but WITHOUT ANY WARRANTY; without even the implied warranty of
     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     GNU General Public License for more details.
 
     You should have received a copy of the GNU General Public License
-    along with this program; if not, write to the Free Software
-    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+    along with DCP-o-matic.  If not, see <http://www.gnu.org/licenses/>.
 
 */
 
  *  @brief A dialogue to edit DCP-o-matic configuration.
  */
 
-#include <iostream>
-#include <boost/lexical_cast.hpp>
-#include <boost/filesystem.hpp>
-#include <wx/stdpaths.h>
-#include <wx/preferences.h>
-#include <wx/filepicker.h>
-#include <wx/spinctrl.h>
-#include <dcp/colour_matrix.h>
-#include <dcp/exceptions.h>
-#include <dcp/signer.h>
+#include "config_dialog.h"
+#include "wx_util.h"
+#include "editable_list.h"
+#include "filter_dialog.h"
+#include "dir_picker_ctrl.h"
+#include "file_picker_ctrl.h"
+#include "isdcf_metadata_dialog.h"
+#include "server_dialog.h"
+#include "make_chain_dialog.h"
+#include "email_dialog.h"
+#include "name_format_editor.h"
+#include "nag_dialog.h"
+#include "config_move_dialog.h"
 #include "lib/config.h"
 #include "lib/ratio.h"
-#include "lib/scaler.h"
 #include "lib/filter.h"
 #include "lib/dcp_content_type.h"
-#include "lib/colour_conversion.h"
 #include "lib/log.h"
 #include "lib/util.h"
 #include "lib/cross.h"
 #include "lib/exceptions.h"
-#include "config_dialog.h"
-#include "wx_util.h"
-#include "editable_list.h"
-#include "filter_dialog.h"
-#include "dir_picker_ctrl.h"
-#include "isdcf_metadata_dialog.h"
-#include "preset_colour_conversion_dialog.h"
-#include "server_dialog.h"
+#include <dcp/locale_convert.h>
+#include <dcp/exceptions.h>
+#include <dcp/certificate_chain.h>
+#include <wx/stdpaths.h>
+#include <wx/preferences.h>
+#include <wx/spinctrl.h>
+#include <wx/filepicker.h>
+#include <RtAudio.h>
+#include <boost/filesystem.hpp>
+#include <boost/foreach.hpp>
+#include <iostream>
 
 using std::vector;
 using std::string;
 using std::list;
 using std::cout;
+using std::pair;
+using std::make_pair;
+using std::map;
 using boost::bind;
 using boost::shared_ptr;
-using boost::lexical_cast;
+using boost::function;
+using boost::optional;
+using dcp::locale_convert;
+
+static
+void
+do_nothing ()
+{
+
+}
 
 class Page
 {
 public:
        Page (wxSize panel_size, int border)
-               : _panel_size (panel_size)
-               , _border (border)
-       {}
+               : _border (border)
+               , _panel (0)
+               , _panel_size (panel_size)
+               , _window_exists (false)
+       {
+               _config_connection = Config::instance()->Changed.connect (boost::bind (&Page::config_changed_wrapper, this));
+       }
+
+       virtual ~Page () {}
 
 protected:
-       wxSize _panel_size;
+       wxWindow* create_window (wxWindow* parent)
+       {
+               _panel = new wxPanel (parent, wxID_ANY, wxDefaultPosition, _panel_size);
+               wxBoxSizer* s = new wxBoxSizer (wxVERTICAL);
+               _panel->SetSizer (s);
+
+               setup ();
+               _window_exists = true;
+               config_changed ();
+
+               _panel->Bind (wxEVT_DESTROY, boost::bind (&Page::window_destroyed, this));
+
+               return _panel;
+       }
+
        int _border;
+       wxPanel* _panel;
+
+private:
+       virtual void config_changed () = 0;
+       virtual void setup () = 0;
+
+       void config_changed_wrapper ()
+       {
+               if (_window_exists) {
+                       config_changed ();
+               }
+       }
+
+       void window_destroyed ()
+       {
+               _window_exists = false;
+       }
+
+       wxSize _panel_size;
+       boost::signals2::scoped_connection _config_connection;
+       bool _window_exists;
 };
 
-class GeneralPage : public wxStockPreferencesPage, public Page
+class StockPage : public wxStockPreferencesPage, public Page
 {
 public:
-       GeneralPage (wxSize panel_size, int border)
-               : wxStockPreferencesPage (Kind_General)
+       StockPage (Kind kind, wxSize panel_size, int border)
+               : wxStockPreferencesPage (kind)
                , Page (panel_size, border)
        {}
 
        wxWindow* CreateWindow (wxWindow* parent)
        {
-               wxPanel* panel = new wxPanel (parent);
-               wxBoxSizer* s = new wxBoxSizer (wxVERTICAL);
-               panel->SetSizer (s);
+               return create_window (parent);
+       }
+};
 
-               wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
-               table->AddGrowableCol (1, 1);
-               s->Add (table, 1, wxALL | wxEXPAND, _border);
-               
-               _set_language = new wxCheckBox (panel, wxID_ANY, _("Set language"));
-               table->Add (_set_language, 1);
-               _language = new wxChoice (panel, wxID_ANY);
-               _language->Append (wxT ("Deutsch"));
-               _language->Append (wxT ("English"));
-               _language->Append (wxT ("Español"));
-               _language->Append (wxT ("Français"));
-               _language->Append (wxT ("Italiano"));
-               _language->Append (wxT ("Nederlands"));
-               _language->Append (wxT ("Svenska"));
-               table->Add (_language);
-               
-               wxStaticText* restart = add_label_to_sizer (table, panel, _("(restart DCP-o-matic to see language changes)"), false);
+class StandardPage : public wxPreferencesPage, public Page
+{
+public:
+       StandardPage (wxSize panel_size, int border)
+               : Page (panel_size, border)
+       {}
+
+       wxWindow* CreateWindow (wxWindow* parent)
+       {
+               return create_window (parent);
+       }
+};
+
+class GeneralPage : public StockPage
+{
+public:
+       GeneralPage (wxSize panel_size, int border)
+               : StockPage (Kind_General, panel_size, border)
+       {}
+
+private:
+       void setup ()
+       {
+               wxGridBagSizer* table = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
+               _panel->GetSizer()->Add (table, 1, wxALL | wxEXPAND, _border);
+
+               int r = 0;
+               _set_language = new wxCheckBox (_panel, wxID_ANY, _("Set language"));
+               table->Add (_set_language, wxGBPosition (r, 0));
+               _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"));
+               checked_set (_language, languages);
+               table->Add (_language, wxGBPosition (r, 1));
+               ++r;
+
+               wxStaticText* 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();
                font.SetStyle (wxFONTSTYLE_ITALIC);
                font.SetPointSize (font.GetPointSize() - 1);
                restart->SetFont (font);
-               table->AddSpacer (0);
-               
-               add_label_to_sizer (table, panel, _("Threads to use for encoding on this host"), true);
-               _num_local_encoding_threads = new wxSpinCtrl (panel);
-               table->Add (_num_local_encoding_threads, 1);
+               ++r;
+
+               add_label_to_sizer (table, _panel, _("Number of threads DCP-o-matic should use"), true, wxGBPosition (r, 0));
+               _master_encoding_threads = new wxSpinCtrl (_panel);
+               table->Add (_master_encoding_threads, wxGBPosition (r, 1));
+               ++r;
+
+               add_label_to_sizer (table, _panel, _("Number of threads DCP-o-matic encode server should use"), true, wxGBPosition (r, 0));
+               _server_encoding_threads = new wxSpinCtrl (_panel);
+               table->Add (_server_encoding_threads, wxGBPosition (r, 1));
+               ++r;
+
+               add_label_to_sizer (table, _panel, _("Configuration file"), true, wxGBPosition (r, 0));
+               _config_file = new FilePickerCtrl (_panel, _("Select configuration file"), "*.xml", true);
+               table->Add (_config_file, wxGBPosition (r, 1));
+               ++r;
+
+               add_label_to_sizer (table, _panel, _("Cinema and screen database file"), true, wxGBPosition (r, 0));
+               _cinemas_file = new FilePickerCtrl (_panel, _("Select cinema and screen database file"), "*.xml", true);
+               table->Add (_cinemas_file, wxGBPosition (r, 1));
+               ++r;
+
+               _preview_sound = new wxCheckBox (_panel, wxID_ANY, _("Play sound in the preview via"));
+               table->Add (_preview_sound, wxGBPosition (r, 0));
+                _preview_sound_output = new wxChoice (_panel, wxID_ANY);
+                table->Add (_preview_sound_output, wxGBPosition (r, 1));
+                ++r;
+
+#ifdef DCPOMATIC_HAVE_EBUR128_PATCHED_FFMPEG
+               _analyse_ebur128 = new wxCheckBox (_panel, wxID_ANY, _("Find integrated loudness, true peak and loudness range when analysing audio"));
+               table->Add (_analyse_ebur128, wxGBPosition (r, 0), wxGBSpan (1, 2));
+               ++r;
+#endif
 
-               _check_for_updates = new wxCheckBox (panel, wxID_ANY, _("Check for updates on startup"));
-               table->Add (_check_for_updates, 1, wxEXPAND | wxALL);
-               table->AddSpacer (0);
-               
-               _check_for_test_updates = new wxCheckBox (panel, wxID_ANY, _("Check for testing updates as well as stable ones"));
-               table->Add (_check_for_test_updates, 1, wxEXPAND | wxALL);
-               table->AddSpacer (0);
-               
+               _automatic_audio_analysis = new wxCheckBox (_panel, wxID_ANY, _("Automatically analyse content audio"));
+               table->Add (_automatic_audio_analysis, wxGBPosition (r, 0), wxGBSpan (1, 2));
+               ++r;
+
+               _check_for_updates = new wxCheckBox (_panel, wxID_ANY, _("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"));
+               table->Add (_check_for_test_updates, wxGBPosition (r, 0), wxGBSpan (1, 2));
+               ++r;
+
+               wxFlexGridSizer* bottom_table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
+               bottom_table->AddGrowableCol (1, 1);
+
+               add_label_to_sizer (bottom_table, _panel, _("Issuer"), true);
+               _issuer = new wxTextCtrl (_panel, wxID_ANY);
+               bottom_table->Add (_issuer, 1, wxALL | wxEXPAND);
+
+               add_label_to_sizer (bottom_table, _panel, _("Creator"), true);
+               _creator = new wxTextCtrl (_panel, wxID_ANY);
+               bottom_table->Add (_creator, 1, wxALL | wxEXPAND);
+
+               table->Add (bottom_table, wxGBPosition (r, 0), wxGBSpan (2, 2), wxEXPAND);
+               ++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) {
+                                _preview_sound_output->Append (std_to_wx (dev.name));
+                        }
+                }
+
+               _set_language->Bind         (wxEVT_CHECKBOX,           boost::bind (&GeneralPage::set_language_changed,  this));
+               _language->Bind             (wxEVT_CHOICE,             boost::bind (&GeneralPage::language_changed,      this));
+               _config_file->Bind          (wxEVT_FILEPICKER_CHANGED, boost::bind (&GeneralPage::config_file_changed,   this));
+               _cinemas_file->Bind         (wxEVT_FILEPICKER_CHANGED, boost::bind (&GeneralPage::cinemas_file_changed,  this));
+               _preview_sound->Bind        (wxEVT_CHECKBOX,           boost::bind (&GeneralPage::preview_sound_changed, this));
+               _preview_sound_output->Bind (wxEVT_CHOICE,             boost::bind (&GeneralPage::preview_sound_output_changed, this));
+
+               _master_encoding_threads->SetRange (1, 128);
+               _master_encoding_threads->Bind (wxEVT_SPINCTRL, boost::bind (&GeneralPage::master_encoding_threads_changed, this));
+               _server_encoding_threads->SetRange (1, 128);
+               _server_encoding_threads->Bind (wxEVT_SPINCTRL, boost::bind (&GeneralPage::server_encoding_threads_changed, this));
+
+#ifdef DCPOMATIC_HAVE_EBUR128_PATCHED_FFMPEG
+               _analyse_ebur128->Bind (wxEVT_CHECKBOX, boost::bind (&GeneralPage::analyse_ebur128_changed, this));
+#endif
+               _automatic_audio_analysis->Bind (wxEVT_CHECKBOX, boost::bind (&GeneralPage::automatic_audio_analysis_changed, this));
+               _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));
+
+               _issuer->Bind (wxEVT_TEXT, boost::bind (&GeneralPage::issuer_changed, this));
+               _creator->Bind (wxEVT_TEXT, boost::bind (&GeneralPage::creator_changed, this));
+       }
+
+       void config_changed ()
+       {
                Config* config = Config::instance ();
-               
-               _set_language->SetValue (config->language ());
-               
-               if (config->language().get_value_or ("") == "fr") {
-                       _language->SetSelection (3);
-               } else if (config->language().get_value_or ("") == "it") {
-                       _language->SetSelection (4);
-               } else if (config->language().get_value_or ("") == "es") {
-                       _language->SetSelection (2);
-               } else if (config->language().get_value_or ("") == "sv") {
-                       _language->SetSelection (6);
-               } else if (config->language().get_value_or ("") == "de") {
-                       _language->SetSelection (0);
-               } else if (config->language().get_value_or ("") == "nl") {
-                       _language->SetSelection (5);
-               } else {
-                       _language->SetSelection (1);
+
+               checked_set (_set_language, static_cast<bool>(config->language()));
+
+               /* Backwards compatibility of config file */
+
+               map<string, string> compat_map;
+               compat_map["fr"] = "fr_FR";
+               compat_map["it"] = "it_IT";
+               compat_map["es"] = "es_ES";
+               compat_map["sv"] = "sv_SE";
+               compat_map["de"] = "de_DE";
+               compat_map["nl"] = "nl_NL";
+               compat_map["ru"] = "ru_RU";
+               compat_map["pl"] = "pl_PL";
+               compat_map["da"] = "da_DK";
+               compat_map["pt"] = "pt_PT";
+               compat_map["sk"] = "sk_SK";
+               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 ()) {
+                       lang = compat_map[lang];
                }
-               
-               setup_language_sensitivity ();
-               
-               _set_language->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&GeneralPage::set_language_changed, this));
-               _language->Bind     (wxEVT_COMMAND_CHOICE_SELECTED,  boost::bind (&GeneralPage::language_changed,     this));
-               
-               _num_local_encoding_threads->SetRange (1, 128);
-               _num_local_encoding_threads->SetValue (config->num_local_encoding_threads ());
-               _num_local_encoding_threads->Bind (wxEVT_COMMAND_SPINCTRL_UPDATED, boost::bind (&GeneralPage::num_local_encoding_threads_changed, this));
-
-               _check_for_updates->SetValue (config->check_for_updates ());
-               _check_for_updates->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&GeneralPage::check_for_updates_changed, this));
-               _check_for_test_updates->SetValue (config->check_for_test_updates ());
-               _check_for_test_updates->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&GeneralPage::check_for_test_updates_changed, this));
-               
-               return panel;
-       }
-
-private:       
-       void setup_language_sensitivity ()
+
+               checked_set (_language, lang);
+
+               checked_set (_master_encoding_threads, config->master_encoding_threads ());
+               checked_set (_server_encoding_threads, config->server_encoding_threads ());
+#ifdef DCPOMATIC_HAVE_EBUR128_PATCHED_FFMPEG
+               checked_set (_analyse_ebur128, config->analyse_ebur128 ());
+#endif
+               checked_set (_automatic_audio_analysis, config->automatic_audio_analysis ());
+               checked_set (_check_for_updates, config->check_for_updates ());
+               checked_set (_check_for_test_updates, config->check_for_test_updates ());
+               checked_set (_issuer, config->dcp_issuer ());
+               checked_set (_creator, config->dcp_creator ());
+               checked_set (_config_file, config->config_file());
+               checked_set (_cinemas_file, config->cinemas_file());
+               checked_set (_preview_sound, config->preview_sound());
+
+                optional<string> const current_so = get_preview_sound_output ();
+                optional<string> configured_so;
+
+                if (config->preview_sound_output()) {
+                        configured_so = config->preview_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 _preview_sound_output with the configured value */
+                        unsigned int i = 0;
+                        while (i < _preview_sound_output->GetCount()) {
+                                if (_preview_sound_output->GetString(i) == std_to_wx(*configured_so)) {
+                                        _preview_sound_output->SetSelection (i);
+                                        break;
+                                }
+                                ++i;
+                        }
+                }
+
+               setup_sensitivity ();
+       }
+
+        /** @return Currently-selected preview sound output in the dialogue */
+        optional<string> get_preview_sound_output ()
+        {
+                int const sel = _preview_sound_output->GetSelection ();
+                if (sel == wxNOT_FOUND) {
+                        return optional<string> ();
+                }
+
+                return wx_to_std (_preview_sound_output->GetString (sel));
+        }
+
+       void setup_sensitivity ()
        {
                _language->Enable (_set_language->GetValue ());
+               _check_for_test_updates->Enable (_check_for_updates->GetValue ());
+               _preview_sound_output->Enable (_preview_sound->GetValue ());
        }
 
        void set_language_changed ()
        {
-               setup_language_sensitivity ();
+               setup_sensitivity ();
                if (_set_language->GetValue ()) {
                        language_changed ();
                } else {
@@ -175,216 +396,328 @@ private:
 
        void language_changed ()
        {
-               switch (_language->GetSelection ()) {
-               case 0:
-                       Config::instance()->set_language ("de");
-                       break;
-               case 1:
-                       Config::instance()->set_language ("en");
-                       break;
-               case 2:
-                       Config::instance()->set_language ("es");
-                       break;
-               case 3:
-                       Config::instance()->set_language ("fr");
-                       break;
-               case 4:
-                       Config::instance()->set_language ("it");
-                       break;
-               case 5:
-                       Config::instance()->set_language ("nl");
-                       break;
-               case 6:
-                       Config::instance()->set_language ("sv");
-                       break;
+               int const sel = _language->GetSelection ();
+               if (sel != -1) {
+                       Config::instance()->set_language (string_client_data (_language->GetClientObject (sel)));
+               } else {
+                       Config::instance()->unset_language ();
                }
        }
-       
+
+#ifdef DCPOMATIC_HAVE_EBUR128_PATCHED_FFMPEG
+       void analyse_ebur128_changed ()
+       {
+               Config::instance()->set_analyse_ebur128 (_analyse_ebur128->GetValue ());
+       }
+#endif
+
+       void automatic_audio_analysis_changed ()
+       {
+               Config::instance()->set_automatic_audio_analysis (_automatic_audio_analysis->GetValue ());
+       }
+
        void check_for_updates_changed ()
        {
                Config::instance()->set_check_for_updates (_check_for_updates->GetValue ());
        }
-       
+
        void check_for_test_updates_changed ()
        {
                Config::instance()->set_check_for_test_updates (_check_for_test_updates->GetValue ());
        }
 
-       void num_local_encoding_threads_changed ()
+       void master_encoding_threads_changed ()
+       {
+               Config::instance()->set_master_encoding_threads (_master_encoding_threads->GetValue ());
+       }
+
+       void server_encoding_threads_changed ()
+       {
+               Config::instance()->set_server_encoding_threads (_server_encoding_threads->GetValue ());
+       }
+
+       void issuer_changed ()
+       {
+               Config::instance()->set_dcp_issuer (wx_to_std (_issuer->GetValue ()));
+       }
+
+       void creator_changed ()
+       {
+               Config::instance()->set_dcp_creator (wx_to_std (_creator->GetValue ()));
+       }
+
+       void config_file_changed ()
+       {
+               Config* config = Config::instance();
+               boost::filesystem::path new_file = wx_to_std(_config_file->GetPath());
+               if (new_file == config->config_file()) {
+                       return;
+               }
+               bool copy_and_link = true;
+               if (boost::filesystem::exists(new_file)) {
+                       ConfigMoveDialog* d = new ConfigMoveDialog (_panel, new_file);
+                       if (d->ShowModal() == wxID_OK) {
+                               copy_and_link = false;
+                       }
+                       d->Destroy ();
+               }
+
+               if (copy_and_link) {
+                       config->write ();
+                       if (new_file != config->config_file()) {
+                               config->copy_and_link (new_file);
+                       }
+               } else {
+                       config->link (new_file);
+               }
+       }
+
+       void cinemas_file_changed ()
+       {
+               Config::instance()->set_cinemas_file (wx_to_std (_cinemas_file->GetPath ()));
+       }
+
+       void preview_sound_changed ()
        {
-               Config::instance()->set_num_local_encoding_threads (_num_local_encoding_threads->GetValue ());
+               Config::instance()->set_preview_sound (_preview_sound->GetValue ());
        }
 
+        void preview_sound_output_changed ()
+        {
+                RtAudio audio (DCPOMATIC_RTAUDIO_API);
+                optional<string> const so = get_preview_sound_output();
+                if (!so || *so == audio.getDeviceInfo(audio.getDefaultOutputDevice()).name) {
+                        Config::instance()->unset_preview_sound_output ();
+                } else {
+                        Config::instance()->set_preview_sound_output (*so);
+                }
+        }
+
        wxCheckBox* _set_language;
        wxChoice* _language;
-       wxSpinCtrl* _num_local_encoding_threads;
+       wxSpinCtrl* _master_encoding_threads;
+       wxSpinCtrl* _server_encoding_threads;
+       FilePickerCtrl* _config_file;
+       FilePickerCtrl* _cinemas_file;
+       wxCheckBox* _preview_sound;
+       wxChoice* _preview_sound_output;
+#ifdef DCPOMATIC_HAVE_EBUR128_PATCHED_FFMPEG
+       wxCheckBox* _analyse_ebur128;
+#endif
+       wxCheckBox* _automatic_audio_analysis;
        wxCheckBox* _check_for_updates;
        wxCheckBox* _check_for_test_updates;
+       wxTextCtrl* _issuer;
+       wxTextCtrl* _creator;
 };
 
-class DefaultsPage : public wxPreferencesPage, public Page
+class DefaultsPage : public StandardPage
 {
 public:
        DefaultsPage (wxSize panel_size, int border)
-               : Page (panel_size, border)
+               : StandardPage (panel_size, border)
        {}
-       
+
        wxString GetName () const
        {
                return _("Defaults");
        }
 
-#ifdef DCPOMATIC_OSX   
+#ifdef DCPOMATIC_OSX
        wxBitmap GetLargeIcon () const
        {
                return wxBitmap ("defaults", wxBITMAP_TYPE_PNG_RESOURCE);
        }
-#endif 
+#endif
 
-       wxWindow* CreateWindow (wxWindow* parent)
+private:
+       void setup ()
        {
-               wxPanel* panel = new wxPanel (parent);
-               wxBoxSizer* s = new wxBoxSizer (wxVERTICAL);
-               panel->SetSizer (s);
-
                wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
                table->AddGrowableCol (1, 1);
-               s->Add (table, 1, wxALL | wxEXPAND, _border);
-               
+               _panel->GetSizer()->Add (table, 1, wxALL | wxEXPAND, _border);
+
                {
-                       add_label_to_sizer (table, panel, _("Default duration of still images"), true);
+                       add_label_to_sizer (table, _panel, _("Default duration of still images"), true);
                        wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
-                       _still_length = new wxSpinCtrl (panel);
+                       _still_length = new wxSpinCtrl (_panel);
                        s->Add (_still_length);
-                       add_label_to_sizer (s, panel, _("s"), false);
+                       add_label_to_sizer (s, _panel, _("s"), false);
                        table->Add (s, 1);
                }
-               
-               add_label_to_sizer (table, panel, _("Default directory for new films"), true);
-#ifdef DCPOMATIC_USE_OWN_DIR_PICKER
-               _directory = new DirPickerCtrl (panel);
-#else  
-               _directory = new wxDirPickerCtrl (panel, wxDD_DIR_MUST_EXIST);
+
+               add_label_to_sizer (table, _panel, _("Default directory for new films"), true);
+#ifdef DCPOMATIC_USE_OWN_PICKER
+               _directory = new DirPickerCtrl (_panel);
+#else
+               _directory = new wxDirPickerCtrl (_panel, wxDD_DIR_MUST_EXIST);
 #endif
                table->Add (_directory, 1, wxEXPAND);
-               
-               add_label_to_sizer (table, panel, _("Default ISDCF name details"), true);
-               _isdcf_metadata_button = new wxButton (panel, wxID_ANY, _("Edit..."));
+
+               add_label_to_sizer (table, _panel, _("Default ISDCF name details"), true);
+               _isdcf_metadata_button = new wxButton (_panel, wxID_ANY, _("Edit..."));
                table->Add (_isdcf_metadata_button);
 
-               add_label_to_sizer (table, panel, _("Default scale to"), true);
-               _scale = new wxChoice (panel, wxID_ANY);
-               table->Add (_scale);
-               
-               add_label_to_sizer (table, panel, _("Default container"), true);
-               _container = new wxChoice (panel, wxID_ANY);
+               add_label_to_sizer (table, _panel, _("Default container"), true);
+               _container = new wxChoice (_panel, wxID_ANY);
                table->Add (_container);
-               
-               add_label_to_sizer (table, panel, _("Default content type"), true);
-               _dcp_content_type = new wxChoice (panel, wxID_ANY);
+
+               add_label_to_sizer (table, _panel, _("Default scale-to"), true);
+               _scale_to = new wxChoice (_panel, wxID_ANY);
+               table->Add (_scale_to);
+
+               add_label_to_sizer (table, _panel, _("Default content type"), true);
+               _dcp_content_type = new wxChoice (_panel, wxID_ANY);
                table->Add (_dcp_content_type);
-               
+
+               add_label_to_sizer (table, _panel, _("Default DCP audio channels"), true);
+               _dcp_audio_channels = new wxChoice (_panel, wxID_ANY);
+               table->Add (_dcp_audio_channels);
+
                {
-                       add_label_to_sizer (table, panel, _("Default JPEG2000 bandwidth"), true);
+                       add_label_to_sizer (table, _panel, _("Default JPEG2000 bandwidth"), true);
                        wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
-                       _j2k_bandwidth = new wxSpinCtrl (panel);
+                       _j2k_bandwidth = new wxSpinCtrl (_panel);
                        s->Add (_j2k_bandwidth);
-                       add_label_to_sizer (s, panel, _("Mbit/s"), false);
+                       add_label_to_sizer (s, _panel, _("Mbit/s"), false);
                        table->Add (s, 1);
                }
-               
+
                {
-                       add_label_to_sizer (table, panel, _("Default audio delay"), true);
+                       add_label_to_sizer (table, _panel, _("Default audio delay"), true);
                        wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
-                       _audio_delay = new wxSpinCtrl (panel);
+                       _audio_delay = new wxSpinCtrl (_panel);
                        s->Add (_audio_delay);
-                       add_label_to_sizer (s, panel, _("ms"), false);
+                       add_label_to_sizer (s, _panel, _("ms"), false);
                        table->Add (s, 1);
                }
 
-               add_label_to_sizer (table, panel, _("Default issuer"), true);
-               _issuer = new wxTextCtrl (panel, wxID_ANY);
-               table->Add (_issuer, 1, wxEXPAND);
+               add_label_to_sizer (table, _panel, _("Default standard"), true);
+               _standard = new wxChoice (_panel, wxID_ANY);
+               table->Add (_standard);
+
+               add_label_to_sizer (table, _panel, _("Default KDM directory"), true);
+#ifdef DCPOMATIC_USE_OWN_PICKER
+               _kdm_directory = new DirPickerCtrl (_panel);
+#else
+               _kdm_directory = new wxDirPickerCtrl (_panel, wxDD_DIR_MUST_EXIST);
+#endif
+               table->Add (_kdm_directory, 1, wxEXPAND);
 
-               add_label_to_sizer (table, panel, _("Default creator"), true);
-               _creator = new wxTextCtrl (panel, wxID_ANY);
-               table->Add (_creator, 1, wxEXPAND);
-               
-               Config* config = Config::instance ();
-               
                _still_length->SetRange (1, 3600);
-               _still_length->SetValue (config->default_still_length ());
-               _still_length->Bind (wxEVT_COMMAND_SPINCTRL_UPDATED, boost::bind (&DefaultsPage::still_length_changed, this));
-               
-               _directory->SetPath (std_to_wx (config->default_directory_or (wx_to_std (wxStandardPaths::Get().GetDocumentsDir())).string ()));
-               _directory->Bind (wxEVT_COMMAND_DIRPICKER_CHANGED, boost::bind (&DefaultsPage::directory_changed, this));
-               
-               _isdcf_metadata_button->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&DefaultsPage::edit_isdcf_metadata_clicked, this, parent));
-               
-               vector<Ratio const *> ratio = Ratio::all ();
-               int n = 0;
-               for (vector<Ratio const *>::iterator i = ratio.begin(); i != ratio.end(); ++i) {
-                       _scale->Append (std_to_wx ((*i)->nickname ()));
-                       if (*i == config->default_scale ()) {
-                               _scale->SetSelection (n);
+               _still_length->Bind (wxEVT_SPINCTRL, boost::bind (&DefaultsPage::still_length_changed, this));
+
+               _directory->Bind (wxEVT_DIRPICKER_CHANGED, boost::bind (&DefaultsPage::directory_changed, this));
+               _kdm_directory->Bind (wxEVT_DIRPICKER_CHANGED, boost::bind (&DefaultsPage::kdm_directory_changed, this));
+
+               _isdcf_metadata_button->Bind (wxEVT_BUTTON, boost::bind (&DefaultsPage::edit_isdcf_metadata_clicked, this));
+
+               BOOST_FOREACH (Ratio const * i, Ratio::containers()) {
+                       _container->Append (std_to_wx(i->container_nickname()));
+               }
+
+               _container->Bind (wxEVT_CHOICE, boost::bind (&DefaultsPage::container_changed, this));
+
+               _scale_to->Append (_("Guess from content"));
+
+               BOOST_FOREACH (Ratio const * i, Ratio::all()) {
+                       _scale_to->Append (std_to_wx(i->image_nickname()));
+               }
+
+               _scale_to->Bind (wxEVT_CHOICE, boost::bind (&DefaultsPage::scale_to_changed, this));
+
+               BOOST_FOREACH (DCPContentType const * i, DCPContentType::all()) {
+                       _dcp_content_type->Append (std_to_wx (i->pretty_name ()));
+               }
+
+               setup_audio_channels_choice (_dcp_audio_channels, 2);
+
+               _dcp_content_type->Bind (wxEVT_CHOICE, boost::bind (&DefaultsPage::dcp_content_type_changed, this));
+               _dcp_audio_channels->Bind (wxEVT_CHOICE, boost::bind (&DefaultsPage::dcp_audio_channels_changed, this));
+
+               _j2k_bandwidth->SetRange (50, 250);
+               _j2k_bandwidth->Bind (wxEVT_SPINCTRL, boost::bind (&DefaultsPage::j2k_bandwidth_changed, this));
+
+               _audio_delay->SetRange (-1000, 1000);
+               _audio_delay->Bind (wxEVT_SPINCTRL, boost::bind (&DefaultsPage::audio_delay_changed, this));
+
+               _standard->Append (_("SMPTE"));
+               _standard->Append (_("Interop"));
+               _standard->Bind (wxEVT_CHOICE, boost::bind (&DefaultsPage::standard_changed, this));
+       }
+
+       void config_changed ()
+       {
+               Config* config = Config::instance ();
+
+               vector<Ratio const *> containers = Ratio::containers ();
+               for (size_t i = 0; i < containers.size(); ++i) {
+                       if (containers[i] == config->default_container ()) {
+                               _container->SetSelection (i);
                        }
-                       _container->Append (std_to_wx ((*i)->nickname ()));
-                       if (*i == config->default_container ()) {
-                               _container->SetSelection (n);
+               }
+
+               vector<Ratio const *> ratios = Ratio::all ();
+               for (size_t i = 0; i < ratios.size(); ++i) {
+                       if (ratios[i] == config->default_scale_to ()) {
+                               _scale_to->SetSelection (i + 1);
                        }
-                       ++n;
                }
-               
-               _scale->Bind (wxEVT_COMMAND_CHOICE_SELECTED, boost::bind (&DefaultsPage::scale_changed, this));
-               _container->Bind (wxEVT_COMMAND_CHOICE_SELECTED, boost::bind (&DefaultsPage::container_changed, this));
-               
+
+               if (!config->default_scale_to()) {
+                       _scale_to->SetSelection (0);
+               }
+
                vector<DCPContentType const *> const ct = DCPContentType::all ();
-               n = 0;
-               for (vector<DCPContentType const *>::const_iterator i = ct.begin(); i != ct.end(); ++i) {
-                       _dcp_content_type->Append (std_to_wx ((*i)->pretty_name ()));
-                       if (*i == config->default_dcp_content_type ()) {
-                               _dcp_content_type->SetSelection (n);
+               for (size_t i = 0; i < ct.size(); ++i) {
+                       if (ct[i] == config->default_dcp_content_type ()) {
+                               _dcp_content_type->SetSelection (i);
                        }
-                       ++n;
                }
-               
-               _dcp_content_type->Bind (wxEVT_COMMAND_CHOICE_SELECTED, boost::bind (&DefaultsPage::dcp_content_type_changed, this));
-               
-               _j2k_bandwidth->SetRange (50, 250);
-               _j2k_bandwidth->SetValue (config->default_j2k_bandwidth() / 1000000);
-               _j2k_bandwidth->Bind (wxEVT_COMMAND_SPINCTRL_UPDATED, boost::bind (&DefaultsPage::j2k_bandwidth_changed, this));
-               
-               _audio_delay->SetRange (-1000, 1000);
-               _audio_delay->SetValue (config->default_audio_delay ());
-               _audio_delay->Bind (wxEVT_COMMAND_SPINCTRL_UPDATED, boost::bind (&DefaultsPage::audio_delay_changed, this));
-
-               _issuer->SetValue (std_to_wx (config->dcp_metadata().issuer));
-               _issuer->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&DefaultsPage::issuer_changed, this));
-               _creator->SetValue (std_to_wx (config->dcp_metadata().creator));
-               _creator->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&DefaultsPage::creator_changed, this));
 
-               return panel;
+               checked_set (_still_length, config->default_still_length ());
+               _directory->SetPath (std_to_wx (config->default_directory_or (wx_to_std (wxStandardPaths::Get().GetDocumentsDir())).string ()));
+               _kdm_directory->SetPath (std_to_wx (config->default_kdm_directory_or (wx_to_std (wxStandardPaths::Get().GetDocumentsDir())).string ()));
+               checked_set (_j2k_bandwidth, config->default_j2k_bandwidth() / 1000000);
+               _j2k_bandwidth->SetRange (50, config->maximum_j2k_bandwidth() / 1000000);
+               checked_set (_dcp_audio_channels, locale_convert<string> (config->default_dcp_audio_channels()));
+               checked_set (_audio_delay, config->default_audio_delay ());
+               checked_set (_standard, config->default_interop() ? 1 : 0);
        }
 
-private:
        void j2k_bandwidth_changed ()
        {
                Config::instance()->set_default_j2k_bandwidth (_j2k_bandwidth->GetValue() * 1000000);
        }
-       
+
        void audio_delay_changed ()
        {
                Config::instance()->set_default_audio_delay (_audio_delay->GetValue());
        }
 
+       void dcp_audio_channels_changed ()
+       {
+               int const s = _dcp_audio_channels->GetSelection ();
+               if (s != wxNOT_FOUND) {
+                       Config::instance()->set_default_dcp_audio_channels (
+                               locale_convert<int> (string_client_data (_dcp_audio_channels->GetClientObject (s)))
+                               );
+               }
+       }
+
        void directory_changed ()
        {
                Config::instance()->set_default_directory (wx_to_std (_directory->GetPath ()));
        }
 
-       void edit_isdcf_metadata_clicked (wxWindow* parent)
+       void kdm_directory_changed ()
        {
-               ISDCFMetadataDialog* d = new ISDCFMetadataDialog (parent, Config::instance()->default_isdcf_metadata ());
+               Config::instance()->set_default_kdm_directory (wx_to_std (_kdm_directory->GetPath ()));
+       }
+
+       void edit_isdcf_metadata_clicked ()
+       {
+               ISDCFMetadataDialog* d = new ISDCFMetadataDialog (_panel, Config::instance()->default_isdcf_metadata (), false);
                d->ShowModal ();
                Config::instance()->set_default_isdcf_metadata (d->isdcf_metadata ());
                d->Destroy ();
@@ -395,101 +728,97 @@ private:
                Config::instance()->set_default_still_length (_still_length->GetValue ());
        }
 
-       void scale_changed ()
-       {
-               vector<Ratio const *> ratio = Ratio::all ();
-               Config::instance()->set_default_scale (ratio[_scale->GetSelection()]);
-       }
-       
        void container_changed ()
        {
-               vector<Ratio const *> ratio = Ratio::all ();
+               vector<Ratio const *> ratio = Ratio::containers ();
                Config::instance()->set_default_container (ratio[_container->GetSelection()]);
        }
-       
+
+       void scale_to_changed ()
+       {
+               int const s = _scale_to->GetSelection ();
+               if (s == 0) {
+                       Config::instance()->set_default_scale_to (0);
+               } else {
+                       vector<Ratio const *> ratio = Ratio::all ();
+                       Config::instance()->set_default_scale_to (ratio[s - 1]);
+               }
+       }
+
        void dcp_content_type_changed ()
        {
                vector<DCPContentType const *> ct = DCPContentType::all ();
                Config::instance()->set_default_dcp_content_type (ct[_dcp_content_type->GetSelection()]);
        }
 
-       void issuer_changed ()
+       void standard_changed ()
        {
-               dcp::XMLMetadata m = Config::instance()->dcp_metadata ();
-               m.issuer = wx_to_std (_issuer->GetValue ());
-               Config::instance()->set_dcp_metadata (m);
+               Config::instance()->set_default_interop (_standard->GetSelection() == 1);
        }
-       
-       void creator_changed ()
-       {
-               dcp::XMLMetadata m = Config::instance()->dcp_metadata ();
-               m.creator = wx_to_std (_creator->GetValue ());
-               Config::instance()->set_dcp_metadata (m);
-       }
-       
+
        wxSpinCtrl* _j2k_bandwidth;
        wxSpinCtrl* _audio_delay;
        wxButton* _isdcf_metadata_button;
        wxSpinCtrl* _still_length;
-#ifdef DCPOMATIC_USE_OWN_DIR_PICKER
+#ifdef DCPOMATIC_USE_OWN_PICKER
        DirPickerCtrl* _directory;
+       DirPickerCtrl* _kdm_directory;
 #else
        wxDirPickerCtrl* _directory;
+       wxDirPickerCtrl* _kdm_directory;
 #endif
-       wxChoice* _scale;
        wxChoice* _container;
+       wxChoice* _scale_to;
        wxChoice* _dcp_content_type;
-       wxTextCtrl* _issuer;
-       wxTextCtrl* _creator;
+       wxChoice* _dcp_audio_channels;
+       wxChoice* _standard;
 };
 
-class EncodingServersPage : public wxPreferencesPage, public Page
+class EncodingServersPage : public StandardPage
 {
 public:
        EncodingServersPage (wxSize panel_size, int border)
-               : Page (panel_size, border)
+               : StandardPage (panel_size, border)
        {}
-       
+
        wxString GetName () const
        {
                return _("Servers");
        }
 
-#ifdef DCPOMATIC_OSX   
+#ifdef DCPOMATIC_OSX
        wxBitmap GetLargeIcon () const
        {
                return wxBitmap ("servers", wxBITMAP_TYPE_PNG_RESOURCE);
        }
-#endif 
+#endif
 
-       wxWindow* CreateWindow (wxWindow* parent)
+private:
+       void setup ()
        {
-               wxPanel* panel = new wxPanel (parent, wxID_ANY, wxDefaultPosition, _panel_size);
-               wxBoxSizer* s = new wxBoxSizer (wxVERTICAL);
-               panel->SetSizer (s);
-               
-               _use_any_servers = new wxCheckBox (panel, wxID_ANY, _("Use all servers"));
-               s->Add (_use_any_servers, 0, wxALL, _border);
-               
+               _use_any_servers = new wxCheckBox (_panel, wxID_ANY, _("Search network for servers"));
+               _panel->GetSizer()->Add (_use_any_servers, 0, wxALL, _border);
+
                vector<string> columns;
                columns.push_back (wx_to_std (_("IP address / host name")));
                _servers_list = new EditableList<string, ServerDialog> (
-                       panel,
+                       _panel,
                        columns,
                        boost::bind (&Config::servers, Config::instance()),
                        boost::bind (&Config::set_servers, Config::instance(), _1),
                        boost::bind (&EncodingServersPage::server_column, this, _1)
                        );
-               
-               s->Add (_servers_list, 1, wxEXPAND | wxALL, _border);
-               
-               _use_any_servers->SetValue (Config::instance()->use_any_servers ());
-               _use_any_servers->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&EncodingServersPage::use_any_servers_changed, this));
 
-               return panel;
+               _panel->GetSizer()->Add (_servers_list, 1, wxEXPAND | wxALL, _border);
+
+               _use_any_servers->Bind (wxEVT_CHECKBOX, boost::bind (&EncodingServersPage::use_any_servers_changed, this));
        }
 
-private:       
+       void config_changed ()
+       {
+               checked_set (_use_any_servers, Config::instance()->use_any_servers ());
+               _servers_list->refresh ();
+       }
 
        void use_any_servers_changed ()
        {
@@ -505,84 +834,37 @@ private:
        EditableList<string, ServerDialog>* _servers_list;
 };
 
-class ColourConversionsPage : public wxPreferencesPage, public Page
+class CertificateChainEditor : public wxPanel
 {
 public:
-       ColourConversionsPage (wxSize panel_size, int border)
-               : Page (panel_size, border)
-       {}
-       
-       wxString GetName () const
-       {
-               return _("Colour Conversions");
-       }
-
-#ifdef DCPOMATIC_OSX   
-       wxBitmap GetLargeIcon () const
-       {
-               return wxBitmap ("colour_conversions", wxBITMAP_TYPE_PNG_RESOURCE);
-       }
-#endif 
-       wxWindow* CreateWindow (wxWindow* parent)
-       {
-               wxPanel* panel = new wxPanel (parent, wxID_ANY, wxDefaultPosition, _panel_size);
-               wxBoxSizer* s = new wxBoxSizer (wxVERTICAL);
-               panel->SetSizer (s);
-
-               vector<string> columns;
-               columns.push_back (wx_to_std (_("Name")));
-               wxPanel* list = new EditableList<PresetColourConversion, PresetColourConversionDialog> (
-                       panel,
-                       columns,
-                       boost::bind (&Config::colour_conversions, Config::instance()),
-                       boost::bind (&Config::set_colour_conversions, Config::instance(), _1),
-                       boost::bind (&ColourConversionsPage::colour_conversion_column, this, _1),
-                       300
-                       );
-
-               s->Add (list, 1, wxEXPAND | wxALL, _border);
-               return panel;
-       }
-
-private:
-       string colour_conversion_column (PresetColourConversion c)
-       {
-               return c.name;
-       }
-};
-
-class KeysPage : public wxPreferencesPage, public Page
-{
-public:
-       KeysPage (wxSize panel_size, int border)
-               : Page (panel_size, border)
-       {}
-
-       wxString GetName () const
-       {
-               return _("Keys");
-       }
-
-#ifdef DCPOMATIC_OSX
-       wxBitmap GetLargeIcon () const
-       {
-               return wxBitmap ("keys", wxBITMAP_TYPE_PNG_RESOURCE);
-       }
-#endif 
+       CertificateChainEditor (
+               wxWindow* parent,
+               wxString title,
+               int border,
+               function<void (shared_ptr<dcp::CertificateChain>)> set,
+               function<shared_ptr<const dcp::CertificateChain> (void)> get,
+               function<void (void)> nag_remake
+               )
+               : wxPanel (parent)
+               , _set (set)
+               , _get (get)
+               , _nag_remake (nag_remake)
+       {
+               wxFont subheading_font (*wxNORMAL_FONT);
+               subheading_font.SetWeight (wxFONTWEIGHT_BOLD);
+
+               _sizer = new wxBoxSizer (wxVERTICAL);
 
-       wxWindow* CreateWindow (wxWindow* parent)
-       {
-               _panel = new wxPanel (parent, wxID_ANY, wxDefaultPosition, _panel_size);
-               wxBoxSizer* overall_sizer = new wxBoxSizer (wxVERTICAL);
-               _panel->SetSizer (overall_sizer);
+               {
+                       wxStaticText* m = new wxStaticText (this, wxID_ANY, title);
+                       m->SetFont (subheading_font);
+                       _sizer->Add (m, 0, wxALL, border);
+               }
 
-               wxStaticText* m = new wxStaticText (_panel, wxID_ANY, _("Certificate chain for signing DCPs and KDMs:"));
-               overall_sizer->Add (m, 0, wxALL, _border);
-               
                wxBoxSizer* certificates_sizer = new wxBoxSizer (wxHORIZONTAL);
-               overall_sizer->Add (certificates_sizer, 0, wxLEFT | wxRIGHT, _border);
-               
-               _certificates = new wxListCtrl (_panel, wxID_ANY, wxDefaultPosition, wxSize (400, 200), wxLC_REPORT | wxLC_SINGLE_SEL);
+               _sizer->Add (certificates_sizer, 0, wxLEFT | wxRIGHT, border);
+
+               _certificates = new wxListCtrl (this, wxID_ANY, wxDefaultPosition, wxSize (440, 150), wxLC_REPORT | wxLC_SINGLE_SEL);
 
                {
                        wxListItem ip;
@@ -596,12 +878,12 @@ public:
                        wxListItem ip;
                        ip.SetId (1);
                        ip.SetText (_("Thumbprint"));
-                       ip.SetWidth (300);
+                       ip.SetWidth (340);
 
                        wxFont font = ip.GetFont ();
                        font.SetFamily (wxFONTFAMILY_TELETYPE);
                        ip.SetFont (font);
-                       
+
                        _certificates->InsertColumn (1, ip);
                }
 
@@ -609,96 +891,112 @@ public:
 
                {
                        wxSizer* s = new wxBoxSizer (wxVERTICAL);
-                       _add_certificate = new wxButton (_panel, wxID_ANY, _("Add..."));
+                       _add_certificate = new wxButton (this, wxID_ANY, _("Add..."));
                        s->Add (_add_certificate, 0, wxTOP | wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
-                       _remove_certificate = new wxButton (_panel, wxID_ANY, _("Remove"));
+                       _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);
                        certificates_sizer->Add (s, 0, wxLEFT, DCPOMATIC_SIZER_X_GAP);
                }
 
-               wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
-               table->AddGrowableCol (1, 1);
-               overall_sizer->Add (table, 1, wxALL | wxEXPAND, _border);
-
-               add_label_to_sizer (table, _panel, _("Private key for leaf certificate"), true);
-               {
-                       wxSizer* s = new wxBoxSizer (wxHORIZONTAL);
-                       _signer_private_key = new wxStaticText (_panel, wxID_ANY, wxT (""));
-                       wxFont font = _signer_private_key->GetFont ();
-                       font.SetFamily (wxFONTFAMILY_TELETYPE);
-                       _signer_private_key->SetFont (font);
-                       s->Add (_signer_private_key, 1, wxLEFT | wxRIGHT | wxALIGN_CENTER_VERTICAL, DCPOMATIC_SIZER_X_GAP);
-                       _load_signer_private_key = new wxButton (_panel, wxID_ANY, _("Load..."));
-                       s->Add (_load_signer_private_key, 0, wxLEFT, DCPOMATIC_SIZER_X_GAP);
-                       table->Add (s, 0);
-               }
-
-               add_label_to_sizer (table, _panel, _("Certificate for decrypting DCPs"), true);
-               {
-                       wxSizer* s = new wxBoxSizer (wxHORIZONTAL);
-                       _decryption_certificate = new wxStaticText (_panel, wxID_ANY, wxT (""));
-                       wxFont font = _decryption_certificate->GetFont ();
-                       font.SetFamily (wxFONTFAMILY_TELETYPE);
-                       _decryption_certificate->SetFont (font);
-                       s->Add (_decryption_certificate, 1, wxLEFT | wxRIGHT | wxALIGN_CENTER_VERTICAL, DCPOMATIC_SIZER_X_GAP);
-                       _load_decryption_certificate = new wxButton (_panel, wxID_ANY, _("Load..."));
-                       s->Add (_load_decryption_certificate, 0, wxLEFT, DCPOMATIC_SIZER_X_GAP);
-                       table->Add (s, 0);
-               }
-
-               add_label_to_sizer (table, _panel, _("Private key for decrypting DCPs"), true);
-               {
-                       wxSizer* s = new wxBoxSizer (wxHORIZONTAL);
-                       _decryption_private_key = new wxStaticText (_panel, wxID_ANY, wxT (""));
-                       wxFont font = _decryption_private_key->GetFont ();
-                       font.SetFamily (wxFONTFAMILY_TELETYPE);
-                       _decryption_private_key->SetFont (font);
-                       s->Add (_decryption_private_key, 1, wxLEFT | wxRIGHT | wxALIGN_CENTER_VERTICAL, DCPOMATIC_SIZER_X_GAP);
-                       _load_decryption_private_key = new wxButton (_panel, wxID_ANY, _("Load..."));
-                       s->Add (_load_decryption_private_key, 0, wxLEFT, DCPOMATIC_SIZER_X_GAP);
-                       table->Add (s, 0);
-               }
-
-               _export_decryption_certificate = new wxButton (_panel, wxID_ANY, _("Export DCP decryption certificate..."));
-               table->Add (_export_decryption_certificate);
-               table->AddSpacer (0);
-               
-               _add_certificate->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&KeysPage::add_certificate, this));
-               _remove_certificate->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&KeysPage::remove_certificate, this));
-               _certificates->Bind (wxEVT_COMMAND_LIST_ITEM_SELECTED, boost::bind (&KeysPage::update_sensitivity, this));
-               _certificates->Bind (wxEVT_COMMAND_LIST_ITEM_DESELECTED, boost::bind (&KeysPage::update_sensitivity, this));
-               _load_signer_private_key->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&KeysPage::load_signer_private_key, this));
-               _load_decryption_certificate->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&KeysPage::load_decryption_certificate, this));
-               _load_decryption_private_key->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&KeysPage::load_decryption_private_key, this));
-               _export_decryption_certificate->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&KeysPage::export_decryption_certificate, this));
-
-               _signer.reset (new dcp::Signer (*Config::instance()->signer().get ()));
+               wxGridBagSizer* table = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
+               _sizer->Add (table, 1, wxALL | wxEXPAND, border);
+               int r = 0;
+
+               add_label_to_sizer (table, this, _("Leaf private key"), true, wxGBPosition (r, 0));
+               _private_key = new wxStaticText (this, wxID_ANY, 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);
+               _load_private_key = new wxButton (this, wxID_ANY, _("Load..."));
+               table->Add (_load_private_key, wxGBPosition (r, 2));
+               _export_private_key = new wxButton (this, wxID_ANY, _("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\nand 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!"));
+               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));
+               _load_private_key->Bind    (wxEVT_BUTTON,       boost::bind (&CertificateChainEditor::load_private_key, this));
+               _export_private_key->Bind  (wxEVT_BUTTON,       boost::bind (&CertificateChainEditor::export_private_key, this));
+
+               SetSizerAndFit (_sizer);
+       }
+
+       void config_changed ()
+       {
+               _chain.reset (new dcp::CertificateChain (*_get().get ()));
 
                update_certificate_list ();
-               update_signer_private_key ();
-               update_decryption_certificate ();
-               update_decryption_private_key ();
+               update_private_key ();
                update_sensitivity ();
+       }
 
-               return _panel;
+       void add_button (wxWindow* button)
+       {
+               _button_sizer->Add (button, 0, wxLEFT | wxRIGHT, DCPOMATIC_SIZER_X_GAP);
+               _sizer->Layout ();
        }
 
 private:
        void add_certificate ()
        {
-               wxFileDialog* d = new wxFileDialog (_panel, _("Select Certificate File"));
-               
+               wxFileDialog* d = new wxFileDialog (this, _("Select Certificate File"));
+
                if (d->ShowModal() == wxID_OK) {
                        try {
-                               dcp::Certificate c (dcp::file_to_string (wx_to_std (d->GetPath ())));
-                               _signer->certificates().add (c);
-                               Config::instance()->set_signer (_signer);
-                               update_certificate_list ();
+                               dcp::Certificate c;
+                               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 load certificate (%s)"), d->GetPath().data()));
+                                       d->Destroy ();
+                                       return;
+                               }
+
+                               if (!extra.empty ()) {
+                                       message_dialog (
+                                               this,
+                                               _("This file contains other certificates (or other data) after its first certificate. "
+                                                 "Only the first certificate will be used.")
+                                               );
+                               }
+                               _chain->add (c);
+                               if (!_chain->chain_valid ()) {
+                                       error_dialog (
+                                               this,
+                                               _("Adding this certificate would make the chain inconsistent, so it will not be added. "
+                                                 "Add certificates in order from root to intermediate to leaf.")
+                                               );
+                                       _chain->remove (c);
+                               } else {
+                                       _set (_chain);
+                                       update_certificate_list ();
+                               }
                        } catch (dcp::MiscError& e) {
-                               error_dialog (_panel, wxString::Format (_("Could not read certificate file (%s)"), e.what ()));
+                               error_dialog (this, wxString::Format (_("Could not read certificate file (%s)"), e.what ()));
                        }
                }
-               
+
                d->Destroy ();
 
                update_sensitivity ();
@@ -710,115 +1008,271 @@ private:
                if (i == -1) {
                        return;
                }
-               
+
                _certificates->DeleteItem (i);
-               _signer->certificates().remove (i);
-               Config::instance()->set_signer (_signer);
+               _chain->remove (i);
+               _set (_chain);
 
                update_sensitivity ();
        }
 
+       void export_certificate ()
+       {
+               int i = _certificates->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
+               if (i == -1) {
+                       return;
+               }
+
+               wxFileDialog* d = new wxFileDialog (
+                       this, _("Select Certificate File"), wxEmptyString, wxEmptyString, wxT ("PEM files (*.pem)|*.pem"),
+                       wxFD_SAVE | wxFD_OVERWRITE_PROMPT
+                       );
+
+               dcp::CertificateChain::List all = _chain->root_to_leaf ();
+               dcp::CertificateChain::List::iterator j = all.begin ();
+               for (int k = 0; k < i; ++k) {
+                       ++j;
+               }
+
+               if (d->ShowModal () == wxID_OK) {
+                       FILE* f = fopen_boost (wx_to_std (d->GetPath ()), "w");
+                       if (!f) {
+                               throw OpenFileError (wx_to_std (d->GetPath ()), errno, false);
+                       }
+
+                       string const s = j->certificate (true);
+                       fwrite (s.c_str(), 1, s.length(), f);
+                       fclose (f);
+               }
+               d->Destroy ();
+       }
+
        void update_certificate_list ()
        {
                _certificates->DeleteAllItems ();
-               dcp::CertificateChain::List certs = _signer->certificates().root_to_leaf ();
                size_t n = 0;
-               for (dcp::CertificateChain::List::const_iterator i = certs.begin(); i != certs.end(); ++i) {
+               dcp::CertificateChain::List certs = _chain->root_to_leaf ();
+               BOOST_FOREACH (dcp::Certificate const & i, certs) {
                        wxListItem item;
                        item.SetId (n);
                        _certificates->InsertItem (item);
-                       _certificates->SetItem (n, 1, std_to_wx (i->thumbprint ()));
+                       _certificates->SetItem (n, 1, std_to_wx (i.thumbprint ()));
+
+                       if (n == 0) {
+                               _certificates->SetItem (n, 0, _("Root"));
+                       } else if (n == (certs.size() - 1)) {
+                               _certificates->SetItem (n, 0, _("Leaf"));
+                       } else {
+                               _certificates->SetItem (n, 0, _("Intermediate"));
+                       }
+
+                       ++n;
+               }
+
+               static wxColour normal = _private_key_bad->GetForegroundColour ();
+
+               if (_chain->private_key_valid ()) {
+                       _private_key_bad->Hide ();
+                       _private_key_bad->SetForegroundColour (normal);
+               } else {
+                       _private_key_bad->Show ();
+                       _private_key_bad->SetForegroundColour (wxColour (255, 0, 0));
+               }
+       }
+
+       void remake_certificates ()
+       {
+               shared_ptr<const dcp::CertificateChain> chain = _get ();
+
+               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 ();
+
+               if (all.size() >= 1) {
+                       /* Have a root */
+                       subject_organization_name = chain->root().subject_organization_name ();
+                       subject_organizational_unit_name = chain->root().subject_organizational_unit_name ();
+                       root_common_name = chain->root().subject_common_name ();
+               }
+
+               if (all.size() >= 2) {
+                       /* Have a leaf */
+                       leaf_common_name = chain->leaf().subject_common_name ();
+               }
+
+               if (all.size() >= 3) {
+                       /* Have an intermediate */
+                       dcp::CertificateChain::List::iterator i = all.begin ();
+                       ++i;
+                       intermediate_common_name = i->subject_common_name ();
+               }
 
-                       if (n == 0) {
-                               _certificates->SetItem (n, 0, _("Root"));
-                       } else if (n == (certs.size() - 1)) {
-                               _certificates->SetItem (n, 0, _("Leaf"));
-                       } else {
-                               _certificates->SetItem (n, 0, _("Intermediate"));
-                       }
+               _nag_remake ();
 
-                       ++n;
+               MakeChainDialog* d = new MakeChainDialog (
+                       this,
+                       subject_organization_name,
+                       subject_organizational_unit_name,
+                       root_common_name,
+                       intermediate_common_name,
+                       leaf_common_name
+                       );
+
+               if (d->ShowModal () == wxID_OK) {
+                       _chain.reset (
+                               new dcp::CertificateChain (
+                                       openssl_path (),
+                                       d->organisation (),
+                                       d->organisational_unit (),
+                                       d->root_common_name (),
+                                       d->intermediate_common_name (),
+                                       d->leaf_common_name ()
+                                       )
+                               );
+
+                       _set (_chain);
+                       update_certificate_list ();
+                       update_private_key ();
                }
+
+               d->Destroy ();
        }
 
        void update_sensitivity ()
        {
-               _remove_certificate->Enable (_certificates->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) != -1);
+               /* 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);
        }
 
-       void update_signer_private_key ()
+       void update_private_key ()
        {
-               _signer_private_key->SetLabel (std_to_wx (dcp::private_key_fingerprint (_signer->key ())));
-       }       
+               checked_set (_private_key, dcp::private_key_fingerprint (_chain->key().get ()));
+               _sizer->Layout ();
+       }
 
-       void load_signer_private_key ()
+       void load_private_key ()
        {
-               wxFileDialog* d = new wxFileDialog (_panel, _("Select Key File"));
+               wxFileDialog* d = new wxFileDialog (this, _("Select Key File"));
 
                if (d->ShowModal() == wxID_OK) {
                        try {
                                boost::filesystem::path p (wx_to_std (d->GetPath ()));
-                               if (boost::filesystem::file_size (p) > 1024) {
-                                       error_dialog (_panel, wxString::Format (_("Could not read key file (%s)"), std_to_wx (p.string ())));
+                               if (boost::filesystem::file_size (p) > 8192) {
+                                       error_dialog (
+                                               this,
+                                               wxString::Format (_("Could not read key file; file is too long (%s)"), std_to_wx (p.string ()))
+                                               );
                                        return;
                                }
-                               
-                               _signer->set_key (dcp::file_to_string (p));
-                               Config::instance()->set_signer (_signer);
-                               update_signer_private_key ();
+
+                               _chain->set_key (dcp::file_to_string (p));
+                               _set (_chain);
+                               update_private_key ();
                        } catch (dcp::MiscError& e) {
-                               error_dialog (_panel, wxString::Format (_("Could not read certificate file (%s)"), e.what ()));
+                               error_dialog (this, wxString::Format (_("Could not read certificate file (%s)"), e.what ()));
                        }
                }
-               
+
                d->Destroy ();
 
                update_sensitivity ();
-
        }
 
-       void load_decryption_certificate ()
+       void export_private_key ()
        {
-               wxFileDialog* d = new wxFileDialog (_panel, _("Select Certificate File"));
-               
-               if (d->ShowModal() == wxID_OK) {
-                       try {
-                               dcp::Certificate c (dcp::file_to_string (wx_to_std (d->GetPath ())));
-                               Config::instance()->set_decryption_certificate (c);
-                               update_decryption_certificate ();
-                       } catch (dcp::MiscError& e) {
-                               error_dialog (_panel, wxString::Format (_("Could not read certificate file (%s)"), e.what ()));
+               optional<string> key = _chain->key ();
+               if (!key) {
+                       return;
+               }
+
+               wxFileDialog* d = new wxFileDialog (
+                       this, _("Select Key File"), wxEmptyString, wxEmptyString, wxT ("PEM files (*.pem)|*.pem"),
+                       wxFD_SAVE | wxFD_OVERWRITE_PROMPT
+                       );
+
+               if (d->ShowModal () == wxID_OK) {
+                       FILE* f = fopen_boost (wx_to_std (d->GetPath ()), "w");
+                       if (!f) {
+                               throw OpenFileError (wx_to_std (d->GetPath ()), errno, false);
                        }
+
+                       string const s = _chain->key().get ();
+                       fwrite (s.c_str(), 1, s.length(), f);
+                       fclose (f);
                }
-               
                d->Destroy ();
        }
 
-       void update_decryption_certificate ()
+       wxListCtrl* _certificates;
+       wxButton* _add_certificate;
+       wxButton* _export_certificate;
+       wxButton* _remove_certificate;
+       wxButton* _remake_certificates;
+       wxStaticText* _private_key;
+       wxButton* _load_private_key;
+       wxButton* _export_private_key;
+       wxStaticText* _private_key_bad;
+       wxSizer* _sizer;
+       wxBoxSizer* _button_sizer;
+       shared_ptr<dcp::CertificateChain> _chain;
+       boost::function<void (shared_ptr<dcp::CertificateChain>)> _set;
+       boost::function<shared_ptr<const dcp::CertificateChain> (void)> _get;
+       boost::function<void (void)> _nag_remake;
+};
+
+class KeysPage : public StandardPage
+{
+public:
+       KeysPage (wxSize panel_size, int border)
+               : StandardPage (panel_size, border)
+       {}
+
+       wxString GetName () const
        {
-               _decryption_certificate->SetLabel (std_to_wx (Config::instance()->decryption_certificate().thumbprint ()));
+               return _("Keys");
        }
 
-       void load_decryption_private_key ()
+#ifdef DCPOMATIC_OSX
+       wxBitmap GetLargeIcon () const
        {
-               wxFileDialog* d = new wxFileDialog (_panel, _("Select Key File"));
-
-               if (d->ShowModal() == wxID_OK) {
-                       try {
-                               boost::filesystem::path p (wx_to_std (d->GetPath ()));
-                               Config::instance()->set_decryption_private_key (dcp::file_to_string (p));
-                               update_decryption_private_key ();
-                       } catch (dcp::MiscError& e) {
-                               error_dialog (_panel, wxString::Format (_("Could not read key file (%s)"), e.what ()));
-                       }
-               }
-               
-               d->Destroy ();
+               return wxBitmap ("keys", wxBITMAP_TYPE_PNG_RESOURCE);
        }
+#endif
+
+private:
 
-       void update_decryption_private_key ()
+       void setup ()
        {
-               _decryption_private_key->SetLabel (std_to_wx (dcp::private_key_fingerprint (Config::instance()->decryption_private_key())));
+               _signer = new CertificateChainEditor (
+                       _panel, _("Signing DCPs and KDMs"), _border,
+                       boost::bind (&Config::set_signer_chain, Config::instance (), _1),
+                       boost::bind (&Config::signer_chain, Config::instance ()),
+                       boost::bind (&do_nothing)
+                       );
+
+               _panel->GetSizer()->Add (_signer);
+
+               _decryption = new CertificateChainEditor (
+                       _panel, _("Decrypting KDMs"), _border,
+                       boost::bind (&Config::set_decryption_chain, Config::instance (), _1),
+                       boost::bind (&Config::decryption_chain, Config::instance ()),
+                       boost::bind (&KeysPage::nag_remake_decryption_chain, this)
+                       );
+
+               _panel->GetSizer()->Add (_decryption);
+
+               _export_decryption_certificate = new wxButton (_decryption, wxID_ANY, _("Export KDM decryption\ncertificate..."));
+               _decryption->add_button (_export_decryption_certificate);
+               _export_decryption_chain = new wxButton (_decryption, wxID_ANY, _("Export KDM decryption\nchain..."));
+               _decryption->add_button (_export_decryption_chain);
+
+               _export_decryption_certificate->Bind (wxEVT_BUTTON, boost::bind (&KeysPage::export_decryption_certificate, this));
+               _export_decryption_chain->Bind (wxEVT_BUTTON, boost::bind (&KeysPage::export_decryption_chain, this));
        }
 
        void export_decryption_certificate ()
@@ -827,39 +1281,66 @@ private:
                        _panel, _("Select Certificate File"), wxEmptyString, wxEmptyString, wxT ("PEM files (*.pem)|*.pem"),
                        wxFD_SAVE | wxFD_OVERWRITE_PROMPT
                        );
-               
+
                if (d->ShowModal () == wxID_OK) {
                        FILE* f = fopen_boost (wx_to_std (d->GetPath ()), "w");
                        if (!f) {
-                               throw OpenFileError (wx_to_std (d->GetPath ()));
+                               throw OpenFileError (wx_to_std (d->GetPath ()), errno, false);
                        }
 
-                       string const s = Config::instance()->decryption_certificate().certificate (true);
+                       string const s = Config::instance()->decryption_chain()->leaf().certificate (true);
                        fwrite (s.c_str(), 1, s.length(), f);
                        fclose (f);
                }
                d->Destroy ();
        }
 
-       wxPanel* _panel;
-       wxListCtrl* _certificates;
-       wxButton* _add_certificate;
-       wxButton* _remove_certificate;
-       wxStaticText* _signer_private_key;
-       wxButton* _load_signer_private_key;
-       wxStaticText* _decryption_certificate;
-       wxButton* _load_decryption_certificate;
-       wxStaticText* _decryption_private_key;
-       wxButton* _load_decryption_private_key;
+       void export_decryption_chain ()
+       {
+               wxFileDialog* d = new wxFileDialog (
+                       _panel, _("Select Chain File"), wxEmptyString, wxEmptyString, wxT ("PEM files (*.pem)|*.pem"),
+                       wxFD_SAVE | wxFD_OVERWRITE_PROMPT
+                       );
+
+               if (d->ShowModal () == wxID_OK) {
+                       FILE* f = fopen_boost (wx_to_std (d->GetPath ()), "w");
+                       if (!f) {
+                               throw OpenFileError (wx_to_std (d->GetPath ()), errno, false);
+                       }
+
+                       string const s = Config::instance()->decryption_chain()->chain();
+                       fwrite (s.c_str(), 1, s.length(), f);
+                       fclose (f);
+               }
+               d->Destroy ();
+       }
+
+       void config_changed ()
+       {
+               _signer->config_changed ();
+               _decryption->config_changed ();
+       }
+
+       void nag_remake_decryption_chain ()
+       {
+               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!")
+                       );
+       }
+
+       CertificateChainEditor* _signer;
+       CertificateChainEditor* _decryption;
        wxButton* _export_decryption_certificate;
-       shared_ptr<dcp::Signer> _signer;
+       wxButton* _export_decryption_chain;
 };
 
-class TMSPage : public wxPreferencesPage, public Page
+class TMSPage : public StandardPage
 {
 public:
        TMSPage (wxSize panel_size, int border)
-               : Page (panel_size, border)
+               : StandardPage (panel_size, border)
        {}
 
        wxString GetName () const
@@ -867,189 +1348,223 @@ public:
                return _("TMS");
        }
 
-#ifdef DCPOMATIC_OSX   
+#ifdef DCPOMATIC_OSX
        wxBitmap GetLargeIcon () const
        {
                return wxBitmap ("tms", wxBITMAP_TYPE_PNG_RESOURCE);
        }
-#endif 
+#endif
 
-       wxWindow* CreateWindow (wxWindow* parent)
+private:
+       void setup ()
        {
-               wxPanel* panel = new wxPanel (parent, wxID_ANY, wxDefaultPosition, _panel_size);
-               wxBoxSizer* s = new wxBoxSizer (wxVERTICAL);
-               panel->SetSizer (s);
-
                wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
                table->AddGrowableCol (1, 1);
-               s->Add (table, 1, wxALL | wxEXPAND, _border);
-               
-               add_label_to_sizer (table, panel, _("IP address"), true);
-               _tms_ip = new wxTextCtrl (panel, wxID_ANY);
+               _panel->GetSizer()->Add (table, 1, wxALL | wxEXPAND, _border);
+
+               add_label_to_sizer (table, _panel, _("Protocol"), true);
+               _tms_protocol = new wxChoice (_panel, wxID_ANY);
+               table->Add (_tms_protocol, 1, wxEXPAND);
+
+               add_label_to_sizer (table, _panel, _("IP address"), true);
+               _tms_ip = new wxTextCtrl (_panel, wxID_ANY);
                table->Add (_tms_ip, 1, wxEXPAND);
-               
-               add_label_to_sizer (table, panel, _("Target path"), true);
-               _tms_path = new wxTextCtrl (panel, wxID_ANY);
+
+               add_label_to_sizer (table, _panel, _("Target path"), true);
+               _tms_path = new wxTextCtrl (_panel, wxID_ANY);
                table->Add (_tms_path, 1, wxEXPAND);
-               
-               add_label_to_sizer (table, panel, _("User name"), true);
-               _tms_user = new wxTextCtrl (panel, wxID_ANY);
+
+               add_label_to_sizer (table, _panel, _("User name"), true);
+               _tms_user = new wxTextCtrl (_panel, wxID_ANY);
                table->Add (_tms_user, 1, wxEXPAND);
-               
-               add_label_to_sizer (table, panel, _("Password"), true);
-               _tms_password = new wxTextCtrl (panel, wxID_ANY);
+
+               add_label_to_sizer (table, _panel, _("Password"), true);
+               _tms_password = new wxTextCtrl (_panel, wxID_ANY);
                table->Add (_tms_password, 1, wxEXPAND);
-               
+
+               _tms_protocol->Append (_("SCP (for AAM and Doremi)"));
+               _tms_protocol->Append (_("FTP (for Dolby)"));
+
+               _tms_protocol->Bind (wxEVT_CHOICE, boost::bind (&TMSPage::tms_protocol_changed, this));
+               _tms_ip->Bind (wxEVT_TEXT, boost::bind (&TMSPage::tms_ip_changed, this));
+               _tms_path->Bind (wxEVT_TEXT, boost::bind (&TMSPage::tms_path_changed, this));
+               _tms_user->Bind (wxEVT_TEXT, boost::bind (&TMSPage::tms_user_changed, this));
+               _tms_password->Bind (wxEVT_TEXT, boost::bind (&TMSPage::tms_password_changed, this));
+       }
+
+       void config_changed ()
+       {
                Config* config = Config::instance ();
-               
-               _tms_ip->SetValue (std_to_wx (config->tms_ip ()));
-               _tms_ip->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&TMSPage::tms_ip_changed, this));
-               _tms_path->SetValue (std_to_wx (config->tms_path ()));
-               _tms_path->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&TMSPage::tms_path_changed, this));
-               _tms_user->SetValue (std_to_wx (config->tms_user ()));
-               _tms_user->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&TMSPage::tms_user_changed, this));
-               _tms_password->SetValue (std_to_wx (config->tms_password ()));
-               _tms_password->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&TMSPage::tms_password_changed, this));
 
-               return panel;
+               checked_set (_tms_protocol, config->tms_protocol ());
+               checked_set (_tms_ip, config->tms_ip ());
+               checked_set (_tms_path, config->tms_path ());
+               checked_set (_tms_user, config->tms_user ());
+               checked_set (_tms_password, config->tms_password ());
+       }
+
+       void tms_protocol_changed ()
+       {
+               Config::instance()->set_tms_protocol (static_cast<Protocol> (_tms_protocol->GetSelection ()));
        }
 
-private:
        void tms_ip_changed ()
        {
                Config::instance()->set_tms_ip (wx_to_std (_tms_ip->GetValue ()));
        }
-       
+
        void tms_path_changed ()
        {
                Config::instance()->set_tms_path (wx_to_std (_tms_path->GetValue ()));
        }
-       
+
        void tms_user_changed ()
        {
                Config::instance()->set_tms_user (wx_to_std (_tms_user->GetValue ()));
        }
-       
+
        void tms_password_changed ()
        {
                Config::instance()->set_tms_password (wx_to_std (_tms_password->GetValue ()));
        }
 
+       wxChoice* _tms_protocol;
        wxTextCtrl* _tms_ip;
        wxTextCtrl* _tms_path;
        wxTextCtrl* _tms_user;
        wxTextCtrl* _tms_password;
 };
 
-class KDMEmailPage : public wxPreferencesPage, public Page
+static string
+column (string s)
+{
+       return s;
+}
+
+class KDMEmailPage : public StandardPage
 {
 public:
 
        KDMEmailPage (wxSize panel_size, int border)
-               : Page (panel_size, border)
+#ifdef DCPOMATIC_OSX
+               /* We have to force both width and height of this one */
+               : StandardPage (wxSize (480, 128), border)
+#else
+               : StandardPage (panel_size, border)
+#endif
        {}
-       
+
        wxString GetName () const
        {
                return _("KDM Email");
        }
 
-#ifdef DCPOMATIC_OSX   
+#ifdef DCPOMATIC_OSX
        wxBitmap GetLargeIcon () const
        {
                return wxBitmap ("kdm_email", wxBITMAP_TYPE_PNG_RESOURCE);
        }
-#endif 
+#endif
 
-       wxWindow* CreateWindow (wxWindow* parent)
+private:
+       void setup ()
        {
-               /* We have to force both width and height of this one */
-#ifdef DCPOMATIC_OSX
-               wxPanel* panel = new wxPanel (parent, wxID_ANY, wxDefaultPosition, wxSize (480, 128));
-#else          
-               wxPanel* panel = new wxPanel (parent);
-#endif         
-               wxBoxSizer* s = new wxBoxSizer (wxVERTICAL);
-               panel->SetSizer (s);
-
                wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
                table->AddGrowableCol (1, 1);
-               s->Add (table, 1, wxEXPAND | wxALL, _border);
-
-               add_label_to_sizer (table, panel, _("Outgoing mail server"), true);
-               _mail_server = new wxTextCtrl (panel, wxID_ANY);
-               table->Add (_mail_server, 1, wxEXPAND | wxALL);
-               
-               add_label_to_sizer (table, panel, _("Mail user name"), true);
-               _mail_user = new wxTextCtrl (panel, wxID_ANY);
+               _panel->GetSizer()->Add (table, 1, wxEXPAND | wxALL, _border);
+
+               add_label_to_sizer (table, _panel, _("Outgoing mail server"), true);
+               {
+                       wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
+                       _mail_server = new wxTextCtrl (_panel, wxID_ANY);
+                       s->Add (_mail_server, 1, wxEXPAND | wxALL);
+                       add_label_to_sizer (s, _panel, _("port"), false);
+                       _mail_port = new wxSpinCtrl (_panel, wxID_ANY);
+                       _mail_port->SetRange (0, 65535);
+                       s->Add (_mail_port);
+                       table->Add (s, 1, wxEXPAND | wxALL);
+               }
+
+               add_label_to_sizer (table, _panel, _("Mail user name"), true);
+               _mail_user = new wxTextCtrl (_panel, wxID_ANY);
                table->Add (_mail_user, 1, wxEXPAND | wxALL);
-               
-               add_label_to_sizer (table, panel, _("Mail password"), true);
-               _mail_password = new wxTextCtrl (panel, wxID_ANY);
+
+               add_label_to_sizer (table, _panel, _("Mail password"), true);
+               _mail_password = new wxTextCtrl (_panel, wxID_ANY);
                table->Add (_mail_password, 1, wxEXPAND | wxALL);
-               
-               wxStaticText* plain = add_label_to_sizer (table, panel, _("(password will be stored on disk in plaintext)"), false);
-               wxFont font = plain->GetFont();
-               font.SetStyle (wxFONTSTYLE_ITALIC);
-               font.SetPointSize (font.GetPointSize() - 1);
-               plain->SetFont (font);
-               table->AddSpacer (0);
 
-               add_label_to_sizer (table, panel, _("Subject"), true);
-               _kdm_subject = new wxTextCtrl (panel, wxID_ANY);
+               add_label_to_sizer (table, _panel, _("Subject"), true);
+               _kdm_subject = new wxTextCtrl (_panel, wxID_ANY);
                table->Add (_kdm_subject, 1, wxEXPAND | wxALL);
-               
-               add_label_to_sizer (table, panel, _("From address"), true);
-               _kdm_from = new wxTextCtrl (panel, wxID_ANY);
+
+               add_label_to_sizer (table, _panel, _("From address"), true);
+               _kdm_from = new wxTextCtrl (_panel, wxID_ANY);
                table->Add (_kdm_from, 1, wxEXPAND | wxALL);
 
-               add_label_to_sizer (table, panel, _("CC address"), true);
-               _kdm_cc = new wxTextCtrl (panel, wxID_ANY);
+               vector<string> columns;
+               columns.push_back (wx_to_std (_("Address")));
+               add_label_to_sizer (table, _panel, _("CC addresses"), true);
+               _kdm_cc = new EditableList<string, EmailDialog> (
+                       _panel,
+                       columns,
+                       bind (&Config::kdm_cc, Config::instance()),
+                       bind (&Config::set_kdm_cc, Config::instance(), _1),
+                       bind (&column, _1)
+                       );
                table->Add (_kdm_cc, 1, wxEXPAND | wxALL);
 
-               add_label_to_sizer (table, panel, _("BCC address"), true);
-               _kdm_bcc = new wxTextCtrl (panel, wxID_ANY);
+               add_label_to_sizer (table, _panel, _("BCC address"), true);
+               _kdm_bcc = new wxTextCtrl (_panel, wxID_ANY);
                table->Add (_kdm_bcc, 1, wxEXPAND | wxALL);
-               
-               _kdm_email = new wxTextCtrl (panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (480, 128), wxTE_MULTILINE);
-               s->Add (_kdm_email, 1, wxEXPAND | wxALL, _border);
 
-               _reset_kdm_email = new wxButton (panel, wxID_ANY, _("Reset to default text"));
-               s->Add (_reset_kdm_email, 0, wxEXPAND | wxALL, _border);
+               _kdm_email = new wxTextCtrl (_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (-1, 200), wxTE_MULTILINE);
+               _panel->GetSizer()->Add (_kdm_email, 0, wxEXPAND | wxALL, _border);
+
+               _reset_kdm_email = new wxButton (_panel, wxID_ANY, _("Reset to default subject and text"));
+               _panel->GetSizer()->Add (_reset_kdm_email, 0, wxEXPAND | wxALL, _border);
+
+               _kdm_cc->layout ();
+
+               _mail_server->Bind (wxEVT_TEXT, boost::bind (&KDMEmailPage::mail_server_changed, this));
+               _mail_port->Bind (wxEVT_SPINCTRL, boost::bind (&KDMEmailPage::mail_port_changed, this));
+               _mail_user->Bind (wxEVT_TEXT, boost::bind (&KDMEmailPage::mail_user_changed, this));
+               _mail_password->Bind (wxEVT_TEXT, boost::bind (&KDMEmailPage::mail_password_changed, this));
+               _kdm_subject->Bind (wxEVT_TEXT, boost::bind (&KDMEmailPage::kdm_subject_changed, this));
+               _kdm_from->Bind (wxEVT_TEXT, boost::bind (&KDMEmailPage::kdm_from_changed, this));
+               _kdm_bcc->Bind (wxEVT_TEXT, boost::bind (&KDMEmailPage::kdm_bcc_changed, this));
+               _kdm_email->Bind (wxEVT_TEXT, boost::bind (&KDMEmailPage::kdm_email_changed, this));
+               _reset_kdm_email->Bind (wxEVT_BUTTON, boost::bind (&KDMEmailPage::reset_kdm_email, this));
+       }
 
+       void config_changed ()
+       {
                Config* config = Config::instance ();
-               _mail_server->SetValue (std_to_wx (config->mail_server ()));
-               _mail_server->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&KDMEmailPage::mail_server_changed, this));
-               _mail_user->SetValue (std_to_wx (config->mail_user ()));
-               _mail_user->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&KDMEmailPage::mail_user_changed, this));
-               _mail_password->SetValue (std_to_wx (config->mail_password ()));
-               _mail_password->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&KDMEmailPage::mail_password_changed, this));
-               _kdm_subject->SetValue (std_to_wx (config->kdm_subject ()));
-               _kdm_subject->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&KDMEmailPage::kdm_subject_changed, this));
-               _kdm_from->SetValue (std_to_wx (config->kdm_from ()));
-               _kdm_from->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&KDMEmailPage::kdm_from_changed, this));
-               _kdm_cc->SetValue (std_to_wx (config->kdm_cc ()));
-               _kdm_cc->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&KDMEmailPage::kdm_cc_changed, this));
-               _kdm_bcc->SetValue (std_to_wx (config->kdm_bcc ()));
-               _kdm_bcc->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&KDMEmailPage::kdm_bcc_changed, this));
-               _kdm_email->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&KDMEmailPage::kdm_email_changed, this));
-               _kdm_email->SetValue (std_to_wx (Config::instance()->kdm_email ()));
-               _reset_kdm_email->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&KDMEmailPage::reset_kdm_email, this));
-
-               return panel;
+
+               checked_set (_mail_server, config->mail_server ());
+               checked_set (_mail_port, config->mail_port ());
+               checked_set (_mail_user, config->mail_user ());
+               checked_set (_mail_password, config->mail_password ());
+               checked_set (_kdm_subject, config->kdm_subject ());
+               checked_set (_kdm_from, config->kdm_from ());
+               checked_set (_kdm_bcc, config->kdm_bcc ());
+               checked_set (_kdm_email, Config::instance()->kdm_email ());
        }
 
-private:
        void mail_server_changed ()
        {
                Config::instance()->set_mail_server (wx_to_std (_mail_server->GetValue ()));
        }
-       
+
+       void mail_port_changed ()
+       {
+               Config::instance()->set_mail_port (_mail_port->GetValue ());
+       }
+
        void mail_user_changed ()
        {
                Config::instance()->set_mail_user (wx_to_std (_mail_user->GetValue ()));
        }
-       
+
        void mail_password_changed ()
        {
                Config::instance()->set_mail_password (wx_to_std (_mail_password->GetValue ()));
@@ -1059,113 +1574,259 @@ private:
        {
                Config::instance()->set_kdm_subject (wx_to_std (_kdm_subject->GetValue ()));
        }
-       
+
        void kdm_from_changed ()
        {
                Config::instance()->set_kdm_from (wx_to_std (_kdm_from->GetValue ()));
        }
 
-       void kdm_cc_changed ()
-       {
-               Config::instance()->set_kdm_cc (wx_to_std (_kdm_cc->GetValue ()));
-       }
-
        void kdm_bcc_changed ()
        {
                Config::instance()->set_kdm_bcc (wx_to_std (_kdm_bcc->GetValue ()));
        }
-       
+
        void kdm_email_changed ()
        {
+               if (_kdm_email->GetValue().IsEmpty ()) {
+                       /* Sometimes we get sent an erroneous notification that the email
+                          is empty; I don't know why.
+                       */
+                       return;
+               }
                Config::instance()->set_kdm_email (wx_to_std (_kdm_email->GetValue ()));
        }
 
        void reset_kdm_email ()
        {
                Config::instance()->reset_kdm_email ();
-               _kdm_email->SetValue (wx_to_std (Config::instance()->kdm_email ()));
+               checked_set (_kdm_email, Config::instance()->kdm_email ());
        }
 
        wxTextCtrl* _mail_server;
+       wxSpinCtrl* _mail_port;
        wxTextCtrl* _mail_user;
        wxTextCtrl* _mail_password;
        wxTextCtrl* _kdm_subject;
        wxTextCtrl* _kdm_from;
-       wxTextCtrl* _kdm_cc;
+       EditableList<string, EmailDialog>* _kdm_cc;
        wxTextCtrl* _kdm_bcc;
        wxTextCtrl* _kdm_email;
        wxButton* _reset_kdm_email;
 };
 
+class CoverSheetPage : public StandardPage
+{
+public:
+
+       CoverSheetPage (wxSize panel_size, int border)
+#ifdef DCPOMATIC_OSX
+               /* We have to force both width and height of this one */
+               : StandardPage (wxSize (480, 128), border)
+#else
+               : StandardPage (panel_size, border)
+#endif
+       {}
+
+       wxString GetName () const
+       {
+               return _("Cover Sheet");
+       }
+
+#ifdef DCPOMATIC_OSX
+       wxBitmap GetLargeIcon () const
+       {
+               return wxBitmap ("cover_sheet", wxBITMAP_TYPE_PNG_RESOURCE);
+       }
+#endif
+
+private:
+       void setup ()
+       {
+               _cover_sheet = new wxTextCtrl (_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (-1, 200), wxTE_MULTILINE);
+               _panel->GetSizer()->Add (_cover_sheet, 0, wxEXPAND | wxALL, _border);
+
+               _reset_cover_sheet = new wxButton (_panel, wxID_ANY, _("Reset to default text"));
+               _panel->GetSizer()->Add (_reset_cover_sheet, 0, wxEXPAND | wxALL, _border);
+
+               _cover_sheet->Bind (wxEVT_TEXT, boost::bind (&CoverSheetPage::cover_sheet_changed, this));
+               _reset_cover_sheet->Bind (wxEVT_BUTTON, boost::bind (&CoverSheetPage::reset_cover_sheet, this));
+       }
+
+       void config_changed ()
+       {
+               checked_set (_cover_sheet, Config::instance()->cover_sheet ());
+       }
+
+       void cover_sheet_changed ()
+       {
+               if (_cover_sheet->GetValue().IsEmpty ()) {
+                       /* Sometimes we get sent an erroneous notification that the cover sheet
+                          is empty; I don't know why.
+                       */
+                       return;
+               }
+               Config::instance()->set_cover_sheet (wx_to_std (_cover_sheet->GetValue ()));
+       }
+
+       void reset_cover_sheet ()
+       {
+               Config::instance()->reset_cover_sheet ();
+               checked_set (_cover_sheet, Config::instance()->cover_sheet ());
+       }
+
+       wxTextCtrl* _cover_sheet;
+       wxButton* _reset_cover_sheet;
+};
+
+
 /** @class AdvancedPage
  *  @brief Advanced page of the preferences dialog.
  */
-class AdvancedPage : public wxStockPreferencesPage, public Page
+class AdvancedPage : public StockPage
 {
 public:
-
        AdvancedPage (wxSize panel_size, int border)
-               : wxStockPreferencesPage (Kind_Advanced)
-               , Page (panel_size, border)
+               : StockPage (Kind_Advanced, panel_size, border)
+               , _maximum_j2k_bandwidth (0)
+               , _allow_any_dcp_frame_rate (0)
+               , _only_servers_encode (0)
+               , _log_general (0)
+               , _log_warning (0)
+               , _log_error (0)
+               , _log_timing (0)
+               , _log_debug_decode (0)
+               , _log_debug_encode (0)
+               , _log_debug_email (0)
        {}
-       
-       wxWindow* CreateWindow (wxWindow* parent)
-       {
-               wxPanel* panel = new wxPanel (parent);
 
-               wxBoxSizer* s = new wxBoxSizer (wxVERTICAL);
-               panel->SetSizer (s);
+private:
+       void add_top_aligned_label_to_sizer (wxSizer* table, wxWindow* parent, wxString text)
+       {
+               int flags = wxALIGN_TOP | wxTOP | wxLEFT;
+#ifdef __WXOSX__
+               flags |= wxALIGN_RIGHT;
+               text += wxT (":");
+#endif
+               wxStaticText* m = new wxStaticText (parent, wxID_ANY, text);
+               table->Add (m, 0, flags, DCPOMATIC_SIZER_Y_GAP);
+       }
 
+       void setup ()
+       {
                wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
                table->AddGrowableCol (1, 1);
-               s->Add (table, 1, wxALL | wxEXPAND, _border);
+               _panel->GetSizer()->Add (table, 1, wxALL | wxEXPAND, _border);
 
                {
-                       add_label_to_sizer (table, panel, _("Maximum JPEG2000 bandwidth"), true);
+                       add_label_to_sizer (table, _panel, _("Maximum JPEG2000 bandwidth"), true);
                        wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
-                       _maximum_j2k_bandwidth = new wxSpinCtrl (panel);
+                       _maximum_j2k_bandwidth = new wxSpinCtrl (_panel);
                        s->Add (_maximum_j2k_bandwidth, 1);
-                       add_label_to_sizer (s, panel, _("Mbit/s"), false);
+                       add_label_to_sizer (s, _panel, _("Mbit/s"), false);
                        table->Add (s, 1);
                }
 
-               _allow_any_dcp_frame_rate = new wxCheckBox (panel, wxID_ANY, _("Allow any DCP frame rate"));
+               _allow_any_dcp_frame_rate = new wxCheckBox (_panel, wxID_ANY, _("Allow any DCP frame rate"));
                table->Add (_allow_any_dcp_frame_rate, 1, wxEXPAND | wxALL);
                table->AddSpacer (0);
 
-               add_label_to_sizer (table, panel, _("Log"), true);
-               _log_general = new wxCheckBox (panel, wxID_ANY, _("General"));
-               table->Add (_log_general, 1, wxEXPAND | wxALL);
-               _log_warning = new wxCheckBox (panel, wxID_ANY, _("Warnings"));
+               _only_servers_encode = new wxCheckBox (_panel, wxID_ANY, _("Only servers encode"));
+               table->Add (_only_servers_encode, 1, wxEXPAND | wxALL);
                table->AddSpacer (0);
-               table->Add (_log_warning, 1, wxEXPAND | wxALL);
-               _log_error = new wxCheckBox (panel, wxID_ANY, _("Errors"));
-               table->AddSpacer (0);
-               table->Add (_log_error, 1, wxEXPAND | wxALL);
-               _log_timing = new wxCheckBox (panel, wxID_ANY, S_("Config|Timing"));
+
+               {
+                       add_top_aligned_label_to_sizer (table, _panel, _("DCP metadata filename format"));
+                       dcp::NameFormat::Map titles;
+                       titles['t'] = "type (cpl/pkl)";
+                       dcp::NameFormat::Map examples;
+                       examples['t'] = "cpl";
+                       _dcp_metadata_filename_format = new NameFormatEditor (
+                               _panel, Config::instance()->dcp_metadata_filename_format(), titles, examples, "_eb1c112c-ca3c-4ae6-9263-c6714ff05d64.xml"
+                               );
+                       table->Add (_dcp_metadata_filename_format->panel(), 1, wxEXPAND | wxALL);
+               }
+
+               {
+                       add_top_aligned_label_to_sizer (table, _panel, _("DCP asset filename format"));
+                       dcp::NameFormat::Map titles;
+                       titles['t'] = "type (j2c/pcm/sub)";
+                       titles['r'] = "reel number";
+                       titles['n'] = "number of reels";
+                       titles['c'] = "content filename";
+                       dcp::NameFormat::Map examples;
+                       examples['t'] = "j2c";
+                       examples['r'] = "1";
+                       examples['n'] = "4";
+                       examples['c'] = "myfile.mp4";
+                       _dcp_asset_filename_format = new NameFormatEditor (
+                               _panel, Config::instance()->dcp_asset_filename_format(), titles, examples, "_eb1c112c-ca3c-4ae6-9263-c6714ff05d64.mxf"
+                               );
+                       table->Add (_dcp_asset_filename_format->panel(), 1, wxEXPAND | wxALL);
+               }
+
+               {
+                       add_top_aligned_label_to_sizer (table, _panel, _("Log"));
+                       wxBoxSizer* t = new wxBoxSizer (wxVERTICAL);
+                       _log_general = new wxCheckBox (_panel, wxID_ANY, _("General"));
+                       t->Add (_log_general, 1, wxEXPAND | wxALL);
+                       _log_warning = new wxCheckBox (_panel, wxID_ANY, _("Warnings"));
+                       t->Add (_log_warning, 1, wxEXPAND | wxALL);
+                       _log_error = new wxCheckBox (_panel, wxID_ANY, _("Errors"));
+                       t->Add (_log_error, 1, wxEXPAND | wxALL);
+                       /// TRANSLATORS: translate the word "Timing" here; do not include the "Config|" prefix
+                       _log_timing = new wxCheckBox (_panel, wxID_ANY, S_("Config|Timing"));
+                       t->Add (_log_timing, 1, wxEXPAND | wxALL);
+                       _log_debug_decode = new wxCheckBox (_panel, wxID_ANY, _("Debug: decode"));
+                       t->Add (_log_debug_decode, 1, wxEXPAND | wxALL);
+                       _log_debug_encode = new wxCheckBox (_panel, wxID_ANY, _("Debug: encode"));
+                       t->Add (_log_debug_encode, 1, wxEXPAND | wxALL);
+                       _log_debug_email = new wxCheckBox (_panel, wxID_ANY, _("Debug: email sending"));
+                       t->Add (_log_debug_email, 1, wxEXPAND | wxALL);
+                       table->Add (t, 0, wxALL, 6);
+               }
+
+#ifdef DCPOMATIC_WINDOWS
+               _win32_console = new wxCheckBox (_panel, wxID_ANY, _("Open console window"));
+               table->Add (_win32_console, 1, wxEXPAND | wxALL);
                table->AddSpacer (0);
-               table->Add (_log_timing, 1, wxEXPAND | wxALL);
+#endif
 
-               Config* config = Config::instance ();
-               
-               _maximum_j2k_bandwidth->SetRange (1, 500);
-               _maximum_j2k_bandwidth->SetValue (config->maximum_j2k_bandwidth() / 1000000);
-               _maximum_j2k_bandwidth->Bind (wxEVT_COMMAND_SPINCTRL_UPDATED, boost::bind (&AdvancedPage::maximum_j2k_bandwidth_changed, this));
-               _allow_any_dcp_frame_rate->SetValue (config->allow_any_dcp_frame_rate ());
-               _allow_any_dcp_frame_rate->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&AdvancedPage::allow_any_dcp_frame_rate_changed, this));
-               _log_general->SetValue (config->log_types() & Log::TYPE_GENERAL);
-               _log_general->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&AdvancedPage::log_changed, this));
-               _log_warning->SetValue (config->log_types() & Log::TYPE_WARNING);
-               _log_warning->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&AdvancedPage::log_changed, this));
-               _log_error->SetValue (config->log_types() & Log::TYPE_ERROR);
-               _log_error->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&AdvancedPage::log_changed, this));
-               _log_timing->SetValue (config->log_types() & Log::TYPE_TIMING);
-               _log_timing->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&AdvancedPage::log_changed, this));
-               
-               return panel;
+               _maximum_j2k_bandwidth->SetRange (1, 1000);
+               _maximum_j2k_bandwidth->Bind (wxEVT_SPINCTRL, boost::bind (&AdvancedPage::maximum_j2k_bandwidth_changed, this));
+               _allow_any_dcp_frame_rate->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::allow_any_dcp_frame_rate_changed, this));
+               _only_servers_encode->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::only_servers_encode_changed, this));
+               _dcp_metadata_filename_format->Changed.connect (boost::bind (&AdvancedPage::dcp_metadata_filename_format_changed, this));
+               _dcp_asset_filename_format->Changed.connect (boost::bind (&AdvancedPage::dcp_asset_filename_format_changed, this));
+               _log_general->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::log_changed, this));
+               _log_warning->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::log_changed, this));
+               _log_error->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::log_changed, this));
+               _log_timing->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::log_changed, this));
+               _log_debug_decode->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::log_changed, this));
+               _log_debug_encode->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::log_changed, this));
+               _log_debug_email->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::log_changed, this));
+#ifdef DCPOMATIC_WINDOWS
+               _win32_console->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::win32_console_changed, this));
+#endif
        }
 
-private:
+       void config_changed ()
+       {
+               Config* config = Config::instance ();
+
+               checked_set (_maximum_j2k_bandwidth, config->maximum_j2k_bandwidth() / 1000000);
+               checked_set (_allow_any_dcp_frame_rate, config->allow_any_dcp_frame_rate ());
+               checked_set (_only_servers_encode, config->only_servers_encode ());
+               checked_set (_log_general, config->log_types() & LogEntry::TYPE_GENERAL);
+               checked_set (_log_warning, config->log_types() & LogEntry::TYPE_WARNING);
+               checked_set (_log_error, config->log_types() & LogEntry::TYPE_ERROR);
+               checked_set (_log_timing, config->log_types() & LogEntry::TYPE_TIMING);
+               checked_set (_log_debug_decode, config->log_types() & LogEntry::TYPE_DEBUG_DECODE);
+               checked_set (_log_debug_encode, config->log_types() & LogEntry::TYPE_DEBUG_ENCODE);
+               checked_set (_log_debug_email, config->log_types() & LogEntry::TYPE_DEBUG_EMAIL);
+#ifdef DCPOMATIC_WINDOWS
+               checked_set (_win32_console, config->win32_console());
+#endif
+       }
 
        void maximum_j2k_bandwidth_changed ()
        {
@@ -1177,32 +1838,72 @@ private:
                Config::instance()->set_allow_any_dcp_frame_rate (_allow_any_dcp_frame_rate->GetValue ());
        }
 
+       void only_servers_encode_changed ()
+       {
+               Config::instance()->set_only_servers_encode (_only_servers_encode->GetValue ());
+       }
+
+       void dcp_metadata_filename_format_changed ()
+       {
+               Config::instance()->set_dcp_metadata_filename_format (_dcp_metadata_filename_format->get ());
+       }
+
+       void dcp_asset_filename_format_changed ()
+       {
+               Config::instance()->set_dcp_asset_filename_format (_dcp_asset_filename_format->get ());
+       }
+
        void log_changed ()
        {
                int types = 0;
                if (_log_general->GetValue ()) {
-                       types |= Log::TYPE_GENERAL;
+                       types |= LogEntry::TYPE_GENERAL;
                }
                if (_log_warning->GetValue ()) {
-                       types |= Log::TYPE_WARNING;
+                       types |= LogEntry::TYPE_WARNING;
                }
                if (_log_error->GetValue ())  {
-                       types |= Log::TYPE_ERROR;
+                       types |= LogEntry::TYPE_ERROR;
                }
                if (_log_timing->GetValue ()) {
-                       types |= Log::TYPE_TIMING;
+                       types |= LogEntry::TYPE_TIMING;
+               }
+               if (_log_debug_decode->GetValue ()) {
+                       types |= LogEntry::TYPE_DEBUG_DECODE;
+               }
+               if (_log_debug_encode->GetValue ()) {
+                       types |= LogEntry::TYPE_DEBUG_ENCODE;
+               }
+               if (_log_debug_email->GetValue ()) {
+                       types |= LogEntry::TYPE_DEBUG_EMAIL;
                }
                Config::instance()->set_log_types (types);
        }
-       
+
+#ifdef DCPOMATIC_WINDOWS
+       void win32_console_changed ()
+       {
+               Config::instance()->set_win32_console (_win32_console->GetValue ());
+       }
+#endif
+
        wxSpinCtrl* _maximum_j2k_bandwidth;
        wxCheckBox* _allow_any_dcp_frame_rate;
+       wxCheckBox* _only_servers_encode;
+       NameFormatEditor* _dcp_metadata_filename_format;
+       NameFormatEditor* _dcp_asset_filename_format;
        wxCheckBox* _log_general;
        wxCheckBox* _log_warning;
        wxCheckBox* _log_error;
        wxCheckBox* _log_timing;
+       wxCheckBox* _log_debug_decode;
+       wxCheckBox* _log_debug_encode;
+       wxCheckBox* _log_debug_email;
+#ifdef DCPOMATIC_WINDOWS
+       wxCheckBox* _win32_console;
+#endif
 };
-       
+
 wxPreferencesEditor*
 create_config_dialog ()
 {
@@ -1213,20 +1914,20 @@ create_config_dialog ()
           the containing window doesn't shrink too much when we select those panels.
           This is obviously an unpleasant hack.
        */
-       wxSize ps = wxSize (480, -1);
+       wxSize ps = wxSize (520, -1);
        int const border = 16;
 #else
        wxSize ps = wxSize (-1, -1);
        int const border = 8;
 #endif
-       
+
        e->AddPage (new GeneralPage (ps, border));
        e->AddPage (new DefaultsPage (ps, border));
        e->AddPage (new EncodingServersPage (ps, border));
-       e->AddPage (new ColourConversionsPage (ps, border));
        e->AddPage (new KeysPage (ps, border));
        e->AddPage (new TMSPage (ps, border));
        e->AddPage (new KDMEmailPage (ps, border));
+       e->AddPage (new CoverSheetPage (ps, border));
        e->AddPage (new AdvancedPage (ps, border));
        return e;
 }