Nag about potential problems when remaking the decryption chain.
[dcpomatic.git] / src / wx / config_dialog.cc
index 81fffbbbc457c3158019867f31b110f1bdf14b2b..a62164896f9369ce4b611a920ecb5f8aa86f6336 100644 (file)
@@ -1,19 +1,20 @@
 /*
-    Copyright (C) 2012-2015 Carl Hetherington <cth@carlh.net>
+    Copyright (C) 2012-2017 Carl Hetherington <cth@carlh.net>
 
-    This program is free software; you can redistribute it and/or modify
+    This file is part of DCP-o-matic.
+
+    DCP-o-matic is free software; you can redistribute it and/or modify
     it under the terms of the GNU General Public License as published by
     the Free Software Foundation; either version 2 of the License, or
     (at your option) any later version.
 
-    This program is distributed in the hope that it will be useful,
+    DCP-o-matic is distributed in the hope that it will be useful,
     but WITHOUT ANY WARRANTY; without even the implied warranty of
     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     GNU General Public License for more details.
 
     You should have received a copy of the GNU General Public License
-    along with this program; if not, write to the Free Software
-    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+    along with DCP-o-matic.  If not, see <http://www.gnu.org/licenses/>.
 
 */
 
 #include "editable_list.h"
 #include "filter_dialog.h"
 #include "dir_picker_ctrl.h"
+#include "file_picker_ctrl.h"
 #include "isdcf_metadata_dialog.h"
 #include "server_dialog.h"
 #include "make_chain_dialog.h"
+#include "email_dialog.h"
+#include "name_format_editor.h"
+#include "nag_dialog.h"
 #include "lib/config.h"
 #include "lib/ratio.h"
 #include "lib/filter.h"
 #include "lib/dcp_content_type.h"
 #include "lib/log.h"
 #include "lib/util.h"
-#include "lib/raw_convert.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 <wx/preferences.h>
-#include <wx/filepicker.h>
 #include <wx/spinctrl.h>
-#include <boost/lexical_cast.hpp>
+#include <wx/filepicker.h>
+#include <RtAudio.h>
 #include <boost/filesystem.hpp>
 #include <boost/foreach.hpp>
 #include <iostream>
@@ -53,10 +58,21 @@ using std::vector;
 using std::string;
 using std::list;
 using std::cout;
+using std::pair;
+using std::make_pair;
+using std::map;
 using boost::bind;
 using boost::shared_ptr;
-using boost::lexical_cast;
 using boost::function;
+using boost::optional;
+using dcp::locale_convert;
+
+static
+void
+do_nothing ()
+{
+
+}
 
 class Page
 {
@@ -156,20 +172,28 @@ private:
                _set_language = new wxCheckBox (_panel, wxID_ANY, _("Set language"));
                table->Add (_set_language, wxGBPosition (r, 0));
                _language = new wxChoice (_panel, wxID_ANY);
-               _language->Append (wxT ("Deutsch"));
-               _language->Append (wxT ("English"));
-               _language->Append (wxT ("Español"));
-               _language->Append (wxT ("Français"));
-               _language->Append (wxT ("Italiano"));
-               _language->Append (wxT ("Nederlands"));
-               _language->Append (wxT ("Svenska"));
-               _language->Append (wxT ("Русский"));
-               _language->Append (wxT ("Polski"));
-               _language->Append (wxT ("Danske"));
+               vector<pair<string, string> > languages;
+               languages.push_back (make_pair ("Čeština", "cs_CZ"));
+               languages.push_back (make_pair ("汉语/漢語", "zh_CN"));
+               languages.push_back (make_pair ("Dansk", "da_DK"));
+               languages.push_back (make_pair ("Deutsch", "de_DE"));
+               languages.push_back (make_pair ("English", "en_GB"));
+               languages.push_back (make_pair ("Español", "es_ES"));
+               languages.push_back (make_pair ("Français", "fr_FR"));
+               languages.push_back (make_pair ("Italiano", "it_IT"));
+               languages.push_back (make_pair ("Nederlands", "nl_NL"));
+               languages.push_back (make_pair ("Русский", "ru_RU"));
+               languages.push_back (make_pair ("Polski", "pl_PL"));
+               languages.push_back (make_pair ("Português europeu", "pt_PT"));
+               languages.push_back (make_pair ("Português do Brasil", "pt_BR"));
+               languages.push_back (make_pair ("Svenska", "sv_SE"));
+               languages.push_back (make_pair ("Slovenský jazyk", "sk_SK"));
+               languages.push_back (make_pair ("українська мова", "uk_UA"));
+               checked_set (_language, languages);
                table->Add (_language, wxGBPosition (r, 1));
                ++r;
 
-               wxStaticText* restart = add_label_to_grid_bag_sizer (
+               wxStaticText* restart = add_label_to_sizer (
                        table, _panel, _("(restart DCP-o-matic to see language changes)"), false, wxGBPosition (r, 0), wxGBSpan (1, 2)
                        );
                wxFont font = restart->GetFont();
@@ -178,11 +202,33 @@ private:
                restart->SetFont (font);
                ++r;
 
-               add_label_to_grid_bag_sizer (table, _panel, _("Threads to use for encoding on this host"), true, wxGBPosition (r, 0));
-               _num_local_encoding_threads = new wxSpinCtrl (_panel);
-               table->Add (_num_local_encoding_threads, wxGBPosition (r, 1));
+               add_label_to_sizer (table, _panel, _("Number of threads DCP-o-matic should use"), true, wxGBPosition (r, 0));
+               _master_encoding_threads = new wxSpinCtrl (_panel);
+               table->Add (_master_encoding_threads, wxGBPosition (r, 1));
                ++r;
 
+               add_label_to_sizer (table, _panel, _("Number of threads DCP-o-matic encode server should use"), true, wxGBPosition (r, 0));
+               _server_encoding_threads = new wxSpinCtrl (_panel);
+               table->Add (_server_encoding_threads, wxGBPosition (r, 1));
+               ++r;
+
+               add_label_to_sizer (table, _panel, _("Cinema and screen database file"), true, wxGBPosition (r, 0));
+               _cinemas_file = new FilePickerCtrl (_panel, _("Select cinema and screen database file"), "*.xml", true);
+               table->Add (_cinemas_file, wxGBPosition (r, 1));
+               ++r;
+
+               _preview_sound = new wxCheckBox (_panel, wxID_ANY, _("Play sound in the preview via"));
+               table->Add (_preview_sound, wxGBPosition (r, 0));
+                _preview_sound_output = new wxChoice (_panel, wxID_ANY);
+                table->Add (_preview_sound_output, wxGBPosition (r, 1));
+                ++r;
+
+#ifdef DCPOMATIC_HAVE_EBUR128_PATCHED_FFMPEG
+               _analyse_ebur128 = new wxCheckBox (_panel, wxID_ANY, _("Find integrated loudness, true peak and loudness range when analysing audio"));
+               table->Add (_analyse_ebur128, wxGBPosition (r, 0), wxGBSpan (1, 2));
+               ++r;
+#endif
+
                _automatic_audio_analysis = new wxCheckBox (_panel, wxID_ANY, _("Automatically analyse content audio"));
                table->Add (_automatic_audio_analysis, wxGBPosition (r, 0), wxGBSpan (1, 2));
                ++r;
@@ -191,7 +237,7 @@ private:
                table->Add (_check_for_updates, wxGBPosition (r, 0), wxGBSpan (1, 2));
                ++r;
 
-               _check_for_test_updates = new wxCheckBox (_panel, wxID_ANY, _("Check for testing updates as well as stable ones"));
+               _check_for_test_updates = new wxCheckBox (_panel, wxID_ANY, _("Check for testing updates on startup"));
                table->Add (_check_for_test_updates, wxGBPosition (r, 0), wxGBSpan (1, 2));
                ++r;
 
@@ -209,66 +255,126 @@ private:
                table->Add (bottom_table, wxGBPosition (r, 0), wxGBSpan (2, 2), wxEXPAND);
                ++r;
 
-               _set_language->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&GeneralPage::set_language_changed, this));
-               _language->Bind     (wxEVT_COMMAND_CHOICE_SELECTED,  boost::bind (&GeneralPage::language_changed,     this));
-
-               _num_local_encoding_threads->SetRange (1, 128);
-               _num_local_encoding_threads->Bind (wxEVT_COMMAND_SPINCTRL_UPDATED, boost::bind (&GeneralPage::num_local_encoding_threads_changed, this));
-
-               _automatic_audio_analysis->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&GeneralPage::automatic_audio_analysis_changed, this));
-               _check_for_updates->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&GeneralPage::check_for_updates_changed, this));
-               _check_for_test_updates->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&GeneralPage::check_for_test_updates_changed, this));
+                RtAudio audio (DCPOMATIC_RTAUDIO_API);
+                for (unsigned int i = 0; i < audio.getDeviceCount(); ++i) {
+                        RtAudio::DeviceInfo dev = audio.getDeviceInfo (i);
+                        if (dev.probed && dev.outputChannels > 0) {
+                                _preview_sound_output->Append (std_to_wx (dev.name));
+                        }
+                }
+
+               _set_language->Bind         (wxEVT_CHECKBOX,           boost::bind (&GeneralPage::set_language_changed,  this));
+               _language->Bind             (wxEVT_CHOICE,             boost::bind (&GeneralPage::language_changed,      this));
+               _cinemas_file->Bind         (wxEVT_FILEPICKER_CHANGED, boost::bind (&GeneralPage::cinemas_file_changed,  this));
+               _preview_sound->Bind        (wxEVT_CHECKBOX,           boost::bind (&GeneralPage::preview_sound_changed, this));
+               _preview_sound_output->Bind (wxEVT_CHOICE,             boost::bind (&GeneralPage::preview_sound_output_changed, this));
+
+               _master_encoding_threads->SetRange (1, 128);
+               _master_encoding_threads->Bind (wxEVT_SPINCTRL, boost::bind (&GeneralPage::master_encoding_threads_changed, this));
+               _server_encoding_threads->SetRange (1, 128);
+               _server_encoding_threads->Bind (wxEVT_SPINCTRL, boost::bind (&GeneralPage::server_encoding_threads_changed, this));
+
+#ifdef DCPOMATIC_HAVE_EBUR128_PATCHED_FFMPEG
+               _analyse_ebur128->Bind (wxEVT_CHECKBOX, boost::bind (&GeneralPage::analyse_ebur128_changed, this));
+#endif
+               _automatic_audio_analysis->Bind (wxEVT_CHECKBOX, boost::bind (&GeneralPage::automatic_audio_analysis_changed, this));
+               _check_for_updates->Bind (wxEVT_CHECKBOX, boost::bind (&GeneralPage::check_for_updates_changed, this));
+               _check_for_test_updates->Bind (wxEVT_CHECKBOX, boost::bind (&GeneralPage::check_for_test_updates_changed, this));
 
