Move some settings to new non-standard page.
[dcpomatic.git] / src / wx / full_config_dialog.cc
index 56a765af297c9203e9bdb6fd3ab57112c44ad126..4dff7b5bf780cb9916f78e55dcc614e65307335d 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 "dcpomatic_choice.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 "language_tag_widget.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 "config_dialog.h"
+#include "name_format_editor.h"
+#include "password_entry.h"
+#include "region_subtag_widget.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 FullGeneralPage : public GeneralPage
 {
@@ -84,9 +95,9 @@ public:
        {}
 
 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;
@@ -103,63 +114,56 @@ 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));
+               auto export_cinemas = new Button (_panel, _("Export..."));
+               table->Add (export_cinemas, wxGBPosition (r, 2));
                ++r;
 
-               add_play_sound_controls (table, r);
+               add_label_to_sizer(table, _panel, _("Default \"add file\" location"), true, wxGBPosition(r, 0));
+               _default_add_file_location = new Choice(_panel);
+               table->Add(_default_add_file_location, 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;
 
                add_update_controls (table, r);
 
-               wxFlexGridSizer* bottom_table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
-               bottom_table->AddGrowableCol (1, 1);
-
-               add_label_to_sizer (bottom_table, _panel, _("Issuer"), true);
-               _issuer = new wxTextCtrl (_panel, wxID_ANY);
-               bottom_table->Add (_issuer, 1, wxALL | wxEXPAND);
-
-               add_label_to_sizer (bottom_table, _panel, _("Creator"), true);
-               _creator = new wxTextCtrl (_panel, wxID_ANY);
-               bottom_table->Add (_creator, 1, wxALL | wxEXPAND);
-
-               table->Add (bottom_table, wxGBPosition (r, 0), wxGBSpan (2, 2), wxEXPAND);
-               ++r;
+               _default_add_file_location->add(_("Same place as last time"));
+               _default_add_file_location->add(_("Same place as project"));
+               _default_add_file_location->bind(&FullGeneralPage::default_add_file_location_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));
+               _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 (&FullGeneralPage::master_encoding_threads_changed, this));
                _server_encoding_threads->SetRange (1, 128);
                _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 (&FullGeneralPage::analyse_ebur128_changed, this));
+               _analyse_ebur128->bind(&FullGeneralPage::analyse_ebur128_changed, this);
 #endif
-               _automatic_audio_analysis->Bind (wxEVT_CHECKBOX, boost::bind (&FullGeneralPage::automatic_audio_analysis_changed, this));
-
-               _issuer->Bind (wxEVT_TEXT, boost::bind (&FullGeneralPage::issuer_changed, this));
-               _creator->Bind (wxEVT_TEXT, boost::bind (&FullGeneralPage::creator_changed, this));
+               _automatic_audio_analysis->bind(&FullGeneralPage::automatic_audio_analysis_changed, this);
        }
 
-       void config_changed ()
+       void config_changed () override
        {
-               Config* config = Config::instance ();
+               auto config = Config::instance ();
 
                checked_set (_master_encoding_threads, config->master_encoding_threads ());
                checked_set (_server_encoding_threads, config->server_encoding_threads ());
@@ -167,66 +171,65 @@ private:
                checked_set (_analyse_ebur128, config->analyse_ebur128 ());
 #endif
                checked_set (_automatic_audio_analysis, config->automatic_audio_analysis ());
-               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(_default_add_file_location, config->default_add_file_location() == Config::DefaultAddFileLocation::SAME_AS_LAST_TIME ? 0 : 1);
 
                GeneralPage::config_changed ();
        }
 
+       void export_cinemas_file ()
+       {
+               wxFileDialog dialog(
+                       _panel, _("Select Cinemas File"), wxEmptyString, wxEmptyString, wxT("XML files (*.xml)|*.xml"),
+                       wxFD_SAVE | wxFD_OVERWRITE_PROMPT
+                );
+
+               if (dialog.ShowModal() == wxID_OK) {
+                       boost::filesystem::copy_file(Config::instance()->cinemas_file(), wx_to_std(dialog.GetPath()), boost::filesystem::copy_option::overwrite_if_exists);
+               }
+       }
 
 #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 ());
+               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);
-                       if (d->ShowModal() == wxID_OK) {
+                       ConfigMoveDialog dialog(_panel, new_file);
+                       if (dialog.ShowModal() == wxID_OK) {
                                copy_and_link = false;
                        }
-                       d->Destroy ();
                }
 
                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 {
@@ -239,54 +242,61 @@ private:
                Config::instance()->set_cinemas_file (wx_to_std (_cinemas_file->GetPath ()));
        }
 
+       void default_add_file_location_changed()
+       {
+               Config::instance()->set_default_add_file_location(
+                       _default_add_file_location->get().get_value_or(0) == 0 ? Config::DefaultAddFileLocation::SAME_AS_LAST_TIME : Config::DefaultAddFileLocation::SAME_AS_PROJECT
+                       );
+       }
+
+       Choice* _default_add_file_location;
        wxSpinCtrl* _master_encoding_threads;
        wxSpinCtrl* _server_encoding_threads;
        FilePickerCtrl* _config_file;
        FilePickerCtrl* _cinemas_file;
 #ifdef DCPOMATIC_HAVE_EBUR128_PATCHED_FFMPEG
-       wxCheckBox* _analyse_ebur128;
+       CheckBox* _analyse_ebur128;
 #endif
-       wxCheckBox* _automatic_audio_analysis;
-       wxTextCtrl* _issuer;
-       wxTextCtrl* _creator;
+       CheckBox* _automatic_audio_analysis;
 };
 
