Merge master.
authorCarl Hetherington <cth@carlh.net>
Wed, 10 Sep 2014 22:30:31 +0000 (23:30 +0100)
committerCarl Hetherington <cth@carlh.net>
Wed, 10 Sep 2014 22:30:31 +0000 (23:30 +0100)
1  2 
ChangeLog
run/dcpomatic
src/wx/config_dialog.cc

diff --combined ChangeLog
index 980bfba88561e77a83335562cddb0955619f9a09,a529f6a810154307fe5ff0f342704b355c2bcdb0..89185d4e62fca3f08dab100a666b55988d9156e1
+++ b/ChangeLog
@@@ -1,44 -1,19 +1,38 @@@
  2014-09-10  Carl Hetherington  <cth@carlh.net>
  
 -      * Fix hidden advanced preferences button in some locales.
 +      * Version 2.0.8 released.
  
 -2014-09-08  Carl Hetherington  <cth@carlh.net>
 +2014-09-10  Carl Hetherington  <cth@carlh.net>
 +
 +      * Fix loading of 1.x films.
 +
 +      * Fix crash on audio analysis in some cases.
 +
 +2014-09-09  Carl Hetherington  <cth@carlh.net>
 +
 +      * Version 2.0.7 released.
 +
 +2014-09-09  Carl Hetherington  <cth@carlh.net>
  
 -      * Version 1.73.4 released.
 +      * Version 2.0.6 released.
 +
 +2014-09-09  Carl Hetherington  <cth@carlh.net>
 +
 +      * Fix missing OS X dependencies.
 +
 +      * Use a different directory for DCP-o-matic 2
 +      configuration (not the same as 1.x).
  
  2014-09-08  Carl Hetherington  <cth@carlh.net>
  
 -      * Fix failure to load Targa files.
 +      * Version 2.0.5 released.
  
- 2014-09-08  Carl Hetherington  <cth@carlh.net>
-       * Version 1.73.4 released.
 -2014-09-07  Carl Hetherington  <cth@carlh.net>
++      * Fix hidden advanced preferences button in some locales.
  
 -      * Version 1.73.3 released.
 +2014-09-08  Carl Hetherington  <cth@carlh.net>
 +
 +      * Fix failure to load Targa files.
  
- 2014-09-07  Carl Hetherington  <cth@carlh.net>
-       * Version 1.73.3 released.
  2014-09-07  Carl Hetherington  <cth@carlh.net>
  
        * Put no stretch / no scale in the set of choices for default
  
        * Fix a few bad fuzzy translations from the preferences dialog.
  