-               _issuer->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&GeneralPage::issuer_changed, this));
-               _creator->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&GeneralPage::creator_changed, this));
+               _issuer->Bind (wxEVT_TEXT, boost::bind (&GeneralPage::issuer_changed, this));
+               _creator->Bind (wxEVT_TEXT, boost::bind (&GeneralPage::creator_changed, this));
        }
 
        void config_changed ()
        {
                Config* config = Config::instance ();
 
-               checked_set (_set_language, config->language ());
-
-               if (config->language().get_value_or ("") == "fr") {
-                       checked_set (_language, 3);
-               } else if (config->language().get_value_or ("") == "it") {
-                       checked_set (_language, 4);
-               } else if (config->language().get_value_or ("") == "es") {
-                       checked_set (_language, 2);
-               } else if (config->language().get_value_or ("") == "sv") {
-                       checked_set (_language, 6);
-               } else if (config->language().get_value_or ("") == "de") {
-                       checked_set (_language, 0);
-               } else if (config->language().get_value_or ("") == "nl") {
-                       checked_set (_language, 5);
-               } else if (config->language().get_value_or ("") == "ru") {
-                       checked_set (_language, 7);
-               } else if (config->language().get_value_or ("") == "pl") {
-                       checked_set (_language, 8);
-               } else if (config->language().get_value_or ("") == "da") {
-                       checked_set (_language, 9);
-               } else {
-                       _language->SetSelection (1);
+               checked_set (_set_language, static_cast<bool>(config->language()));
+
+               /* Backwards compatibility of config file */
+
+               map<string, string> compat_map;
+               compat_map["fr"] = "fr_FR";
+               compat_map["it"] = "it_IT";
+               compat_map["es"] = "es_ES";
+               compat_map["sv"] = "sv_SE";
+               compat_map["de"] = "de_DE";
+               compat_map["nl"] = "nl_NL";
+               compat_map["ru"] = "ru_RU";
+               compat_map["pl"] = "pl_PL";
+               compat_map["da"] = "da_DK";
+               compat_map["pt"] = "pt_PT";
+               compat_map["sk"] = "sk_SK";
+               compat_map["cs"] = "cs_CZ";
+               compat_map["uk"] = "uk_UA";
+
+               string lang = config->language().get_value_or ("en_GB");
+               if (compat_map.find (lang) != compat_map.end ()) {
+                       lang = compat_map[lang];
                }
 
-               setup_language_sensitivity ();
+               checked_set (_language, lang);
 
-               checked_set (_num_local_encoding_threads, config->num_local_encoding_threads ());
+               checked_set (_master_encoding_threads, config->master_encoding_threads ());
+               checked_set (_server_encoding_threads, config->server_encoding_threads ());
+#ifdef DCPOMATIC_HAVE_EBUR128_PATCHED_FFMPEG
+               checked_set (_analyse_ebur128, config->analyse_ebur128 ());
+#endif
                checked_set (_automatic_audio_analysis, config->automatic_audio_analysis ());
                checked_set (_check_for_updates, config->check_for_updates ());
                checked_set (_check_for_test_updates, config->check_for_test_updates ());
                checked_set (_issuer, config->dcp_issuer ());
                checked_set (_creator, config->dcp_creator ());
-       }
-
-       void setup_language_sensitivity ()
+               checked_set (_cinemas_file, config->cinemas_file());
+               checked_set (_preview_sound, config->preview_sound());
+
+                optional<string> const current_so = get_preview_sound_output ();
+                string configured_so;
+
+                if (config->preview_sound_output()) {
+                        configured_so = config->preview_sound_output().get();
+                } else {
+                        /* No configured output means we should use the default */
+                        RtAudio audio (DCPOMATIC_RTAUDIO_API);
+                        configured_so = audio.getDeviceInfo(audio.getDefaultOutputDevice()).name;
+                }
+
+                if (!current_so || *current_so != configured_so) {
+                        /* Update _preview_sound_output with the configured value */
+                        unsigned int i = 0;
+                        while (i < _preview_sound_output->GetCount()) {
+                                if (_preview_sound_output->GetString(i) == std_to_wx(configured_so)) {
+                                        _preview_sound_output->SetSelection (i);
+                                        break;
+                                }
+                                ++i;
+                        }
+                }
+
+               setup_sensitivity ();
+       }
+
+        /** @return Currently-selected preview sound output in the dialogue */
+        optional<string> get_preview_sound_output ()
+        {
+                int const sel = _preview_sound_output->GetSelection ();
+                if (sel == wxNOT_FOUND) {
+                        return optional<string> ();
+                }
+
+                return wx_to_std (_preview_sound_output->GetString (sel));
+        }
+
+       void setup_sensitivity ()
        {
                _language->Enable (_set_language->GetValue ());
+               _check_for_test_updates->Enable (_check_for_updates->GetValue ());
+               _preview_sound_output->Enable (_preview_sound->GetValue ());
        }
 
        void set_language_changed ()
        {
-               setup_language_sensitivity ();
+               setup_sensitivity ();
                if (_set_language->GetValue ()) {
                        language_changed ();
                } else {
@@ -278,40 +384,21 @@ private:
 
        void language_changed ()
        {
-               switch (_language->GetSelection ()) {
-               case 0:
-                       Config::instance()->set_language ("de");
-                       break;
-               case 1:
-                       Config::instance()->set_language ("en");
-                       break;
-               case 2:
-                       Config::instance()->set_language ("es");
-                       break;
-               case 3:
-                       Config::instance()->set_language ("fr");
-                       break;
-               case 4:
-                       Config::instance()->set_language ("it");
-                       break;
-               case 5:
-                       Config::instance()->set_language ("nl");
-                       break;
-               case 6:
-                       Config::instance()->set_language ("sv");
-                       break;
-               case 7:
-                       Config::instance()->set_language ("ru");
-                       break;
-               case 8:
-                       Config::instance()->set_language ("pl");
-                       break;
-               case 9:
-                       Config::instance()->set_language ("da");
-                       break;
+               int const sel = _language->GetSelection ();
+               if (sel != -1) {
+                       Config::instance()->set_language (string_client_data (_language->GetClientObject (sel)));
+               } else {
+                       Config::instance()->unset_language ();
                }
        }
 
+#ifdef DCPOMATIC_HAVE_EBUR128_PATCHED_FFMPEG
+       void analyse_ebur128_changed ()
+       {
+               Config::instance()->set_analyse_ebur128 (_analyse_ebur128->GetValue ());
+       }
+#endif
+
        void automatic_audio_analysis_changed ()
        {
                Config::instance()->set_automatic_audio_analysis (_automatic_audio_analysis->GetValue ());
@@ -327,9 +414,14 @@ private:
                Config::instance()->set_check_for_test_updates (_check_for_test_updates->GetValue ());
        }
 
-       void num_local_encoding_threads_changed ()
+       void master_encoding_threads_changed ()
        {
-               Config::instance()->set_num_local_encoding_threads (_num_local_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 ()
@@ -342,9 +434,37 @@ private:
                Config::instance()->set_dcp_creator (wx_to_std (_creator->GetValue ()));
        }
 
+       void cinemas_file_changed ()
+       {
+               Config::instance()->set_cinemas_file (wx_to_std (_cinemas_file->GetPath ()));
+       }
+
+       void preview_sound_changed ()
+       {
+               Config::instance()->set_preview_sound (_preview_sound->GetValue ());
+       }
+
+        void preview_sound_output_changed ()
+        {
+                RtAudio audio (DCPOMATIC_RTAUDIO_API);
+                optional<string> const so = get_preview_sound_output();
+                if (!so || *so == audio.getDeviceInfo(audio.getDefaultOutputDevice()).name) {
+                        Config::instance()->unset_preview_sound_output ();
+                } else {
+                        Config::instance()->set_preview_sound_output (*so);
+                }
+        }
+
        wxCheckBox* _set_language;
        wxChoice* _language;
-       wxSpinCtrl* _num_local_encoding_threads;
+       wxSpinCtrl* _master_encoding_threads;
+       wxSpinCtrl* _server_encoding_threads;
+       FilePickerCtrl* _cinemas_file;
+       wxCheckBox* _preview_sound;
+       wxChoice* _preview_sound_output;
+#ifdef DCPOMATIC_HAVE_EBUR128_PATCHED_FFMPEG
+       wxCheckBox* _analyse_ebur128;
+#endif
        wxCheckBox* _automatic_audio_analysis;
        wxCheckBox* _check_for_updates;
        wxCheckBox* _check_for_test_updates;
@@ -388,7 +508,7 @@ private:
                }
 
                add_label_to_sizer (table, _panel, _("Default directory for new films"), true);
-#ifdef DCPOMATIC_USE_OWN_DIR_PICKER
+#ifdef DCPOMATIC_USE_OWN_PICKER
                _directory = new DirPickerCtrl (_panel);
 #else
                _directory = new wxDirPickerCtrl (_panel, wxDD_DIR_MUST_EXIST);
@@ -403,10 +523,18 @@ private:
                _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);
                _dcp_content_type = new wxChoice (_panel, wxID_ANY);
                table->Add (_dcp_content_type);
 
+               add_label_to_sizer (table, _panel, _("Default DCP audio channels"), true);
+               _dcp_audio_channels = new wxChoice (_panel, wxID_ANY);
+               table->Add (_dcp_audio_channels);
+
                {
                        add_label_to_sizer (table, _panel, _("Default JPEG2000 bandwidth"), true);
                        wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
@@ -425,32 +553,60 @@ private:
                        table->Add (s, 1);
                }
 
+               add_label_to_sizer (table, _panel, _("Default standard"), true);
+               _standard = new wxChoice (_panel, wxID_ANY);
+               table->Add (_standard);
+
+               add_label_to_sizer (table, _panel, _("Default KDM directory"), true);
+#ifdef DCPOMATIC_USE_OWN_PICKER
+               _kdm_directory = new DirPickerCtrl (_panel);
+#else
+               _kdm_directory = new wxDirPickerCtrl (_panel, wxDD_DIR_MUST_EXIST);
+#endif
+               table->Add (_kdm_directory, 1, wxEXPAND);
+
                _still_length->SetRange (1, 3600);
-               _still_length->Bind (wxEVT_COMMAND_SPINCTRL_UPDATED, boost::bind (&DefaultsPage::still_length_changed, this));
+               _still_length->Bind (wxEVT_SPINCTRL, boost::bind (&DefaultsPage::still_length_changed, this));
 
-               _directory->Bind (wxEVT_COMMAND_DIRPICKER_CHANGED, boost::bind (&DefaultsPage::directory_changed, this));
+               _directory->Bind (wxEVT_DIRPICKER_CHANGED, boost::bind (&DefaultsPage::directory_changed, this));
+               _kdm_directory->Bind (wxEVT_DIRPICKER_CHANGED, boost::bind (&DefaultsPage::kdm_directory_changed, this));
 
-               _isdcf_metadata_button->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&DefaultsPage::edit_isdcf_metadata_clicked, this));
+               _isdcf_metadata_button->Bind (wxEVT_BUTTON, boost::bind (&DefaultsPage::edit_isdcf_metadata_clicked, this));
 
                vector<Ratio const *> ratios = Ratio::all ();
                for (size_t i = 0; i < ratios.size(); ++i) {
                        _container->Append (std_to_wx (ratios[i]->nickname ()));
                }
 