-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(icon_path("defaults"), 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
@@ -294,49 +304,59 @@ 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);
-               _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);
+               _enable_audio_language = new CheckBox(_panel, _("Default audio language"));
+               table->Add(_enable_audio_language, 1, wxEXPAND | wxALIGN_CENTRE_VERTICAL);
+               _audio_language = new LanguageTagWidget(_panel, _("Default audio language to use for new DCPs"), Config::instance()->default_audio_language(), wxString("cmnr-Hant-"));
+               table->Add(_audio_language->sizer());
+
+               _enable_territory = new CheckBox(_panel, _("Default territory"));
+               table->Add(_enable_territory, 1, wxEXPAND | wxALIGN_CENTRE_VERTICAL);
+               _territory = new RegionSubtagWidget(_panel, _("Default territory to use for new DCPs"), Config::instance()->default_territory(), wxString("cmnr-Hant-"));
+               table->Add(_territory->sizer());
+
+               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
@@ -344,29 +364,37 @@ 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));
-
-               BOOST_FOREACH (Ratio const * i, Ratio::containers()) {
-                       _container->Append (std_to_wx(i->container_nickname()));
-               }
-
-               _container->Bind (wxEVT_CHOICE, boost::bind (&DefaultsPage::container_changed, this));
-
-               _scale_to->Append (_("Guess from content"));
-
-               BOOST_FOREACH (Ratio const * i, Ratio::all()) {
-                       _scale_to->Append (std_to_wx(i->image_nickname()));
-               }
+               _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));
 
-               _scale_to->Bind (wxEVT_CHOICE, boost::bind (&DefaultsPage::scale_to_changed, this));
+               _use_isdcf_name_by_default->bind(&DefaultsPage::use_isdcf_name_by_default_changed, this);
 
-               BOOST_FOREACH (DCPContentType const * i, DCPContentType::all()) {
+               for (auto i: DCPContentType::all()) {
                        _dcp_content_type->Append (std_to_wx (i->pretty_name ()));
                }
 
@@ -384,33 +412,29 @@ private:
                _standard->Append (_("SMPTE"));
                _standard->Append (_("Interop"));
                _standard->Bind (wxEVT_CHOICE, boost::bind (&DefaultsPage::standard_changed, this));
-       }
-
-       void config_changed ()
-       {
-               Config* config = Config::instance ();
 
-               vector<Ratio const *> containers = Ratio::containers ();
-               for (size_t i = 0; i < containers.size(); ++i) {
-                       if (containers[i] == config->default_container ()) {
-                               _container->SetSelection (i);
-                       }
+               for (auto const& i: _enable_metadata) {
+                       i.second->bind(&DefaultsPage::metadata_changed, this);
                }
 
-               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);
-                       }
+               for (auto const& i: _metadata) {
+                       i.second->Bind (wxEVT_TEXT, boost::bind(&DefaultsPage::metadata_changed, this));
                }
 
-               if (!config->default_scale_to()) {
-                       _scale_to->SetSelection (0);
-               }
+               _enable_audio_language->bind(&DefaultsPage::enable_audio_language_toggled, this);
+               _audio_language->Changed.connect(boost::bind(&DefaultsPage::audio_language_changed, this));
+
+               _enable_territory->bind(&DefaultsPage::enable_territory_toggled, this);
+               _territory->Changed.connect(boost::bind(&DefaultsPage::territory_changed, this));
+       }
+
+       void config_changed () override
+       {
+               auto config = Config::instance ();
 
-               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);
                        }
                }
@@ -418,11 +442,82 @@ 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 dal = config->default_audio_language();
+               checked_set(_enable_audio_language, static_cast<bool>(dal));
+               checked_set(_audio_language, dal ? dal : boost::none);
+               auto dt = config->default_territory();
+               checked_set(_enable_territory, static_cast<bool>(dt));
+               checked_set(_territory, dt ? dt : boost::none);
+
+               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 ()
@@ -440,7 +535,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)))
                                );
                }
        }
@@ -455,12 +550,14 @@ private:
                Config::instance()->set_default_kdm_directory (wx_to_std (_kdm_directory->GetPath ()));
        }
 
-       void edit_isdcf_metadata_clicked ()
+       void kdm_type_changed ()
+       {
+               Config::instance()->set_default_kdm_type(_kdm_type->get());
+       }
+
+       void use_isdcf_name_by_default_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_use_isdcf_name_by_default (_use_isdcf_name_by_default->GetValue());
        }
 
        void still_length_changed ()
@@ -468,37 +565,70 @@ private:
                Config::instance()->set_default_still_length (_still_length->GetValue ());
        }
 