--2014-09-03  Carl Hetherington  <cth@carlh.net>
--
--      * Version 1.73.2 released.
--
  2014-09-03  Carl Hetherington  <cth@carlh.net>
  
        * Fix server certificate downloads on OS X (#376).
  
  2014-08-29  Carl Hetherington  <cth@carlh.net>
  
 +      * Version 2.0.4 released.
 +
 +2014-08-24  Carl Hetherington  <cth@carlh.net>
 +
 +      * Version 2.0.3 released.
 +
 +2014-08-24  Carl Hetherington  <cth@carlh.net>
 +
 +      * Version 2.0.2 released.
 +
 +2014-08-06  Carl Hetherington  <cth@carlh.net>
 +
 +      * Version 2.0.1 released.
 +
 +2014-07-15  Carl Hetherington  <cth@carlh.net>
 +
 +      * A variety of changes were made on the 2.0 branch
 +      but not documented in the ChangeLog.  Most sigificantly:
 +
 +      - DCP import
 +      - Creation of DCPs with proper XML subtitles
 +      - Import of .srt and .xml subtitles
 +      - Audio processing framework (with some basic processors).
 +
 +2014-03-07  Carl Hetherington  <cth@carlh.net>
 +
 +      * Add subtitle view.
        * Some improvements to the manual.
  
  2014-08-26  Carl Hetherington  <cth@carlh.net>
        * Attempt to fix random crashes on OS X (especially during encodes)
        thought to be caused by multiple threads using (different) stringstreams
        at the same time; see src/lib/safe_stringstream.
 +>>>>>>> origin/master
  
  2014-08-09  Carl Hetherington  <cth@carlh.net>
  
  2014-07-10  Carl Hetherington  <cth@carlh.net>
  
        * Version 1.72.2 released.
 +>>>>>>> origin/master
  
  2014-07-10  Carl Hetherington  <cth@carlh.net>
  
diff --combined run/dcpomatic
index eb6f52d235aeabc61c2f16b9af567275f2fe088b,278ee8c5f80e1b5d91afbaaa48727f1a5b1e8ad3..74714865ae0b05224e2562b957f3a912aa44c5c6
@@@ -64,7 -64,7 +64,7 @@@ if [ `uname -s` == "Darwin" ]; the
    cp icons/kdm_email.png $resources
  
    for lang in de_DE es_ES fr_FR it_IT sv_SE nl_NL; do
-     mkdir "$resources/$lang/LC_MESSAGES"
+     mkdir -p "$resources/$lang/LC_MESSAGES"
      cp build/src/lib/mo/$lang/*.mo "$resources/$lang/LC_MESSAGES"
      cp build/src/wx/mo/$lang/*.mo "$resources/$lang/LC_MESSAGES"
      cp build/src/tools/mo/$lang/*.mo "$resources/$lang/LC_MESSAGES"
@@@ -90,24 -90,24 +90,24 @@@ els
    export LD_LIBRARY_PATH=build/src/lib:build/src/wx:build/src/asdcplib/src:$LD_LIBRARY_PATH
    if [ "$1" == "--debug" ]; then
        shift
 -      gdb --args build/src/tools/dcpomatic $*
 +      gdb --args build/src/tools/dcpomatic2 $*
    elif [ "$1" == "--valgrind" ]; then
        shift
 -      valgrind --tool="memcheck" build/src/tools/dcpomatic $*
 +      valgrind --tool="memcheck" build/src/tools/dcpomatic2 $*
    elif [ "$1" == "--callgrind" ]; then
        shift
 -      valgrind --tool="callgrind" build/src/tools/dcpomatic $*
 +      valgrind --tool="callgrind" build/src/tools/dcpomatic2 $*
    elif [ "$1" == "--massif" ]; then
        shift
 -      valgrind --tool="massif" build/src/tools/dcpomatic $*
 +      valgrind --tool="massif" build/src/tools/dcpomatic2 $*
    elif [ "$1" == "--i18n" ]; then
        shift
 -      LANGUAGE=fr_FR.UTF8 LANG=fr_FR.UTF8 LC_ALL=fr_FR.UTF8 build/src/tools/dcpomatic "$*"
 +      LANGUAGE=fr_FR.UTF8 LANG=fr_FR.UTF8 LC_ALL=fr_FR.UTF8 build/src/tools/dcpomatic2 "$*"
    elif [ "$1" == "--perf" ]; then
        shift
 -      perf record build/src/tools/dcpomatic $*
 +      perf record build/src/tools/dcpomatic2 $*
    else
 -      build/src/tools/dcpomatic $*
 +      build/src/tools/dcpomatic2 $*
    fi
  fi
  
diff --combined src/wx/config_dialog.cc
index fa7ccf7cc707b305d450acd00301927c0663ed22,816602355f8c26efed3bd0eb1631385bda4776b2..e71b19e3721438059eec18050e8b814de1e9240b
  #include <wx/preferences.h>
  #include <wx/filepicker.h>
  #include <wx/spinctrl.h>
 -#include <libdcp/colour_matrix.h>
 +#include <dcp/colour_matrix.h>
 +#include <dcp/exceptions.h>
 +#include <dcp/signer.h>
  #include "lib/config.h"
  #include "lib/ratio.h"
  #include "lib/scaler.h"
  #include "lib/filter.h"
  #include "lib/dcp_content_type.h"
  #include "lib/colour_conversion.h"
 +#include "lib/log.h"
 +#include "lib/util.h"
 +#include "lib/cross.h"
 +#include "lib/exceptions.h"
  #include "config_dialog.h"
  #include "wx_util.h"
  #include "editable_list.h"
@@@ -67,6 -61,14 +67,14 @@@ public
        {}
  
  protected:
+       wxPanel* make_panel (wxWindow* parent)
+       {
+               wxPanel* panel = new wxPanel (parent, wxID_ANY, wxDefaultPosition, _panel_size);
+               wxBoxSizer* s = new wxBoxSizer (wxVERTICAL);
+               panel->SetSizer (s);
+               return panel;
+       }
+       
        wxSize _panel_size;
        int _border;
  };
@@@ -81,13 -83,11 +89,11 @@@ public
  
        wxWindow* CreateWindow (wxWindow* parent)
        {
-               wxPanel* panel = new wxPanel (parent);
-               wxBoxSizer* s = new wxBoxSizer (wxVERTICAL);
-               panel->SetSizer (s);
+               wxPanel* panel = make_panel (parent);
  
                wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
                table->AddGrowableCol (1, 1);
-               s->Add (table, 1, wxALL | wxEXPAND, _border);
+               panel->GetSizer()->Add (table, 1, wxALL | wxEXPAND, _border);
                
                _set_language = new wxCheckBox (panel, wxID_ANY, _("Set language"));
                table->Add (_set_language, 1);
                _num_local_encoding_threads = new wxSpinCtrl (panel);
                table->Add (_num_local_encoding_threads, 1);
  
 -              
                _check_for_updates = new wxCheckBox (panel, wxID_ANY, _("Check for updates on startup"));
                table->Add (_check_for_updates, 1, wxEXPAND | wxALL);
                table->AddSpacer (0);
@@@ -243,13 -244,11 +249,11 @@@ public
  
        wxWindow* CreateWindow (wxWindow* parent)
        {
-               wxPanel* panel = new wxPanel (parent);
-               wxBoxSizer* s = new wxBoxSizer (wxVERTICAL);
-               panel->SetSizer (s);
+               wxPanel* panel = make_panel (parent);
  
                wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
                table->AddGrowableCol (1, 1);
-               s->Add (table, 1, wxALL | wxEXPAND, _border);
+               panel->GetSizer()->Add (table, 1, wxALL | wxEXPAND, _border);
                
                {
                        add_label_to_sizer (table, panel, _("Default duration of still images"), true);
@@@ -448,12 -447,10 +452,10 @@@ public
  
        wxWindow* CreateWindow (wxWindow* parent)
        {
-               wxPanel* panel = new wxPanel (parent, wxID_ANY, wxDefaultPosition, _panel_size);
-               wxBoxSizer* s = new wxBoxSizer (wxVERTICAL);
-               panel->SetSizer (s);
+               wxPanel* panel = make_panel (parent);
                
                _use_any_servers = new wxCheckBox (panel, wxID_ANY, _("Use all servers"));
-               s->Add (_use_any_servers, 0, wxALL, _border);
+               panel->GetSizer()->Add (_use_any_servers, 0, wxALL, _border);
                
                vector<string> columns;
                columns.push_back (wx_to_std (_("IP address / host name")));
                        boost::bind (&EncodingServersPage::server_column, this, _1)
                        );
                
-               s->Add (_servers_list, 1, wxEXPAND | wxALL, _border);
+               panel->GetSizer()->Add (_servers_list, 1, wxEXPAND | wxALL, _border);
                
                _use_any_servers->SetValue (Config::instance()->use_any_servers ());
                _use_any_servers->Bind (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&EncodingServersPage::use_any_servers_changed, this));
@@@ -509,9 -506,7 +511,7 @@@ public
  #endif        
        wxWindow* CreateWindow (wxWindow* parent)
        {
-               wxPanel* panel = new wxPanel (parent, wxID_ANY, wxDefaultPosition, _panel_size);
-               wxBoxSizer* s = new wxBoxSizer (wxVERTICAL);
-               panel->SetSizer (s);
+               wxPanel* panel = make_panel (parent);
  
                vector<string> columns;
                columns.push_back (wx_to_std (_("Name")));
                        300
                        );
  
-               s->Add (list, 1, wxEXPAND | wxALL, _border);
+               panel->GetSizer()->Add (list, 1, wxEXPAND | wxALL, _border);
                return panel;
        }
  
@@@ -535,310 -530,6 +535,310 @@@ private
        }
  };
  
 +class KeysPage : public wxPreferencesPage, public Page
 +{
 +public:
 +      KeysPage (wxSize panel_size, int border)
 +              : Page (panel_size, border)
 +      {}
 +
 +      wxString GetName () const
 +      {
 +              return _("Keys");
 +      }
 +
 +#ifdef DCPOMATIC_OSX
 +      wxBitmap GetLargeIcon () const
 +      {
 +              return wxBitmap ("keys", wxBITMAP_TYPE_PNG_RESOURCE);
 +      }
 +#endif        
 +
 +      wxWindow* CreateWindow (wxWindow* parent)
 +      {
 +              _panel = new wxPanel (parent, wxID_ANY, wxDefaultPosition, _panel_size);
 +              wxBoxSizer* overall_sizer = new wxBoxSizer (wxVERTICAL);
 +              _panel->SetSizer (overall_sizer);
 +
 +              wxStaticText* m = new wxStaticText (_panel, wxID_ANY, _("Certificate chain for signing DCPs and KDMs:"));
 +              overall_sizer->Add (m, 0, wxALL, _border);
 +              
 +              wxBoxSizer* certificates_sizer = new wxBoxSizer (wxHORIZONTAL);
 +              overall_sizer->Add (certificates_sizer, 0, wxLEFT | wxRIGHT, _border);
 +              
 +              _certificates = new wxListCtrl (_panel, wxID_ANY, wxDefaultPosition, wxSize (400, 200), wxLC_REPORT | wxLC_SINGLE_SEL);
 +
 +              {
 +                      wxListItem ip;
 +                      ip.SetId (0);
 +                      ip.SetText (_("Type"));
 +                      ip.SetWidth (100);
 +                      _certificates->InsertColumn (0, ip);
 +              }
 +
 +              {
 +                      wxListItem ip;
 +                      ip.SetId (1);
 +                      ip.SetText (_("Thumbprint"));
 +                      ip.SetWidth (300);
 +
 +                      wxFont font = ip.GetFont ();
 +                      font.SetFamily (wxFONTFAMILY_TELETYPE);
 +                      ip.SetFont (font);
 +                      
 +                      _certificates->InsertColumn (1, ip);
 +              }
 +
 +              certificates_sizer->Add (_certificates, 1, wxEXPAND);
 +
 +              {
 +                      wxSizer* s = new wxBoxSizer (wxVERTICAL);
 +                      _add_certificate = new wxButton (_panel, wxID_ANY, _("Add..."));
 +                      s->Add (_add_certificate, 0, wxTOP | wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
 +                      _remove_certificate = new wxButton (_panel, wxID_ANY, _("Remove"));
 +                      s->Add (_remove_certificate, 0, wxTOP | wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
 +                      certificates_sizer->Add (s, 0, wxLEFT, DCPOMATIC_SIZER_X_GAP);
 +              }
 +
 +              wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
 +              table->AddGrowableCol (1, 1);
 +              overall_sizer->Add (table, 1, wxALL | wxEXPAND, _border);
 +
 +              add_label_to_sizer (table, _panel, _("Private key for leaf certificate"), true);
 +              {
 +                      wxSizer* s = new wxBoxSizer (wxHORIZONTAL);
 +                      _signer_private_key = new wxStaticText (_panel, wxID_ANY, wxT (""));
 +                      wxFont font = _signer_private_key->GetFont ();
 +                      font.SetFamily (wxFONTFAMILY_TELETYPE);
 +                      _signer_private_key->SetFont (font);
 +                      s->Add (_signer_private_key, 1, wxLEFT | wxRIGHT | wxALIGN_CENTER_VERTICAL, DCPOMATIC_SIZER_X_GAP);
 +                      _load_signer_private_key = new wxButton (_panel, wxID_ANY, _("Load..."));
 +                      s->Add (_load_signer_private_key, 0, wxLEFT, DCPOMATIC_SIZER_X_GAP);
 +                      table->Add (s, 0);
 +              }
 +
 +              add_label_to_sizer (table, _panel, _("Certificate for decrypting DCPs"), true);
 +              {
 +                      wxSizer* s = new wxBoxSizer (wxHORIZONTAL);
 +                      _decryption_certificate = new wxStaticText (_panel, wxID_ANY, wxT (""));
 +                      wxFont font = _decryption_certificate->GetFont ();
 +                      font.SetFamily (wxFONTFAMILY_TELETYPE);
 +                      _decryption_certificate->SetFont (font);
 +                      s->Add (_decryption_certificate, 1, wxLEFT | wxRIGHT | wxALIGN_CENTER_VERTICAL, DCPOMATIC_SIZER_X_GAP);
 +                      _load_decryption_certificate = new wxButton (_panel, wxID_ANY, _("Load..."));
 +                      s->Add (_load_decryption_certificate, 0, wxLEFT, DCPOMATIC_SIZER_X_GAP);
 +                      table->Add (s, 0);
 +              }
 +
 +              add_label_to_sizer (table, _panel, _("Private key for decrypting DCPs"), true);
 +              {
 +                      wxSizer* s = new wxBoxSizer (wxHORIZONTAL);
 +                      _decryption_private_key = new wxStaticText (_panel, wxID_ANY, wxT (""));
 +                      wxFont font = _decryption_private_key->GetFont ();
 +                      font.SetFamily (wxFONTFAMILY_TELETYPE);
 +                      _decryption_private_key->SetFont (font);
 +                      s->Add (_decryption_private_key, 1, wxLEFT | wxRIGHT | wxALIGN_CENTER_VERTICAL, DCPOMATIC_SIZER_X_GAP);
 +                      _load_decryption_private_key = new wxButton (_panel, wxID_ANY, _("Load..."));
 +                      s->Add (_load_decryption_private_key, 0, wxLEFT, DCPOMATIC_SIZER_X_GAP);
 +                      table->Add (s, 0);
 +              }
 +
 +              _export_decryption_certificate = new wxButton (_panel, wxID_ANY, _("Export DCP decryption certificate..."));
 +              table->Add (_export_decryption_certificate);
 +              table->AddSpacer (0);
 +              
 +              _add_certificate->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&KeysPage::add_certificate, this));
 +              _remove_certificate->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&KeysPage::remove_certificate, this));
 +              _certificates->Bind (wxEVT_COMMAND_LIST_ITEM_SELECTED, boost::bind (&KeysPage::update_sensitivity, this));
 +              _certificates->Bind (wxEVT_COMMAND_LIST_ITEM_DESELECTED, boost::bind (&KeysPage::update_sensitivity, this));
 +              _load_signer_private_key->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&KeysPage::load_signer_private_key, this));
 +              _load_decryption_certificate->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&KeysPage::load_decryption_certificate, this));
 +              _load_decryption_private_key->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&KeysPage::load_decryption_private_key, this));
 +              _export_decryption_certificate->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&KeysPage::export_decryption_certificate, this));
 +
 +              _signer.reset (new dcp::Signer (*Config::instance()->signer().get ()));
 +
 +              update_certificate_list ();
 +              update_signer_private_key ();
 +              update_decryption_certificate ();
 +              update_decryption_private_key ();
 +              update_sensitivity ();
 +
 +              return _panel;
 +      }
 +
 +private:
 +      void add_certificate ()
 +      {
 +              wxFileDialog* d = new wxFileDialog (_panel, _("Select Certificate File"));
 +              
 +              if (d->ShowModal() == wxID_OK) {
 +                      try {
 +                              dcp::Certificate c (dcp::file_to_string (wx_to_std (d->GetPath ())));
 +                              _signer->certificates().add (c);
 +                              Config::instance()->set_signer (_signer);
 +                              update_certificate_list ();
 +                      } catch (dcp::MiscError& e) {
 +                              error_dialog (_panel, wxString::Format (_("Could not read certificate file (%s)"), e.what ()));
 +                      }
 +              }
 +              
 +              d->Destroy ();
 +
 +              update_sensitivity ();
 +      }
 +
 +      void remove_certificate ()
 +      {
 +              int i = _certificates->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
 +              if (i == -1) {
 +                      return;
 +              }
 +              
 +              _certificates->DeleteItem (i);
 +              _signer->certificates().remove (i);
 +              Config::instance()->set_signer (_signer);
 +
 +              update_sensitivity ();
 +      }
 +
 +      void update_certificate_list ()
 +      {
 +              _certificates->DeleteAllItems ();
 +              dcp::CertificateChain::List certs = _signer->certificates().root_to_leaf ();
 +              size_t n = 0;
 +              for (dcp::CertificateChain::List::const_iterator i = certs.begin(); i != certs.end(); ++i) {
 +                      wxListItem item;
 +                      item.SetId (n);
 +                      _certificates->InsertItem (item);
 +                      _certificates->SetItem (n, 1, std_to_wx (i->thumbprint ()));
 +
 +                      if (n == 0) {
 +                              _certificates->SetItem (n, 0, _("Root"));
 +                      } else if (n == (certs.size() - 1)) {
 +                              _certificates->SetItem (n, 0, _("Leaf"));
 +                      } else {
 +                              _certificates->SetItem (n, 0, _("Intermediate"));
 +                      }
 +
 +                      ++n;
 +              }
 +      }
 +
 +      void update_sensitivity ()
 +      {
 +              _remove_certificate->Enable (_certificates->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) != -1);
 +      }
 +
 +      void update_signer_private_key ()
 +      {
 +              _signer_private_key->SetLabel (std_to_wx (dcp::private_key_fingerprint (_signer->key ())));
 +      }       
 +
 +      void load_signer_private_key ()
 +      {
 +              wxFileDialog* d = new wxFileDialog (_panel, _("Select Key File"));
 +
 +              if (d->ShowModal() == wxID_OK) {
 +                      try {
 +                              boost::filesystem::path p (wx_to_std (d->GetPath ()));
 +                              if (boost::filesystem::file_size (p) > 1024) {
 +                                      error_dialog (_panel, wxString::Format (_("Could not read key file (%s)"), std_to_wx (p.string ())));
 +                                      return;
 +                              }
 +                              
 +                              _signer->set_key (dcp::file_to_string (p));
 +                              Config::instance()->set_signer (_signer);
 +                              update_signer_private_key ();
 +                      } catch (dcp::MiscError& e) {
 +                              error_dialog (_panel, wxString::Format (_("Could not read certificate file (%s)"), e.what ()));
 +                      }
 +              }
 +              
 +              d->Destroy ();
 +
 +              update_sensitivity ();
 +
 +      }
 +
 +      void load_decryption_certificate ()
 +      {
 +              wxFileDialog* d = new wxFileDialog (_panel, _("Select Certificate File"));
 +              
 +              if (d->ShowModal() == wxID_OK) {
 +                      try {
 +                              dcp::Certificate c (dcp::file_to_string (wx_to_std (d->GetPath ())));
 +                              Config::instance()->set_decryption_certificate (c);
 +                              update_decryption_certificate ();
 +                      } catch (dcp::MiscError& e) {
 +                              error_dialog (_panel, wxString::Format (_("Could not read certificate file (%s)"), e.what ()));
 +                      }
 +              }
 +              
 +              d->Destroy ();
 +      }
 +
 +      void update_decryption_certificate ()
 +      {
 +              _decryption_certificate->SetLabel (std_to_wx (Config::instance()->decryption_certificate().thumbprint ()));
 +      }
 +
 +      void load_decryption_private_key ()
 +      {
 +              wxFileDialog* d = new wxFileDialog (_panel, _("Select Key File"));
 +
 +              if (d->ShowModal() == wxID_OK) {
 +                      try {
 +                              boost::filesystem::path p (wx_to_std (d->GetPath ()));
 +                              Config::instance()->set_decryption_private_key (dcp::file_to_string (p));
 +                              update_decryption_private_key ();
 +                      } catch (dcp::MiscError& e) {
 +                              error_dialog (_panel, wxString::Format (_("Could not read key file (%s)"), e.what ()));
 +                      }
 +              }
 +              
 +              d->Destroy ();
 +      }
 +
 +      void update_decryption_private_key ()
 +      {
 +              _decryption_private_key->SetLabel (std_to_wx (dcp::private_key_fingerprint (Config::instance()->decryption_private_key())));
 +      }
 +
 +      void export_decryption_certificate ()
 +      {
 +              wxFileDialog* d = new wxFileDialog (
 +                      _panel, _("Select Certificate File"), wxEmptyString, wxEmptyString, wxT ("PEM files (*.pem)|*.pem"),
 +                      wxFD_SAVE | wxFD_OVERWRITE_PROMPT
 +                      );
 +              
 +              if (d->ShowModal () == wxID_OK) {
 +                      FILE* f = fopen_boost (wx_to_std (d->GetPath ()), "w");
 +                      if (!f) {
 +                              throw OpenFileError (wx_to_std (d->GetPath ()));
 +                      }
 +
 +                      string const s = Config::instance()->decryption_certificate().certificate (true);
 +                      fwrite (s.c_str(), 1, s.length(), f);
 +                      fclose (f);
 +              }
 +              d->Destroy ();
 +      }
 +
 +      wxPanel* _panel;
 +      wxListCtrl* _certificates;
 +      wxButton* _add_certificate;
 +      wxButton* _remove_certificate;
 +      wxStaticText* _signer_private_key;
 +      wxButton* _load_signer_private_key;
 +      wxStaticText* _decryption_certificate;
 +      wxButton* _load_decryption_certificate;
 +      wxStaticText* _decryption_private_key;
 +      wxButton* _load_decryption_private_key;
 +      wxButton* _export_decryption_certificate;
 +      shared_ptr<dcp::Signer> _signer;
 +};
 +
  class TMSPage : public wxPreferencesPage, public Page
  {
  public:
  
        wxWindow* CreateWindow (wxWindow* parent)
        {
-               wxPanel* panel = new wxPanel (parent, wxID_ANY, wxDefaultPosition, _panel_size);
-               wxBoxSizer* s = new wxBoxSizer (wxVERTICAL);
-               panel->SetSizer (s);
+               wxPanel* panel = make_panel (parent);
  
                wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
                table->AddGrowableCol (1, 1);
-               s->Add (table, 1, wxALL | wxEXPAND, _border);
+               panel->GetSizer()->Add (table, 1, wxALL | wxEXPAND, _border);
                
                add_label_to_sizer (table, panel, _("IP address"), true);
                _tms_ip = new wxTextCtrl (panel, wxID_ANY);
@@@ -947,18 -636,18 +945,18 @@@ public
  
        wxWindow* CreateWindow (wxWindow* parent)
        {
+ #ifdef DCPOMATIC_OSX          
                /* We have to force both width and height of this one */
- #ifdef DCPOMATIC_OSX
                wxPanel* panel = new wxPanel (parent, wxID_ANY, wxDefaultPosition, wxSize (480, 128));
- #else         
+ #else
                wxPanel* panel = new wxPanel (parent);
- #endif                
+ #endif
                wxBoxSizer* s = new wxBoxSizer (wxVERTICAL);
                panel->SetSizer (s);
  
                wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
                table->AddGrowableCol (1, 1);
-               s->Add (table, 1, wxEXPAND | wxALL, _border);
+               panel->GetSizer()->Add (table, 1, wxEXPAND | wxALL, _border);
  
                add_label_to_sizer (table, panel, _("Outgoing mail server"), true);
                _mail_server = new wxTextCtrl (panel, wxID_ANY);
                table->Add (_kdm_bcc, 1, wxEXPAND | wxALL);
                
                _kdm_email = new wxTextCtrl (panel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (480, 128), wxTE_MULTILINE);
-               s->Add (_kdm_email, 1, wxEXPAND | wxALL, _border);
+               panel->GetSizer()->Add (_kdm_email, 1, wxEXPAND | wxALL, _border);
  
                _reset_kdm_email = new wxButton (panel, wxID_ANY, _("Reset to default text"));
-               s->Add (_reset_kdm_email, 0, wxEXPAND | wxALL, _border);
+               panel->GetSizer()->Add (_reset_kdm_email, 0, wxEXPAND | wxALL, _border);
  
                Config* config = Config::instance ();
                _mail_server->SetValue (std_to_wx (config->mail_server ()));
@@@ -1081,9 -770,6 +1079,9 @@@ private
        wxButton* _reset_kdm_email;
  };
  
 +/** @class AdvancedPage
 + *  @brief Advanced page of the preferences dialog.
 + */
  class AdvancedPage : public wxStockPreferencesPage, public Page
  {
  public:
        
        wxWindow* CreateWindow (wxWindow* parent)
        {
-               wxPanel* panel = new wxPanel (parent);
-               wxBoxSizer* s = new wxBoxSizer (wxVERTICAL);
-               panel->SetSizer (s);
+               wxPanel* panel = make_panel (parent);
  
                wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
                table->AddGrowableCol (1, 1);
-               s->Add (table, 1, wxALL | wxEXPAND, _border);
+               panel->GetSizer()->Add (table, 1, wxALL | wxEXPAND, _border);
  
                {
                        add_label_to_sizer (table, panel, _("Maximum JPEG2000 bandwidth"), true);
                table->Add (_allow_any_dcp_frame_rate, 1, wxEXPAND | wxALL);
                table->AddSpacer (0);
  
-               int flags = wxALIGN_TOP | wxLEFT | wxRIGHT | wxEXPAND | wxALL;
  #ifdef __WXOSX__
-               flags |= wxALIGN_RIGHT;
-               t += wxT (":");
- #endif        
+               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, flags, 6);
+               table->Add (m, 0, wxALIGN_TOP | wxLEFT | wxRIGHT | wxEXPAND | wxALL, 6);
+ #endif                
                
                {
                        wxBoxSizer* t = new wxBoxSizer (wxVERTICAL);
@@@ -1205,7 -888,7 +1200,7 @@@ create_config_dialog (
           the containing window doesn't shrink too much when we select those panels.
           This is obviously an unpleasant hack.
        */
-       wxSize ps = wxSize (480, -1);
+       wxSize ps = wxSize (520, -1);
        int const border = 16;
  #else
        wxSize ps = wxSize (-1, -1);
        e->AddPage (new DefaultsPage (ps, border));
        e->AddPage (new EncodingServersPage (ps, border));
        e->AddPage (new ColourConversionsPage (ps, border));
 +      e->AddPage (new KeysPage (ps, border));
        e->AddPage (new TMSPage (ps, border));
        e->AddPage (new KDMEmailPage (ps, border));
        e->AddPage (new AdvancedPage (ps, border));