-               _container->Bind (wxEVT_COMMAND_CHOICE_SELECTED, boost::bind (&DefaultsPage::container_changed, this));
+               _container->Bind (wxEVT_CHOICE, boost::bind (&DefaultsPage::container_changed, this));
+
+               _scale_to->Append (_("Guess from content"));
+
+               for (size_t i = 0; i < ratios.size(); ++i) {
+                       _scale_to->Append (std_to_wx (ratios[i]->nickname ()));
+               }
+
+               _scale_to->Bind (wxEVT_CHOICE, boost::bind (&DefaultsPage::scale_to_changed, this));
 
                vector<DCPContentType const *> const ct = DCPContentType::all ();
                for (size_t i = 0; i < ct.size(); ++i) {
                        _dcp_content_type->Append (std_to_wx (ct[i]->pretty_name ()));
                }
 
-               _dcp_content_type->Bind (wxEVT_COMMAND_CHOICE_SELECTED, boost::bind (&DefaultsPage::dcp_content_type_changed, this));
+               setup_audio_channels_choice (_dcp_audio_channels, 2);
+
+               _dcp_content_type->Bind (wxEVT_CHOICE, boost::bind (&DefaultsPage::dcp_content_type_changed, this));
+               _dcp_audio_channels->Bind (wxEVT_CHOICE, boost::bind (&DefaultsPage::dcp_audio_channels_changed, this));
 
                _j2k_bandwidth->SetRange (50, 250);