-       void container_changed ()
+       void dcp_content_type_changed ()
+       {
+               auto ct = DCPContentType::all ();
+               Config::instance()->set_default_dcp_content_type (ct[_dcp_content_type->GetSelection()]);
+       }
+
+       void standard_changed ()
+       {
+               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 enable_audio_language_toggled()
        {
-               vector<Ratio const *> ratio = Ratio::containers ();
-               Config::instance()->set_default_container (ratio[_container->GetSelection()]);
+               setup_sensitivity();
+               audio_language_changed();
        }
 
-       void scale_to_changed ()
+       void audio_language_changed()
        {
-               int const s = _scale_to->GetSelection ();
-               if (s == 0) {
-                       Config::instance()->set_default_scale_to (0);
+               if (_enable_audio_language->get()) {
+                       Config::instance()->set_default_audio_language(_audio_language->get().get_value_or(dcp::LanguageTag("en-US")));
                } else {
-                       vector<Ratio const *> ratio = Ratio::all ();
-                       Config::instance()->set_default_scale_to (ratio[s - 1]);
+                       Config::instance()->unset_default_audio_language();
                }
        }
 
-       void dcp_content_type_changed ()
+       void enable_territory_toggled()
        {
-               vector<DCPContentType const *> ct = DCPContentType::all ();
-               Config::instance()->set_default_dcp_content_type (ct[_dcp_content_type->GetSelection()]);
+               setup_sensitivity();
+               territory_changed();
        }
 
-       void standard_changed ()
+       void territory_changed()
        {
-               Config::instance()->set_default_interop (_standard->GetSelection() == 1);
+               if (_enable_territory->get()) {
+                       Config::instance()->set_default_territory(_territory->get().get_value_or(dcp::LanguageTag::RegionSubtag("US")));
+               } else {
+                       Config::instance()->unset_default_territory();
+               }
+       }
+
+       void setup_sensitivity ()
+       {
+               _audio_language->enable(_enable_audio_language->get());
+               _territory->enable(_enable_territory->get());
+               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;
@@ -507,54 +637,65 @@ private:
        wxDirPickerCtrl* _directory;
        wxDirPickerCtrl* _kdm_directory;
 #endif
-       wxChoice* _container;
-       wxChoice* _scale_to;
+       KDMChoice* _kdm_type;
+       wxSpinCtrl* _kdm_duration;
+       wxChoice* _kdm_duration_unit;
+       CheckBox* _use_isdcf_name_by_default;
        wxChoice* _dcp_content_type;
        wxChoice* _dcp_audio_channels;
        wxChoice* _standard;
+       CheckBox* _enable_audio_language;
+       LanguageTagWidget* _audio_language;
+       CheckBox* _enable_territory;
+       RegionSubtagWidget* _territory;
+       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(icon_path("servers"), 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),
+                       EditableListTitle::INVISIBLE,
+                       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(&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 ();
@@ -570,875 +711,916 @@ private:
                return s;
        }
 
-       wxCheckBox* _use_any_servers;
+       CheckBox* _use_any_servers;
        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);
+       TMSPage (wxSize panel_size, int border)
+               : Page (panel_size, border)
+       {}
 
-               {
-                       wxStaticText* m = new wxStaticText (this, wxID_ANY, title);
-                       m->SetFont (subheading_font);
-                       _sizer->Add (m, 0, wxALL, border);
-               }
+       wxString GetName () const override
+       {
+               return _("TMS");
+       }
 
-               wxBoxSizer* certificates_sizer = new wxBoxSizer (wxHORIZONTAL);
-               _sizer->Add (certificates_sizer, 0, wxLEFT | wxRIGHT, border);
+#ifdef DCPOMATIC_OSX
+       wxBitmap GetLargeIcon () const override
+       {
+               return wxBitmap(icon_path("tms"), wxBITMAP_TYPE_PNG);
+       }
+#endif
 
-               _certificates = new wxListCtrl (this, wxID_ANY, wxDefaultPosition, wxSize (440, 150), wxLC_REPORT | wxLC_SINGLE_SEL);
+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 (0);
-                       ip.SetText (_("Type"));
-                       ip.SetWidth (100);
-                       _certificates->InsertColumn (0, ip);
-               }
+               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);
 
-               {
-                       wxListItem ip;
-                       ip.SetId (1);
-                       ip.SetText (_("Thumbprint"));
-                       ip.SetWidth (340);
+               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);
 
-                       wxFont font = ip.GetFont ();
-                       font.SetFamily (wxFONTFAMILY_TELETYPE);
-                       ip.SetFont (font);
+               _tms_passive = new CheckBox(_panel, _("Passive mode"));
+               table->Add(_tms_passive, 1, wxEXPAND);
+               table->AddSpacer(0);
 
-                       _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;
+               _tms_protocol->Append (_("SCP (for AAM and Doremi)"));
+               _tms_protocol->Append (_("FTP (for Dolby)"));
 
-               _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;
+               _upload->bind(&TMSPage::upload_changed, this);
+               _tms_protocol->Bind (wxEVT_CHOICE, boost::bind (&TMSPage::tms_protocol_changed, this));
+               _tms_passive->bind(&TMSPage::tms_passive_changed, this);
 
-               _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;
+               _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 config_changed () override
+       {
+               auto config = Config::instance ();
 
-               _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));
+               checked_set (_upload, config->upload_after_make_dcp());
+               checked_set (_tms_protocol, static_cast<int>(config->tms_protocol()));
+               checked_set(_tms_passive, config->tms_protocol() == FileTransferProtocol::FTP && config->tms_passive());
+               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 ());
 
-               SetSizerAndFit (_sizer);
+               _tms_passive->Enable(config->tms_protocol() == FileTransferProtocol::FTP);
        }
 
-       void config_changed ()
+       void upload_changed ()
        {
-               _chain.reset (new dcp::CertificateChain (*_get().get ()));
+               Config::instance()->set_upload_after_make_dcp (_upload->GetValue());
+       }
 
-               update_certificate_list ();
-               update_private_key ();
-               update_sensitivity ();
+       void tms_protocol_changed ()
+       {
+               Config::instance()->set_tms_protocol(static_cast<FileTransferProtocol>(_tms_protocol->GetSelection()));
        }
 
-       void add_button (wxWindow* button)
+       void tms_passive_changed()
        {
-               _button_sizer->Add (button, 0, wxLEFT | wxRIGHT, DCPOMATIC_SIZER_X_GAP);
-               _sizer->Layout ();
+               Config::instance()->set_tms_passive(_tms_passive->get());
        }
 
-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 ()));
-                       }
-               }
+       void tms_ip_changed ()
+       {
+               Config::instance()->set_tms_ip (wx_to_std (_tms_ip->GetValue ()));
+       }
 
-               d->Destroy ();
+       void tms_path_changed ()
+       {
+               Config::instance()->set_tms_path (wx_to_std (_tms_path->GetValue ()));
+       }
 
-               update_sensitivity ();
+       void tms_user_changed ()
+       {
+               Config::instance()->set_tms_user (wx_to_std (_tms_user->GetValue ()));
        }
 
