Change bitmap_path to take a full name with extension.
[dcpomatic.git] / src / wx / full_config_dialog.cc
index 155472a38edd82f5f0c3e019533aaef67d16fe65..bb804cae876c837951e387db26de934118c5a9be 100644 (file)
@@ -1,5 +1,5 @@
 /*
-    Copyright (C) 2012-2019 Carl Hetherington <cth@carlh.net>
+    Copyright (C) 2012-2021 Carl Hetherington <cth@carlh.net>
 
     This file is part of DCP-o-matic.
 
 
 */
 
+
 /** @file src/full_config_dialog.cc
  *  @brief A dialogue to edit all DCP-o-matic configuration.
  */
 
-#include "full_config_dialog.h"
-#include "wx_util.h"
-#include "editable_list.h"
-#include "filter_dialog.h"
+
+#include "check_box.h"
+#include "config_dialog.h"
+#include "config_move_dialog.h"
+#include "dcpomatic_button.h"
 #include "dir_picker_ctrl.h"
+#include "editable_list.h"
+#include "email_dialog.h"
 #include "file_picker_ctrl.h"
-#include "server_dialog.h"
+#include "filter_dialog.h"
+#include "full_config_dialog.h"
+#include "kdm_choice.h"
 #include "make_chain_dialog.h"
-#include "email_dialog.h"
-#include "name_format_editor.h"
 #include "nag_dialog.h"
-#include "config_move_dialog.h"
-#include "config_dialog.h"
-#include "static_text.h"
-#include "check_box.h"
-#include "dcpomatic_button.h"
+#include "name_format_editor.h"
 #include "password_entry.h"
+#include "send_test_email_dialog.h"
+#include "server_dialog.h"
+#include "static_text.h"
+#include "wx_util.h"
 #include "lib/config.h"
-#include "lib/ratio.h"
-#include "lib/filter.h"
+#include "lib/cross.h"
 #include "lib/dcp_content_type.h"
+#include "lib/emailer.h"
+#include "lib/exceptions.h"
+#include "lib/filter.h"
 #include "lib/log.h"
+#include "lib/ratio.h"
 #include "lib/util.h"
-#include "lib/cross.h"
-#include "lib/exceptions.h"
-#include <dcp/locale_convert.h>
-#include <dcp/exceptions.h>
 #include <dcp/certificate_chain.h>
-#include <wx/stdpaths.h>
+#include <dcp/exceptions.h>
+#include <dcp/locale_convert.h>
+#include <dcp/warnings.h>
+LIBDCP_DISABLE_WARNINGS
+#include <wx/filepicker.h>
 #include <wx/preferences.h>
 #include <wx/spinctrl.h>
-#include <wx/filepicker.h>
+#include <wx/stdpaths.h>
+LIBDCP_ENABLE_WARNINGS
 #include <RtAudio.h>
 #include <boost/filesystem.hpp>
 #include <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 boost::bind;
+using std::pair;
 using std::shared_ptr;
-using boost::function;
+using std::string;
+using std::vector;
+using boost::bind;
 using boost::optional;
 #if BOOST_VERSION >= 106100
 using namespace boost::placeholders;
 #endif
 using dcp::locale_convert;
 
+
 class FullGeneralPage : public GeneralPage
 {
 public:
@@ -82,9 +92,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;
@@ -124,8 +134,8 @@ private:
 
                add_update_controls (table, r);
 
-               _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));
@@ -139,9 +149,9 @@ private:
                _automatic_audio_analysis->Bind (wxEVT_CHECKBOX, boost::bind (&FullGeneralPage::automatic_audio_analysis_changed, this));
        }
 