-               _j2k_bandwidth->Bind (wxEVT_COMMAND_SPINCTRL_UPDATED, boost::bind (&DefaultsPage::j2k_bandwidth_changed, this));
+               _j2k_bandwidth->Bind (wxEVT_SPINCTRL, boost::bind (&DefaultsPage::j2k_bandwidth_changed, this));
 
                _audio_delay->SetRange (-1000, 1000);
-               _audio_delay->Bind (wxEVT_COMMAND_SPINCTRL_UPDATED, boost::bind (&DefaultsPage::audio_delay_changed, this));
+               _audio_delay->Bind (wxEVT_SPINCTRL, boost::bind (&DefaultsPage::audio_delay_changed, this));
+
+               _standard->Append (_("SMPTE"));
+               _standard->Append (_("Interop"));
+               _standard->Bind (wxEVT_CHOICE, boost::bind (&DefaultsPage::standard_changed, this));
        }
 
        void config_changed ()
@@ -462,6 +618,13 @@ private:
                        if (ratios[i] == config->default_container ()) {
                                _container->SetSelection (i);
                        }
+                       if (ratios[i] == config->default_scale_to ()) {
+                               _scale_to->SetSelection (i + 1);
+                       }
+               }
+
+               if (!config->default_scale_to()) {
+                       _scale_to->SetSelection (0);
                }
 
                vector<DCPContentType const *> const ct = DCPContentType::all ();
@@ -473,9 +636,12 @@ 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 ()));
                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);
        }
 
        void j2k_bandwidth_changed ()
@@ -488,11 +654,26 @@ private:
                Config::instance()->set_default_audio_delay (_audio_delay->GetValue());
        }
 
+       void dcp_audio_channels_changed ()
+       {
+               int const s = _dcp_audio_channels->GetSelection ();
+               if (s != wxNOT_FOUND) {
+                       Config::instance()->set_default_dcp_audio_channels (
+                               locale_convert<int> (string_client_data (_dcp_audio_channels->GetClientObject (s)))
+                               );
+               }
+       }
+
        void directory_changed ()
        {
                Config::instance()->set_default_directory (wx_to_std (_directory->GetPath ()));
        }
 
+       void kdm_directory_changed ()
+       {
+               Config::instance()->set_default_kdm_directory (wx_to_std (_kdm_directory->GetPath ()));
+       }
+
        void edit_isdcf_metadata_clicked ()
        {
                ISDCFMetadataDialog* d = new ISDCFMetadataDialog (_panel, Config::instance()->default_isdcf_metadata (), false);
@@ -512,23 +693,44 @@ private:
                Config::instance()->set_default_container (ratio[_container->GetSelection()]);
        }
 
+       void scale_to_changed ()
+       {
+               int const s = _scale_to->GetSelection ();
+               if (s == 0) {
+                       Config::instance()->set_default_scale_to (0);
+               } else {
+                       vector<Ratio const *> ratio = Ratio::all ();
+                       Config::instance()->set_default_scale_to (ratio[s - 1]);
+               }
+       }
+
        void dcp_content_type_changed ()
        {
                vector<DCPContentType const *> ct = DCPContentType::all ();
                Config::instance()->set_default_dcp_content_type (ct[_dcp_content_type->GetSelection()]);
        }
 
+       void standard_changed ()
+       {
+               Config::instance()->set_default_interop (_standard->GetSelection() == 1);
+       }
+
        wxSpinCtrl* _j2k_bandwidth;
        wxSpinCtrl* _audio_delay;
        wxButton* _isdcf_metadata_button;
        wxSpinCtrl* _still_length;
-#ifdef DCPOMATIC_USE_OWN_DIR_PICKER
+#ifdef DCPOMATIC_USE_OWN_PICKER
        DirPickerCtrl* _directory;
+       DirPickerCtrl* _kdm_directory;
 #else
        wxDirPickerCtrl* _directory;
+       wxDirPickerCtrl* _kdm_directory;
 #endif
        wxChoice* _container;
+       wxChoice* _scale_to;
        wxChoice* _dcp_content_type;
+       wxChoice* _dcp_audio_channels;
+       wxChoice* _standard;
 };
 
 class EncodingServersPage : public StandardPage
@@ -553,7 +755,7 @@ public:
 private:
        void setup ()
        {
-               _use_any_servers = new wxCheckBox (_panel, wxID_ANY, _("Use all servers"));
+               _use_any_servers = new wxCheckBox (_panel, wxID_ANY, _("Search network for servers"));
                _panel->GetSizer()->Add (_use_any_servers, 0, wxALL, _border);
 
                vector<string> columns;
@@ -568,7 +770,7 @@ private:
 
                _panel->GetSizer()->Add (_servers_list, 1, wxEXPAND | wxALL, _border);
 
-               _use_any_servers->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&EncodingServersPage::use_any_servers_changed, this));
+               _use_any_servers->Bind (wxEVT_CHECKBOX, boost::bind (&EncodingServersPage::use_any_servers_changed, this));
        }
 
        void config_changed ()
@@ -599,11 +801,13 @@ public:
                wxString title,
                int border,
                function<void (shared_ptr<dcp::CertificateChain>)> set,
-               function<shared_ptr<const dcp::CertificateChain> (void)> get
+               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);
@@ -619,7 +823,7 @@ public:
                wxBoxSizer* certificates_sizer = new wxBoxSizer (wxHORIZONTAL);
                _sizer->Add (certificates_sizer, 0, wxLEFT | wxRIGHT, border);
 
-               _certificates = new wxListCtrl (this, wxID_ANY, wxDefaultPosition, wxSize (400, 150), wxLC_REPORT | wxLC_SINGLE_SEL);
+               _certificates = new wxListCtrl (this, wxID_ANY, wxDefaultPosition, wxSize (440, 150), wxLC_REPORT | wxLC_SINGLE_SEL);
 
                {
                        wxListItem ip;
@@ -633,7 +837,7 @@ public:
                        wxListItem ip;
                        ip.SetId (1);
                        ip.SetText (_("Thumbprint"));
-                       ip.SetWidth (300);
+                       ip.SetWidth (340);
 
                        wxFont font = ip.GetFont ();
                        font.SetFamily (wxFONTFAMILY_TELETYPE);
@@ -659,7 +863,7 @@ public:
                _sizer->Add (table, 1, wxALL | wxEXPAND, border);
                int r = 0;
 
-               add_label_to_grid_bag_sizer (table, this, _("Leaf private key"), true, wxGBPosition (r, 0));
+               add_label_to_sizer (table, this, _("Leaf private key"), true, wxGBPosition (r, 0));
                _private_key = new wxStaticText (this, wxID_ANY, wxT (""));
                wxFont font = _private_key->GetFont ();
                font.SetFamily (wxFONTFAMILY_TELETYPE);
@@ -667,21 +871,24 @@ public:
                table->Add (_private_key, wxGBPosition (r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
                _load_private_key = new wxButton (this, wxID_ANY, _("Load..."));
                table->Add (_load_private_key, wxGBPosition (r, 2));
+               _export_private_key = new wxButton (this, wxID_ANY, _("Export..."));
+               table->Add (_export_private_key, wxGBPosition (r, 3));
                ++r;
 
                _button_sizer = new wxBoxSizer (wxHORIZONTAL);
-               _remake_certificates = new wxButton (this, wxID_ANY, _("Re-make certificates and key..."));
+               _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, 3));
+               table->Add (_button_sizer, wxGBPosition (r, 0), wxGBSpan (1, 4));
                ++r;
 
-               _add_certificate->Bind     (wxEVT_COMMAND_BUTTON_CLICKED,       boost::bind (&CertificateChainEditor::add_certificate, this));
-               _remove_certificate->Bind  (wxEVT_COMMAND_BUTTON_CLICKED,       boost::bind (&CertificateChainEditor::remove_certificate, this));
-               _export_certificate->Bind  (wxEVT_COMMAND_BUTTON_CLICKED,       boost::bind (&CertificateChainEditor::export_certificate, this));
-               _certificates->Bind        (wxEVT_COMMAND_LIST_ITEM_SELECTED,   boost::bind (&CertificateChainEditor::update_sensitivity, this));
-               _certificates->Bind        (wxEVT_COMMAND_LIST_ITEM_DESELECTED, boost::bind (&CertificateChainEditor::update_sensitivity, this));
-               _remake_certificates->Bind (wxEVT_COMMAND_BUTTON_CLICKED,       boost::bind (&CertificateChainEditor::remake_certificates, this));
-               _load_private_key->Bind    (wxEVT_COMMAND_BUTTON_CLICKED,       boost::bind (&CertificateChainEditor::load_private_key, this));
+               _add_certificate->Bind     (wxEVT_BUTTON,       boost::bind (&CertificateChainEditor::add_certificate, this));
+               _remove_certificate->Bind  (wxEVT_BUTTON,       boost::bind (&CertificateChainEditor::remove_certificate, this));
+               _export_certificate->Bind  (wxEVT_BUTTON,       boost::bind (&CertificateChainEditor::export_certificate, this));
+               _certificates->Bind        (wxEVT_LIST_ITEM_SELECTED,   boost::bind (&CertificateChainEditor::update_sensitivity, this));
+               _certificates->Bind        (wxEVT_LIST_ITEM_DESELECTED, boost::bind (&CertificateChainEditor::update_sensitivity, this));
+               _remake_certificates->Bind (wxEVT_BUTTON,       boost::bind (&CertificateChainEditor::remake_certificates, this));
+               _load_private_key->Bind    (wxEVT_BUTTON,       boost::bind (&CertificateChainEditor::load_private_key, this));
+               _export_private_key->Bind  (wxEVT_BUTTON,       boost::bind (&CertificateChainEditor::export_private_key, this));
 
                SetSizerAndFit (_sizer);
        }