-       void remove_certificate ()
+       void tms_password_changed ()
        {
-               int i = _certificates->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
-               if (i == -1) {
-                       return;
-               }
+               Config::instance()->set_tms_password (_tms_password->get());
+       }
+
+       CheckBox* _upload;
+       CheckBox* _tms_passive;
+       wxChoice* _tms_protocol;
+       wxTextCtrl* _tms_ip;
+       wxTextCtrl* _tms_path;
+       wxTextCtrl* _tms_user;
+       PasswordEntry* _tms_password;
+};
 
-               _certificates->DeleteItem (i);
-               _chain->remove (i);
-               _set (_chain);
 
-               update_sensitivity ();
-       }
+class EmailPage : public Page
+{
+public:
+       EmailPage (wxSize panel_size, int border)
+               : Page (panel_size, border)
+       {}
 
-       void export_certificate ()
+       wxString GetName () const 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
-                       );
+               return _("Email");
+       }
 
-               dcp::CertificateChain::List all = _chain->root_to_leaf ();
-               dcp::CertificateChain::List::iterator j = all.begin ();
-               for (int k = 0; k < i; ++k) {
-                       ++j;
-               }
+#ifdef DCPOMATIC_OSX
+       wxBitmap GetLargeIcon () const override
+       {
+               return wxBitmap(icon_path("email"), wxBITMAP_TYPE_PNG);
+       }
+#endif
 
-               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);
-                       }
+private:
+       void setup () override
+       {
+               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);
 
-                       string const s = j->certificate (true);
-                       fwrite (s.c_str(), 1, s.length(), f);
-                       fclose (f);
+               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);
                }
-               d->Destroy ();
-       }
-
-       void update_certificate_list ()
-       {
-               _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;
-               }
+               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);
 
-               static wxColour normal = _private_key_bad->GetForegroundColour ();
+               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);
 
-               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));
+               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 remake_certificates ()
+       void server_changed ()
        {
-               shared_ptr<const dcp::CertificateChain> chain = _get ();
+               Config::instance()->set_mail_server(wx_to_std(_server->GetValue()));
+       }
 
-               string subject_organization_name;
-               string subject_organizational_unit_name;
-               string root_common_name;
-               string intermediate_common_name;
-               string leaf_common_name;
+       void port_changed ()
+       {
+               Config::instance()->set_mail_port(_port->GetValue());
+       }
 
-               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 ();
+       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 update_sensitivity ()
+       void user_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_mail_user (wx_to_std (_user->GetValue ()));
        }
 
-       void update_private_key ()
+       void password_changed ()
        {
-               checked_set (_private_key, dcp::private_key_fingerprint (_chain->key().get ()));
-               _sizer->Layout ();
+               Config::instance()->set_mail_password(_password->get());
        }
 
-       void load_private_key ()
+       void send_test_email_clicked ()
        {
-               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 ();
-       }
-
-       void export_private_key ()
-       {
-               optional<string> key = _chain->key ();
-               if (!key) {
+               SendTestEmailDialog dialog(_panel);
+               if (dialog.ShowModal() != wxID_OK) {
                        return;
                }
 
-               wxFileDialog* d = new wxFileDialog (
-                       this, _("Select Key File"), wxEmptyString, wxEmptyString, wxT ("PEM files (*.pem)|*.pem"),
-                       wxFD_SAVE | wxFD_OVERWRITE_PROMPT
+               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."))
                        );
-
-               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);
+               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;
                }
-               d->Destroy ();
-       }
-
-       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;
+               message_dialog(_panel, _("Test email sent."));
+       }
+
+       wxTextCtrl* _server;
+       wxSpinCtrl* _port;
+       wxChoice* _protocol;
+       wxTextCtrl* _user;
+       PasswordEntry* _password;
+       Button* _send_test_email;
 };
 
-class KeysPage : public StandardPage
+
+class KDMEmailPage : public Page
 {
 public:
-       KeysPage (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 _("Keys");
+               return _("KDM Email");
        }
 
 #ifdef DCPOMATIC_OSX
-       wxBitmap GetLargeIcon () const
+       wxBitmap GetLargeIcon () const override
        {
-               return wxBitmap ("keys", wxBITMAP_TYPE_PNG_RESOURCE);
+               return wxBitmap(icon_path("kdm_email"), 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)
-                       );
+               auto table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
+               table->AddGrowableCol (1, 1);
+               _panel->GetSizer()->Add (table, 0, wxEXPAND | wxALL, _border);
+
+               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);
 
-               _panel->GetSizer()->Add (_signer);
+               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);
 
-               _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)
+               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;
+                       },
+                       EditableListTitle::VISIBLE,
+                       EditableListButton::NEW | EditableListButton::EDIT | EditableListButton::REMOVE
                        );
+               table->Add (_cc, 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);
+
+               _email = new wxTextCtrl (_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (-1, 200), wxTE_MULTILINE);
+               _panel->GetSizer()->Add (_email, 0, wxEXPAND | wxALL, _border);
 
-               _panel->GetSizer()->Add (_decryption);
+               _reset_email = new Button (_panel, _("Reset to default subject and text"));
+               _panel->GetSizer()->Add (_reset_email, 0, wxEXPAND | wxALL, _border);
 
-               _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);
+               _cc->layout ();
 