-       void config_changed ()
+       void config_changed () override
        {
-               Config* config = Config::instance ();
+               auto config = Config::instance ();
 
                checked_set (_master_encoding_threads, config->master_encoding_threads ());
                checked_set (_server_encoding_threads, config->server_encoding_threads ());
@@ -149,7 +159,7 @@ private:
                checked_set (_analyse_ebur128, config->analyse_ebur128 ());
 #endif
                checked_set (_automatic_audio_analysis, config->automatic_audio_analysis ());
-               checked_set (_config_file, config->config_file());
+               checked_set (_config_file, config->config_read_file());
                checked_set (_cinemas_file, config->cinemas_file());
 
                GeneralPage::config_changed ();
@@ -157,8 +167,8 @@ private:
 
        void export_cinemas_file ()
        {
-               wxFileDialog* d = new wxFileDialog (
-                       _panel, _("Select Cinemas File"), wxEmptyString, wxEmptyString, wxT ("XML files (*.xml)|*.xml"),
+               auto d = new wxFileDialog (
+                       _panel, _("Select Cinemas File"), wxEmptyString, wxEmptyString, wxT("XML files (*.xml)|*.xml"),
                        wxFD_SAVE | wxFD_OVERWRITE_PROMPT
                 );
 
@@ -171,35 +181,35 @@ private:
 #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 ());
+               Config::instance()->set_server_encoding_threads (_server_encoding_threads->GetValue());
        }
 
        void config_file_changed ()
        {
-               Config* config = Config::instance();
+               auto config = Config::instance();
                boost::filesystem::path new_file = wx_to_std(_config_file->GetPath());
-               if (new_file == config->config_file()) {
+               if (new_file == config->config_read_file()) {
                        return;
                }
                bool copy_and_link = true;
                if (boost::filesystem::exists(new_file)) {
-                       ConfigMoveDialog* d = new ConfigMoveDialog (_panel, new_file);
+                       auto d = new ConfigMoveDialog (_panel, new_file);
                        if (d->ShowModal() == wxID_OK) {
                                copy_and_link = false;
                        }
@@ -208,7 +218,7 @@ private:
 
                if (copy_and_link) {
                        config->write ();
-                       if (new_file != config->config_file()) {
+                       if (new_file != config->config_read_file()) {
                                config->copy_and_link (new_file);
                        }
                } else {
@@ -231,6 +241,7 @@ private:
        wxCheckBox* _automatic_audio_analysis;
 };
 
+
 class DefaultsPage : public Page
 {
 public:
@@ -238,28 +249,28 @@ public:
                : Page (panel_size, border)
        {}
 
-       wxString GetName () const
+       wxString GetName () const override
        {
                return _("Defaults");
        }
 
 #ifdef DCPOMATIC_OSX
-       wxBitmap GetLargeIcon () const
+       wxBitmap GetLargeIcon () const override
        {
-               return wxBitmap ("defaults", wxBITMAP_TYPE_PNG_RESOURCE);
+               return wxBitmap(bitmap_path("defaults.png"), wxBITMAP_TYPE_PNG);
        }
 #endif
 
 private:
-       void setup ()
+       void setup () override
        {
-               wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
+               auto table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
                table->AddGrowableCol (1, 1);
                _panel->GetSizer()->Add (table, 1, wxALL | wxEXPAND, _border);
 
                {
                        add_label_to_sizer (table, _panel, _("Default duration of still images"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
-                       wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
+                       auto s = new wxBoxSizer (wxHORIZONTAL);
                        _still_length = new wxSpinCtrl (_panel);
                        s->Add (_still_length);
                        add_label_to_sizer (s, _panel, _("s"), false, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
@@ -288,7 +299,7 @@ private:
 
                {
                        add_label_to_sizer (table, _panel, _("Default JPEG2000 bandwidth"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
-                       wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
+                       auto s = new wxBoxSizer (wxHORIZONTAL);
                        _j2k_bandwidth = new wxSpinCtrl (_panel);
                        s->Add (_j2k_bandwidth);
                        add_label_to_sizer (s, _panel, _("Mbit/s"), false, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
@@ -297,7 +308,7 @@ private:
 
                {
                        add_label_to_sizer (table, _panel, _("Default audio delay"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
-                       wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
+                       auto s = new wxBoxSizer (wxHORIZONTAL);
                        _audio_delay = new wxSpinCtrl (_panel);
                        s->Add (_audio_delay);
                        add_label_to_sizer (s, _panel, _("ms"), false, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
@@ -308,20 +319,55 @@ private:
                _standard = new wxChoice (_panel, wxID_ANY);
                table->Add (_standard);
 
+               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
                _kdm_directory = new wxDirPickerCtrl (_panel, wxDD_DIR_MUST_EXIST);
 #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"));
+
+               _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));
+
+               _use_isdcf_name_by_default->Bind (wxEVT_CHECKBOX, boost::bind(&DefaultsPage::use_isdcf_name_by_default_changed, this));
 
                for (auto i: Ratio::containers()) {
                        _container->Append (std_to_wx(i->container_nickname()));
@@ -347,22 +393,30 @@ private:
                _standard->Append (_("SMPTE"));
                _standard->Append (_("Interop"));
                _standard->Bind (wxEVT_CHOICE, boost::bind (&DefaultsPage::standard_changed, this));
+
+               for (auto const& i: _enable_metadata) {
+                       i.second->Bind (wxEVT_CHECKBOX, boost::bind(&DefaultsPage::metadata_changed, this));
+               }
+
+               for (auto const& i: _metadata) {
+                       i.second->Bind (wxEVT_TEXT, boost::bind(&DefaultsPage::metadata_changed, this));
+               }
        }
 
-       void config_changed ()
+       void config_changed () override
        {
-               Config* config = Config::instance ();
+               auto config = Config::instance ();
 
-               vector<Ratio const *> containers = Ratio::containers ();
+               auto containers = Ratio::containers ();
                for (size_t i = 0; i < containers.size(); ++i) {
-                       if (containers[i] == config->default_container ()) {
+                       if (containers[i] == config->default_container()) {
                                _container->SetSelection (i);
                        }
                }
 
-               vector<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);
                        }
                }
@@ -370,11 +424,76 @@ private:
                checked_set (_still_length, config->default_still_length ());
                _directory->SetPath (std_to_wx (config->default_directory_or (wx_to_std (wxStandardPaths::Get().GetDocumentsDir())).string ()));
                _kdm_directory->SetPath (std_to_wx (config->default_kdm_directory_or (wx_to_std (wxStandardPaths::Get().GetDocumentsDir())).string ()));
+               _kdm_type->set (config->default_kdm_type());
+               checked_set (_use_isdcf_name_by_default, config->use_isdcf_name_by_default());
                checked_set (_j2k_bandwidth, config->default_j2k_bandwidth() / 1000000);
                _j2k_bandwidth->SetRange (50, config->maximum_j2k_bandwidth() / 1000000);
                checked_set (_dcp_audio_channels, locale_convert<string> (config->default_dcp_audio_channels()));
                checked_set (_audio_delay, config->default_audio_delay ());
                checked_set (_standard, config->default_interop() ? 1 : 0);
+
+               auto metadata = config->default_metadata();
+
+               for (auto const& i: metadata) {
+                       _enable_metadata[i.first]->SetValue(true);
+                       checked_set (_metadata[i.first], i.second);
+               }
+
+               for (auto const& i: _enable_metadata) {
+                       if (metadata.find(i.first) == metadata.end()) {
+                               checked_set (i.second, false);
+                       }
+               }
+
+               for (auto const& i: _metadata) {
+                       if (metadata.find(i.first) == metadata.end()) {
+                               checked_set (i.second, wxT(""));
+                       }
+               }
+
+               checked_set (_kdm_duration, config->default_kdm_duration().duration);
+               switch (config->default_kdm_duration().unit) {
+                       case RoughDuration::Unit::DAYS:
+                               _kdm_duration->SetRange(1, 365);
+                               checked_set (_kdm_duration_unit, 0);
+                               break;
+                       case RoughDuration::Unit::WEEKS:
+                               _kdm_duration->SetRange(1, 52);
+                               checked_set (_kdm_duration_unit, 1);
+                               break;
+                       case RoughDuration::Unit::MONTHS:
+                               _kdm_duration->SetRange(1, 12);
+                               checked_set (_kdm_duration_unit, 2);
+                               break;
+                       case RoughDuration::Unit::YEARS:
+                               _kdm_duration->SetRange(1, 40);
+                               checked_set (_kdm_duration_unit, 3);
+                               break;
+               }
+
+               setup_sensitivity ();
+       }
+
+       void kdm_duration_changed ()
+       {
+               auto config = Config::instance();
+               auto duration = _kdm_duration->GetValue();
+               RoughDuration::Unit unit = RoughDuration::Unit::DAYS;
+               switch (_kdm_duration_unit->GetSelection()) {
+               case 0:
+                       unit = RoughDuration::Unit::DAYS;
+                       break;
+               case 1:
+                       unit = RoughDuration::Unit::WEEKS;
+                       break;
+               case 2:
+                       unit = RoughDuration::Unit::MONTHS;
+                       break;
+               case 3:
+                       unit = RoughDuration::Unit::YEARS;
+                       break;
+               }
+               config->set_default_kdm_duration (RoughDuration(duration, unit));
        }
 
        void j2k_bandwidth_changed ()
@@ -392,7 +511,7 @@ private:
                int const s = _dcp_audio_channels->GetSelection ();
                if (s != wxNOT_FOUND) {
                        Config::instance()->set_default_dcp_audio_channels (
-                               locale_convert<int> (string_client_data (_dcp_audio_channels->GetClientObject (s)))
+                               locale_convert<int>(string_client_data(_dcp_audio_channels->GetClientObject(s)))
                                );
                }
        }
@@ -407,6 +526,16 @@ private:
                Config::instance()->set_default_kdm_directory (wx_to_std (_kdm_directory->GetPath ()));
        }
 
+       void kdm_type_changed ()
+       {
+               Config::instance()->set_default_kdm_type(_kdm_type->get());
+       }
+
+       void use_isdcf_name_by_default_changed ()
+       {
+               Config::instance()->set_use_isdcf_name_by_default (_use_isdcf_name_by_default->GetValue());
+       }
+
        void still_length_changed ()
        {
                Config::instance()->set_default_still_length (_still_length->GetValue ());
@@ -414,13 +543,13 @@ private:
 
        void container_changed ()
        {
-               vector<Ratio const *> ratio = Ratio::containers ();
+               auto ratio = Ratio::containers ();
                Config::instance()->set_default_container (ratio[_container->GetSelection()]);
        }
 
        void dcp_content_type_changed ()
        {
-               vector<DCPContentType const *> ct = DCPContentType::all ();
+               auto ct = DCPContentType::all ();
                Config::instance()->set_default_dcp_content_type (ct[_dcp_content_type->GetSelection()]);
        }
 
@@ -429,6 +558,25 @@ private:
                Config::instance()->set_default_interop (_standard->GetSelection() == 1);
        }
 
+       void metadata_changed ()
+       {
+               map<string, string> metadata;
+               for (auto const& i: _enable_metadata) {
+                       if (i.second->GetValue()) {
+                               metadata[i.first] = wx_to_std(_metadata[i.first]->GetValue());
+                       }
+               }
+               Config::instance()->set_default_metadata (metadata);
+               setup_sensitivity ();
+       }
+
+       void setup_sensitivity ()
+       {
+               for (auto const& i: _enable_metadata) {
+                       _metadata[i.first]->Enable(i.second->GetValue());
+               }
+       }
+
        wxSpinCtrl* _j2k_bandwidth;
        wxSpinCtrl* _audio_delay;
        wxSpinCtrl* _still_length;
@@ -439,12 +587,19 @@ private:
        wxDirPickerCtrl* _directory;
        wxDirPickerCtrl* _kdm_directory;
 #endif
+       KDMChoice* _kdm_type;
+       wxSpinCtrl* _kdm_duration;
+       wxChoice* _kdm_duration_unit;
+       wxCheckBox* _use_isdcf_name_by_default;
        wxChoice* _container;
        wxChoice* _dcp_content_type;
        wxChoice* _dcp_audio_channels;
        wxChoice* _standard;
+       map<string, CheckBox*> _enable_metadata;
+       map<string, wxTextCtrl*> _metadata;
 };
 
+
 class EncodingServersPage : public Page
 {
 public:
@@ -452,20 +607,20 @@ public:
                : Page (panel_size, border)
        {}
 
-       wxString GetName () const
+       wxString GetName () const override
        {
                return _("Servers");
        }
 
 #ifdef DCPOMATIC_OSX
-       wxBitmap GetLargeIcon () const
+       wxBitmap GetLargeIcon () const override
        {
-               return wxBitmap ("servers", wxBITMAP_TYPE_PNG_RESOURCE);
+               return wxBitmap(bitmap_path("servers.png"), wxBITMAP_TYPE_PNG);
        }
 #endif
 
 private:
-       void setup ()
+       void setup () override
        {
                _use_any_servers = new CheckBox (_panel, _("Search network for servers"));
                _panel->GetSizer()->Add (_use_any_servers, 0, wxALL, _border);
@@ -477,15 +632,17 @@ private:
                        columns,
                        boost::bind (&Config::servers, Config::instance()),
                        boost::bind (&Config::set_servers, Config::instance(), _1),
-                       boost::bind (&EncodingServersPage::server_column, this, _1)
+                       boost::bind (&EncodingServersPage::server_column, this, _1),
+                       false,
+                       EditableListButton::NEW | EditableListButton::EDIT | EditableListButton::REMOVE
                        );
 
                _panel->GetSizer()->Add (_servers_list, 1, wxEXPAND | wxALL, _border);
 
-               _use_any_servers->Bind (wxEVT_CHECKBOX, boost::bind (&EncodingServersPage::use_any_servers_changed, this));
+               _use_any_servers->Bind (wxEVT_CHECKBOX, boost::bind(&EncodingServersPage::use_any_servers_changed, this));
        }
 
-       void config_changed ()
+       void config_changed () override
        {
                checked_set (_use_any_servers, Config::instance()->use_any_servers ());
                _servers_list->refresh ();
@@ -505,6 +662,7 @@ private:
        EditableList<string, ServerDialog>* _servers_list;
 };
 
+
 class TMSPage : public Page
 {
 public:
@@ -512,20 +670,20 @@ public:
                : Page (panel_size, border)
        {}
 
-       wxString GetName () const
+       wxString GetName () const override
        {
                return _("TMS");
        }
 
 #ifdef DCPOMATIC_OSX
-       wxBitmap GetLargeIcon () const
+       wxBitmap GetLargeIcon () const override
        {
-               return wxBitmap ("tms", wxBITMAP_TYPE_PNG_RESOURCE);
+               return wxBitmap(bitmap_path("tms.png"), wxBITMAP_TYPE_PNG);
        }
 #endif
 
 private:
-       void setup ()
+       void setup () override
        {
                _upload = new CheckBox (_panel, _("Upload DCP to TMS after creation"));
                _panel->GetSizer()->Add (_upload, 0, wxALL | wxEXPAND, _border);
@@ -565,9 +723,9 @@ private:
                _tms_password->Changed.connect (boost::bind (&TMSPage::tms_password_changed, this));
        }
 
-       void config_changed ()
+       void config_changed () override
        {
-               Config* config = Config::instance ();
+               auto config = Config::instance ();
 
                checked_set (_upload, config->upload_after_make_dcp());
                checked_set (_tms_protocol, static_cast<int>(config->tms_protocol()));
@@ -615,11 +773,6 @@ private:
        PasswordEntry* _tms_password;
 };
 
-static string
-column (string s)
-{
-       return s;
-}
 
 class EmailPage : public Page
 {
@@ -628,22 +781,22 @@ public:
                : Page (panel_size, border)
        {}
 
-       wxString GetName () const
+       wxString GetName () const override
        {
                return _("Email");
        }
 
 #ifdef DCPOMATIC_OSX
-       wxBitmap GetLargeIcon () const
+       wxBitmap GetLargeIcon () const override
        {
-               return wxBitmap ("email", wxBITMAP_TYPE_PNG_RESOURCE);
+               return wxBitmap(bitmap_path("email.png"), wxBITMAP_TYPE_PNG);
        }
 #endif
 
 private:
-       void setup ()
+       void setup () override
        {
-               wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
+               auto table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
                table->AddGrowableCol (1, 1);
                _panel->GetSizer()->Add (table, 1, wxEXPAND | wxALL, _border);
 
@@ -663,7 +816,7 @@ private:
                        _protocol->Append (_("Plain"));
                        _protocol->Append (_("STARTTLS"));
                        _protocol->Append (_("SSL"));
-                       s->Add (_protocol);
+                       s->Add (_protocol, 1, wxALIGN_CENTER_VERTICAL);
                        table->Add (s, 1, wxEXPAND | wxALL);
                }
 
@@ -675,19 +828,24 @@ private:
                _password = new PasswordEntry (_panel);
                table->Add (_password->get_panel(), 1, wxEXPAND | wxALL);
 
-               _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));
+               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 ()
+       void config_changed () override
        {
                auto config = Config::instance ();
 
-               checked_set (_server, config->mail_server ());
-               checked_set (_port, config->mail_port ());
+               checked_set (_server, config->mail_server());
+               checked_set (_port, config->mail_port());
                switch (config->mail_protocol()) {
                case EmailProtocol::AUTO:
                        checked_set (_protocol, 0);
@@ -702,18 +860,18 @@ private:
                        checked_set (_protocol, 3);
                        break;
                }
-               checked_set (_user, config->mail_user ());
+               checked_set (_user, config->mail_user());
                checked_set (_password, config->mail_password());
        }
 
        void server_changed ()
        {
-               Config::instance()->set_mail_server (wx_to_std (_server->GetValue ()));
+               Config::instance()->set_mail_server(wx_to_std(_server->GetValue()));
        }
 
        void port_changed ()
        {
-               Config::instance()->set_mail_port (_port->GetValue ());
+               Config::instance()->set_mail_port(_port->GetValue());
        }
 
        void protocol_changed ()
@@ -744,13 +902,44 @@ private:
                Config::instance()->set_mail_password(_password->get());
        }
 
+       void send_test_email_clicked ()
+       {
+               auto dialog = new SendTestEmailDialog(_panel);
+               auto result = dialog->ShowModal();
+               dialog->Destroy();
+               if (result == wxID_OK) {
+                       Emailer emailer(
+                               wx_to_std(dialog->from()),
+                               { wx_to_std(dialog->to()) },
+                               wx_to_std(_("DCP-o-matic test email")),
+                               wx_to_std(_("This is a test email from DCP-o-matic."))
+                               );
+                       auto config = Config::instance();
+                       try {
+                               emailer.send (config->mail_server(), config->mail_port(), config->mail_protocol(), config->mail_user(), config->mail_password());
+                       } catch (NetworkError& e) {
+                               error_dialog (_panel, std_to_wx(e.summary()), std_to_wx(e.detail().get_value_or("")));
+                               return;
+                       } catch (std::exception& e) {
+                               error_dialog (_panel, _("Test email sending failed."), std_to_wx(e.what()));
+                               return;
+                       } catch (...) {
+                               error_dialog (_panel, _("Test email sending failed."));
+                               return;
+                       }
+                       message_dialog (_panel, _("Test email sent."));
+               }
+       }
+
        wxTextCtrl* _server;
        wxSpinCtrl* _port;
        wxChoice* _protocol;
        wxTextCtrl* _user;
        PasswordEntry* _password;
+       Button* _send_test_email;
 };
 
+
 class KDMEmailPage : public Page
 {
 public:
@@ -764,24 +953,24 @@ public:
 #endif
        {}
 
-       wxString GetName () const
+       wxString GetName () const override
        {
                return _("KDM Email");
        }
 
 #ifdef DCPOMATIC_OSX
-       wxBitmap GetLargeIcon () const
+       wxBitmap GetLargeIcon () const override
        {
-               return wxBitmap ("kdm_email", wxBITMAP_TYPE_PNG_RESOURCE);
+               return wxBitmap(bitmap_path("kdm_email.png"), wxBITMAP_TYPE_PNG);
        }
 #endif
 
 private:
-       void setup ()
+       void setup () override
        {
-               wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
+               auto table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
                table->AddGrowableCol (1, 1);
-               _panel->GetSizer()->Add (table, 1, wxEXPAND | wxALL, _border);
+               _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);
@@ -799,7 +988,11 @@ private:
                        columns,
                        bind (&Config::kdm_cc, Config::instance()),
                        bind (&Config::set_kdm_cc, Config::instance(), _1),
-                       bind (&column, _1)
+                       [] (string s, int) {
+                               return s;
+                       },
+                       true,
+                       EditableListButton::NEW | EditableListButton::EDIT | EditableListButton::REMOVE
                        );
                table->Add (_cc, 1, wxEXPAND | wxALL);
 
@@ -822,9 +1015,9 @@ private:
                _reset_email->Bind (wxEVT_BUTTON, boost::bind (&KDMEmailPage::reset_email, this));
        }
 
-       void config_changed ()
+       void config_changed () override
        {
-               Config* config = Config::instance ();
+               auto config = Config::instance ();
 
                checked_set (_subject, config->kdm_subject ());
                checked_set (_from, config->kdm_from ());
@@ -872,6 +1065,7 @@ private:
        wxButton* _reset_email;
 };
 
+
 class NotificationsPage : public Page
 {
 public:
@@ -884,24 +1078,24 @@ public:
 #endif
        {}
 
-       wxString GetName () const
+       wxString GetName () const override
        {
                return _("Notifications");
        }
 
 #ifdef DCPOMATIC_OSX
-       wxBitmap GetLargeIcon () const
+       wxBitmap GetLargeIcon () const override
        {
-               return wxBitmap ("notifications", wxBITMAP_TYPE_PNG_RESOURCE);
+               return wxBitmap(bitmap_path("notifications.png"), wxBITMAP_TYPE_PNG);
        }
 #endif
 
 private:
-       void setup ()
+       void setup () override
        {
-               wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
+               auto table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
                table->AddGrowableCol (1, 1);
-               _panel->GetSizer()->Add (table, 1, wxEXPAND | wxALL, _border);
+               _panel->GetSizer()->Add (table, 0, wxEXPAND | wxALL, _border);
 
                _enable_message_box = new CheckBox (_panel, _("Message box"));
                table->Add (_enable_message_box, 1, wxEXPAND | wxALL);
@@ -931,7 +1125,11 @@ private:
                        columns,
                        bind (&Config::notification_cc, Config::instance()),
                        bind (&Config::set_notification_cc, Config::instance(), _1),
-                       bind (&column, _1)
+                       [] (string s, int) {
+                               return s;
+                       },
+                       true,
+                       EditableListButton::NEW | EditableListButton::EDIT | EditableListButton::REMOVE
                        );
                table->Add (_cc, 1, wxEXPAND | wxALL);
 
@@ -957,10 +1155,10 @@ private:
                _email->Bind (wxEVT_TEXT, boost::bind (&NotificationsPage::notification_email_changed, this));
                _reset_email->Bind (wxEVT_BUTTON, boost::bind (&NotificationsPage::reset_email, this));
 
-               update_sensitivity ();
+               setup_sensitivity ();
        }
 
-       void update_sensitivity ()
+       void setup_sensitivity ()
        {
                bool const s = _enable_email->GetValue();
                _subject->Enable(s);
@@ -972,9 +1170,9 @@ private:
                _reset_email->Enable(s);
        }
 
-       void config_changed ()
+       void config_changed () override
        {
-               Config* config = Config::instance ();
+               auto config = Config::instance ();
 
                checked_set (_enable_message_box, config->notification(Config::MESSAGE_BOX));
                checked_set (_enable_email, config->notification(Config::EMAIL));
@@ -984,50 +1182,50 @@ private:
                checked_set (_bcc, config->notification_bcc ());
                checked_set (_email, Config::instance()->notification_email ());
 
-               update_sensitivity ();
+               setup_sensitivity ();
        }
 
        void notification_subject_changed ()
        {
-               Config::instance()->set_notification_subject (wx_to_std (_subject->GetValue ()));
+               Config::instance()->set_notification_subject(wx_to_std(_subject->GetValue()));
        }
 
        void notification_from_changed ()
        {
-               Config::instance()->set_notification_from (wx_to_std (_from->GetValue ()));
+               Config::instance()->set_notification_from(wx_to_std(_from->GetValue()));
        }
 
        void notification_to_changed ()
        {
-               Config::instance()->set_notification_to (wx_to_std (_to->GetValue ()));
+               Config::instance()->set_notification_to(wx_to_std(_to->GetValue()));
        }
 
        void notification_bcc_changed ()
        {
-               Config::instance()->set_notification_bcc (wx_to_std (_bcc->GetValue ()));
+               Config::instance()->set_notification_bcc(wx_to_std(_bcc->GetValue()));
        }
 
        void notification_email_changed ()
        {
-               if (_email->GetValue().IsEmpty ()) {
+               if (_email->GetValue().IsEmpty()) {
                        /* Sometimes we get sent an erroneous notification that the email
                           is empty; I don't know why.
                        */
                        return;
                }
-               Config::instance()->set_notification_email (wx_to_std (_email->GetValue ()));
+               Config::instance()->set_notification_email(wx_to_std(_email->GetValue()));
        }
 
        void reset_email ()
        {
-               Config::instance()->reset_notification_email ();
-               checked_set (_email, Config::instance()->notification_email ());
+               Config::instance()->reset_notification_email();
+               checked_set (_email, Config::instance()->notification_email());
        }
 
        void type_changed (wxCheckBox* b, Config::Notification n)
        {
                Config::instance()->set_notification(n, b->GetValue());
-               update_sensitivity ();
+               setup_sensitivity ();
        }
 
        wxCheckBox* _enable_message_box;
@@ -1042,6 +1240,7 @@ private:
        wxButton* _reset_email;
 };
 
+
 class CoverSheetPage : public Page
 {
 public:
@@ -1055,20 +1254,20 @@ public:
 #endif
        {}
 
-       wxString GetName () const
+       wxString GetName () const override
        {
                return _("Cover Sheet");
        }
 
 #ifdef DCPOMATIC_OSX
-       wxBitmap GetLargeIcon () const
+       wxBitmap GetLargeIcon () const override
        {
-               return wxBitmap ("cover_sheet", wxBITMAP_TYPE_PNG_RESOURCE);
+               return wxBitmap(bitmap_path("cover_sheet.png"), wxBITMAP_TYPE_PNG);
        }
 #endif
 
 private:
-       void setup ()
+       void setup () override
        {
                _cover_sheet = new wxTextCtrl (_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (-1, 200), wxTE_MULTILINE);
                _panel->GetSizer()->Add (_cover_sheet, 0, wxEXPAND | wxALL, _border);
@@ -1080,9 +1279,9 @@ private:
                _reset_cover_sheet->Bind (wxEVT_BUTTON, boost::bind (&CoverSheetPage::reset_cover_sheet, this));
        }
 
-       void config_changed ()
+       void config_changed () override
        {
-               checked_set (_cover_sheet, Config::instance()->cover_sheet ());
+               checked_set (_cover_sheet, Config::instance()->cover_sheet());
        }
 
        void cover_sheet_changed ()
@@ -1093,13 +1292,13 @@ private:
                        */
                        return;
                }
-               Config::instance()->set_cover_sheet (wx_to_std (_cover_sheet->GetValue ()));
+               Config::instance()->set_cover_sheet(wx_to_std(_cover_sheet->GetValue()));
        }
 
        void reset_cover_sheet ()
        {
-               Config::instance()->reset_cover_sheet ();
-               checked_set (_cover_sheet, Config::instance()->cover_sheet ());
+               Config::instance()->reset_cover_sheet();
+               checked_set (_cover_sheet, Config::instance()->cover_sheet());
        }
 
        wxTextCtrl* _cover_sheet;
@@ -1114,22 +1313,22 @@ public:
                : Page (panel_size, border)
        {}
 
-       wxString GetName () const
+       wxString GetName () const override
        {
                return _("Identifiers");
        }
 
 #ifdef DCPOMATIC_OSX
-       wxBitmap GetLargeIcon () const
+       wxBitmap GetLargeIcon () const override
        {
-               return wxBitmap ("identifiers", wxBITMAP_TYPE_PNG_RESOURCE);
+               return wxBitmap(bitmap_path("identifiers.png"), wxBITMAP_TYPE_PNG);
        }
 #endif
 
 private:
-       void setup ()
+       void setup () override
        {
-               wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
+               auto table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
                table->AddGrowableCol (1, 1);
 
                add_label_to_sizer (table, _panel, _("Issuer"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
@@ -1172,9 +1371,9 @@ private:
                _j2k_comment->Bind (wxEVT_TEXT, boost::bind(&IdentifiersPage::j2k_comment_changed, this));
        }
 
-       void config_changed ()
+       void config_changed () override
        {
-               Config* config = Config::instance ();
+               auto config = Config::instance ();
                checked_set (_issuer, config->dcp_issuer ());
                checked_set (_creator, config->dcp_creator ());
                checked_set (_company_name, config->dcp_company_name ());
@@ -1185,27 +1384,27 @@ private:
 
        void issuer_changed ()
        {
-               Config::instance()->set_dcp_issuer (wx_to_std (_issuer->GetValue ()));
+               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_dcp_creator(wx_to_std(_creator->GetValue()));
        }
 
        void company_name_changed ()
        {
-               Config::instance()->set_dcp_company_name (wx_to_std(_company_name->GetValue()));
+               Config::instance()->set_dcp_company_name(wx_to_std(_company_name->GetValue()));
        }
 
        void product_name_changed ()
        {
-               Config::instance()->set_dcp_product_name (wx_to_std(_product_name->GetValue()));
+               Config::instance()->set_dcp_product_name(wx_to_std(_product_name->GetValue()));
        }
 
        void product_version_changed ()
        {
-               Config::instance()->set_dcp_product_version (wx_to_std(_product_version->GetValue()));
+               Config::instance()->set_dcp_product_version(wx_to_std(_product_version->GetValue()));
        }
 
        void j2k_comment_changed ()
@@ -1230,32 +1429,17 @@ class AdvancedPage : public Page
 public:
        AdvancedPage (wxSize panel_size, int border)
                : Page (panel_size, border)
-               , _maximum_j2k_bandwidth (0)
-               , _allow_any_dcp_frame_rate (0)
-               , _allow_any_container (0)
-               , _show_experimental_audio_processors (0)
-               , _only_servers_encode (0)
-               , _log_general (0)
-               , _log_warning (0)
-               , _log_error (0)
-               , _log_timing (0)
-               , _log_debug_threed (0)
-               , _log_debug_encode (0)
-               , _log_debug_email (0)
-               , _log_debug_video_view (0)
-               , _log_debug_player (0)
-               , _log_debug_audio_analysis (0)
        {}
 
-       wxString GetName () const
+       wxString GetName () const override
        {
                return _("Advanced");
        }
 
 #ifdef DCPOMATIC_OSX
-       wxBitmap GetLargeIcon () const
+       wxBitmap GetLargeIcon () const override
        {
-               return wxBitmap ("advanced", wxBITMAP_TYPE_PNG_RESOURCE);
+               return wxBitmap(bitmap_path("advanced.png"), wxBITMAP_TYPE_PNG);
        }
 #endif
 
@@ -1271,9 +1455,9 @@ private:
                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);
 
@@ -1282,7 +1466,7 @@ private:
                        wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
                        _maximum_j2k_bandwidth = new wxSpinCtrl (_panel);
                        s->Add (_maximum_j2k_bandwidth, 1);
-                       add_label_to_sizer (s, _panel, _("Mbit/s"), false);
+                       add_label_to_sizer (s, _panel, _("Mbit/s"), false, 0, wxLEFT | wxALIGN_CENTRE_VERTICAL);
                        table->Add (s, 1);
                }
 
@@ -1290,43 +1474,56 @@ private:
                _video_display_mode = new wxChoice (_panel, wxID_ANY);
                table->Add (_video_display_mode);
 
-               wxStaticText* restart = add_label_to_sizer (table, _panel, _("(restart DCP-o-matic to change display mode)"), false);
-               wxFont font = restart->GetFont();
+               auto restart = add_label_to_sizer (table, _panel, _("(restart DCP-o-matic to change display mode)"), false);
+               auto font = restart->GetFont();
                font.SetStyle (wxFONTSTYLE_ITALIC);
                font.SetPointSize (font.GetPointSize() - 1);
                restart->SetFont (font);
                table->AddSpacer (0);
 
                _allow_any_dcp_frame_rate = new CheckBox (_panel, _("Allow any DCP frame rate"));
-               table->Add (_allow_any_dcp_frame_rate, 1, wxEXPAND | wxALL);
+               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 | wxALL);
-               table->AddSpacer (0);
-
-               restart = add_label_to_sizer (table, _panel, _("(restart DCP-o-matic to see all ratios)"), false);
+               table->Add (_allow_any_container, 1, wxEXPAND | wxLEFT, DCPOMATIC_SIZER_GAP);
+               restart = new StaticText (_panel, _("(restart DCP-o-matic to see all ratios)"));
+               table->Add (restart, 1, wxEXPAND | wxALL | wxALIGN_CENTRE_VERTICAL);
                restart->SetFont (font);
+
+               _allow_96khz_audio = new CheckBox (_panel, _("Allow creation of DCPs with 96kHz audio"));
+               table->Add (_allow_96khz_audio, 1, wxEXPAND | wxLEFT, DCPOMATIC_SIZER_GAP);
                table->AddSpacer (0);
 
+               _use_all_audio_channels = new CheckBox(_panel, _("Allow mapping to all audio channels"));
+               table->Add(_use_all_audio_channels, 1, wxEXPAND | wxLEFT, DCPOMATIC_SIZER_GAP);
+               table->AddSpacer(0);
+
                _show_experimental_audio_processors = new CheckBox (_panel, _("Show experimental audio processors"));
-               table->Add (_show_experimental_audio_processors, 1, wxEXPAND | wxALL);
+               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 | wxALL);
+               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);
-                       wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
+                       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'] = wx_to_std (_("type (cpl/pkl)"));
                        dcp::NameFormat::Map examples;
@@ -1338,7 +1535,14 @@ 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'] = wx_to_std (_("type (j2c/pcm/sub)"));
                        titles['r'] = wx_to_std (_("reel number"));
@@ -1357,7 +1561,7 @@ private:
 
                {
                        add_top_aligned_label_to_sizer (table, _panel, _("Log"));
-                       wxBoxSizer* t = new wxBoxSizer (wxVERTICAL);
+                       auto t = new wxFlexGridSizer (2);
                        _log_general = new CheckBox (_panel, _("General"));
                        t->Add (_log_general, 1, wxEXPAND | wxALL);
                        _log_warning = new CheckBox (_panel, _("Warnings"));
@@ -1391,10 +1595,14 @@ private:
                _maximum_j2k_bandwidth->SetRange (1, 1000);
                _maximum_j2k_bandwidth->Bind (wxEVT_SPINCTRL, boost::bind (&AdvancedPage::maximum_j2k_bandwidth_changed, this));
                _video_display_mode->Append (_("Simple (safer)"));
+#if wxCHECK_VERSION(3, 1, 0)
                _video_display_mode->Append (_("OpenGL (faster)"));
+#endif
                _video_display_mode->Bind (wxEVT_CHOICE, boost::bind(&AdvancedPage::video_display_mode_changed, this));
                _allow_any_dcp_frame_rate->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::allow_any_dcp_frame_rate_changed, this));
                _allow_any_container->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::allow_any_container_changed, this));
+               _allow_96khz_audio->Bind (wxEVT_CHECKBOX, boost::bind(&AdvancedPage::allow_96khz_audio_changed, this));
+               _use_all_audio_channels->Bind(wxEVT_CHECKBOX, boost::bind(&AdvancedPage::use_all_channels_changed, this));
                _show_experimental_audio_processors->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::show_experimental_audio_processors_changed, this));
                _only_servers_encode->Bind (wxEVT_CHECKBOX, boost::bind (&AdvancedPage::only_servers_encode_changed, this));
                _frames_in_memory_multiplier->Bind (wxEVT_SPINCTRL, boost::bind(&AdvancedPage::frames_in_memory_multiplier_changed, this));
@@ -1415,9 +1623,9 @@ private:
 #endif
        }
 
-       void config_changed ()
+       void config_changed () override
        {
-               Config* config = Config::instance ();
+               auto config = Config::instance ();
 
                checked_set (_maximum_j2k_bandwidth, config->maximum_j2k_bandwidth() / 1000000);
                switch (config->video_view_type()) {
@@ -1430,6 +1638,8 @@ private:
                }
                checked_set (_allow_any_dcp_frame_rate, config->allow_any_dcp_frame_rate ());
                checked_set (_allow_any_container, config->allow_any_container ());
+               checked_set (_allow_96khz_audio, config->allow_96khz_audio());
+               checked_set (_use_all_audio_channels, config->use_all_audio_channels());
                checked_set (_show_experimental_audio_processors, config->show_experimental_audio_processors ());
                checked_set (_only_servers_encode, config->only_servers_encode ());
                checked_set (_log_general, config->log_types() & LogEntry::TYPE_GENERAL);
@@ -1450,51 +1660,61 @@ private:
 
        void maximum_j2k_bandwidth_changed ()
        {
-               Config::instance()->set_maximum_j2k_bandwidth (_maximum_j2k_bandwidth->GetValue() * 1000000);
+               Config::instance()->set_maximum_j2k_bandwidth(_maximum_j2k_bandwidth->GetValue() * 1000000);
        }
 
        void video_display_mode_changed ()
        {
                if (_video_display_mode->GetSelection() == 0) {
-                       Config::instance()->set_video_view_type (Config::VIDEO_VIEW_SIMPLE);
+                       Config::instance()->set_video_view_type(Config::VIDEO_VIEW_SIMPLE);
                } else {
-                       Config::instance()->set_video_view_type (Config::VIDEO_VIEW_OPENGL);
+                       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());
+               Config::instance()->set_frames_in_memory_multiplier(_frames_in_memory_multiplier->GetValue());
        }
 
        void allow_any_dcp_frame_rate_changed ()
        {
-               Config::instance()->set_allow_any_dcp_frame_rate (_allow_any_dcp_frame_rate->GetValue ());
+               Config::instance()->set_allow_any_dcp_frame_rate(_allow_any_dcp_frame_rate->GetValue());
        }
 
        void allow_any_container_changed ()
        {
-               Config::instance()->set_allow_any_container (_allow_any_container->GetValue ());
+               Config::instance()->set_allow_any_container(_allow_any_container->GetValue());
+       }
+
+       void allow_96khz_audio_changed ()
+       {
+               Config::instance()->set_allow_96hhz_audio(_allow_96khz_audio->GetValue());
+       }
+
+       void use_all_channels_changed ()
+       {
+               Config::instance()->set_use_all_audio_channels(_use_all_audio_channels->GetValue());
        }
 
        void show_experimental_audio_processors_changed ()
        {
-               Config::instance()->set_show_experimental_audio_processors (_show_experimental_audio_processors->GetValue ());
+               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 ()
@@ -1536,62 +1756,65 @@ private:
 #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;
-       wxChoice* _video_display_mode;
-       wxSpinCtrl* _frames_in_memory_multiplier;
-       wxCheckBox* _allow_any_dcp_frame_rate;
-       wxCheckBox* _allow_any_container;
-       wxCheckBox* _show_experimental_audio_processors;
-       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_threed;
-       wxCheckBox* _log_debug_encode;
-       wxCheckBox* _log_debug_email;
-       wxCheckBox* _log_debug_video_view;
-       wxCheckBox* _log_debug_player;
-       wxCheckBox* _log_debug_audio_analysis;
+       wxSpinCtrl* _maximum_j2k_bandwidth = nullptr;
+       wxChoice* _video_display_mode = nullptr;
+       wxSpinCtrl* _frames_in_memory_multiplier = nullptr;
+       wxCheckBox* _allow_any_dcp_frame_rate = nullptr;
+       wxCheckBox* _allow_any_container = nullptr;
+       wxCheckBox* _allow_96khz_audio = nullptr;
+       wxCheckBox* _use_all_audio_channels = nullptr;
+       wxCheckBox* _show_experimental_audio_processors = nullptr;
+       wxCheckBox* _only_servers_encode = nullptr;
+       NameFormatEditor* _dcp_metadata_filename_format = nullptr;
+       NameFormatEditor* _dcp_asset_filename_format = nullptr;
+       wxCheckBox* _log_general = nullptr;
+       wxCheckBox* _log_warning = nullptr;
+       wxCheckBox* _log_error = nullptr;
+       wxCheckBox* _log_timing = nullptr;
+       wxCheckBox* _log_debug_threed = nullptr;
+       wxCheckBox* _log_debug_encode = nullptr;
+       wxCheckBox* _log_debug_email = nullptr;
+       wxCheckBox* _log_debug_video_view = nullptr;
+       wxCheckBox* _log_debug_player = nullptr;
+       wxCheckBox* _log_debug_audio_analysis = nullptr;
 #ifdef DCPOMATIC_WINDOWS
-       wxCheckBox* _win32_console;
+       wxCheckBox* _win32_console = nullptr;
 #endif
 };
 
+
 wxPreferencesEditor*
 create_full_config_dialog ()
 {
-       wxPreferencesEditor* e = new wxPreferencesEditor ();
+       auto e = new wxPreferencesEditor ();
 
 #ifdef DCPOMATIC_OSX
        /* Width that we force some of the config panels to be on OSX so that
           the containing window doesn't shrink too much when we select those panels.
           This is obviously an unpleasant hack.
        */
-       wxSize ps = wxSize (700, -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 SoundPage (ps, border));
-       e->AddPage (new DefaultsPage (ps, border));
-       e->AddPage (new EncodingServersPage (ps, border));
-       e->AddPage (new KeysPage (ps, border));
-       e->AddPage (new TMSPage (ps, border));
-       e->AddPage (new EmailPage (ps, border));
-       e->AddPage (new KDMEmailPage (ps, border));
-       e->AddPage (new NotificationsPage (ps, border));
-       e->AddPage (new CoverSheetPage (ps, border));
-       e->AddPage (new IdentifiersPage (ps, border));
-       e->AddPage (new AdvancedPage (ps, border));
+       e->AddPage (new FullGeneralPage    (ps, border));
+       e->AddPage (new SoundPage          (ps, border));
+       e->AddPage (new DefaultsPage       (ps, border));
+       e->AddPage (new EncodingServersPage(ps, border));
+       e->AddPage (new KeysPage           (ps, border));
+       e->AddPage (new TMSPage            (ps, border));
+       e->AddPage (new EmailPage          (ps, border));
+       e->AddPage (new KDMEmailPage       (ps, border));
+       e->AddPage (new NotificationsPage  (ps, border));
+       e->AddPage (new CoverSheetPage     (ps, border));
+       e->AddPage (new IdentifiersPage    (ps, border));
+       e->AddPage (new AdvancedPage       (ps, border));
        return e;
 }