@@ -697,7 +904,7 @@ public:
 
        void add_button (wxWindow* button)
        {
-               _button_sizer->Add (button);
+               _button_sizer->Add (button, 0, wxLEFT | wxRIGHT, DCPOMATIC_SIZER_X_GAP);
                _sizer->Layout ();
        }
 
@@ -708,7 +915,15 @@ private:
 
                if (d->ShowModal() == wxID_OK) {
                        try {
-                               dcp::Certificate c (dcp::file_to_string (wx_to_std (d->GetPath ())));
+                               dcp::Certificate c;
+                               string const extra = c.read_string (dcp::file_to_string (wx_to_std (d->GetPath ())));
+                               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);
                                _set (_chain);
                                update_certificate_list ();
@@ -757,7 +972,7 @@ private:
                if (d->ShowModal () == wxID_OK) {
                        FILE* f = fopen_boost (wx_to_std (d->GetPath ()), "w");
                        if (!f) {
-                               throw OpenFileError (wx_to_std (d->GetPath ()));
+                               throw OpenFileError (wx_to_std (d->GetPath ()), errno, false);
                        }
 
                        string const s = j->certificate (true);
@@ -794,21 +1009,42 @@ private:
        {
                shared_ptr<const dcp::CertificateChain> chain = _get ();
 
+               string subject_organization_name;
+               string subject_organizational_unit_name;
+               string root_common_name;
                string intermediate_common_name;
-               if (chain->root_to_leaf().size() >= 3) {
-                       dcp::CertificateChain::List all = chain->root_to_leaf ();
+               string leaf_common_name;
+
+               dcp::CertificateChain::List all = chain->root_to_leaf ();
+
+               if (all.size() >= 1) {
+                       /* Have a root */
+                       subject_organization_name = chain->root().subject_organization_name ();
+                       subject_organizational_unit_name = chain->root().subject_organizational_unit_name ();
+                       root_common_name = chain->root().subject_common_name ();
+               }
+
+               if (all.size() >= 2) {
+                       /* Have a leaf */
+                       leaf_common_name = chain->leaf().subject_common_name ();
+               }
+
+               if (all.size() >= 3) {
+                       /* Have an intermediate */
                        dcp::CertificateChain::List::iterator i = all.begin ();
                        ++i;
                        intermediate_common_name = i->subject_common_name ();
                }
 
+               _nag_remake ();
+
                MakeChainDialog* d = new MakeChainDialog (
                        this,
-                       chain->root().subject_organization_name (),
-                       chain->root().subject_organizational_unit_name (),
-                       chain->root().subject_common_name (),
+                       subject_organization_name,
+                       subject_organizational_unit_name,
+                       root_common_name,
                        intermediate_common_name,
-                       chain->leaf().subject_common_name ()
+                       leaf_common_name
                        );
 
                if (d->ShowModal () == wxID_OK) {
@@ -850,8 +1086,11 @@ private:
                if (d->ShowModal() == wxID_OK) {
                        try {
                                boost::filesystem::path p (wx_to_std (d->GetPath ()));
-                               if (boost::filesystem::file_size (p) > 1024) {
-                                       error_dialog (this, wxString::Format (_("Could not read key file (%s)"), std_to_wx (p.string ())));
+                               if (boost::filesystem::file_size (p) > 8192) {
+                                       error_dialog (
+                                               this,
+                                               wxString::Format (_("Could not read key file; file is too long (%s)"), std_to_wx (p.string ()))
+                                               );
                                        return;
                                }
 
@@ -868,6 +1107,31 @@ private:
                update_sensitivity ();
        }
 
+       void export_private_key ()
+       {
+               optional<string> key = _chain->key ();
+               if (!key) {
+                       return;
+               }
+
+               wxFileDialog* d = new wxFileDialog (
+                       this, _("Select Key File"), wxEmptyString, wxEmptyString, wxT ("PEM files (*.pem)|*.pem"),
+                       wxFD_SAVE | wxFD_OVERWRITE_PROMPT
+                       );
+
+               if (d->ShowModal () == wxID_OK) {
+                       FILE* f = fopen_boost (wx_to_std (d->GetPath ()), "w");
+                       if (!f) {
+                               throw OpenFileError (wx_to_std (d->GetPath ()), errno, false);
+                       }
+
+                       string const s = _chain->key().get ();
+                       fwrite (s.c_str(), 1, s.length(), f);
+                       fclose (f);
+               }
+               d->Destroy ();
+       }
+
        wxListCtrl* _certificates;
        wxButton* _add_certificate;
        wxButton* _export_certificate;
@@ -875,11 +1139,13 @@ private:
        wxButton* _remake_certificates;
        wxStaticText* _private_key;
        wxButton* _load_private_key;
+       wxButton* _export_private_key;
        wxSizer* _sizer;
        wxBoxSizer* _button_sizer;
        shared_ptr<dcp::CertificateChain> _chain;
        boost::function<void (shared_ptr<dcp::CertificateChain>)> _set;
        boost::function<shared_ptr<const dcp::CertificateChain> (void)> _get;
+       boost::function<void (void)> _nag_remake;
 };
 
 class KeysPage : public StandardPage
@@ -908,23 +1174,28 @@ private:
                _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 (&Config::signer_chain, Config::instance ()),
+                       boost::bind (&do_nothing)
                        );
 
                _panel->GetSizer()->Add (_signer);
 
                _decryption = new CertificateChainEditor (
-                       _panel, _("Decrypting DCPs"), _border,
+                       _panel, _("Decrypting KDMs"), _border,
                        boost::bind (&Config::set_decryption_chain, Config::instance (), _1),
-                       boost::bind (&Config::decryption_chain, Config::instance ())
+                       boost::bind (&Config::decryption_chain, Config::instance ()),
+                       boost::bind (&KeysPage::nag_remake_decryption_chain, this)
                        );
 
                _panel->GetSizer()->Add (_decryption);
 
-               _export_decryption_certificate = new wxButton (_decryption, wxID_ANY, _("Export DCP decryption certificate..."));
+               _export_decryption_certificate = new wxButton (_decryption, wxID_ANY, _("Export KDM decryption\ncertificate..."));
                _decryption->add_button (_export_decryption_certificate);
+               _export_decryption_chain = new wxButton (_decryption, wxID_ANY, _("Export KDM decryption\nchain..."));
+               _decryption->add_button (_export_decryption_chain);
 
-               _export_decryption_certificate->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&KeysPage::export_decryption_certificate, this));
+               _export_decryption_certificate->Bind (wxEVT_BUTTON, boost::bind (&KeysPage::export_decryption_certificate, this));
+               _export_decryption_chain->Bind (wxEVT_BUTTON, boost::bind (&KeysPage::export_decryption_chain, this));
        }
 
        void export_decryption_certificate ()
@@ -937,7 +1208,7 @@ private:
                if (d->ShowModal () == wxID_OK) {
                        FILE* f = fopen_boost (wx_to_std (d->GetPath ()), "w");
                        if (!f) {
-                               throw OpenFileError (wx_to_std (d->GetPath ()));
+                               throw OpenFileError (wx_to_std (d->GetPath ()), errno, false);
                        }
 
                        string const s = Config::instance()->decryption_chain()->leaf().certificate (true);
@@ -947,15 +1218,45 @@ private:
                d->Destroy ();
        }
 
+       void export_decryption_chain ()
+       {
+               wxFileDialog* d = new wxFileDialog (
+                       _panel, _("Select Chain File"), wxEmptyString, wxEmptyString, wxT ("PEM files (*.pem)|*.pem"),
+                       wxFD_SAVE | wxFD_OVERWRITE_PROMPT
+                       );
+
+               if (d->ShowModal () == wxID_OK) {
+                       FILE* f = fopen_boost (wx_to_std (d->GetPath ()), "w");
+                       if (!f) {
+                               throw OpenFileError (wx_to_std (d->GetPath ()), errno, false);
+                       }
+
+                       string const s = Config::instance()->decryption_chain()->chain();
+                       fwrite (s.c_str(), 1, s.length(), f);
+                       fclose (f);
+               }
+               d->Destroy ();
+       }
+
        void config_changed ()
        {
                _signer->config_changed ();
                _decryption->config_changed ();
        }
 
+       void nag_remake_decryption_chain ()
+       {
+               NagDialog::maybe_nag (
+                       _panel,
+                       Config::NAG_REMAKE_DECRYPTION_CHAIN,
+                       _("If you continue with this operation you will no longer be able to use any DKDMs that you have created.  Also, any KDMs that have been sent to you will become useless.  Proceed with caution!")
+                       );
+       }
+
        CertificateChainEditor* _signer;
        CertificateChainEditor* _decryption;
        wxButton* _export_decryption_certificate;
+       wxButton* _export_decryption_chain;
 };
 
 class TMSPage : public StandardPage
@@ -1007,11 +1308,11 @@ private:
                _tms_protocol->Append (_("SCP (for AAM and Doremi)"));
                _tms_protocol->Append (_("FTP (for Dolby)"));
 
-               _tms_protocol->Bind (wxEVT_COMMAND_CHOICE_SELECTED, boost::bind (&TMSPage::tms_protocol_changed, this));
-               _tms_ip->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&TMSPage::tms_ip_changed, this));
-               _tms_path->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&TMSPage::tms_path_changed, this));
-               _tms_user->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&TMSPage::tms_user_changed, this));
-               _tms_password->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&TMSPage::tms_password_changed, this));
+               _tms_protocol->Bind (wxEVT_CHOICE, boost::bind (&TMSPage::tms_protocol_changed, this));
+               _tms_ip->Bind (wxEVT_TEXT, boost::bind (&TMSPage::tms_ip_changed, this));
+               _tms_path->Bind (wxEVT_TEXT, boost::bind (&TMSPage::tms_path_changed, this));
+               _tms_user->Bind (wxEVT_TEXT, boost::bind (&TMSPage::tms_user_changed, this));
+               _tms_password->Bind (wxEVT_TEXT, boost::bind (&TMSPage::tms_password_changed, this));
        }
 
        void config_changed ()
