Change bitmap_path to take a full name with extension.
[dcpomatic.git] / src / wx / full_config_dialog.cc
index 8007e6bdaaadee21aa8aa169baced9787d494a64..bb804cae876c837951e387db26de934118c5a9be 100644 (file)
@@ -1,5 +1,5 @@
 /*
-    Copyright (C) 2012-2017 Carl Hetherington <cth@carlh.net>
+    Copyright (C) 2012-2021 Carl Hetherington <cth@carlh.net>
 
     This file is part of DCP-o-matic.
 
 
 */
 
+
 /** @file src/full_config_dialog.cc
  *  @brief A dialogue to edit all DCP-o-matic configuration.
  */
 
-#include "full_config_dialog.h"
-#include "wx_util.h"
-#include "editable_list.h"
-#include "filter_dialog.h"
+
+#include "check_box.h"
+#include "config_dialog.h"
+#include "config_move_dialog.h"
+#include "dcpomatic_button.h"
 #include "dir_picker_ctrl.h"
+#include "editable_list.h"
+#include "email_dialog.h"
 #include "file_picker_ctrl.h"
-#include "isdcf_metadata_dialog.h"
-#include "server_dialog.h"
+#include "filter_dialog.h"
+#include "full_config_dialog.h"
+#include "kdm_choice.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 "name_format_editor.h"
+#include "password_entry.h"
+#include "send_test_email_dialog.h"
+#include "server_dialog.h"
+#include "static_text.h"
+#include "wx_util.h"
 #include "lib/config.h"
-#include "lib/ratio.h"
-#include "lib/filter.h"
+#include "lib/cross.h"
 #include "lib/dcp_content_type.h"
+#include "lib/emailer.h"
+#include "lib/exceptions.h"
+#include "lib/filter.h"
 #include "lib/log.h"
+#include "lib/ratio.h"
 #include "lib/util.h"
-#include "lib/cross.h"
-#include "lib/exceptions.h"
-#include <dcp/locale_convert.h>
-#include <dcp/exceptions.h>
 #include <dcp/certificate_chain.h>
-#include <wx/stdpaths.h>
+#include <dcp/exceptions.h>
+#include <dcp/locale_convert.h>
+#include <dcp/warnings.h>
+LIBDCP_DISABLE_WARNINGS
+#include <wx/filepicker.h>
 #include <wx/preferences.h>
 #include <wx/spinctrl.h>
-#include <wx/filepicker.h>
+#include <wx/stdpaths.h>
+LIBDCP_ENABLE_WARNINGS
 #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::function;
+using std::list;
 using std::make_pair;
 using std::map;
+using std::pair;
+using std::shared_ptr;
+using std::string;
+using std::vector;
 using boost::bind;
-using boost::shared_ptr;
-using boost::function;
 using boost::optional;
+#if BOOST_VERSION >= 106100
+using namespace boost::placeholders;
+#endif
 using dcp::locale_convert;
 
-static
-void
-do_nothing ()
-{
-
-}
-
-class Page
-{
-public:
-       Page (wxSize panel_size, int 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:
-       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 StockPage : public wxStockPreferencesPage, public Page
-{
-public:
-       StockPage (Kind kind, wxSize panel_size, int border)
-               : wxStockPreferencesPage (kind)
-               , Page (panel_size, border)
-       {}
-
-       wxWindow* CreateWindow (wxWindow* parent)
-       {
-               return create_window (parent);
-       }
-};
-
-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
+class FullGeneralPage : public GeneralPage
 {
 public:
-       GeneralPage (wxSize panel_size, int border)
-               : StockPage (Kind_General, panel_size, border)
+       FullGeneralPage (wxSize panel_size, int border)
+               : GeneralPage (panel_size, border)
        {}
 
 private:
-       void setup ()
+       void setup () override
        {
-               wxGridBagSizer* table = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
+               auto 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);
-               ++r;
+               add_language_controls (table, 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);
@@ -214,113 +111,47 @@ private:
                ++r;
 
                add_label_to_sizer (table, _panel, _("Configuration file"), true, wxGBPosition (r, 0));
-               _config_file = new FilePickerCtrl (_panel, _("Select configuration file"), "*.xml", true);
+               _config_file = new FilePickerCtrl (_panel, _("Select configuration file"), "*.xml", true, false);
                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);
+               _cinemas_file = new FilePickerCtrl (_panel, _("Select cinema and screen database file"), "*.xml", true, false);
                table->Add (_cinemas_file, wxGBPosition (r, 1));
+               Button* export_cinemas = new Button (_panel, _("Export..."));
+               table->Add (export_cinemas, wxGBPosition (r, 2));
                ++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"));
+               _analyse_ebur128 = new CheckBox (_panel, _("Find integrated loudness, true peak and loudness range when analysing audio"));
                table->Add (_analyse_ebur128, wxGBPosition (r, 0), wxGBSpan (1, 2));
                ++r;
 #endif
 
-               _automatic_audio_analysis = new wxCheckBox (_panel, wxID_ANY, _("Automatically analyse content audio"));
+               _automatic_audio_analysis = new CheckBox (_panel, _("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);
+               add_update_controls (table, r);
 
-               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));
+               _config_file->Bind  (wxEVT_FILEPICKER_CHANGED, boost::bind(&FullGeneralPage::config_file_changed,  this));
+               _cinemas_file->Bind (wxEVT_FILEPICKER_CHANGED, boost::bind(&FullGeneralPage::cinemas_file_changed, this));
 
                _master_encoding_threads->SetRange (1, 128);
-               _master_encoding_threads->Bind (wxEVT_SPINCTRL, boost::bind (&GeneralPage::master_encoding_threads_changed, this));
+               _master_encoding_threads->Bind (wxEVT_SPINCTRL, boost::bind (&FullGeneralPage::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));
+               _server_encoding_threads->Bind (wxEVT_SPINCTRL, boost::bind (&FullGeneralPage::server_encoding_threads_changed, this));
+               export_cinemas->Bind (wxEVT_BUTTON, boost::bind (&FullGeneralPage::export_cinemas_file, this));
 
 #ifdef DCPOMATIC_HAVE_EBUR128_PATCHED_FFMPEG
-               _analyse_ebur128->Bind (wxEVT_CHECKBOX, boost::bind (&GeneralPage::analyse_ebur128_changed, this));
+               _analyse_ebur128->Bind (wxEVT_CHECKBOX, boost::bind (&FullGeneralPage::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));
+               _automatic_audio_analysis->Bind (wxEVT_CHECKBOX, boost::bind (&FullGeneralPage::automatic_audio_analysis_changed, this));
        }
 
-       void config_changed ()
+       void config_changed () override
        {
-               Config* config = Config::instance ();
-
-               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];
-               }
-
-               checked_set (_language, lang);
+               auto config = Config::instance ();
 
                checked_set (_master_encoding_threads, config->master_encoding_threads ());
                checked_set (_server_encoding_threads, config->server_encoding_threads ());
@@ -328,134 +159,57 @@ private:
                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 (_config_file, config->config_read_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 ());
+               GeneralPage::config_changed ();
        }
 
-       void set_language_changed ()
+       void export_cinemas_file ()
        {
-               setup_sensitivity ();
-               if (_set_language->GetValue ()) {
-                       language_changed ();
-               } else {
-                       Config::instance()->unset_language ();
-               }
-       }
+               auto d = new wxFileDialog (
+                       _panel, _("Select Cinemas File"), wxEmptyString, wxEmptyString, wxT("XML files (*.xml)|*.xml"),
+                       wxFD_SAVE | wxFD_OVERWRITE_PROMPT
+                );
 