-               _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));
+               _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 export_decryption_certificate ()
+       void config_changed () override
        {
-               wxFileDialog* d = new wxFileDialog (
-                       _panel, _("Select Certificate File"), wxEmptyString, wxEmptyString, wxT ("PEM files (*.pem)|*.pem"),
-                       wxFD_SAVE | wxFD_OVERWRITE_PROMPT
-                       );
+               auto config = Config::instance ();
 
-               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()->leaf().certificate (true);
-                       fwrite (s.c_str(), 1, s.length(), f);
-                       fclose (f);
-               }
-               d->Destroy ();
+               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 export_decryption_chain ()
+       void kdm_subject_changed ()
        {
-               wxFileDialog* d = new wxFileDialog (
-                       _panel, _("Select Chain File"), wxEmptyString, wxEmptyString, wxT ("PEM files (*.pem)|*.pem"),
-                       wxFD_SAVE | wxFD_OVERWRITE_PROMPT
-                       );
+               Config::instance()->set_kdm_subject (wx_to_std (_subject->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 kdm_from_changed ()
+       {
+               Config::instance()->set_kdm_from (wx_to_std (_from->GetValue ()));
+       }
 
-                       string const s = Config::instance()->decryption_chain()->chain();
-                       fwrite (s.c_str(), 1, s.length(), f);
-                       fclose (f);
-               }
-               d->Destroy ();
+       void kdm_bcc_changed ()
+       {
+               Config::instance()->set_kdm_bcc (wx_to_std (_bcc->GetValue ()));
        }
 
-       void config_changed ()
+       void kdm_email_changed ()
        {
-               _signer->config_changed ();
-               _decryption->config_changed ();
+               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 nag_remake_decryption_chain ()
+       void reset_email ()
        {
-               NagDialog::maybe_nag (
-                       _panel,
-                       Config::NAG_REMAKE_DECRYPTION_CHAIN,
-                       _("If you continue with this operation you will no longer be able to use any DKDMs that you have created.  Also, any KDMs that have been sent to you will become useless.  Proceed with caution!")
-                       );
+               Config::instance()->reset_kdm_email ();
+               checked_set (_email, Config::instance()->kdm_email ());
        }
 
-       CertificateChainEditor* _signer;
-       CertificateChainEditor* _decryption;
-       wxButton* _export_decryption_certificate;
-       wxButton* _export_decryption_chain;
+       wxTextCtrl* _subject;
+       wxTextCtrl* _from;
+       EditableList<string, EmailDialog>* _cc;
+       wxTextCtrl* _bcc;
+       wxTextCtrl* _email;
+       wxButton* _reset_email;
 };
 
-class TMSPage : public StandardPage
+
+class NotificationsPage : public Page
 {
 public:
-       TMSPage (wxSize panel_size, int border)
-               : StandardPage (panel_size, border)
+       NotificationsPage (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 _("Notifications");
        }
 
 #ifdef DCPOMATIC_OSX
-       wxBitmap GetLargeIcon () const
+       wxBitmap GetLargeIcon () const override
        {
-               return wxBitmap ("tms", wxBITMAP_TYPE_PNG_RESOURCE);
+               return wxBitmap(icon_path("notifications"), 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);
+               _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, _("IP address"), true);
-               _tms_ip = new wxTextCtrl (_panel, wxID_ANY);
-               table->Add (_tms_ip, 1, wxEXPAND);
+               _enable_email = new CheckBox (_panel, _("Email"));
+               table->Add (_enable_email, 1, wxEXPAND | wxALL);
+               table->AddSpacer (0);
 
-               add_label_to_sizer (table, _panel, _("Target path"), true);
-               _tms_path = new wxTextCtrl (_panel, wxID_ANY);
-               table->Add (_tms_path, 1, wxEXPAND);
+               add_label_to_sizer (table, _panel, _("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, _("User name"), true);
-               _tms_user = new wxTextCtrl (_panel, wxID_ANY);
-               table->Add (_tms_user, 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, _("Password"), true);
-               _tms_password = new wxTextCtrl (_panel, wxID_ANY);
-               table->Add (_tms_password, 1, wxEXPAND);
+               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);
 
-               _tms_protocol->Append (_("SCP (for AAM and Doremi)"));
-               _tms_protocol->Append (_("FTP (for Dolby)"));
+               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::notification_cc, Config::instance()),
+                       bind (&Config::set_notification_cc, Config::instance(), _1),
+                       [] (string s, int) {
+                               return s;
+                       },
+                       EditableListTitle::VISIBLE,
+                       EditableListButton::NEW | EditableListButton::EDIT | EditableListButton::REMOVE
+                       );
+               table->Add (_cc, 1, wxEXPAND | wxALL);
 
-               _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));
+               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);
+
+               _email = new wxTextCtrl (_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (-1, 200), wxTE_MULTILINE);
+               _panel->GetSizer()->Add (_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);
+
+               _cc->layout ();
+
+               _enable_message_box->bind(&NotificationsPage::type_changed, this, _enable_message_box, Config::MESSAGE_BOX);
+               _enable_email->bind(&NotificationsPage::type_changed, this, _enable_email, Config::EMAIL);
+
+               _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));
+
+               setup_sensitivity ();
        }
 
-       void config_changed ()
+       void setup_sensitivity ()
        {
-               Config* config = Config::instance ();
+               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);
+       }
 