@@ -1057,6 +1358,12 @@ private:
        wxTextCtrl* _tms_password;
 };
 
+static string
+column (string s)
+{
+       return s;
+}
+
 class KDMEmailPage : public StandardPage
 {
 public:
@@ -1066,7 +1373,7 @@ public:
                /* We have to force both width and height of this one */
                : StandardPage (wxSize (480, 128), border)
 #else
-                : StandardPage (panel_size, border)
+               : StandardPage (panel_size, border)
 #endif
        {}
 
@@ -1117,30 +1424,39 @@ private:
                _kdm_from = new wxTextCtrl (_panel, wxID_ANY);
                table->Add (_kdm_from, 1, wxEXPAND | wxALL);
 
-               add_label_to_sizer (table, _panel, _("CC address"), true);
-               _kdm_cc = new wxTextCtrl (_panel, wxID_ANY);
+               vector<string> columns;
+               columns.push_back (wx_to_std (_("Address")));
+               add_label_to_sizer (table, _panel, _("CC addresses"), true);
+               _kdm_cc = new EditableList<string, EmailDialog> (
+                       _panel,
+                       columns,
+                       bind (&Config::kdm_cc, Config::instance()),
+                       bind (&Config::set_kdm_cc, Config::instance(), _1),
+                       bind (&column, _1)
+                       );
                table->Add (_kdm_cc, 1, wxEXPAND | wxALL);
 
                add_label_to_sizer (table, _panel, _("BCC address"), true);
                _kdm_bcc = new wxTextCtrl (_panel, wxID_ANY);
                table->Add (_kdm_bcc, 1, wxEXPAND | wxALL);
 
-               _kdm_email = new wxTextCtrl (_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (480, 128), wxTE_MULTILINE);
-               _panel->GetSizer()->Add (_kdm_email, 1, wxEXPAND | wxALL, _border);
+               _kdm_email = new wxTextCtrl (_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (-1, 200), wxTE_MULTILINE);
+               _panel->GetSizer()->Add (_kdm_email, 0, wxEXPAND | wxALL, _border);
 
-               _reset_kdm_email = new wxButton (_panel, wxID_ANY, _("Reset to default text"));
+               _reset_kdm_email = new wxButton (_panel, wxID_ANY, _("Reset to default subject and text"));
                _panel->GetSizer()->Add (_reset_kdm_email, 0, wxEXPAND | wxALL, _border);
 
-               _mail_server->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&KDMEmailPage::mail_server_changed, this));
-               _mail_port->Bind (wxEVT_COMMAND_SPINCTRL_UPDATED, boost::bind (&KDMEmailPage::mail_port_changed, this));
-               _mail_user->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&KDMEmailPage::mail_user_changed, this));
-               _mail_password->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&KDMEmailPage::mail_password_changed, this));
-               _kdm_subject->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&KDMEmailPage::kdm_subject_changed, this));
-               _kdm_from->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&KDMEmailPage::kdm_from_changed, this));
-               _kdm_cc->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&KDMEmailPage::kdm_cc_changed, this));
-               _kdm_bcc->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&KDMEmailPage::kdm_bcc_changed, this));
-               _kdm_email->Bind (wxEVT_COMMAND_TEXT_UPDATED, boost::bind (&KDMEmailPage::kdm_email_changed, this));
-               _reset_kdm_email->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&KDMEmailPage::reset_kdm_email, this));
+               _kdm_cc->layout ();
+
+               _mail_server->Bind (wxEVT_TEXT, boost::bind (&KDMEmailPage::mail_server_changed, this));
+               _mail_port->Bind (wxEVT_SPINCTRL, boost::bind (&KDMEmailPage::mail_port_changed, this));
+               _mail_user->Bind (wxEVT_TEXT, boost::bind (&KDMEmailPage::mail_user_changed, this));
+               _mail_password->Bind (wxEVT_TEXT, boost::bind (&KDMEmailPage::mail_password_changed, this));
+               _kdm_subject->Bind (wxEVT_TEXT, boost::bind (&KDMEmailPage::kdm_subject_changed, this));
+               _kdm_from->Bind (wxEVT_TEXT, boost::bind (&KDMEmailPage::kdm_from_changed, this));
+               _kdm_bcc->Bind (wxEVT_TEXT, boost::bind (&KDMEmailPage::kdm_bcc_changed, this));
+               _kdm_email->Bind (wxEVT_TEXT, boost::bind (&KDMEmailPage::kdm_email_changed, this));
+               _reset_kdm_email->Bind (wxEVT_BUTTON, boost::bind (&KDMEmailPage::reset_kdm_email, this));
        }
 
        void config_changed ()