-       void language_changed ()
-       {
-               int const sel = _language->GetSelection ();
-               if (sel != -1) {
-                       Config::instance()->set_language (string_client_data (_language->GetClientObject (sel)));
-               } else {
-                       Config::instance()->unset_language ();
+               if (d->ShowModal () == wxID_OK) {
+                       boost::filesystem::copy_file (Config::instance()->cinemas_file(), wx_to_std(d->GetPath()));
                }
+               d->Destroy ();
        }
 
 #ifdef DCPOMATIC_HAVE_EBUR128_PATCHED_FFMPEG
        void analyse_ebur128_changed ()
        {
-               Config::instance()->set_analyse_ebur128 (_analyse_ebur128->GetValue ());
+               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 ());
+               Config::instance()->set_automatic_audio_analysis (_automatic_audio_analysis->GetValue());
        }
 
        void master_encoding_threads_changed ()
        {
-               Config::instance()->set_master_encoding_threads (_master_encoding_threads->GetValue ());
+               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 ()));
+               Config::instance()->set_server_encoding_threads (_server_encoding_threads->GetValue());
        }
 
        void config_file_changed ()
        {
-               Config* config = Config::instance();
+               auto config = Config::instance();
                boost::filesystem::path new_file = wx_to_std(_config_file->GetPath());
-               if (new_file == config->config_file()) {
+               if (new_file == config->config_read_file()) {
                        return;
                }
                bool copy_and_link = true;
                if (boost::filesystem::exists(new_file)) {
-                       ConfigMoveDialog* d = new ConfigMoveDialog (_panel, new_file);
+                       auto d = new ConfigMoveDialog (_panel, new_file);
                        if (d->ShowModal() == wxID_OK) {
                                copy_and_link = false;
                        }
@@ -464,7 +218,7 @@ private:
 
                if (copy_and_link) {
                        config->write ();
-                       if (new_file != config->config_file()) {
+                       if (new_file != config->config_read_file()) {
                                config->copy_and_link (new_file);
                        }
                } else {
@@ -477,76 +231,53 @@ private:
                Config::instance()->set_cinemas_file (wx_to_std (_cinemas_file->GetPath ()));
        }
 
-       void preview_sound_changed ()
-       {
-               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* _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 StandardPage
+
+class DefaultsPage : public Page
 {
 public:
        DefaultsPage (wxSize panel_size, int border)
-               : StandardPage (panel_size, border)
+               : Page (panel_size, border)
        {}
 
-       wxString GetName () const
+       wxString GetName () const override
        {
                return _("Defaults");
        }
 
 #ifdef DCPOMATIC_OSX
-       wxBitmap GetLargeIcon () const
+       wxBitmap GetLargeIcon () const override
        {
-               return wxBitmap ("defaults", wxBITMAP_TYPE_PNG_RESOURCE);
+               return wxBitmap(bitmap_path("defaults.png"), wxBITMAP_TYPE_PNG);
        }
 #endif
 
 private:
-       void setup ()
+       void setup () override
        {
-               wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
+               auto table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
                table->AddGrowableCol (1, 1);
                _panel->GetSizer()->Add (table, 1, wxALL | wxEXPAND, _border);
 
                {
-                       add_label_to_sizer (table, _panel, _("Default duration of still images"), true);
-                       wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
+                       add_label_to_sizer (table, _panel, _("Default duration of still images"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
+                       auto s = new wxBoxSizer (wxHORIZONTAL);
                        _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, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
                        table->Add (s, 1);
                }
 
-               add_label_to_sizer (table, _panel, _("Default directory for new films"), true);
+               add_label_to_sizer (table, _panel, _("Default directory for new films"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
 #ifdef DCPOMATIC_USE_OWN_PICKER
                _directory = new DirPickerCtrl (_panel);
 #else
@@ -554,49 +285,53 @@ private:
 #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..."));
-               table->Add (_isdcf_metadata_button);
-
-               add_label_to_sizer (table, _panel, _("Default container"), true);
+               add_label_to_sizer (table, _panel, _("Default container"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
                _container = new wxChoice (_panel, wxID_ANY);
                table->Add (_container);
 
-               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);
+               add_label_to_sizer (table, _panel, _("Default content type"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
                _dcp_content_type = new wxChoice (_panel, wxID_ANY);
                table->Add (_dcp_content_type);
 
-               add_label_to_sizer (table, _panel, _("Default DCP audio channels"), true);
+               add_label_to_sizer (table, _panel, _("Default DCP audio channels"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
                _dcp_audio_channels = new wxChoice (_panel, wxID_ANY);
                table->Add (_dcp_audio_channels);
 
                {
-                       add_label_to_sizer (table, _panel, _("Default JPEG2000 bandwidth"), true);
-                       wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
+                       add_label_to_sizer (table, _panel, _("Default JPEG2000 bandwidth"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
+                       auto s = new wxBoxSizer (wxHORIZONTAL);
                        _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, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
                        table->Add (s, 1);
                }
 
                {
-                       add_label_to_sizer (table, _panel, _("Default audio delay"), true);
-                       wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
+                       add_label_to_sizer (table, _panel, _("Default audio delay"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
+                       auto s = new wxBoxSizer (wxHORIZONTAL);
                        _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, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
                        table->Add (s, 1);
                }
 
-               add_label_to_sizer (table, _panel, _("Default standard"), true);
+               add_label_to_sizer (table, _panel, _("Default standard"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
                _standard = new wxChoice (_panel, wxID_ANY);
                table->Add (_standard);
 
-               add_label_to_sizer (table, _panel, _("Default KDM directory"), true);
+               table->Add (_enable_metadata["facility"] = new CheckBox (_panel, _("Default facility")), 0, wxALIGN_CENTRE_VERTICAL);
+               table->Add (_metadata["facility"] = new wxTextCtrl (_panel, wxID_ANY, wxT("")), 0, wxEXPAND);
+
+               table->Add (_enable_metadata["studio"] = new CheckBox (_panel, _("Default studio")), 0, wxALIGN_CENTRE_VERTICAL);
+               table->Add (_metadata["studio"] = new wxTextCtrl (_panel, wxID_ANY, wxT("")), 0, wxEXPAND);
+
+               table->Add (_enable_metadata["chain"] = new CheckBox (_panel, _("Default chain")), 0, wxALIGN_CENTRE_VERTICAL);
+               table->Add (_metadata["chain"] = new wxTextCtrl (_panel, wxID_ANY, wxT("")), 0, wxEXPAND);
+
+               table->Add (_enable_metadata["distributor"] = new CheckBox (_panel, _("Default distributor")), 0, wxALIGN_CENTRE_VERTICAL);
+               table->Add (_metadata["distributor"] = new wxTextCtrl (_panel, wxID_ANY, wxT("")), 0, wxEXPAND);
+
+               add_label_to_sizer (table, _panel, _("Default KDM directory"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
 #ifdef DCPOMATIC_USE_OWN_PICKER
                _kdm_directory = new DirPickerCtrl (_panel);
 #else
@@ -604,29 +339,43 @@ private:
 #endif
                table->Add (_kdm_directory, 1, wxEXPAND);
 
+               add_label_to_sizer (table, _panel, _("Default KDM type"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
+               _kdm_type = new KDMChoice (_panel);
+               table->Add (_kdm_type, 1, wxEXPAND);
+
+               add_label_to_sizer (table, _panel, _("Default KDM duration"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
+               _kdm_duration = new wxSpinCtrl (_panel);
+               _kdm_duration_unit = new wxChoice (_panel, wxID_ANY);
+               auto kdm_duration_sizer = new wxBoxSizer (wxHORIZONTAL);
+               kdm_duration_sizer->Add (_kdm_duration, 0, wxEXPAND | wxRIGHT, DCPOMATIC_SIZER_GAP);
+               kdm_duration_sizer->Add (_kdm_duration_unit, 0, wxEXPAND | wxRIGHT, DCPOMATIC_SIZER_GAP);
+               table->Add (kdm_duration_sizer, 1, wxEXPAND);
+
+               table->Add (_use_isdcf_name_by_default = new CheckBox(_panel, _("Use ISDCF name by default")), 0, wxALIGN_CENTRE_VERTICAL);
+
                _still_length->SetRange (1, 3600);
                _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));
+               _kdm_type->Bind (wxEVT_CHOICE, boost::bind(&DefaultsPage::kdm_type_changed, this));
+               _kdm_duration_unit->Append (_("days"));
+               _kdm_duration_unit->Append (_("weeks"));
+               _kdm_duration_unit->Append (_("months"));
+               _kdm_duration_unit->Append (_("years"));
 
-               _isdcf_metadata_button->Bind (wxEVT_BUTTON, boost::bind (&DefaultsPage::edit_isdcf_metadata_clicked, this));
+               _kdm_duration->Bind (wxEVT_SPINCTRL, boost::bind(&DefaultsPage::kdm_duration_changed, this));
+               _kdm_duration_unit->Bind (wxEVT_CHOICE, boost::bind(&DefaultsPage::kdm_duration_changed, this));
 
-               BOOST_FOREACH (Ratio const * i, Ratio::containers()) {
+               _use_isdcf_name_by_default->Bind (wxEVT_CHECKBOX, boost::bind(&DefaultsPage::use_isdcf_name_by_default_changed, this));
+
+               for (auto 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()) {
+               for (auto i: DCPContentType::all()) {
                        _dcp_content_type->Append (std_to_wx (i->pretty_name ()));
                }
 
@@ -644,33 +393,30 @@ private:
                _standard->Append (_("SMPTE"));
                _standard->Append (_("Interop"));
                _standard->Bind (wxEVT_CHOICE, boost::bind (&DefaultsPage::standard_changed, this));
+
+               for (auto const& i: _enable_metadata) {
+                       i.second->Bind (wxEVT_CHECKBOX, boost::bind(&DefaultsPage::metadata_changed, this));
+               }
+
+               for (auto const& i: _metadata) {
+                       i.second->Bind (wxEVT_TEXT, boost::bind(&DefaultsPage::metadata_changed, this));
+               }
        }
 
-       void config_changed ()
+       void config_changed () override
        {
-               Config* config = Config::instance ();
+               auto config = Config::instance ();
 
-               vector<Ratio const *> containers = Ratio::containers ();
+               auto containers = Ratio::containers ();
                for (size_t i = 0; i < containers.size(); ++i) {
-                       if (containers[i] == config->default_container ()) {
+                       if (containers[i] == config->default_container()) {
                                _container->SetSelection (i);
                        }
                }
 
-               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);
-                       }
-               }
-
-               if (!config->default_scale_to()) {
-                       _scale_to->SetSelection (0);
-               }
-
-               vector<DCPContentType const *> const ct = DCPContentType::all ();
+               auto const ct = DCPContentType::all ();
                for (size_t i = 0; i < ct.size(); ++i) {
-                       if (ct[i] == config->default_dcp_content_type ()) {
+                       if (ct[i] == config->default_dcp_content_type()) {
                                _dcp_content_type->SetSelection (i);
                        }
                }
@@ -678,11 +424,76 @@ private:
                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 ()));
+               _kdm_type->set (config->default_kdm_type());
+               checked_set (_use_isdcf_name_by_default, config->use_isdcf_name_by_default());
                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);
+
+               auto metadata = config->default_metadata();
+
+               for (auto const& i: metadata) {
+                       _enable_metadata[i.first]->SetValue(true);
+                       checked_set (_metadata[i.first], i.second);
+               }
+
+               for (auto const& i: _enable_metadata) {
+                       if (metadata.find(i.first) == metadata.end()) {
+                               checked_set (i.second, false);
+                       }
+               }
+
+               for (auto const& i: _metadata) {
+                       if (metadata.find(i.first) == metadata.end()) {
+                               checked_set (i.second, wxT(""));
+                       }
+               }
+
+               checked_set (_kdm_duration, config->default_kdm_duration().duration);
+               switch (config->default_kdm_duration().unit) {
+                       case RoughDuration::Unit::DAYS:
+                               _kdm_duration->SetRange(1, 365);
+                               checked_set (_kdm_duration_unit, 0);
+                               break;
+                       case RoughDuration::Unit::WEEKS:
+                               _kdm_duration->SetRange(1, 52);
+                               checked_set (_kdm_duration_unit, 1);
+                               break;
+                       case RoughDuration::Unit::MONTHS:
+                               _kdm_duration->SetRange(1, 12);
+                               checked_set (_kdm_duration_unit, 2);
+                               break;
+                       case RoughDuration::Unit::YEARS:
+                               _kdm_duration->SetRange(1, 40);
+                               checked_set (_kdm_duration_unit, 3);
+                               break;
+               }
+
+               setup_sensitivity ();
+       }
+
+       void kdm_duration_changed ()
+       {
+               auto config = Config::instance();
+               auto duration = _kdm_duration->GetValue();
+               RoughDuration::Unit unit = RoughDuration::Unit::DAYS;
+               switch (_kdm_duration_unit->GetSelection()) {
+               case 0:
+                       unit = RoughDuration::Unit::DAYS;
+                       break;
+               case 1:
+                       unit = RoughDuration::Unit::WEEKS;
+                       break;
+               case 2:
+                       unit = RoughDuration::Unit::MONTHS;
+                       break;
+               case 3:
+                       unit = RoughDuration::Unit::YEARS;
+                       break;
+               }
+               config->set_default_kdm_duration (RoughDuration(duration, unit));
        }
 
        void j2k_bandwidth_changed ()
@@ -700,7 +511,7 @@ private:
                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)))
+                               locale_convert<int>(string_client_data(_dcp_audio_channels->GetClientObject(s)))
                                );
                }
        }
@@ -715,12 +526,14 @@ private:
                Config::instance()->set_default_kdm_directory (wx_to_std (_kdm_directory->GetPath ()));
        }
 
-       void edit_isdcf_metadata_clicked ()
+       void kdm_type_changed ()
        {
-               ISDCFMetadataDialog* d = new ISDCFMetadataDialog (_panel, Config::instance()->default_isdcf_metadata (), false);
-               d->ShowModal ();
-               Config::instance()->set_default_isdcf_metadata (d->isdcf_metadata ());
-               d->Destroy ();
+               Config::instance()->set_default_kdm_type(_kdm_type->get());
+       }
+
+       void use_isdcf_name_by_default_changed ()
+       {
+               Config::instance()->set_use_isdcf_name_by_default (_use_isdcf_name_by_default->GetValue());
        }
 
        void still_length_changed ()
@@ -730,24 +543,13 @@ private:
 
        void container_changed ()
        {
-               vector<Ratio const *> ratio = Ratio::containers ();
+               auto 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 ();
+               auto ct = DCPContentType::all ();
                Config::instance()->set_default_dcp_content_type (ct[_dcp_content_type->GetSelection()]);
        }
 
@@ -756,9 +558,27 @@ private:
                Config::instance()->set_default_interop (_standard->GetSelection() == 1);
        }
 
+       void metadata_changed ()
+       {
+               map<string, string> metadata;
+               for (auto const& i: _enable_metadata) {
+                       if (i.second->GetValue()) {
+                               metadata[i.first] = wx_to_std(_metadata[i.first]->GetValue());
+                       }
+               }
+               Config::instance()->set_default_metadata (metadata);
+               setup_sensitivity ();
+       }
+
+       void setup_sensitivity ()
+       {
+               for (auto const& i: _enable_metadata) {
+                       _metadata[i.first]->Enable(i.second->GetValue());
+               }
+       }
+
        wxSpinCtrl* _j2k_bandwidth;
        wxSpinCtrl* _audio_delay;
-       wxButton* _isdcf_metadata_button;
        wxSpinCtrl* _still_length;
 #ifdef DCPOMATIC_USE_OWN_PICKER
        DirPickerCtrl* _directory;
@@ -767,54 +587,62 @@ private:
        wxDirPickerCtrl* _directory;
        wxDirPickerCtrl* _kdm_directory;
 #endif
+       KDMChoice* _kdm_type;
+       wxSpinCtrl* _kdm_duration;
+       wxChoice* _kdm_duration_unit;
+       wxCheckBox* _use_isdcf_name_by_default;
        wxChoice* _container;
-       wxChoice* _scale_to;
        wxChoice* _dcp_content_type;
        wxChoice* _dcp_audio_channels;
        wxChoice* _standard;
+       map<string, CheckBox*> _enable_metadata;
+       map<string, wxTextCtrl*> _metadata;
 };
 
-class EncodingServersPage : public StandardPage
+
+class EncodingServersPage : public Page
 {
 public:
        EncodingServersPage (wxSize panel_size, int border)
-               : StandardPage (panel_size, border)
+               : Page (panel_size, border)
        {}
 
-       wxString GetName () const
+       wxString GetName () const override
        {
                return _("Servers");
        }
 
 #ifdef DCPOMATIC_OSX
-       wxBitmap GetLargeIcon () const
+       wxBitmap GetLargeIcon () const override
        {
-               return wxBitmap ("servers", wxBITMAP_TYPE_PNG_RESOURCE);
+               return wxBitmap(bitmap_path("servers.png"), wxBITMAP_TYPE_PNG);
        }
 #endif
 
 private:
-       void setup ()
+       void setup () override
        {
-               _use_any_servers = new wxCheckBox (_panel, wxID_ANY, _("Search network for servers"));
+               _use_any_servers = new CheckBox (_panel, _("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")));
+               vector<EditableListColumn> columns;
+               columns.push_back (EditableListColumn(_("IP address / host name")));
                _servers_list = new EditableList<string, ServerDialog> (
                        _panel,
                        columns,
                        boost::bind (&Config::servers, Config::instance()),
                        boost::bind (&Config::set_servers, Config::instance(), _1),
-                       boost::bind (&EncodingServersPage::server_column, this, _1)
+                       boost::bind (&EncodingServersPage::server_column, this, _1),
+                       false,
+                       EditableListButton::NEW | EditableListButton::EDIT | EditableListButton::REMOVE
                        );
 
                _panel->GetSizer()->Add (_servers_list, 1, wxEXPAND | wxALL, _border);
 
-               _use_any_servers->Bind (wxEVT_CHECKBOX, boost::bind (&EncodingServersPage::use_any_servers_changed, this));
+               _use_any_servers->Bind (wxEVT_CHECKBOX, boost::bind(&EncodingServersPage::use_any_servers_changed, this));
        }
 
-       void config_changed ()
+       void config_changed () override
        {
                checked_set (_use_any_servers, Config::instance()->use_any_servers ());
                _servers_list->refresh ();
@@ -834,827 +662,626 @@ private:
        EditableList<string, ServerDialog>* _servers_list;
 };
 
-class CertificateChainEditor : public wxPanel
+
+class TMSPage : public Page
 {
 public:
-       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);
-
-               {
-                       wxStaticText* m = new wxStaticText (this, wxID_ANY, title);
-                       m->SetFont (subheading_font);
-                       _sizer->Add (m, 0, wxALL, border);
-               }
+       TMSPage (wxSize panel_size, int border)
+               : Page (panel_size, border)
+       {}
 
-               wxBoxSizer* certificates_sizer = new wxBoxSizer (wxHORIZONTAL);
-               _sizer->Add (certificates_sizer, 0, wxLEFT | wxRIGHT, border);
+       wxString GetName () const override
+       {
+               return _("TMS");
+       }
 
-               _certificates = new wxListCtrl (this, wxID_ANY, wxDefaultPosition, wxSize (440, 150), wxLC_REPORT | wxLC_SINGLE_SEL);
+#ifdef DCPOMATIC_OSX
+       wxBitmap GetLargeIcon () const override
+       {
+               return wxBitmap(bitmap_path("tms.png"), wxBITMAP_TYPE_PNG);
+       }
+#endif
 
-               {
-                       wxListItem ip;
-                       ip.SetId (0);
-                       ip.SetText (_("Type"));
-                       ip.SetWidth (100);
-                       _certificates->InsertColumn (0, ip);
-               }
+private:
+       void setup () override
+       {
+               _upload = new CheckBox (_panel, _("Upload DCP to TMS after creation"));
+               _panel->GetSizer()->Add (_upload, 0, wxALL | wxEXPAND, _border);
 
-               {
-                       wxListItem ip;
-                       ip.SetId (1);
-                       ip.SetText (_("Thumbprint"));
-                       ip.SetWidth (340);
+               wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
+               table->AddGrowableCol (1, 1);
+               _panel->GetSizer()->Add (table, 1, wxALL | wxEXPAND, _border);
 
-                       wxFont font = ip.GetFont ();
-                       font.SetFamily (wxFONTFAMILY_TELETYPE);
-                       ip.SetFont (font);
+               add_label_to_sizer (table, _panel, _("Protocol"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
+               _tms_protocol = new wxChoice (_panel, wxID_ANY);
+               table->Add (_tms_protocol, 1, wxEXPAND);
 
-                       _certificates->InsertColumn (1, ip);
-               }
+               add_label_to_sizer (table, _panel, _("IP address"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
+               _tms_ip = new wxTextCtrl (_panel, wxID_ANY);
+               table->Add (_tms_ip, 1, wxEXPAND);
 
-               certificates_sizer->Add (_certificates, 1, wxEXPAND);
+               add_label_to_sizer (table, _panel, _("Target path"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
+               _tms_path = new wxTextCtrl (_panel, wxID_ANY);
+               table->Add (_tms_path, 1, wxEXPAND);
 
-               {
-                       wxSizer* s = new wxBoxSizer (wxVERTICAL);
-                       _add_certificate = new wxButton (this, wxID_ANY, _("Add..."));
-                       s->Add (_add_certificate, 0, wxTOP | wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
-                       _remove_certificate = new wxButton (this, wxID_ANY, _("Remove"));
-                       s->Add (_remove_certificate, 0, wxTOP | wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
-                       _export_certificate = new wxButton (this, wxID_ANY, _("Export"));
-                       s->Add (_export_certificate, 0, wxTOP | wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
-                       certificates_sizer->Add (s, 0, wxLEFT, DCPOMATIC_SIZER_X_GAP);
-               }
+               add_label_to_sizer (table, _panel, _("User name"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
+               _tms_user = new wxTextCtrl (_panel, wxID_ANY);
+               table->Add (_tms_user, 1, wxEXPAND);
 
-               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, _panel, _("Password"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
+               _tms_password = new PasswordEntry (_panel);
+               table->Add (_tms_password->get_panel(), 1, wxEXPAND);
 
-               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_private_key ();
-               update_sensitivity ();
-       }
-
-       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 (this, _("Select Certificate File"));
-
-               if (d->ShowModal() == wxID_OK) {
-                       try {
-                               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 (this, wxString::Format (_("Could not read certificate file (%s)"), e.what ()));
-                       }
-               }
-
-               d->Destroy ();
-
-               update_sensitivity ();
-       }
-
-       void remove_certificate ()
-       {
-               int i = _certificates->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
-               if (i == -1) {
-                       return;
-               }
-
-               _certificates->DeleteItem (i);
-               _chain->remove (i);
-               _set (_chain);
+               _tms_protocol->Append (_("SCP (for AAM and Doremi)"));
+               _tms_protocol->Append (_("FTP (for Dolby)"));
 
-               update_sensitivity ();
+               _upload->Bind (wxEVT_CHECKBOX, boost::bind(&TMSPage::upload_changed, this));
+               _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->Changed.connect (boost::bind (&TMSPage::tms_password_changed, this));
        }
 
-       void export_certificate ()
+       void config_changed () override
        {
-               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);
-                       }
+               auto config = Config::instance ();
 
-                       string const s = j->certificate (true);
-                       fwrite (s.c_str(), 1, s.length(), f);
-                       fclose (f);
-               }
-               d->Destroy ();
+               checked_set (_upload, config->upload_after_make_dcp());
+               checked_set (_tms_protocol, static_cast<int>(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 update_certificate_list ()
+       void upload_changed ()
        {
-               _certificates->DeleteAllItems ();
-               size_t n = 0;
-               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 ()));
-
-                       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));
-               }
+               Config::instance()->set_upload_after_make_dcp (_upload->GetValue());
        }
 
-       void remake_certificates ()
+       void tms_protocol_changed ()
        {
-               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 ();
-               }
-
-               _nag_remake ();
-
-               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 ();
+               Config::instance()->set_tms_protocol(static_cast<FileTransferProtocol>(_tms_protocol->GetSelection()));
        }
 
-       void update_sensitivity ()
+       void tms_ip_changed ()
        {
-               /* 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);
+               Config::instance()->set_tms_ip (wx_to_std (_tms_ip->GetValue ()));
        }
 
-       void update_private_key ()
+       void tms_path_changed ()
        {
-               checked_set (_private_key, dcp::private_key_fingerprint (_chain->key().get ()));
-               _sizer->Layout ();
+               Config::instance()->set_tms_path (wx_to_std (_tms_path->GetValue ()));
        }
 
-       void load_private_key ()
+       void tms_user_changed ()
        {
-               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) > 8192) {
-                                       error_dialog (
-                                               this,
-                                               wxString::Format (_("Could not read key file; file is too long (%s)"), std_to_wx (p.string ()))
-                                               );
-                                       return;
-                               }
-
-                               _chain->set_key (dcp::file_to_string (p));
-                               _set (_chain);
-                               update_private_key ();
-                       } catch (dcp::MiscError& e) {
-                               error_dialog (this, wxString::Format (_("Could not read certificate file (%s)"), e.what ()));
-                       }
-               }
-
-               d->Destroy ();
-
-               update_sensitivity ();
+               Config::instance()->set_tms_user (wx_to_std (_tms_user->GetValue ()));
        }
 
-       void export_private_key ()
+       void tms_password_changed ()
        {
-               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 ();
+               Config::instance()->set_tms_password (_tms_password->get());
        }
 
-       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;
+       CheckBox* _upload;
+       wxChoice* _tms_protocol;
+       wxTextCtrl* _tms_ip;
+       wxTextCtrl* _tms_path;
+       wxTextCtrl* _tms_user;
+       PasswordEntry* _tms_password;
 };
 
-class KeysPage : public StandardPage
+
+class EmailPage : public Page
 {
 public:
-       KeysPage (wxSize panel_size, int border)
-               : StandardPage (panel_size, border)
+       EmailPage (wxSize panel_size, int border)
+               : Page (panel_size, border)
        {}
 
-       wxString GetName () const
+       wxString GetName () const override
        {
-               return _("Keys");
+               return _("Email");
        }
 
 #ifdef DCPOMATIC_OSX
-       wxBitmap GetLargeIcon () const
+       wxBitmap GetLargeIcon () const override
        {
-               return wxBitmap ("keys", wxBITMAP_TYPE_PNG_RESOURCE);
+               return wxBitmap(bitmap_path("email.png"), wxBITMAP_TYPE_PNG);
        }
 #endif
 
 private:
-
-       void setup ()
+       void setup () override
        {
-               _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);
+               auto table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
+               table->AddGrowableCol (1, 1);
+               _panel->GetSizer()->Add (table, 1, wxEXPAND | wxALL, _border);
 
-               _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)
-                       );
+               add_label_to_sizer (table, _panel, _("Outgoing mail server"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
+               {
+                       wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
+                       _server = new wxTextCtrl (_panel, wxID_ANY);
+                       s->Add (_server, 1, wxEXPAND | wxALL);
+                       add_label_to_sizer (s, _panel, _("port"), false, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
+                       _port = new wxSpinCtrl (_panel, wxID_ANY);
+                       _port->SetRange (0, 65535);
+                       s->Add (_port);
+                       add_label_to_sizer (s, _panel, _("protocol"), false, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
+                       _protocol = new wxChoice (_panel, wxID_ANY);
+                       /* Make sure this matches the switches in config_changed and port_changed below */
+                       _protocol->Append (_("Auto"));
+                       _protocol->Append (_("Plain"));
+                       _protocol->Append (_("STARTTLS"));
+                       _protocol->Append (_("SSL"));
+                       s->Add (_protocol, 1, wxALIGN_CENTER_VERTICAL);
+                       table->Add (s, 1, wxEXPAND | wxALL);
+               }
 
-               _panel->GetSizer()->Add (_decryption);
+               add_label_to_sizer (table, _panel, _("User name"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
+               _user = new wxTextCtrl (_panel, wxID_ANY);
+               table->Add (_user, 1, wxEXPAND | wxALL);
 
-               _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);
+               add_label_to_sizer (table, _panel, _("Password"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
+               _password = new PasswordEntry (_panel);
+               table->Add (_password->get_panel(), 1, wxEXPAND | wxALL);
 
-               _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));
+               table->AddSpacer (0);
+               _send_test_email = new Button (_panel, _("Send test email..."));
+               table->Add (_send_test_email);
+
+               _server->Bind (wxEVT_TEXT, boost::bind(&EmailPage::server_changed, this));
+               _port->Bind (wxEVT_SPINCTRL, boost::bind(&EmailPage::port_changed, this));
+               _protocol->Bind (wxEVT_CHOICE, boost::bind(&EmailPage::protocol_changed, this));
+               _user->Bind (wxEVT_TEXT, boost::bind(&EmailPage::user_changed, this));
+               _password->Changed.connect (boost::bind(&EmailPage::password_changed, this));
+               _send_test_email->Bind (wxEVT_BUTTON, boost::bind(&EmailPage::send_test_email_clicked, this));
+       }
+
+       void config_changed () override
+       {
+               auto config = Config::instance ();
+
+               checked_set (_server, config->mail_server());
+               checked_set (_port, config->mail_port());
+               switch (config->mail_protocol()) {
+               case EmailProtocol::AUTO:
+                       checked_set (_protocol, 0);
+                       break;
+               case EmailProtocol::PLAIN:
+                       checked_set (_protocol, 1);
+                       break;
+               case EmailProtocol::STARTTLS:
+                       checked_set (_protocol, 2);
+                       break;
+               case EmailProtocol::SSL:
+                       checked_set (_protocol, 3);
+                       break;
+               }
+               checked_set (_user, config->mail_user());
+               checked_set (_password, config->mail_password());
        }
 
-       void export_decryption_certificate ()
+       void server_changed ()
        {
-               wxFileDialog* d = new wxFileDialog (
-                       _panel, _("Select Certificate File"), wxEmptyString, wxEmptyString, wxT ("PEM files (*.pem)|*.pem"),
-                       wxFD_SAVE | wxFD_OVERWRITE_PROMPT
-                       );
+               Config::instance()->set_mail_server(wx_to_std(_server->GetValue()));
+       }
 
-               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);
-                       }
+       void port_changed ()
+       {
+               Config::instance()->set_mail_port(_port->GetValue());
+       }
 
-                       string const s = Config::instance()->decryption_chain()->leaf().certificate (true);
-                       fwrite (s.c_str(), 1, s.length(), f);
-                       fclose (f);
+       void protocol_changed ()
+       {
+               switch (_protocol->GetSelection()) {
+               case 0:
+                       Config::instance()->set_mail_protocol(EmailProtocol::AUTO);
+                       break;
+               case 1:
+                       Config::instance()->set_mail_protocol(EmailProtocol::PLAIN);
+                       break;
+               case 2:
+                       Config::instance()->set_mail_protocol(EmailProtocol::STARTTLS);
+                       break;
+               case 3:
+                       Config::instance()->set_mail_protocol(EmailProtocol::SSL);
+                       break;
                }
-               d->Destroy ();
        }
 
-       void export_decryption_chain ()
+       void user_changed ()
        {
-               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 ();
+               Config::instance()->set_mail_user (wx_to_std (_user->GetValue ()));
        }
 
-       void config_changed ()
+       void password_changed ()
        {
-               _signer->config_changed ();
-               _decryption->config_changed ();
+               Config::instance()->set_mail_password(_password->get());
        }
 
-       void nag_remake_decryption_chain ()
+       void send_test_email_clicked ()
        {
-               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!")
-                       );
+               auto dialog = new SendTestEmailDialog(_panel);
+               auto result = dialog->ShowModal();
+               dialog->Destroy();
+               if (result == wxID_OK) {
+                       Emailer emailer(
+                               wx_to_std(dialog->from()),
+                               { wx_to_std(dialog->to()) },
+                               wx_to_std(_("DCP-o-matic test email")),
+                               wx_to_std(_("This is a test email from DCP-o-matic."))
+                               );
+                       auto config = Config::instance();
+                       try {
+                               emailer.send (config->mail_server(), config->mail_port(), config->mail_protocol(), config->mail_user(), config->mail_password());
+                       } catch (NetworkError& e) {
+                               error_dialog (_panel, std_to_wx(e.summary()), std_to_wx(e.detail().get_value_or("")));
+                               return;
+                       } catch (std::exception& e) {
+                               error_dialog (_panel, _("Test email sending failed."), std_to_wx(e.what()));
+                               return;
+                       } catch (...) {
+                               error_dialog (_panel, _("Test email sending failed."));
+                               return;
+                       }
+                       message_dialog (_panel, _("Test email sent."));
+               }
        }
 
-       CertificateChainEditor* _signer;
-       CertificateChainEditor* _decryption;
-       wxButton* _export_decryption_certificate;
-       wxButton* _export_decryption_chain;
+       wxTextCtrl* _server;
+       wxSpinCtrl* _port;
+       wxChoice* _protocol;
+       wxTextCtrl* _user;
+       PasswordEntry* _password;
+       Button* _send_test_email;
 };
 
-class TMSPage : public StandardPage
+
+class KDMEmailPage : public Page
 {
 public:
-       TMSPage (wxSize panel_size, int border)
-               : StandardPage (panel_size, border)
+
+       KDMEmailPage (wxSize panel_size, int border)
+#ifdef DCPOMATIC_OSX
+               /* We have to force both width and height of this one */
+               : Page (wxSize (panel_size.GetWidth(), 128), border)
+#else
+               : Page (panel_size, border)
+#endif
        {}
 
-       wxString GetName () const
+       wxString GetName () const override
        {
-               return _("TMS");
+               return _("KDM Email");
        }
 
 #ifdef DCPOMATIC_OSX
-       wxBitmap GetLargeIcon () const
+       wxBitmap GetLargeIcon () const override
        {
-               return wxBitmap ("tms", wxBITMAP_TYPE_PNG_RESOURCE);
+               return wxBitmap(bitmap_path("kdm_email.png"), wxBITMAP_TYPE_PNG);
        }
 #endif
 
 private:
-       void setup ()
+       void setup () override
        {
-               wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
+               auto table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
                table->AddGrowableCol (1, 1);
-               _panel->GetSizer()->Add (table, 1, wxALL | wxEXPAND, _border);
+               _panel->GetSizer()->Add (table, 0, wxEXPAND | wxALL, _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, _("Subject"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
+               _subject = new wxTextCtrl (_panel, wxID_ANY);
+               table->Add (_subject, 1, wxEXPAND | wxALL);
 
-               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, _("From address"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
+               _from = new wxTextCtrl (_panel, wxID_ANY);
+               table->Add (_from, 1, wxEXPAND | wxALL);
 
-               add_label_to_sizer (table, _panel, _("Target path"), true);
-               _tms_path = new wxTextCtrl (_panel, wxID_ANY);
-               table->Add (_tms_path, 1, wxEXPAND);
+               vector<EditableListColumn> columns;
+               columns.push_back (EditableListColumn(_("Address")));
+               add_label_to_sizer (table, _panel, _("CC addresses"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
+               _cc = new EditableList<string, EmailDialog> (
+                       _panel,
+                       columns,
+                       bind (&Config::kdm_cc, Config::instance()),
+                       bind (&Config::set_kdm_cc, Config::instance(), _1),
+                       [] (string s, int) {
+                               return s;
+                       },
+                       true,
+                       EditableListButton::NEW | EditableListButton::EDIT | EditableListButton::REMOVE
+                       );
+               table->Add (_cc, 1, wxEXPAND | wxALL);
 
-               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, _("BCC address"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
+               _bcc = new wxTextCtrl (_panel, wxID_ANY);
+               table->Add (_bcc, 1, wxEXPAND | wxALL);
 
-               add_label_to_sizer (table, _panel, _("Password"), true);
-               _tms_password = new wxTextCtrl (_panel, wxID_ANY);
-               table->Add (_tms_password, 1, wxEXPAND);
+               _email = new wxTextCtrl (_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (-1, 200), wxTE_MULTILINE);
+               _panel->GetSizer()->Add (_email, 0, wxEXPAND | wxALL, _border);
 
-               _tms_protocol->Append (_("SCP (for AAM and Doremi)"));
-               _tms_protocol->Append (_("FTP (for Dolby)"));
+               _reset_email = new Button (_panel, _("Reset to default subject and text"));
+               _panel->GetSizer()->Add (_reset_email, 0, wxEXPAND | wxALL, _border);
 
-               _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));
+               _cc->layout ();
+
+               _subject->Bind (wxEVT_TEXT, boost::bind (&KDMEmailPage::kdm_subject_changed, this));
+               _from->Bind (wxEVT_TEXT, boost::bind (&KDMEmailPage::kdm_from_changed, this));
+               _bcc->Bind (wxEVT_TEXT, boost::bind (&KDMEmailPage::kdm_bcc_changed, this));
+               _email->Bind (wxEVT_TEXT, boost::bind (&KDMEmailPage::kdm_email_changed, this));
+               _reset_email->Bind (wxEVT_BUTTON, boost::bind (&KDMEmailPage::reset_email, this));
        }
 
-       void config_changed ()
+       void config_changed () override
        {
-               Config* config = Config::instance ();
+               auto config = Config::instance ();
 
-               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 ());
+               checked_set (_subject, config->kdm_subject ());
+               checked_set (_from, config->kdm_from ());
+               checked_set (_bcc, config->kdm_bcc ());
+               checked_set (_email, Config::instance()->kdm_email ());
        }
 
-       void tms_protocol_changed ()
+       void kdm_subject_changed ()
        {
-               Config::instance()->set_tms_protocol (static_cast<Protocol> (_tms_protocol->GetSelection ()));
+               Config::instance()->set_kdm_subject (wx_to_std (_subject->GetValue ()));
        }
 
-       void tms_ip_changed ()
+       void kdm_from_changed ()
        {
-               Config::instance()->set_tms_ip (wx_to_std (_tms_ip->GetValue ()));
+               Config::instance()->set_kdm_from (wx_to_std (_from->GetValue ()));
        }
 
-       void tms_path_changed ()
+       void kdm_bcc_changed ()
        {
-               Config::instance()->set_tms_path (wx_to_std (_tms_path->GetValue ()));
+               Config::instance()->set_kdm_bcc (wx_to_std (_bcc->GetValue ()));
        }
 
-       void tms_user_changed ()
+       void kdm_email_changed ()
        {
-               Config::instance()->set_tms_user (wx_to_std (_tms_user->GetValue ()));
+               if (_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 (_email->GetValue ()));
        }
 
-       void tms_password_changed ()
+       void reset_email ()
        {
-               Config::instance()->set_tms_password (wx_to_std (_tms_password->GetValue ()));
+               Config::instance()->reset_kdm_email ();
+               checked_set (_email, Config::instance()->kdm_email ());
        }
 
-       wxChoice* _tms_protocol;
-       wxTextCtrl* _tms_ip;
-       wxTextCtrl* _tms_path;
-       wxTextCtrl* _tms_user;
-       wxTextCtrl* _tms_password;
+       wxTextCtrl* _subject;
+       wxTextCtrl* _from;
+       EditableList<string, EmailDialog>* _cc;
+       wxTextCtrl* _bcc;
+       wxTextCtrl* _email;
+       wxButton* _reset_email;
 };
 
-static string
-column (string s)
-{
-       return s;
-}
 
-class KDMEmailPage : public StandardPage
+class NotificationsPage : public Page
 {
 public:
-
-       KDMEmailPage (wxSize panel_size, int border)
+       NotificationsPage (wxSize panel_size, int border)
 #ifdef DCPOMATIC_OSX
                /* We have to force both width and height of this one */
-               : StandardPage (wxSize (480, 128), border)
+               : Page (wxSize (panel_size.GetWidth(), 128), border)
 #else
-               : StandardPage (panel_size, border)
+               : Page (panel_size, border)
 #endif
        {}
 
-       wxString GetName () const
+       wxString GetName () const override
        {
-               return _("KDM Email");
+               return _("Notifications");
        }
 
 #ifdef DCPOMATIC_OSX
-       wxBitmap GetLargeIcon () const
+       wxBitmap GetLargeIcon () const override
        {
-               return wxBitmap ("kdm_email", wxBITMAP_TYPE_PNG_RESOURCE);
+               return wxBitmap(bitmap_path("notifications.png"), wxBITMAP_TYPE_PNG);
        }
 #endif
 
 private:
-       void setup ()
+       void setup () override
        {
-               wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
+               auto table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
                table->AddGrowableCol (1, 1);
-               _panel->GetSizer()->Add (table, 1, wxEXPAND | wxALL, _border);
+               _panel->GetSizer()->Add (table, 0, 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);
-               }
+               _enable_message_box = new CheckBox (_panel, _("Message box"));
+               table->Add (_enable_message_box, 1, wxEXPAND | wxALL);
+               table->AddSpacer (0);
 
-               add_label_to_sizer (table, _panel, _("Mail user name"), true);
-               _mail_user = new wxTextCtrl (_panel, wxID_ANY);
-               table->Add (_mail_user, 1, wxEXPAND | wxALL);
+               _enable_email = new CheckBox (_panel, _("Email"));
+               table->Add (_enable_email, 1, wxEXPAND | wxALL);
+               table->AddSpacer (0);
 
-               add_label_to_sizer (table, _panel, _("Mail password"), true);
-               _mail_password = new wxTextCtrl (_panel, wxID_ANY);
-               table->Add (_mail_password, 1, wxEXPAND | wxALL);
+               add_label_to_sizer (table, _panel, _("Subject"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
+               _subject = new wxTextCtrl (_panel, wxID_ANY);
+               table->Add (_subject, 1, wxEXPAND | wxALL);
 
-               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, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
+               _from = new wxTextCtrl (_panel, wxID_ANY);
+               table->Add (_from, 1, wxEXPAND | wxALL);
 
-               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, _("To address"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
+               _to = new wxTextCtrl (_panel, wxID_ANY);
+               table->Add (_to, 1, wxEXPAND | wxALL);
 
-               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> (
+               vector<EditableListColumn> columns;
+               columns.push_back (EditableListColumn(_("Address")));
+               add_label_to_sizer (table, _panel, _("CC addresses"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
+               _cc = new EditableList<string, EmailDialog> (
                        _panel,
                        columns,
-                       bind (&Config::kdm_cc, Config::instance()),
-                       bind (&Config::set_kdm_cc, Config::instance(), _1),
-                       bind (&column, _1)
+                       bind (&Config::notification_cc, Config::instance()),
+                       bind (&Config::set_notification_cc, Config::instance(), _1),
+                       [] (string s, int) {
+                               return s;
+                       },
+                       true,
+                       EditableListButton::NEW | EditableListButton::EDIT | EditableListButton::REMOVE
                        );
-               table->Add (_kdm_cc, 1, wxEXPAND | wxALL);
+               table->Add (_cc, 1, wxEXPAND | wxALL);
 
-               add_label_to_sizer (table, _panel, _("BCC address"), true);
-               _kdm_bcc = new wxTextCtrl (_panel, wxID_ANY);
-               table->Add (_kdm_bcc, 1, wxEXPAND | wxALL);
+               add_label_to_sizer (table, _panel, _("BCC address"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
+               _bcc = new wxTextCtrl (_panel, wxID_ANY);
+               table->Add (_bcc, 1, wxEXPAND | wxALL);
 
-               _kdm_email = new wxTextCtrl (_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (-1, 200), wxTE_MULTILINE);
-               _panel->GetSizer()->Add (_kdm_email, 0, wxEXPAND | wxALL, _border);
+               _email = new wxTextCtrl (_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (-1, 200), wxTE_MULTILINE);
+               _panel->GetSizer()->Add (_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);
+               _reset_email = new Button (_panel, _("Reset to default subject and text"));
+               _panel->GetSizer()->Add (_reset_email, 0, wxEXPAND | wxALL, _border);
 
-               _kdm_cc->layout ();
+               _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));
-       }
+               _enable_message_box->Bind (wxEVT_CHECKBOX, boost::bind (&NotificationsPage::type_changed, this, _enable_message_box, Config::MESSAGE_BOX));
+               _enable_email->Bind (wxEVT_CHECKBOX, boost::bind (&NotificationsPage::type_changed, this, _enable_email, Config::EMAIL));
 
-       void config_changed ()
-       {
-               Config* config = Config::instance ();
+               _subject->Bind (wxEVT_TEXT, boost::bind (&NotificationsPage::notification_subject_changed, this));
+               _from->Bind (wxEVT_TEXT, boost::bind (&NotificationsPage::notification_from_changed, this));
+               _to->Bind (wxEVT_TEXT, boost::bind (&NotificationsPage::notification_to_changed, this));
+               _bcc->Bind (wxEVT_TEXT, boost::bind (&NotificationsPage::notification_bcc_changed, this));
+               _email->Bind (wxEVT_TEXT, boost::bind (&NotificationsPage::notification_email_changed, this));
+               _reset_email->Bind (wxEVT_BUTTON, boost::bind (&NotificationsPage::reset_email, this));
 
-               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 ());
+               setup_sensitivity ();
        }
 
-       void mail_server_changed ()
+       void setup_sensitivity ()
        {
-               Config::instance()->set_mail_server (wx_to_std (_mail_server->GetValue ()));
+               bool const s = _enable_email->GetValue();
+               _subject->Enable(s);
+               _from->Enable(s);
+               _to->Enable(s);
+               _cc->Enable(s);
+               _bcc->Enable(s);
+               _email->Enable(s);
+               _reset_email->Enable(s);
        }
 
-       void mail_port_changed ()
+       void config_changed () override
        {
-               Config::instance()->set_mail_port (_mail_port->GetValue ());
-       }
+               auto config = Config::instance ();
 
-       void mail_user_changed ()
-       {
-               Config::instance()->set_mail_user (wx_to_std (_mail_user->GetValue ()));
+               checked_set (_enable_message_box, config->notification(Config::MESSAGE_BOX));
+               checked_set (_enable_email, config->notification(Config::EMAIL));
+               checked_set (_subject, config->notification_subject ());
+               checked_set (_from, config->notification_from ());
+               checked_set (_to, config->notification_to ());
+               checked_set (_bcc, config->notification_bcc ());
+               checked_set (_email, Config::instance()->notification_email ());
+
+               setup_sensitivity ();
        }
 
-       void mail_password_changed ()
+       void notification_subject_changed ()
        {
-               Config::instance()->set_mail_password (wx_to_std (_mail_password->GetValue ()));
+               Config::instance()->set_notification_subject(wx_to_std(_subject->GetValue()));
        }
 
-       void kdm_subject_changed ()
+       void notification_from_changed ()
        {
-               Config::instance()->set_kdm_subject (wx_to_std (_kdm_subject->GetValue ()));
+               Config::instance()->set_notification_from(wx_to_std(_from->GetValue()));
        }
 
-       void kdm_from_changed ()
+       void notification_to_changed ()
        {
-               Config::instance()->set_kdm_from (wx_to_std (_kdm_from->GetValue ()));
+               Config::instance()->set_notification_to(wx_to_std(_to->GetValue()));
        }
 
-       void kdm_bcc_changed ()
+       void notification_bcc_changed ()
        {
-               Config::instance()->set_kdm_bcc (wx_to_std (_kdm_bcc->GetValue ()));
+               Config::instance()->set_notification_bcc(wx_to_std(_bcc->GetValue()));
        }
 
-       void kdm_email_changed ()
+       void notification_email_changed ()
        {
-               if (_kdm_email->GetValue().IsEmpty ()) {
+               if (_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 ()));
+               Config::instance()->set_notification_email(wx_to_std(_email->GetValue()));
        }
 
-       void reset_kdm_email ()
+       void reset_email ()
        {
-               Config::instance()->reset_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;
-       EditableList<string, EmailDialog>* _kdm_cc;
-       wxTextCtrl* _kdm_bcc;
-       wxTextCtrl* _kdm_email;
-       wxButton* _reset_kdm_email;
+               Config::instance()->reset_notification_email();
+               checked_set (_email, Config::instance()->notification_email());
+       }
+
+       void type_changed (wxCheckBox* b, Config::Notification n)
+       {
+               Config::instance()->set_notification(n, b->GetValue());
+               setup_sensitivity ();
+       }
+
+       wxCheckBox* _enable_message_box;
+       wxCheckBox* _enable_email;
+
+       wxTextCtrl* _subject;
+       wxTextCtrl* _from;
+       wxTextCtrl* _to;
+       EditableList<string, EmailDialog>* _cc;
+       wxTextCtrl* _bcc;
+       wxTextCtrl* _email;
+       wxButton* _reset_email;
 };
 
-class CoverSheetPage : public StandardPage
+
+class CoverSheetPage : public Page
 {
 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)
+               : Page (wxSize (panel_size.GetWidth(), 128), border)
 #else
-               : StandardPage (panel_size, border)
+               : Page (panel_size, border)
 #endif
        {}
 
-       wxString GetName () const
+       wxString GetName () const override
        {
                return _("Cover Sheet");
        }
 
 #ifdef DCPOMATIC_OSX
-       wxBitmap GetLargeIcon () const
+       wxBitmap GetLargeIcon () const override
        {
-               return wxBitmap ("cover_sheet", wxBITMAP_TYPE_PNG_RESOURCE);
+               return wxBitmap(bitmap_path("cover_sheet.png"), wxBITMAP_TYPE_PNG);
        }
 #endif
 
 private:
-       void setup ()
+       void setup () override
        {
                _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"));
+               _reset_cover_sheet = new Button (_panel, _("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 ()
+       void config_changed () override
        {
-               checked_set (_cover_sheet, Config::instance()->cover_sheet ());
+               checked_set (_cover_sheet, Config::instance()->cover_sheet());
        }
 
        void cover_sheet_changed ()
@@ -1665,13 +1292,13 @@ private:
                        */
                        return;
                }
-               Config::instance()->set_cover_sheet (wx_to_std (_cover_sheet->GetValue ()));
+               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 ());
+               Config::instance()->reset_cover_sheet();
+               checked_set (_cover_sheet, Config::instance()->cover_sheet());
        }
 
        wxTextCtrl* _cover_sheet;
@@ -1679,26 +1306,143 @@ private:
 };
 
 
+class IdentifiersPage : public Page
+{
+public:
+       IdentifiersPage (wxSize panel_size, int border)
+               : Page (panel_size, border)
+       {}
+
+       wxString GetName () const override
+       {
+               return _("Identifiers");
+       }
+
+#ifdef DCPOMATIC_OSX
+       wxBitmap GetLargeIcon () const override
+       {
+               return wxBitmap(bitmap_path("identifiers.png"), wxBITMAP_TYPE_PNG);
+       }
+#endif
+
+private:
+       void setup () override
+       {
+               auto table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
+               table->AddGrowableCol (1, 1);
+
+               add_label_to_sizer (table, _panel, _("Issuer"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
+               _issuer = new wxTextCtrl (_panel, wxID_ANY);
+               _issuer->SetToolTip (_("This will be written to the DCP's XML files as the <Issuer>.  If it is blank, a default value mentioning DCP-o-matic will be used."));
+               table->Add (_issuer, 1, wxALL | wxEXPAND);
+
+               add_label_to_sizer (table, _panel, _("Creator"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
+               _creator = new wxTextCtrl (_panel, wxID_ANY);
+               _creator->SetToolTip (_("This will be written to the DCP's XML files as the <Creator>.  If it is blank, a default value mentioning DCP-o-matic will be used."));
+               table->Add (_creator, 1, wxALL | wxEXPAND);
+
+               add_label_to_sizer (table, _panel, _("Company name"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
+               _company_name = new wxTextCtrl (_panel, wxID_ANY);
+               _company_name->SetToolTip (_("This will be written to the DCP's MXF files as the 'company name'.  If it is blank, a default value mentioning libdcp (an internal DCP-o-matic library) will be used."));
+               table->Add (_company_name, 1, wxALL | wxEXPAND);
+
+               add_label_to_sizer (table, _panel, _("Product name"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
+               _product_name = new wxTextCtrl (_panel, wxID_ANY);
+               _product_name->SetToolTip (_("This will be written to the DCP's MXF files as the 'product name'.  If it is blank, a default value mentioning libdcp (an internal DCP-o-matic library) will be used."));
+               table->Add (_product_name, 1, wxALL | wxEXPAND);
+
+               add_label_to_sizer (table, _panel, _("Product version"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
+               _product_version = new wxTextCtrl (_panel, wxID_ANY);
+               _product_version->SetToolTip (_("This will be written to the DCP's MXF files as the 'product version'.  If it is blank, a default value mentioning libdcp (an internal DCP-o-matic library) will be used."));
+               table->Add (_product_version, 1, wxALL | wxEXPAND);
+
+               add_label_to_sizer (table, _panel, _("JPEG2000 comment"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
+               _j2k_comment = new wxTextCtrl (_panel, wxID_ANY);
+               _j2k_comment->SetToolTip (_("This will be written to the DCP's JPEG2000 data as a comment.  If it is blank, a default value mentioning libdcp (an internal DCP-o-matic library) will be used."));
+               table->Add (_j2k_comment, 1, wxALL | wxEXPAND);
+
+               _panel->GetSizer()->Add (table, 0, wxEXPAND | wxALL, _border);
+
+               _issuer->Bind (wxEVT_TEXT, boost::bind(&IdentifiersPage::issuer_changed, this));
+               _creator->Bind (wxEVT_TEXT, boost::bind(&IdentifiersPage::creator_changed, this));
+               _company_name->Bind (wxEVT_TEXT, boost::bind(&IdentifiersPage::company_name_changed, this));
+               _product_name->Bind (wxEVT_TEXT, boost::bind(&IdentifiersPage::product_name_changed, this));
+               _product_version->Bind (wxEVT_TEXT, boost::bind(&IdentifiersPage::product_version_changed, this));
+               _j2k_comment->Bind (wxEVT_TEXT, boost::bind(&IdentifiersPage::j2k_comment_changed, this));
+       }
+
+       void config_changed () override
+       {
+               auto config = Config::instance ();
+               checked_set (_issuer, config->dcp_issuer ());
+               checked_set (_creator, config->dcp_creator ());
+               checked_set (_company_name, config->dcp_company_name ());
+               checked_set (_product_name, config->dcp_product_name ());
+               checked_set (_product_version, config->dcp_product_version ());
+               checked_set (_j2k_comment, config->dcp_j2k_comment ());
+       }
+
+       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 company_name_changed ()
+       {
+               Config::instance()->set_dcp_company_name(wx_to_std(_company_name->GetValue()));
+       }
+
+       void product_name_changed ()
+       {
+               Config::instance()->set_dcp_product_name(wx_to_std(_product_name->GetValue()));
+       }
+
+       void product_version_changed ()
+       {
+               Config::instance()->set_dcp_product_version(wx_to_std(_product_version->GetValue()));
+       }
+
+       void j2k_comment_changed ()
+       {
+               Config::instance()->set_dcp_j2k_comment (wx_to_std(_j2k_comment->GetValue()));
+       }
+
+       wxTextCtrl* _issuer;
+       wxTextCtrl* _creator;
+       wxTextCtrl* _company_name;
+       wxTextCtrl* _product_name;
+       wxTextCtrl* _product_version;
+       wxTextCtrl* _j2k_comment;
+};
+
+
 /** @class AdvancedPage
  *  @brief Advanced page of the preferences dialog.
  */
-class AdvancedPage : public StockPage
+class AdvancedPage : public Page
 {
 public:
        AdvancedPage (wxSize panel_size, int 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)
+               : Page (panel_size, border)
        {}
 
+       wxString GetName () const override
+       {
+               return _("Advanced");
+       }
+
+#ifdef DCPOMATIC_OSX
+       wxBitmap GetLargeIcon () const override
+       {
+               return wxBitmap(bitmap_path("advanced.png"), wxBITMAP_TYPE_PNG);
+       }
+#endif
+
 private:
        void add_top_aligned_label_to_sizer (wxSizer* table, wxWindow* parent, wxString text)
        {
@@ -1707,37 +1451,81 @@ private:
                flags |= wxALIGN_RIGHT;
                text += wxT (":");
 #endif
-               wxStaticText* m = new wxStaticText (parent, wxID_ANY, text);
+               wxStaticText* m = new StaticText (parent, text);
                table->Add (m, 0, flags, DCPOMATIC_SIZER_Y_GAP);
        }
 
-       void setup ()
+       void setup () override
        {
-               wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
+               auto table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
                table->AddGrowableCol (1, 1);
                _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, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
                        wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
                        _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, 0, wxLEFT | wxALIGN_CENTRE_VERTICAL);
                        table->Add (s, 1);
                }
 
-               _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);
+               add_label_to_sizer (table, _panel, _("Video display mode"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
+               _video_display_mode = new wxChoice (_panel, wxID_ANY);
+               table->Add (_video_display_mode);
+
+               auto restart = add_label_to_sizer (table, _panel, _("(restart DCP-o-matic to change display mode)"), false);
+               auto font = restart->GetFont();
+               font.SetStyle (wxFONTSTYLE_ITALIC);
+               font.SetPointSize (font.GetPointSize() - 1);
+               restart->SetFont (font);
+               table->AddSpacer (0);
+
+               _allow_any_dcp_frame_rate = new CheckBox (_panel, _("Allow any DCP frame rate"));
+               table->Add (_allow_any_dcp_frame_rate, 1, wxEXPAND | wxLEFT, DCPOMATIC_SIZER_GAP);
+               table->AddSpacer (0);
+
+               _allow_any_container = new CheckBox (_panel, _("Allow full-frame and non-standard container ratios"));
+               table->Add (_allow_any_container, 1, wxEXPAND | wxLEFT, DCPOMATIC_SIZER_GAP);
+               restart = new StaticText (_panel, _("(restart DCP-o-matic to see all ratios)"));
+               table->Add (restart, 1, wxEXPAND | wxALL | wxALIGN_CENTRE_VERTICAL);
+               restart->SetFont (font);
+
+               _allow_96khz_audio = new CheckBox (_panel, _("Allow creation of DCPs with 96kHz audio"));
+               table->Add (_allow_96khz_audio, 1, wxEXPAND | wxLEFT, DCPOMATIC_SIZER_GAP);
                table->AddSpacer (0);
 
-               _only_servers_encode = new wxCheckBox (_panel, wxID_ANY, _("Only servers encode"));
-               table->Add (_only_servers_encode, 1, wxEXPAND | wxALL);
+               _use_all_audio_channels = new CheckBox(_panel, _("Allow mapping to all audio channels"));
+               table->Add(_use_all_audio_channels, 1, wxEXPAND | wxLEFT, DCPOMATIC_SIZER_GAP);
+               table->AddSpacer(0);
+
+               _show_experimental_audio_processors = new CheckBox (_panel, _("Show experimental audio processors"));
+               table->Add (_show_experimental_audio_processors, 1, wxEXPAND | wxLEFT, DCPOMATIC_SIZER_GAP);
+               table->AddSpacer (0);
+
+               _only_servers_encode = new CheckBox (_panel, _("Only servers encode"));
+               table->Add (_only_servers_encode, 1, wxEXPAND | wxLEFT, DCPOMATIC_SIZER_GAP);
                table->AddSpacer (0);
 
                {
-                       add_top_aligned_label_to_sizer (table, _panel, _("DCP metadata filename format"));
+                       add_label_to_sizer (table, _panel, _("Maximum number of frames to store per thread"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
+                       auto s = new wxBoxSizer (wxHORIZONTAL);
+                       _frames_in_memory_multiplier = new wxSpinCtrl (_panel);
+                       s->Add (_frames_in_memory_multiplier, 1);
+                       table->Add (s, 1);
+               }
+
+               {
+                       auto format = create_label (_panel, _("DCP metadata filename format"), true);
+#ifdef DCPOMATIC_OSX
+                       auto align = new wxBoxSizer (wxHORIZONTAL);
+                       align->Add (format, 0, wxTOP, 2);
+                       table->Add (align, 0, wxALIGN_RIGHT | wxRIGHT, DCPOMATIC_SIZER_GAP - 2);
+#else
+                       table->Add (format, 0, wxTOP | wxLEFT | wxRIGHT | wxALIGN_TOP, DCPOMATIC_SIZER_GAP);
+#endif
                        dcp::NameFormat::Map titles;
-                       titles['t'] = "type (cpl/pkl)";
+                       titles['t'] = wx_to_std (_("type (cpl/pkl)"));
                        dcp::NameFormat::Map examples;
                        examples['t'] = "cpl";
                        _dcp_metadata_filename_format = new NameFormatEditor (
@@ -1747,12 +1535,19 @@ private:
                }
 
                {
-                       add_top_aligned_label_to_sizer (table, _panel, _("DCP asset filename format"));
+                       auto format = create_label (_panel, _("DCP asset filename format"), true);
+#ifdef DCPOMATIC_OSX
+                       auto align = new wxBoxSizer (wxHORIZONTAL);
+                       align->Add (format, 0, wxTOP, 2);
+                       table->Add (align, 0, wxALIGN_RIGHT | wxRIGHT, DCPOMATIC_SIZER_GAP - 2);
+#else
+                       table->Add (format, 0, wxTOP | wxLEFT | wxRIGHT | wxALIGN_TOP, DCPOMATIC_SIZER_GAP);
+#endif
                        dcp::NameFormat::Map titles;
-                       titles['t'] = "type (j2c/pcm/sub)";
-                       titles['r'] = "reel number";
-                       titles['n'] = "number of reels";
-                       titles['c'] = "content filename";
+                       titles['t'] = wx_to_std (_("type (j2c/pcm/sub)"));
+                       titles['r'] = wx_to_std (_("reel number"));
+                       titles['n'] = wx_to_std (_("number of reels"));
+                       titles['c'] = wx_to_std (_("content filename"));
                        dcp::NameFormat::Map examples;
                        examples['t'] = "j2c";
                        examples['r'] = "1";
@@ -1766,63 +1561,98 @@ private:
 
                {
                        add_top_aligned_label_to_sizer (table, _panel, _("Log"));
-                       wxBoxSizer* t = new wxBoxSizer (wxVERTICAL);
-                       _log_general = new wxCheckBox (_panel, wxID_ANY, _("General"));
+                       auto t = new wxFlexGridSizer (2);
+                       _log_general = new CheckBox (_panel, _("General"));
                        t->Add (_log_general, 1, wxEXPAND | wxALL);
-                       _log_warning = new wxCheckBox (_panel, wxID_ANY, _("Warnings"));
+                       _log_warning = new CheckBox (_panel, _("Warnings"));
                        t->Add (_log_warning, 1, wxEXPAND | wxALL);
-                       _log_error = new wxCheckBox (_panel, wxID_ANY, _("Errors"));
+                       _log_error = new CheckBox (_panel, _("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"));
+                       _log_timing = new CheckBox (_panel, 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"));
+                       _log_debug_threed = new CheckBox (_panel, _("Debug: 3D"));
+                       t->Add (_log_debug_threed, 1, wxEXPAND | wxALL);
+                       _log_debug_encode = new CheckBox (_panel, _("Debug: encode"));
                        t->Add (_log_debug_encode, 1, wxEXPAND | wxALL);
-                       _log_debug_email = new wxCheckBox (_panel, wxID_ANY, _("Debug: email sending"));
+                       _log_debug_email = new CheckBox (_panel, _("Debug: email sending"));
                        t->Add (_log_debug_email, 1, wxEXPAND | wxALL);
+                       _log_debug_video_view = new CheckBox (_panel, _("Debug: video view"));
+                       t->Add (_log_debug_video_view, 1, wxEXPAND | wxALL);
+                       _log_debug_player = new CheckBox (_panel, _("Debug: player"));
+                       t->Add (_log_debug_player, 1, wxEXPAND | wxALL);
+                       _log_debug_audio_analysis = new CheckBox (_panel, _("Debug: audio analysis"));
+                       t->Add (_log_debug_audio_analysis, 1, wxEXPAND | wxALL);
                        table->Add (t, 0, wxALL, 6);
                }
 
 #ifdef DCPOMATIC_WINDOWS
-               _win32_console = new wxCheckBox (_panel, wxID_ANY, _("Open console window"));
+               _win32_console = new CheckBox (_panel, _("Open console window"));
                table->Add (_win32_console, 1, wxEXPAND | wxALL);
                table->AddSpacer (0);
 #endif
 
                _maximum_j2k_bandwidth->SetRange (1, 1000);
                _maximum_j2k_bandwidth->Bind (wxEVT_SPINCTRL, boost::bind (&AdvancedPage::maximum_j2k_bandwidth_changed, this));
+               _video_display_mode->Append (_("Simple (safer)"));
+#if wxCHECK_VERSION(3, 1, 0)
+               _video_display_mode->Append (_("OpenGL (faster)"));
+#endif
+               _video_display_mode->Bind (wxEVT_CHOICE, boost::bind(&AdvancedPage::video_display_mode_changed, this));
                _allow_any_dcp_frame_rate->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::allow_any_dcp_frame_rate_changed, this));
+               _allow_any_container->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::allow_any_container_changed, this));
+               _allow_96khz_audio->Bind (wxEVT_CHECKBOX, boost::bind(&AdvancedPage::allow_96khz_audio_changed, this));
+               _use_all_audio_channels->Bind(wxEVT_CHECKBOX, boost::bind(&AdvancedPage::use_all_channels_changed, this));
+               _show_experimental_audio_processors->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::show_experimental_audio_processors_changed, this));
                _only_servers_encode->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::only_servers_encode_changed, this));
+               _frames_in_memory_multiplier->Bind (wxEVT_SPINCTRL, boost::bind(&AdvancedPage::frames_in_memory_multiplier_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_threed->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));
+               _log_debug_video_view->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::log_changed, this));
+               _log_debug_player->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::log_changed, this));
+               _log_debug_audio_analysis->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
        }
 
-       void config_changed ()
+       void config_changed () override
        {
-               Config* config = Config::instance ();
+               auto config = Config::instance ();
 
                checked_set (_maximum_j2k_bandwidth, config->maximum_j2k_bandwidth() / 1000000);
+               switch (config->video_view_type()) {
+               case Config::VIDEO_VIEW_SIMPLE:
+                       checked_set (_video_display_mode, 0);
+                       break;
+               case Config::VIDEO_VIEW_OPENGL:
+                       checked_set (_video_display_mode, 1);
+                       break;
+               }
                checked_set (_allow_any_dcp_frame_rate, config->allow_any_dcp_frame_rate ());
+               checked_set (_allow_any_container, config->allow_any_container ());
+               checked_set (_allow_96khz_audio, config->allow_96khz_audio());
+               checked_set (_use_all_audio_channels, config->use_all_audio_channels());
+               checked_set (_show_experimental_audio_processors, config->show_experimental_audio_processors ());
                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_threed, config->log_types() & LogEntry::TYPE_DEBUG_THREE_D);
                checked_set (_log_debug_encode, config->log_types() & LogEntry::TYPE_DEBUG_ENCODE);
                checked_set (_log_debug_email, config->log_types() & LogEntry::TYPE_DEBUG_EMAIL);
+               checked_set (_log_debug_video_view, config->log_types() & LogEntry::TYPE_DEBUG_VIDEO_VIEW);
+               checked_set (_log_debug_player, config->log_types() & LogEntry::TYPE_DEBUG_PLAYER);
+               checked_set (_log_debug_audio_analysis, config->log_types() & LogEntry::TYPE_DEBUG_AUDIO_ANALYSIS);
+               checked_set (_frames_in_memory_multiplier, config->frames_in_memory_multiplier());
 #ifdef DCPOMATIC_WINDOWS
                checked_set (_win32_console, config->win32_console());
 #endif
@@ -1830,27 +1660,61 @@ private:
 
        void maximum_j2k_bandwidth_changed ()
        {
-               Config::instance()->set_maximum_j2k_bandwidth (_maximum_j2k_bandwidth->GetValue() * 1000000);
+               Config::instance()->set_maximum_j2k_bandwidth(_maximum_j2k_bandwidth->GetValue() * 1000000);
+       }
+
+       void video_display_mode_changed ()
+       {
+               if (_video_display_mode->GetSelection() == 0) {
+                       Config::instance()->set_video_view_type(Config::VIDEO_VIEW_SIMPLE);
+               } else {
+                       Config::instance()->set_video_view_type(Config::VIDEO_VIEW_OPENGL);
+               }
+       }
+
+       void frames_in_memory_multiplier_changed ()
+       {
+               Config::instance()->set_frames_in_memory_multiplier(_frames_in_memory_multiplier->GetValue());
        }
 
        void allow_any_dcp_frame_rate_changed ()
        {
-               Config::instance()->set_allow_any_dcp_frame_rate (_allow_any_dcp_frame_rate->GetValue ());
+               Config::instance()->set_allow_any_dcp_frame_rate(_allow_any_dcp_frame_rate->GetValue());
+       }
+
+       void allow_any_container_changed ()
+       {
+               Config::instance()->set_allow_any_container(_allow_any_container->GetValue());
+       }
+
+       void allow_96khz_audio_changed ()
+       {
+               Config::instance()->set_allow_96hhz_audio(_allow_96khz_audio->GetValue());
+       }
+
+       void use_all_channels_changed ()
+       {
+               Config::instance()->set_use_all_audio_channels(_use_all_audio_channels->GetValue());
+       }
+
+       void show_experimental_audio_processors_changed ()
+       {
+               Config::instance()->set_show_experimental_audio_processors(_show_experimental_audio_processors->GetValue());
        }
 
        void only_servers_encode_changed ()
        {
-               Config::instance()->set_only_servers_encode (_only_servers_encode->GetValue ());
+               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 ());
+               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 ());
+               Config::instance()->set_dcp_asset_filename_format(_dcp_asset_filename_format->get());
        }
 
        void log_changed ()
@@ -1868,8 +1732,8 @@ private:
                if (_log_timing->GetValue ()) {
                        types |= LogEntry::TYPE_TIMING;
                }
-               if (_log_debug_decode->GetValue ()) {
-                       types |= LogEntry::TYPE_DEBUG_DECODE;
+               if (_log_debug_threed->GetValue ()) {
+                       types |= LogEntry::TYPE_DEBUG_THREE_D;
                }
                if (_log_debug_encode->GetValue ()) {
                        types |= LogEntry::TYPE_DEBUG_ENCODE;
@@ -1877,57 +1741,80 @@ private:
                if (_log_debug_email->GetValue ()) {
                        types |= LogEntry::TYPE_DEBUG_EMAIL;
                }
+               if (_log_debug_video_view->GetValue()) {
+                       types |= LogEntry::TYPE_DEBUG_VIDEO_VIEW;
+               }
+               if (_log_debug_player->GetValue()) {
+                       types |= LogEntry::TYPE_DEBUG_PLAYER;
+               }
+               if (_log_debug_audio_analysis->GetValue()) {
+                       types |= LogEntry::TYPE_DEBUG_AUDIO_ANALYSIS;
+               }
                Config::instance()->set_log_types (types);
        }
 
 #ifdef DCPOMATIC_WINDOWS
        void win32_console_changed ()
        {
-               Config::instance()->set_win32_console (_win32_console->GetValue ());
+               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;
+       wxSpinCtrl* _maximum_j2k_bandwidth = nullptr;
+       wxChoice* _video_display_mode = nullptr;
+       wxSpinCtrl* _frames_in_memory_multiplier = nullptr;
+       wxCheckBox* _allow_any_dcp_frame_rate = nullptr;
+       wxCheckBox* _allow_any_container = nullptr;
+       wxCheckBox* _allow_96khz_audio = nullptr;
+       wxCheckBox* _use_all_audio_channels = nullptr;
+       wxCheckBox* _show_experimental_audio_processors = nullptr;
+       wxCheckBox* _only_servers_encode = nullptr;
+       NameFormatEditor* _dcp_metadata_filename_format = nullptr;
+       NameFormatEditor* _dcp_asset_filename_format = nullptr;
+       wxCheckBox* _log_general = nullptr;
+       wxCheckBox* _log_warning = nullptr;
+       wxCheckBox* _log_error = nullptr;
+       wxCheckBox* _log_timing = nullptr;
+       wxCheckBox* _log_debug_threed = nullptr;
+       wxCheckBox* _log_debug_encode = nullptr;
+       wxCheckBox* _log_debug_email = nullptr;
+       wxCheckBox* _log_debug_video_view = nullptr;
+       wxCheckBox* _log_debug_player = nullptr;
+       wxCheckBox* _log_debug_audio_analysis = nullptr;
 #ifdef DCPOMATIC_WINDOWS
-       wxCheckBox* _win32_console;
+       wxCheckBox* _win32_console = nullptr;
 #endif
 };
 
+
 wxPreferencesEditor*
 create_full_config_dialog ()
 {
-       wxPreferencesEditor* e = new wxPreferencesEditor ();
+       auto e = new wxPreferencesEditor ();
 
 #ifdef DCPOMATIC_OSX
        /* Width that we force some of the config panels to be on OSX so that
           the containing window doesn't shrink too much when we select those panels.
           This is obviously an unpleasant hack.
        */
-       wxSize ps = wxSize (520, -1);
+       wxSize ps = wxSize (750, -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 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));
+       e->AddPage (new FullGeneralPage    (ps, border));
+       e->AddPage (new SoundPage          (ps, border));
+       e->AddPage (new DefaultsPage       (ps, border));
+       e->AddPage (new EncodingServersPage(ps, border));
+       e->AddPage (new KeysPage           (ps, border));
+       e->AddPage (new TMSPage            (ps, border));
+       e->AddPage (new EmailPage          (ps, border));
+       e->AddPage (new KDMEmailPage       (ps, border));
+       e->AddPage (new NotificationsPage  (ps, border));
+       e->AddPage (new CoverSheetPage     (ps, border));
+       e->AddPage (new IdentifiersPage    (ps, border));
+       e->AddPage (new AdvancedPage       (ps, border));
        return e;
 }