-               checked_set (_tms_protocol, config->tms_protocol ());
-               checked_set (_tms_ip, config->tms_ip ());
-               checked_set (_tms_path, config->tms_path ());
-               checked_set (_tms_user, config->tms_user ());
-               checked_set (_tms_password, config->tms_password ());
+       void config_changed () override
+       {
+               auto config = Config::instance ();
+
+               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 tms_protocol_changed ()
+       void notification_subject_changed ()
        {
-               Config::instance()->set_tms_protocol (static_cast<Protocol> (_tms_protocol->GetSelection ()));
+               Config::instance()->set_notification_subject(wx_to_std(_subject->GetValue()));
        }
 
-       void tms_ip_changed ()
+       void notification_from_changed ()
        {
-               Config::instance()->set_tms_ip (wx_to_std (_tms_ip->GetValue ()));
+               Config::instance()->set_notification_from(wx_to_std(_from->GetValue()));
        }
 
-       void tms_path_changed ()
+       void notification_to_changed ()
        {
-               Config::instance()->set_tms_path (wx_to_std (_tms_path->GetValue ()));
+               Config::instance()->set_notification_to(wx_to_std(_to->GetValue()));
        }
 
-       void tms_user_changed ()
+       void notification_bcc_changed ()
        {
-               Config::instance()->set_tms_user (wx_to_std (_tms_user->GetValue ()));
+               Config::instance()->set_notification_bcc(wx_to_std(_bcc->GetValue()));
        }
 
-       void tms_password_changed ()
+       void notification_email_changed ()
        {
-               Config::instance()->set_tms_password (wx_to_std (_tms_password->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_notification_email(wx_to_std(_email->GetValue()));
        }
 
-       wxChoice* _tms_protocol;
-       wxTextCtrl* _tms_ip;
-       wxTextCtrl* _tms_path;
-       wxTextCtrl* _tms_user;
-       wxTextCtrl* _tms_password;
+       void reset_email ()
+       {
+               Config::instance()->reset_notification_email();
+               checked_set (_email, Config::instance()->notification_email());
+       }
+
+       void type_changed (CheckBox* b, Config::Notification n)
+       {
+               Config::instance()->set_notification(n, b->GetValue());
+               setup_sensitivity ();
+       }
+
+       CheckBox* _enable_message_box;
+       CheckBox* _enable_email;
+
+       wxTextCtrl* _subject;
+       wxTextCtrl* _from;
+       wxTextCtrl* _to;
+       EditableList<string, EmailDialog>* _cc;
+       wxTextCtrl* _bcc;
+       wxTextCtrl* _email;
+       wxButton* _reset_email;
 };
 
-static string
-column (string s)
-{
-       return s;
-}
 
-class KDMEmailPage : public StandardPage
+class CoverSheetPage : public Page
 {
 public:
 
-       KDMEmailPage (wxSize panel_size, int border)
+       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 _("KDM Email");
+               return _("Cover Sheet");
        }
 
 #ifdef DCPOMATIC_OSX
-       wxBitmap GetLargeIcon () const
+       wxBitmap GetLargeIcon () const override
        {
-               return wxBitmap ("kdm_email", wxBITMAP_TYPE_PNG_RESOURCE);
+               return wxBitmap(icon_path("cover_sheet"), wxBITMAP_TYPE_PNG);
        }
 #endif
 
 private:
-       void setup ()
+       void setup () override
        {
-               wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
-               table->AddGrowableCol (1, 1);
-               _panel->GetSizer()->Add (table, 1, wxEXPAND | wxALL, _border);
-
-               add_label_to_sizer (table, _panel, _("Outgoing mail server"), true);
-               {
-                       wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
-                       _mail_server = new wxTextCtrl (_panel, wxID_ANY);
-                       s->Add (_mail_server, 1, wxEXPAND | wxALL);
-                       add_label_to_sizer (s, _panel, _("port"), false);
-                       _mail_port = new wxSpinCtrl (_panel, wxID_ANY);
-                       _mail_port->SetRange (0, 65535);
-                       s->Add (_mail_port);
-                       table->Add (s, 1, wxEXPAND | wxALL);
-               }
-
-               add_label_to_sizer (table, _panel, _("Mail user name"), true);
-               _mail_user = new wxTextCtrl (_panel, wxID_ANY);
-               table->Add (_mail_user, 1, wxEXPAND | wxALL);
+               _cover_sheet = new wxTextCtrl (_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (-1, 200), wxTE_MULTILINE);
+               _panel->GetSizer()->Add (_cover_sheet, 0, wxEXPAND | wxALL, _border);
 
-               add_label_to_sizer (table, _panel, _("Mail password"), true);
-               _mail_password = new wxTextCtrl (_panel, wxID_ANY);
-               table->Add (_mail_password, 1, wxEXPAND | wxALL);
+               _reset_cover_sheet = new Button (_panel, _("Reset to default text"));
+               _panel->GetSizer()->Add (_reset_cover_sheet, 0, wxEXPAND | wxALL, _border);
 
-               add_label_to_sizer (table, _panel, _("Subject"), true);
-               _kdm_subject = new wxTextCtrl (_panel, wxID_ANY);
-               table->Add (_kdm_subject, 1, wxEXPAND | wxALL);
+               _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));
+       }
 
-               add_label_to_sizer (table, _panel, _("From address"), true);
-               _kdm_from = new wxTextCtrl (_panel, wxID_ANY);
-               table->Add (_kdm_from, 1, wxEXPAND | wxALL);
+       void config_changed () override
+       {
+               checked_set (_cover_sheet, Config::instance()->cover_sheet());
+       }
 
-               vector<string> columns;
-               columns.push_back (wx_to_std (_("Address")));
-               add_label_to_sizer (table, _panel, _("CC addresses"), true);
-               _kdm_cc = new EditableList<string, EmailDialog> (
-                       _panel,
-                       columns,
-                       bind (&Config::kdm_cc, Config::instance()),
-                       bind (&Config::set_kdm_cc, Config::instance(), _1),
-                       bind (&column, _1)
-                       );
-               table->Add (_kdm_cc, 1, wxEXPAND | wxALL);
+       void cover_sheet_changed ()
+       {
+               if (_cover_sheet->GetValue().IsEmpty ()) {
+                       /* Sometimes we get sent an erroneous notification that the cover sheet
+                          is empty; I don't know why.
+                       */
+                       return;
+               }
+               Config::instance()->set_cover_sheet(wx_to_std(_cover_sheet->GetValue()));
+       }
 
-               add_label_to_sizer (table, _panel, _("BCC address"), true);
-               _kdm_bcc = new wxTextCtrl (_panel, wxID_ANY);
-               table->Add (_kdm_bcc, 1, wxEXPAND | wxALL);
+       void reset_cover_sheet ()
+       {
+               Config::instance()->reset_cover_sheet();
+               checked_set (_cover_sheet, Config::instance()->cover_sheet());
+       }
 
-               _kdm_email = new wxTextCtrl (_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (-1, 200), wxTE_MULTILINE);
-               _panel->GetSizer()->Add (_kdm_email, 0, wxEXPAND | wxALL, _border);
+       wxTextCtrl* _cover_sheet;
+       wxButton* _reset_cover_sheet;
+};
 