@@ -1153,7 +1469,6 @@ private:
                checked_set (_mail_password, config->mail_password ());
                checked_set (_kdm_subject, config->kdm_subject ());
                checked_set (_kdm_from, config->kdm_from ());
-               checked_set (_kdm_cc, config->kdm_cc ());
                checked_set (_kdm_bcc, config->kdm_bcc ());
                checked_set (_kdm_email, Config::instance()->kdm_email ());
        }
@@ -1188,11 +1503,6 @@ private:
                Config::instance()->set_kdm_from (wx_to_std (_kdm_from->GetValue ()));
        }
 
-       void kdm_cc_changed ()
-       {
-               Config::instance()->set_kdm_cc (wx_to_std (_kdm_cc->GetValue ()));
-       }
-
        void kdm_bcc_changed ()
        {
                Config::instance()->set_kdm_bcc (wx_to_std (_kdm_bcc->GetValue ()));
@@ -1221,12 +1531,77 @@ private:
        wxTextCtrl* _mail_password;
        wxTextCtrl* _kdm_subject;
        wxTextCtrl* _kdm_from;
-       wxTextCtrl* _kdm_cc;
+       EditableList<string, EmailDialog>* _kdm_cc;
        wxTextCtrl* _kdm_bcc;
        wxTextCtrl* _kdm_email;
        wxButton* _reset_kdm_email;
 };
 
+class CoverSheetPage : public StandardPage
+{
+public:
+
+       CoverSheetPage (wxSize panel_size, int border)
+#ifdef DCPOMATIC_OSX
+               /* We have to force both width and height of this one */
+               : StandardPage (wxSize (480, 128), border)
+#else
+               : StandardPage (panel_size, border)
+#endif
+       {}
+
+       wxString GetName () const
+       {
+               return _("Cover Sheet");
+       }
+
+#ifdef DCPOMATIC_OSX
+       wxBitmap GetLargeIcon () const
+       {
+               return wxBitmap ("cover_sheet", wxBITMAP_TYPE_PNG_RESOURCE);
+       }
+#endif
+
+private:
+       void setup ()
+       {
+               _cover_sheet = new wxTextCtrl (_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (-1, 200), wxTE_MULTILINE);
+               _panel->GetSizer()->Add (_cover_sheet, 0, wxEXPAND | wxALL, _border);
+
+               _reset_cover_sheet = new wxButton (_panel, wxID_ANY, _("Reset to default text"));
+               _panel->GetSizer()->Add (_reset_cover_sheet, 0, wxEXPAND | wxALL, _border);
+
+               _cover_sheet->Bind (wxEVT_TEXT, boost::bind (&CoverSheetPage::cover_sheet_changed, this));
+               _reset_cover_sheet->Bind (wxEVT_BUTTON, boost::bind (&CoverSheetPage::reset_cover_sheet, this));
+       }
+
+       void config_changed ()
+       {
+               checked_set (_cover_sheet, Config::instance()->cover_sheet ());
+       }
+
+       void cover_sheet_changed ()
+       {
+               if (_cover_sheet->GetValue().IsEmpty ()) {
+                       /* Sometimes we get sent an erroneous notification that the cover sheet
+                          is empty; I don't know why.
+                       */
+                       return;
+               }
+               Config::instance()->set_cover_sheet (wx_to_std (_cover_sheet->GetValue ()));
+       }
+
+       void reset_cover_sheet ()
+       {
+               Config::instance()->reset_cover_sheet ();
+               checked_set (_cover_sheet, Config::instance()->cover_sheet ());
+       }
+
+       wxTextCtrl* _cover_sheet;
+       wxButton* _reset_cover_sheet;
+};
+
+
 /** @class AdvancedPage
  *  @brief Advanced page of the preferences dialog.
  */
@@ -1244,9 +1619,21 @@ public:
                , _log_timing (0)
                , _log_debug_decode (0)
                , _log_debug_encode (0)
+               , _log_debug_email (0)
        {}
 
 private:
+       void add_top_aligned_label_to_sizer (wxSizer* table, wxWindow* parent, wxString text)
+       {
+               int flags = wxALIGN_TOP | wxTOP | wxLEFT;
+#ifdef __WXOSX__
+               flags |= wxALIGN_RIGHT;
+               text += wxT (":");
+#endif
+               wxStaticText* m = new wxStaticText (parent, wxID_ANY, text);
+               table->Add (m, 0, flags, DCPOMATIC_SIZER_Y_GAP);
+       }
+
        void setup ()
        {
                wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
@@ -1270,15 +1657,38 @@ private:
                table->Add (_only_servers_encode, 1, wxEXPAND | wxALL);
                table->AddSpacer (0);
 
-#ifdef __WXOSX__
-               wxStaticText* m = new wxStaticText (_panel, wxID_ANY, _("Log:"));
-               table->Add (m, 0, wxALIGN_TOP | wxLEFT | wxRIGHT | wxEXPAND | wxALL | wxALIGN_RIGHT, 6);
-#else
-               wxStaticText* m = new wxStaticText (_panel, wxID_ANY, _("Log"));
-               table->Add (m, 0, wxALIGN_TOP | wxLEFT | wxRIGHT | wxEXPAND | wxALL, 6);
-#endif
+               {
+                       add_top_aligned_label_to_sizer (table, _panel, _("DCP metadata filename format"));
+                       dcp::NameFormat::Map titles;
+                       titles['t'] = "type (cpl/pkl)";
+                       dcp::NameFormat::Map examples;
+                       examples['t'] = "cpl";
+                       _dcp_metadata_filename_format = new NameFormatEditor (
+                               _panel, Config::instance()->dcp_metadata_filename_format(), titles, examples, "_eb1c112c-ca3c-4ae6-9263-c6714ff05d64.xml"
+                               );
+                       table->Add (_dcp_metadata_filename_format->panel(), 1, wxEXPAND | wxALL);
+               }
+
+               {
+                       add_top_aligned_label_to_sizer (table, _panel, _("DCP asset filename format"));
+                       dcp::NameFormat::Map titles;
+                       titles['t'] = "type (j2c/pcm/sub)";
+                       titles['r'] = "reel number";
+                       titles['n'] = "number of reels";
+                       titles['c'] = "content filename";
+                       dcp::NameFormat::Map examples;
+                       examples['t'] = "j2c";
+                       examples['r'] = "1";
+                       examples['n'] = "4";
+                       examples['c'] = "myfile.mp4";
+                       _dcp_asset_filename_format = new NameFormatEditor (
+                               _panel, Config::instance()->dcp_asset_filename_format(), titles, examples, "_eb1c112c-ca3c-4ae6-9263-c6714ff05d64.mxf"
+                               );
+                       table->Add (_dcp_asset_filename_format->panel(), 1, wxEXPAND | wxALL);
+               }
 
                {
+                       add_top_aligned_label_to_sizer (table, _panel, _("Log"));
                        wxBoxSizer* t = new wxBoxSizer (wxVERTICAL);
                        _log_general = new wxCheckBox (_panel, wxID_ANY, _("General"));
                        t->Add (_log_general, 1, wxEXPAND | wxALL);
@@ -1286,12 +1696,15 @@ private:
                        t->Add (_log_warning, 1, wxEXPAND | wxALL);
                        _log_error = new wxCheckBox (_panel, wxID_ANY, _("Errors"));
                        t->Add (_log_error, 1, wxEXPAND | wxALL);
+                       /// TRANSLATORS: translate the word "Timing" here; do not include the "Config|" prefix
                        _log_timing = new wxCheckBox (_panel, wxID_ANY, S_("Config|Timing"));
                        t->Add (_log_timing, 1, wxEXPAND | wxALL);
                        _log_debug_decode = new wxCheckBox (_panel, wxID_ANY, _("Debug: decode"));
                        t->Add (_log_debug_decode, 1, wxEXPAND | wxALL);
                        _log_debug_encode = new wxCheckBox (_panel, wxID_ANY, _("Debug: encode"));
                        t->Add (_log_debug_encode, 1, wxEXPAND | wxALL);
+                       _log_debug_email = new wxCheckBox (_panel, wxID_ANY, _("Debug: email sending"));
+                       t->Add (_log_debug_email, 1, wxEXPAND | wxALL);
                        table->Add (t, 0, wxALL, 6);
                }
 
@@ -1302,17 +1715,20 @@ private:
 #endif
 
                _maximum_j2k_bandwidth->SetRange (1, 1000);
-               _maximum_j2k_bandwidth->Bind (wxEVT_COMMAND_SPINCTRL_UPDATED, boost::bind (&AdvancedPage::maximum_j2k_bandwidth_changed, this));
-               _allow_any_dcp_frame_rate->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&AdvancedPage::allow_any_dcp_frame_rate_changed, this));
-               _only_servers_encode->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&AdvancedPage::only_servers_encode_changed, this));
-               _log_general->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&AdvancedPage::log_changed, this));
-               _log_warning->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&AdvancedPage::log_changed, this));
-               _log_error->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&AdvancedPage::log_changed, this));
-               _log_timing->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&AdvancedPage::log_changed, this));
-               _log_debug_decode->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&AdvancedPage::log_changed, this));
-               _log_debug_encode->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&AdvancedPage::log_changed, this));
+               _maximum_j2k_bandwidth->Bind (wxEVT_SPINCTRL, boost::bind (&AdvancedPage::maximum_j2k_bandwidth_changed, this));
+               _allow_any_dcp_frame_rate->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::allow_any_dcp_frame_rate_changed, this));
+               _only_servers_encode->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::only_servers_encode_changed, this));
+               _dcp_metadata_filename_format->Changed.connect (boost::bind (&AdvancedPage::dcp_metadata_filename_format_changed, this));
+               _dcp_asset_filename_format->Changed.connect (boost::bind (&AdvancedPage::dcp_asset_filename_format_changed, this));
+               _log_general->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::log_changed, this));
+               _log_warning->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::log_changed, this));
+               _log_error->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::log_changed, this));
+               _log_timing->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::log_changed, this));
+               _log_debug_decode->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::log_changed, this));
+               _log_debug_encode->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::log_changed, this));
+               _log_debug_email->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::log_changed, this));
 #ifdef DCPOMATIC_WINDOWS