-               _reset_kdm_email = new wxButton (_panel, wxID_ANY, _("Reset to default subject and text"));
-               _panel->GetSizer()->Add (_reset_kdm_email, 0, wxEXPAND | wxALL, _border);
 
-               _kdm_cc->layout ();
+class IdentifiersPage : public Page
+{
+public:
+       IdentifiersPage (wxSize panel_size, int border)
+               : Page (panel_size, border)
+       {}
 
-               _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));
+       wxString GetName () const override
+       {
+               return _("Identifiers");
        }
 
-       void config_changed ()
+#ifdef DCPOMATIC_OSX
+       wxBitmap GetLargeIcon () const override
        {
-               Config* config = Config::instance ();
-
-               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 ());
+               return wxBitmap(icon_path("identifiers"), wxBITMAP_TYPE_PNG);
        }
+#endif
 
-       void mail_server_changed ()
+private:
+       void setup () override
        {
-               Config::instance()->set_mail_server (wx_to_std (_mail_server->GetValue ()));
+               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 mail_port_changed ()
+       void config_changed () override
        {
-               Config::instance()->set_mail_port (_mail_port->GetValue ());
+               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 mail_user_changed ()
+       void issuer_changed ()
        {
-               Config::instance()->set_mail_user (wx_to_std (_mail_user->GetValue ()));
+               Config::instance()->set_dcp_issuer(wx_to_std(_issuer->GetValue()));
        }
 
-       void mail_password_changed ()
+       void creator_changed ()
        {
-               Config::instance()->set_mail_password (wx_to_std (_mail_password->GetValue ()));
+               Config::instance()->set_dcp_creator(wx_to_std(_creator->GetValue()));
        }
 
-       void kdm_subject_changed ()
+       void company_name_changed ()
        {
-               Config::instance()->set_kdm_subject (wx_to_std (_kdm_subject->GetValue ()));
+               Config::instance()->set_dcp_company_name(wx_to_std(_company_name->GetValue()));
        }
 
-       void kdm_from_changed ()
+       void product_name_changed ()
        {
-               Config::instance()->set_kdm_from (wx_to_std (_kdm_from->GetValue ()));
+               Config::instance()->set_dcp_product_name(wx_to_std(_product_name->GetValue()));
        }
 
-       void kdm_bcc_changed ()
+       void product_version_changed ()
        {
-               Config::instance()->set_kdm_bcc (wx_to_std (_kdm_bcc->GetValue ()));
+               Config::instance()->set_dcp_product_version(wx_to_std(_product_version->GetValue()));
        }
 
-       void kdm_email_changed ()
+       void j2k_comment_changed ()
        {
-               if (_kdm_email->GetValue().IsEmpty ()) {
-                       /* Sometimes we get sent an erroneous notification that the email
-                          is empty; I don't know why.
-                       */
-                       return;
-               }
-               Config::instance()->set_kdm_email (wx_to_std (_kdm_email->GetValue ()));
+               Config::instance()->set_dcp_j2k_comment (wx_to_std(_j2k_comment->GetValue()));
        }
 
-       void reset_kdm_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;
+       wxTextCtrl* _issuer;
+       wxTextCtrl* _creator;
+       wxTextCtrl* _company_name;
+       wxTextCtrl* _product_name;
+       wxTextCtrl* _product_version;
+       wxTextCtrl* _j2k_comment;
 };
 
-class CoverSheetPage : public StandardPage
+
+class NonStandardPage : 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)
-#else
-               : StandardPage (panel_size, border)
-#endif
+       NonStandardPage(wxSize panel_size, int border)
+               : Page(panel_size, border)
        {}
 
-       wxString GetName () const
+       wxString GetName() const override
        {
-               return _("Cover Sheet");
+               return _("Non-standard");
        }
 
 #ifdef DCPOMATIC_OSX
-       wxBitmap GetLargeIcon () const
+       wxBitmap GetLargeIcon() const override
        {
-               return wxBitmap ("cover_sheet", wxBITMAP_TYPE_PNG_RESOURCE);
+               return wxBitmap(icon_path("non_standard"), 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);
+               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);
 
-               _reset_cover_sheet = new wxButton (_panel, wxID_ANY, _("Reset to default text"));
-               _panel->GetSizer()->Add (_reset_cover_sheet, 0, wxEXPAND | wxALL, _border);
+               {
+                       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, 0, wxLEFT | wxALIGN_CENTRE_VERTICAL);
+                       table->Add(s, 1);
+               }
 
-               _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));
+               _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);
+               auto restart = new StaticText(_panel, _("(restart DCP-o-matic to see all ratios)"));
+               auto font = restart->GetFont();
+               font.SetStyle(wxFONTSTYLE_ITALIC);
+               font.SetPointSize(font.GetPointSize() - 1);
+               restart->SetFont(font);
+               table->Add(restart, 1, wxALIGN_CENTRE_VERTICAL | wxBOTTOM, DCPOMATIC_CHECKBOX_BOTTOM_PAD);
+               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);
+
+               _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);
+
+               _maximum_j2k_bandwidth->SetRange(1, 1000);
+               _maximum_j2k_bandwidth->Bind(wxEVT_SPINCTRL, boost::bind(&NonStandardPage::maximum_j2k_bandwidth_changed, this));
+               _allow_any_dcp_frame_rate->bind(&NonStandardPage::allow_any_dcp_frame_rate_changed, this);
+               _allow_any_container->bind(&NonStandardPage::allow_any_container_changed, this);
+               _allow_96khz_audio->bind(&NonStandardPage::allow_96khz_audio_changed, this);
+               _use_all_audio_channels->bind(&NonStandardPage::use_all_channels_changed, this);
        }
 