-               _win32_console->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&AdvancedPage::win32_console_changed, this));
+               _win32_console->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::win32_console_changed, this));
 #endif
        }
 
@@ -1323,12 +1739,13 @@ private:
                checked_set (_maximum_j2k_bandwidth, config->maximum_j2k_bandwidth() / 1000000);
                checked_set (_allow_any_dcp_frame_rate, config->allow_any_dcp_frame_rate ());
                checked_set (_only_servers_encode, config->only_servers_encode ());
-               checked_set (_log_general, config->log_types() & Log::TYPE_GENERAL);
-               checked_set (_log_warning, config->log_types() & Log::TYPE_WARNING);
-               checked_set (_log_error, config->log_types() & Log::TYPE_ERROR);
-               checked_set (_log_timing, config->log_types() & Log::TYPE_TIMING);
-               checked_set (_log_debug_decode, config->log_types() & Log::TYPE_DEBUG_DECODE);
-               checked_set (_log_debug_encode, config->log_types() & Log::TYPE_DEBUG_ENCODE);
+               checked_set (_log_general, config->log_types() & LogEntry::TYPE_GENERAL);
+               checked_set (_log_warning, config->log_types() & LogEntry::TYPE_WARNING);
+               checked_set (_log_error, config->log_types() & LogEntry::TYPE_ERROR);
+               checked_set (_log_timing, config->log_types() & LogEntry::TYPE_TIMING);
+               checked_set (_log_debug_decode, config->log_types() & LogEntry::TYPE_DEBUG_DECODE);
+               checked_set (_log_debug_encode, config->log_types() & LogEntry::TYPE_DEBUG_ENCODE);
+               checked_set (_log_debug_email, config->log_types() & LogEntry::TYPE_DEBUG_EMAIL);
 #ifdef DCPOMATIC_WINDOWS
                checked_set (_win32_console, config->win32_console());
 #endif
@@ -1349,26 +1766,39 @@ private:
                Config::instance()->set_only_servers_encode (_only_servers_encode->GetValue ());
        }
 
+       void dcp_metadata_filename_format_changed ()
+       {
+               Config::instance()->set_dcp_metadata_filename_format (_dcp_metadata_filename_format->get ());
+       }
+
+       void dcp_asset_filename_format_changed ()
+       {
+               Config::instance()->set_dcp_asset_filename_format (_dcp_asset_filename_format->get ());
+       }
+
        void log_changed ()
        {
                int types = 0;
                if (_log_general->GetValue ()) {
-                       types |= Log::TYPE_GENERAL;
+                       types |= LogEntry::TYPE_GENERAL;
                }
                if (_log_warning->GetValue ()) {
-                       types |= Log::TYPE_WARNING;
+                       types |= LogEntry::TYPE_WARNING;
                }
                if (_log_error->GetValue ())  {
-                       types |= Log::TYPE_ERROR;
+                       types |= LogEntry::TYPE_ERROR;
                }
                if (_log_timing->GetValue ()) {
-                       types |= Log::TYPE_TIMING;
+                       types |= LogEntry::TYPE_TIMING;
                }
                if (_log_debug_decode->GetValue ()) {
-                       types |= Log::TYPE_DEBUG_DECODE;
+                       types |= LogEntry::TYPE_DEBUG_DECODE;
                }
                if (_log_debug_encode->GetValue ()) {
-                       types |= Log::TYPE_DEBUG_ENCODE;
+                       types |= LogEntry::TYPE_DEBUG_ENCODE;
+               }
+               if (_log_debug_email->GetValue ()) {
+                       types |= LogEntry::TYPE_DEBUG_EMAIL;
                }
                Config::instance()->set_log_types (types);
        }
@@ -1383,12 +1813,15 @@ private:
        wxSpinCtrl* _maximum_j2k_bandwidth;
        wxCheckBox* _allow_any_dcp_frame_rate;
        wxCheckBox* _only_servers_encode;
+       NameFormatEditor* _dcp_metadata_filename_format;
+       NameFormatEditor* _dcp_asset_filename_format;
        wxCheckBox* _log_general;
        wxCheckBox* _log_warning;
        wxCheckBox* _log_error;
        wxCheckBox* _log_timing;
        wxCheckBox* _log_debug_decode;
        wxCheckBox* _log_debug_encode;
+       wxCheckBox* _log_debug_email;
 #ifdef DCPOMATIC_WINDOWS
        wxCheckBox* _win32_console;
 #endif
@@ -1417,6 +1850,7 @@ create_config_dialog ()
        e->AddPage (new KeysPage (ps, border));
        e->AddPage (new TMSPage (ps, border));
        e->AddPage (new KDMEmailPage (ps, border));
+       e->AddPage (new CoverSheetPage (ps, border));
        e->AddPage (new AdvancedPage (ps, border));
        return e;
 }