-       void config_changed ()
+       void config_changed() override
        {
-               checked_set (_cover_sheet, Config::instance()->cover_sheet ());
+               auto config = Config::instance();
+
+               checked_set(_maximum_j2k_bandwidth, config->maximum_j2k_bandwidth() / 1000000);
+               checked_set(_allow_any_dcp_frame_rate, config->allow_any_dcp_frame_rate());
+               checked_set(_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());
        }
 
-       void cover_sheet_changed ()
+       void maximum_j2k_bandwidth_changed()
        {
-               if (_cover_sheet->GetValue().IsEmpty ()) {
-                       /* Sometimes we get sent an erroneous notification that the cover sheet
-                          is empty; I don't know why.
-                       */
-                       return;
-               }
-               Config::instance()->set_cover_sheet (wx_to_std (_cover_sheet->GetValue ()));
+               Config::instance()->set_maximum_j2k_bandwidth(_maximum_j2k_bandwidth->GetValue() * 1000000);
        }
 
-       void reset_cover_sheet ()
+       void allow_any_dcp_frame_rate_changed()
        {
-               Config::instance()->reset_cover_sheet ();
-               checked_set (_cover_sheet, Config::instance()->cover_sheet ());
+               Config::instance()->set_allow_any_dcp_frame_rate(_allow_any_dcp_frame_rate->GetValue());
        }
 
-       wxTextCtrl* _cover_sheet;
-       wxButton* _reset_cover_sheet;
+       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());
+       }
+
+       wxSpinCtrl* _maximum_j2k_bandwidth = nullptr;
+       CheckBox* _allow_any_dcp_frame_rate = nullptr;
+       CheckBox* _allow_any_container = nullptr;
+       CheckBox* _allow_96khz_audio = nullptr;
+       CheckBox* _use_all_audio_channels = nullptr;
 };
 
 
+
 /** @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(icon_path("advanced"), wxBITMAP_TYPE_PNG);
+       }
+#endif
+
 private:
        void add_top_aligned_label_to_sizer (wxSizer* table, wxWindow* parent, wxString text)
        {
@@ -1447,37 +1629,54 @@ 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);
-                       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);
-                       table->Add (s, 1);
-               }
+               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);
 
-               _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);
+               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);
 
-               _only_servers_encode = new wxCheckBox (_panel, wxID_ANY, _("Only servers encode"));
-               table->Add (_only_servers_encode, 1, wxEXPAND | wxALL);
+               _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_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);
+               }
+
                {
-                       add_top_aligned_label_to_sizer (table, _panel, _("DCP metadata filename format"));
+                       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 (
@@ -1487,12 +1686,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";
@@ -1506,91 +1712,124 @@ 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));
-               _allow_any_dcp_frame_rate->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::allow_any_dcp_frame_rate_changed, this));
-               _only_servers_encode->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::only_servers_encode_changed, this));
+               _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));
+               _show_experimental_audio_processors->bind(&AdvancedPage::show_experimental_audio_processors_changed, this);
+               _only_servers_encode->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_encode->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::log_changed, this));
-               _log_debug_email->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::log_changed, this));
+               _log_general->bind(&AdvancedPage::log_changed, this);
+               _log_warning->bind(&AdvancedPage::log_changed, this);
+               _log_error->bind(&AdvancedPage::log_changed, this);
+               _log_timing->bind(&AdvancedPage::log_changed, this);
+               _log_debug_threed->bind(&AdvancedPage::log_changed, this);
+               _log_debug_encode->bind(&AdvancedPage::log_changed, this);
+               _log_debug_email->bind(&AdvancedPage::log_changed, this);
+               _log_debug_video_view->bind(&AdvancedPage::log_changed, this);
+               _log_debug_player->bind(&AdvancedPage::log_changed, this);
+               _log_debug_audio_analysis->bind(&AdvancedPage::log_changed, this);
 #ifdef DCPOMATIC_WINDOWS
-               _win32_console->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::win32_console_changed, this));
+               _win32_console->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);
-               checked_set (_allow_any_dcp_frame_rate, config->allow_any_dcp_frame_rate ());
+               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 (_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
        }
 
-       void maximum_j2k_bandwidth_changed ()
+       void video_display_mode_changed ()
        {
-               Config::instance()->set_maximum_j2k_bandwidth (_maximum_j2k_bandwidth->GetValue() * 1000000);
+               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 ()
+       void show_experimental_audio_processors_changed ()
        {
-               Config::instance()->set_allow_any_dcp_frame_rate (_allow_any_dcp_frame_rate->GetValue ());
+               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 ()
@@ -1608,8 +1847,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;
@@ -1617,57 +1856,76 @@ 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;
+       wxChoice* _video_display_mode = nullptr;
+       wxSpinCtrl* _frames_in_memory_multiplier = nullptr;
+       CheckBox* _show_experimental_audio_processors = nullptr;
+       CheckBox* _only_servers_encode = nullptr;
+       NameFormatEditor* _dcp_metadata_filename_format = nullptr;
+       NameFormatEditor* _dcp_asset_filename_format = nullptr;
+       CheckBox* _log_general = nullptr;
+       CheckBox* _log_warning = nullptr;
+       CheckBox* _log_error = nullptr;
+       CheckBox* _log_timing = nullptr;
+       CheckBox* _log_debug_threed = nullptr;
+       CheckBox* _log_debug_encode = nullptr;
+       CheckBox* _log_debug_email = nullptr;
+       CheckBox* _log_debug_video_view = nullptr;
+       CheckBox* _log_debug_player = nullptr;
+       CheckBox* _log_debug_audio_analysis = nullptr;
 #ifdef DCPOMATIC_WINDOWS
-       wxCheckBox* _win32_console;
+       CheckBox* _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 FullGeneralPage (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 NonStandardPage    (ps, border));
+       e->AddPage (new AdvancedPage       (ps, border));
        return e;
 }