Merge master.
authorCarl Hetherington <cth@carlh.net>
Mon, 25 Aug 2014 15:56:17 +0000 (16:56 +0100)
committerCarl Hetherington <cth@carlh.net>
Mon, 25 Aug 2014 15:56:17 +0000 (16:56 +0100)
1  2 
ChangeLog
src/lib/config.cc
src/lib/config.h
src/lib/util.h
src/tools/dcpomatic.cc
src/wx/film_editor.cc
src/wx/film_editor.h
src/wx/film_viewer.cc
src/wx/film_viewer.h

diff --combined ChangeLog
index d1d008b5e96feff8b417de07099e5ca98035074b,c3d8a77c4877479c5657c6ab756968faefa5f22d..c34750ad6595a5e25ea72c042e903a728c8bb5f5
+++ b/ChangeLog
@@@ -1,29 -1,7 +1,33 @@@
 +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.
 +
+ 2014-08-25  Carl Hetherington  <cth@carlh.net>
+       * Basic recent files list in the File menu.
  2014-08-23  Carl Hetherington  <cth@carlh.net>
  
        * Version 1.72.12 released.
@@@ -44,7 -22,6 +48,7 @@@
        * 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 src/lib/config.cc
index d20536f14afc00f3c4c2539322a7af89499f75e1,04f28579ba580ab927e9ac77a58958632e7fa8a9..7e37625878191a87db0e7bf1cf3959e5b3fb68e3
  #include <glib.h>
  #include <boost/filesystem.hpp>
  #include <boost/algorithm/string.hpp>
 -#include <libdcp/colour_matrix.h>
 -#include <libdcp/raw_convert.h>
 +#include <dcp/colour_matrix.h>
 +#include <dcp/raw_convert.h>
 +#include <dcp/signer.h>
 +#include <dcp/certificate_chain.h>
  #include <libcxml/cxml.h>
  #include "config.h"
  #include "server.h"
  #include "filter.h"
  #include "ratio.h"
  #include "dcp_content_type.h"
 -#include "sound_processor.h"
 +#include "cinema_sound_processor.h"
  #include "colour_conversion.h"
  #include "cinema.h"
  #include "util.h"
 +#include "cross.h"
  
  #include "i18n.h"
  
  using std::vector;
+ using std::cout;
  using std::ifstream;
  using std::string;
  using std::list;
  using std::max;
+ using std::remove;
  using std::exception;
  using std::cerr;
  using boost::shared_ptr;
  using boost::optional;
  using boost::algorithm::is_any_of;
  using boost::algorithm::split;
 -using libdcp::raw_convert;
 +using dcp::raw_convert;
  
  Config* Config::_instance = 0;
  
@@@ -62,7 -61,7 +64,7 @@@ Config::Config (
        , _server_port_base (6192)
        , _use_any_servers (true)
        , _tms_path (".")
 -      , _sound_processor (SoundProcessor::from_id (N_("dolby_cp750")))
 +      , _cinema_sound_processor (CinemaSoundProcessor::from_id (N_("dolby_cp750")))
        , _allow_any_dcp_frame_rate (false)
        , _default_still_length (10)
        , _default_scale (Ratio::from_id ("185"))
@@@ -82,9 -81,9 +84,9 @@@
        _allowed_dcp_frame_rates.push_back (50);
        _allowed_dcp_frame_rates.push_back (60);
  
 -      _colour_conversions.push_back (PresetColourConversion (_("sRGB"), 2.4, true, libdcp::colour_matrix::srgb_to_xyz, 2.6));
 -      _colour_conversions.push_back (PresetColourConversion (_("sRGB non-linearised"), 2.4, false, libdcp::colour_matrix::srgb_to_xyz, 2.6));
 -      _colour_conversions.push_back (PresetColourConversion (_("Rec. 709"), 2.2, false, libdcp::colour_matrix::rec709_to_xyz, 2.6));
 +      _colour_conversions.push_back (PresetColourConversion (_("sRGB"), 2.4, true, dcp::colour_matrix::srgb_to_xyz, 2.6));
 +      _colour_conversions.push_back (PresetColourConversion (_("sRGB non-linearised"), 2.4, false, dcp::colour_matrix::srgb_to_xyz, 2.6));
 +      _colour_conversions.push_back (PresetColourConversion (_("Rec. 709"), 2.2, false, dcp::colour_matrix::rec709_to_xyz, 2.6));
  
        reset_kdm_email ();
  }
@@@ -93,10 -92,7 +95,10 @@@ voi
  Config::read ()
  {
        if (!boost::filesystem::exists (file (false))) {
 -              read_old_metadata ();
 +              /* Make a new set of signing certificates and key */
 +              _signer.reset (new dcp::Signer (openssl_path ()));
 +              /* And decryption keys */
 +              make_decryption_keys ();
                return;
        }
  
  
        c = f.optional_string_child ("SoundProcessor");
        if (c) {
 -              _sound_processor = SoundProcessor::from_id (c.get ());
 +              _cinema_sound_processor = CinemaSoundProcessor::from_id (c.get ());
 +      }
 +      c = f.optional_string_child ("CinemaSoundProcessor");
 +      if (c) {
 +              _cinema_sound_processor = CinemaSoundProcessor::from_id (c.get ());
        }
  
        _language = f.optional_string_child ("Language");
                /* Loading version 0 (before Rec. 709 was added as a preset).
                   Add it in.
                */
 -              _colour_conversions.push_back (PresetColourConversion (_("Rec. 709"), 2.2, false, libdcp::colour_matrix::rec709_to_xyz, 2.6));
 +              _colour_conversions.push_back (PresetColourConversion (_("Rec. 709"), 2.2, false, dcp::colour_matrix::rec709_to_xyz, 2.6));
        }
  
        list<cxml::NodePtr> cin = f.node_children ("Cinema");
  
        _log_types = f.optional_number_child<int> ("LogTypes").get_value_or (Log::TYPE_GENERAL | Log::TYPE_WARNING | Log::TYPE_ERROR);
  
 -}
+       list<cxml::NodePtr> his = f.node_children ("History");
+       for (list<cxml::NodePtr>::const_iterator i = his.begin(); i != his.end(); ++i) {
+               _history.push_back ((*i)->content ());
+       }
 -void
 -Config::read_old_metadata ()
 -{
 -      /* XXX: this won't work with non-Latin filenames */
 -      ifstream f (file(true).string().c_str ());
 -      string line;
 -
 -      while (getline (f, line)) {
 -              if (line.empty ()) {
 -                      continue;
 +      cxml::NodePtr signer = f.optional_node_child ("Signer");
 +      dcp::CertificateChain signer_chain;
 +      if (signer) {
 +              /* Read the signing certificates and private key in from the config file */
 +              list<cxml::NodePtr> certificates = signer->node_children ("Certificate");
 +              for (list<cxml::NodePtr>::const_iterator i = certificates.begin(); i != certificates.end(); ++i) {
 +                      signer_chain.add (dcp::Certificate ((*i)->content ()));
                }
  
 -              if (line[0] == '#') {
 -                      continue;
 -              }
 +              _signer.reset (new dcp::Signer (signer_chain, signer->string_child ("PrivateKey")));
 +      } else {
 +              /* Make a new set of signing certificates and key */
 +              _signer.reset (new dcp::Signer (openssl_path ()));
 +      }
  
 -              size_t const s = line.find (' ');
 -              if (s == string::npos) {
 -                      continue;
 -              }
 -              
 -              string const k = line.substr (0, s);
 -              string const v = line.substr (s + 1);
 -
 -              if (k == N_("num_local_encoding_threads")) {
 -                      _num_local_encoding_threads = atoi (v.c_str ());
 -              } else if (k == N_("default_directory")) {
 -                      _default_directory = v;
 -              } else if (k == N_("server_port")) {
 -                      _server_port_base = atoi (v.c_str ());
 -              } else if (k == N_("server")) {
 -                      vector<string> b;
 -                      split (b, v, is_any_of (" "));
 -                      if (b.size() == 2) {
 -                              _servers.push_back (b[0]);
 -                      }
 -              } else if (k == N_("tms_ip")) {
 -                      _tms_ip = v;
 -              } else if (k == N_("tms_path")) {
 -                      _tms_path = v;
 -              } else if (k == N_("tms_user")) {
 -                      _tms_user = v;
 -              } else if (k == N_("tms_password")) {
 -                      _tms_password = v;
 -              } else if (k == N_("sound_processor")) {
 -                      _sound_processor = SoundProcessor::from_id (v);
 -              } else if (k == "language") {
 -                      _language = v;
 -              } else if (k == "default_container") {
 -                      _default_container = Ratio::from_id (v);
 -              } else if (k == "default_dcp_content_type") {
 -                      _default_dcp_content_type = DCPContentType::from_isdcf_name (v);
 -              } else if (k == "dcp_metadata_issuer") {
 -                      _dcp_metadata.issuer = v;
 -              } else if (k == "dcp_metadata_creator") {
 -                      _dcp_metadata.creator = v;
 -              } else if (k == "dcp_metadata_issue_date") {
 -                      _dcp_metadata.issue_date = v;
 -              }
 +      if (f.optional_string_child ("DecryptionCertificate")) {
 +              _decryption_certificate = dcp::Certificate (f.string_child ("DecryptionCertificate"));
 +      }
 +
 +      if (f.optional_string_child ("DecryptionPrivateKey")) {
 +              _decryption_private_key = f.string_child ("DecryptionPrivateKey");
 +      }
  
 -              _default_isdcf_metadata.read_old_metadata (k, v);
 +      if (!f.optional_string_child ("DecryptionCertificate") || !f.optional_string_child ("DecryptionPrivateKey")) {
 +              /* Generate our own decryption certificate and key if either is not present in config */
 +              make_decryption_keys ();
        }
  }
  
 +void
 +Config::make_decryption_keys ()
 +{
 +      boost::filesystem::path p = dcp::make_certificate_chain (openssl_path ());
 +      _decryption_certificate = dcp::Certificate (dcp::file_to_string (p / "leaf.signed.pem"));
 +      _decryption_private_key = dcp::file_to_string (p / "leaf.key");
 +      boost::filesystem::remove_all (p);
 +}
 +
  /** @return Filename to write configuration to */
  boost::filesystem::path
  Config::file (bool old) const
        return p;
  }
  
 -boost::filesystem::path
 -Config::signer_chain_directory () const
 -{
 -      boost::filesystem::path p;
 -      p /= g_get_user_config_dir ();
 -      p /= "dcpomatic";
 -      p /= "crypt";
 -      boost::filesystem::create_directories (p);
 -      return p;
 -}
 -
  /** @return Singleton instance */
  Config *
  Config::instance ()
@@@ -313,8 -348,8 +320,8 @@@ Config::write () cons
        root->add_child("TMSPath")->add_child_text (_tms_path);
        root->add_child("TMSUser")->add_child_text (_tms_user);
        root->add_child("TMSPassword")->add_child_text (_tms_password);
 -      if (_sound_processor) {
 -              root->add_child("SoundProcessor")->add_child_text (_sound_processor->id ());
 +      if (_cinema_sound_processor) {
 +              root->add_child("CinemaSoundProcessor")->add_child_text (_cinema_sound_processor->id ());
        }
        if (_language) {
                root->add_child("Language")->add_child_text (_language.get());
        root->add_child("AllowAnyDCPFrameRate")->add_child_text (_allow_any_dcp_frame_rate ? "1" : "0");
        root->add_child("LogTypes")->add_child_text (raw_convert<string> (_log_types));
  
 +      xmlpp::Element* signer = root->add_child ("Signer");
 +      dcp::CertificateChain::List certs = _signer->certificates().root_to_leaf ();
 +      for (dcp::CertificateChain::List::const_iterator i = certs.begin(); i != certs.end(); ++i) {
 +              signer->add_child("Certificate")->add_child_text (i->certificate (true));
 +      }
 +      signer->add_child("PrivateKey")->add_child_text (_signer->key ());
 +
 +      root->add_child("DecryptionCertificate")->add_child_text (_decryption_certificate.certificate (true));
 +      root->add_child("DecryptionPrivateKey")->add_child_text (_decryption_private_key);
 +
+       for (vector<boost::filesystem::path>::const_iterator i = _history.begin(); i != _history.end(); ++i) {
+               root->add_child("History")->add_child_text (i->string ());
+       }
+       
        doc.write_to_file_formatted (file(false).string ());
  }
  
@@@ -416,3 -445,17 +427,17 @@@ Config::reset_kdm_email (
                "Best regards,\nDCP-o-matic"
                );
  }
+ void
+ Config::add_to_history (boost::filesystem::path p)
+ {
+       /* Remove existing instances of this path in the history */
+       _history.erase (remove (_history.begin(), _history.end(), p), _history.end ());
+       
+       _history.insert (_history.begin (), p);
+       if (_history.size() > HISTORY_SIZE) {
+               _history.pop_back ();
+       }
+       changed ();
+ }
diff --combined src/lib/config.h
index 3cfaa12007677a1c4f6939a9751ba4a127dee824,aa3c06356320cd28464b14b1e7e358a403f87df5..05bc7945f1df80bb77c6f846d01c0a161ac80810
  #include <boost/shared_ptr.hpp>
  #include <boost/signals2.hpp>
  #include <boost/filesystem.hpp>
 -#include <libdcp/metadata.h>
 +#include <dcp/metadata.h>
 +#include <dcp/certificates.h>
 +#include <dcp/signer.h>
  #include "isdcf_metadata.h"
  #include "colour_conversion.h"
 -#include "server.h"
  
  class ServerDescription;
  class Scaler;
  class Filter;
 -class SoundProcessor;
 +class CinemaSoundProcessor;
  class DCPContentType;
  class Ratio;
  class Cinema;
@@@ -105,9 -104,9 +105,9 @@@ public
                return _tms_password;
        }
  
 -      /** @return The sound processor that we are using */
 -      SoundProcessor const * sound_processor () const {
 -              return _sound_processor;
 +      /** @return The cinema sound processor that we are using */
 +      CinemaSoundProcessor const * cinema_sound_processor () const {
 +              return _cinema_sound_processor;
        }
  
        std::list<boost::shared_ptr<Cinema> > cinemas () const {
                return _default_dcp_content_type;
        }
  
 -      libdcp::XMLMetadata dcp_metadata () const {
 +      dcp::XMLMetadata dcp_metadata () const {
                return _dcp_metadata;
        }
  
                return _kdm_email;
        }
  
 +      boost::shared_ptr<const dcp::Signer> signer () const {
 +              return _signer;
 +      }
 +
 +      dcp::Certificate decryption_certificate () const {
 +              return _decryption_certificate;
 +      }
 +
 +      std::string decryption_private_key () const {
 +              return _decryption_private_key;
 +      }
 +
        bool check_for_updates () const {
                return _check_for_updates;
        }
        int log_types () const {
                return _log_types;
        }
-       
+       std::vector<boost::filesystem::path> history () const {
+               return _history;
+       }
        /** @param n New number of local encoding threads */
        void set_num_local_encoding_threads (int n) {
                _num_local_encoding_threads = n;
                changed ();
        }
  
 -      void set_dcp_metadata (libdcp::XMLMetadata m) {
 +      void set_dcp_metadata (dcp::XMLMetadata m) {
                _dcp_metadata = m;
                changed ();
        }
  
        void reset_kdm_email ();
  
 +      void set_signer (boost::shared_ptr<const dcp::Signer> s) {
 +              _signer = s;
 +              changed ();
 +      }
 +
 +      void set_decryption_certificate (dcp::Certificate c) {
 +              _decryption_certificate = c;
 +              changed ();
 +      }
 +
 +      void set_decryption_private_key (std::string k) {
 +              _decryption_private_key = k;
 +              changed ();
 +      }
 +
        void set_check_for_updates (bool c) {
                _check_for_updates = c;
                changed ();
                _log_types = t;
                changed ();
        }
+       void clear_history () {
+               _history.clear ();
+               changed ();
+       }
+       void add_to_history (boost::filesystem::path p);
        
 -      boost::filesystem::path signer_chain_directory () const;
 -
        void changed ();
        boost::signals2::signal<void ()> Changed;
  
@@@ -425,8 -410,8 +436,8 @@@ private
        Config ();
        boost::filesystem::path file (bool) const;
        void read ();
 -      void read_old_metadata ();
        void write () const;
 +      void make_decryption_keys ();
  
        /** number of threads to use for J2K encoding on the local machine */
        int _num_local_encoding_threads;
        std::string _tms_user;
        /** Password to log into the TMS with */
        std::string _tms_password;
 -      /** Our sound processor */
 -      SoundProcessor const * _sound_processor;
 +      /** Our cinema sound processor */
 +      CinemaSoundProcessor const * _cinema_sound_processor;
        std::list<int> _allowed_dcp_frame_rates;
        /** Allow any video frame rate for the DCP; if true, overrides _allowed_dcp_frame_rates */
        bool _allow_any_dcp_frame_rate;
        Ratio const * _default_scale;
        Ratio const * _default_container;
        DCPContentType const * _default_dcp_content_type;
 -      libdcp::XMLMetadata _dcp_metadata;
 +      dcp::XMLMetadata _dcp_metadata;
        int _default_j2k_bandwidth;
        int _default_audio_delay;
        std::vector<PresetColourConversion> _colour_conversions;
        std::string _kdm_cc;
        std::string _kdm_bcc;
        std::string _kdm_email;
 +      boost::shared_ptr<const dcp::Signer> _signer;
 +      dcp::Certificate _decryption_certificate;
 +      std::string _decryption_private_key;
        /** true to check for updates on startup */
        bool _check_for_updates;
        bool _check_for_test_updates;
        /** maximum allowed J2K bandwidth in bits per second */
        int _maximum_j2k_bandwidth;
        int _log_types;
+       std::vector<boost::filesystem::path> _history;
+       
        /** Singleton instance, or 0 */
        static Config* _instance;
  };
diff --combined src/lib/util.h
index 1bbdfb2cf23dc56864241f1669b6cc87f8feea97,675c8d03e6304a42a56f4911e88eb4bdeeaa352d..724e8937ca3e4fb3b7b22c36bd2e6650efaf9b7b
@@@ -31,7 -31,7 +31,7 @@@
  #include <boost/asio.hpp>
  #include <boost/optional.hpp>
  #include <boost/filesystem.hpp>
 -#include <libdcp/util.h>
 +#include <dcp/util.h>
  extern "C" {
  #include <libavcodec/avcodec.h>
  #include <libavfilter/avfilter.h>
  
  /** The maximum number of audio channels that we can have in a DCP */
  #define MAX_DCP_AUDIO_CHANNELS 12
  #define DCPOMATIC_HELLO "Boys, you gotta learn not to talk to nuns that way"
+ #define HISTORY_SIZE 10
  
 -namespace libdcp {
 -      class Signer;
 -}
 -
  class Job;
 +struct AVSubtitle;
  
  extern std::string seconds_to_hms (int);
  extern std::string seconds_to_approximate_hms (int);
@@@ -66,12 -69,12 +66,12 @@@ extern bool valid_image_file (boost::fi
  extern boost::filesystem::path mo_path ();
  #endif
  extern std::string tidy_for_filename (std::string);
 -extern boost::shared_ptr<const libdcp::Signer> make_signer ();
 -extern libdcp::Size fit_ratio_within (float ratio, libdcp::Size);
 +extern dcp::Size fit_ratio_within (float ratio, dcp::Size, int);
  extern std::string entities_to_text (std::string e);
  extern std::map<std::string, std::string> split_get_request (std::string url);
  extern int dcp_audio_frame_rate (int);
  extern int stride_round_up (int, int const *, int);
 +extern int round_to (float n, int r);
  extern std::multimap<std::string, std::string> read_key_value (std::istream& s);
  extern int get_required_int (std::multimap<std::string, std::string> const & kv, std::string k);
  extern float get_required_float (std::multimap<std::string, std::string> const & kv, std::string k);
@@@ -80,7 -83,6 +80,7 @@@ extern int get_optional_int (std::multi
  extern std::string get_optional_string (std::multimap<std::string, std::string> const & kv, std::string k);
  extern void* wrapped_av_malloc (size_t);
  extern int64_t divide_with_round (int64_t a, int64_t b);
 +extern ContentTimePeriod subtitle_period (AVSubtitle const &);
  
  /** @class Socket
   *  @brief A class to wrap a boost::asio::ip::tcp::socket with some things
@@@ -123,20 -125,16 +123,20 @@@ private
  
  extern int64_t video_frames_to_audio_frames (VideoContent::Frame v, float audio_sample_rate, float frames_per_second);
  
 +/** @class ScopedTemporary
 + *  @brief A temporary file which is deleted when the ScopedTemporary object goes out of scope.
 + */
  class ScopedTemporary
  {
  public:
        ScopedTemporary ();
        ~ScopedTemporary ();
  
 +      /** @return temporary filename */
        boost::filesystem::path file () const {
                return _file;
        }
 -      
 +
        char const * c_str () const;
        FILE* open (char const *);
        void close ();
diff --combined src/tools/dcpomatic.cc
index 4246455378fb8c401aec5ac96206d54ff093ff0b,09aebd39ce3f953c48d3a2950117ab78f5957519..8763e35cb2abc2e2000323c2dd06ba62787f0595
@@@ -30,7 -30,7 +30,7 @@@
  #include <wx/stdpaths.h>
  #include <wx/cmdline.h>
  #include <wx/preferences.h>
 -#include <libdcp/exceptions.h>
 +#include <dcp/exceptions.h>
  #include "wx/film_viewer.h"
  #include "wx/film_editor.h"
  #include "wx/job_manager_view.h"
@@@ -45,7 -45,6 +45,7 @@@
  #include "wx/servers_list_dialog.h"
  #include "wx/hints_dialog.h"
  #include "wx/update_dialog.h"
 +#include "wx/content_panel.h"
  #include "lib/film.h"
  #include "lib/config.h"
  #include "lib/util.h"
@@@ -64,6 -63,7 +64,7 @@@
  
  using std::cout;
  using std::string;
+ using std::vector;
  using std::wstring;
  using std::map;
  using std::make_pair;
@@@ -72,22 -72,16 +73,16 @@@ using std::exception
  using boost::shared_ptr;
  using boost::dynamic_pointer_cast;
  
- static shared_ptr<Film> film;
- static std::string film_to_load;
- static std::string film_to_create;
- static std::string content_to_add;
- static wxMenu* jobs_menu = 0;
  // #define DCPOMATIC_WINDOWS_CONSOLE 1
  
  class FilmChangedDialog
  {
  public:
-       FilmChangedDialog ()
+       FilmChangedDialog (string name)
        {
                _dialog = new wxMessageDialog (
                        0,
-                       wxString::Format (_("Save changes to film \"%s\" before closing?"), std_to_wx (film->name ()).data()),
+                       wxString::Format (_("Save changes to film \"%s\" before closing?"), std_to_wx (name).data()),
                        _("Film changed"),
                        wxYES_NO | wxYES_DEFAULT | wxICON_QUESTION
                        );
@@@ -110,51 -104,6 +105,6 @@@ private
        wxMessageDialog* _dialog;
  };
  
- static void
- maybe_save_then_delete_film ()
- {
-       if (!film) {
-               return;
-       }
-                       
-       if (film->dirty ()) {
-               FilmChangedDialog d;
-               switch (d.run ()) {
-               case wxID_NO:
-                       break;
-               case wxID_YES:
-                       film->write_metadata ();
-                       break;
-               }
-       }
-       
-       film.reset ();
- }
- static void
- check_film_state_version (int v)
- {
-       if (v == 4) {
-               error_dialog (
-                       0,
-                       _("This film was created with an old version of DVD-o-matic and may not load correctly "
-                         "in this version.  Please check the film's settings carefully.")
-                       );
-       }
- }
- static void
- load_film (boost::filesystem::path file)
- {
-       film.reset (new Film (file));
-       list<string> const notes = film->read_metadata ();
-       check_film_state_version (film->state_version ());
-       for (list<string>::const_iterator i = notes.begin(); i != notes.end(); ++i) {
-               error_dialog (0, std_to_wx (*i));
-       }
- }
  #define ALWAYS                       0x0
  #define NEEDS_FILM                   0x1
  #define NOT_DURING_DCP_CREATION      0x2
  
  map<wxMenuItem*, int> menu_items;
        
- static void
- add_item (wxMenu* menu, wxString text, int id, int sens)
- {
-       wxMenuItem* item = menu->Append (id, text);
-       menu_items.insert (make_pair (item, sens));
- }
  enum {
        ID_file_new = 1,
        ID_file_open,
        ID_file_save,
        ID_file_properties,
-       ID_content_scale_to_fit_width,
+       ID_file_history,
+       /* Allow spare IDs after _history for the recent files list */
+       ID_content_scale_to_fit_width = 100,
        ID_content_scale_to_fit_height,
        ID_jobs_make_dcp,
        ID_jobs_make_kdms,
        ID_tools_check_for_updates
  };
  
- static void
- setup_menu (wxMenuBar* m)
- {
-       wxMenu* file = new wxMenu;
-       add_item (file, _("New..."), ID_file_new, ALWAYS);
-       add_item (file, _("&Open..."), ID_file_open, ALWAYS);
-       file->AppendSeparator ();
-       add_item (file, _("&Save"), ID_file_save, NEEDS_FILM);
-       file->AppendSeparator ();
-       add_item (file, _("&Properties..."), ID_file_properties, NEEDS_FILM);
- #ifndef __WXOSX__     
-       file->AppendSeparator ();
- #endif
- #ifdef __WXOSX__      
-       add_item (file, _("&Exit"), wxID_EXIT, ALWAYS);
- #else
-       add_item (file, _("&Quit"), wxID_EXIT, ALWAYS);
- #endif        
-       
- #ifdef __WXOSX__      
-       add_item (file, _("&Preferences..."), wxID_PREFERENCES, ALWAYS);
- #else
-       wxMenu* edit = new wxMenu;
-       add_item (edit, _("&Preferences..."), wxID_PREFERENCES, ALWAYS);
- #endif
-       wxMenu* content = new wxMenu;
-       add_item (content, _("Scale to fit &width"), ID_content_scale_to_fit_width, NEEDS_FILM | NEEDS_SELECTED_VIDEO_CONTENT);
-       add_item (content, _("Scale to fit &height"), ID_content_scale_to_fit_height, NEEDS_FILM | NEEDS_SELECTED_VIDEO_CONTENT);
-       jobs_menu = new wxMenu;
-       add_item (jobs_menu, _("&Make DCP"), ID_jobs_make_dcp, NEEDS_FILM | NOT_DURING_DCP_CREATION);
-       add_item (jobs_menu, _("Make &KDMs..."), ID_jobs_make_kdms, NEEDS_FILM);
-       add_item (jobs_menu, _("&Send DCP to TMS"), ID_jobs_send_dcp_to_tms, NEEDS_FILM | NOT_DURING_DCP_CREATION | NEEDS_CPL);
-       add_item (jobs_menu, _("S&how DCP"), ID_jobs_show_dcp, NEEDS_FILM | NOT_DURING_DCP_CREATION | NEEDS_CPL);
-       wxMenu* tools = new wxMenu;
-       add_item (tools, _("Hints..."), ID_tools_hints, 0);
-       add_item (tools, _("Encoding servers..."), ID_tools_encoding_servers, 0);
-       add_item (tools, _("Check for updates"), ID_tools_check_for_updates, 0);
-       wxMenu* help = new wxMenu;
- #ifdef __WXOSX__      
-       add_item (help, _("About DCP-o-matic"), wxID_ABOUT, ALWAYS);
- #else 
-       add_item (help, _("About"), wxID_ABOUT, ALWAYS);
- #endif        
-       m->Append (file, _("&File"));
- #ifndef __WXOSX__     
-       m->Append (edit, _("&Edit"));
- #endif
-       m->Append (content, _("&Content"));
-       m->Append (jobs_menu, _("&Jobs"));
-       m->Append (tools, _("&Tools"));
-       m->Append (help, _("&Help"));
- }
  class Frame : public wxFrame
  {
  public:
                , _hints_dialog (0)
                , _servers_list_dialog (0)
                , _config_dialog (0)
+               , _file_menu (0)
+               , _history_items (0)
+               , _history_position (0)
+               , _history_separator (0)
        {
  #if defined(DCPOMATIC_WINDOWS) && defined(DCPOMATIC_WINDOWS_CONSOLE)
                  AllocConsole();
                setup_menu (bar);
                SetMenuBar (bar);
  
+               Config::instance()->Changed.connect (boost::bind (&Frame::config_changed, this));
+               config_changed ();
                Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&Frame::file_new, this),                ID_file_new);
                Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&Frame::file_open, this),               ID_file_open);
                Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&Frame::file_save, this),               ID_file_save);
                Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&Frame::file_properties, this),         ID_file_properties);
+               Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&Frame::file_history, this, _1),        ID_file_history, ID_file_history + HISTORY_SIZE);
                Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&Frame::file_exit, this),               wxID_EXIT);
                Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&Frame::edit_preferences, this),        wxID_PREFERENCES);
                Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&Frame::content_scale_to_fit_width, this), ID_content_scale_to_fit_width);
                */
                wxPanel* overall_panel = new wxPanel (this, wxID_ANY);
  
-               _film_editor = new FilmEditor (film, overall_panel);
-               _film_viewer = new FilmViewer (film, overall_panel);
+               _film_editor = new FilmEditor (overall_panel);
+               _film_viewer = new FilmViewer (overall_panel);
                JobManagerView* job_manager_view = new JobManagerView (overall_panel, static_cast<JobManagerView::Buttons> (0));
  
                wxBoxSizer* right_sizer = new wxBoxSizer (wxVERTICAL);
                set_menu_sensitivity ();
  
                _film_editor->FileChanged.connect (bind (&Frame::file_changed, this, _1));
-               if (film) {
-                       file_changed (film->directory ());
-               } else {
-                       file_changed ("");
-               }
+               file_changed ("");
  
                JobManager::instance()->ActiveJobsChanged.connect (boost::bind (&Frame::set_menu_sensitivity, this));
  
-               set_film ();
                overall_panel->SetSizer (main_sizer);
        }
  
- private:
+       void new_film (boost::filesystem::path path)
+       {
+               shared_ptr<Film> film (new Film (path));
+               film->write_metadata ();
+               film->set_name (path.filename().generic_string());
+               set_film (film);
+       }
  
-       void set_film ()
+       void load_film (boost::filesystem::path file)
+       try
        {
-               _film_viewer->set_film (film);
-               _film_editor->set_film (film);
+               maybe_save_then_delete_film ();
+               
+               shared_ptr<Film> film (new Film (file));
+               list<string> const notes = film->read_metadata ();
+               if (film->state_version() == 4) {
+                       error_dialog (
+                               0,
+                               _("This film was created with an old version of DVD-o-matic and may not load correctly "
+                                 "in this version.  Please check the film's settings carefully.")
+                               );
+               }
+               
+               for (list<string>::const_iterator i = notes.begin(); i != notes.end(); ++i) {
+                       error_dialog (0, std_to_wx (*i));
+               }
+               
+               set_film (film);
+       }
+       catch (std::exception& e) {
+               wxString p = std_to_wx (file.string ());
+               wxCharBuffer b = p.ToUTF8 ();
+               error_dialog (this, wxString::Format (_("Could not open film at %s (%s)"), p.data(), std_to_wx (e.what()).data()));
+       }
+       void set_film (shared_ptr<Film> film)
+       {
+               _film = film;
+               _film_viewer->set_film (_film);
+               _film_editor->set_film (_film);
                set_menu_sensitivity ();
+               Config::instance()->add_to_history (_film->directory ());
        }
  
+       shared_ptr<Film> film () const {
+               return _film;
+       }
+       
+ private:
        void file_changed (boost::filesystem::path f)
        {
                string s = wx_to_std (_("DCP-o-matic"));
                        }
                        
                        maybe_save_then_delete_film ();
-                       film.reset (new Film (d->get_path ()));
-                       film->write_metadata ();
-                       film->set_name (boost::filesystem::path (d->get_path()).filename().generic_string());
-                       set_film ();
+                       new_film (d->get_path ());
                }
                
                d->Destroy ();
                }
                        
                if (r == wxID_OK) {
-                       maybe_save_then_delete_film ();
-                       try {
-                               load_film (wx_to_std (c->GetPath ()));
-                               set_film ();
-                       } catch (std::exception& e) {
-                               wxString p = c->GetPath ();
-                               wxCharBuffer b = p.ToUTF8 ();
-                               error_dialog (this, wxString::Format (_("Could not open film at %s (%s)"), p.data(), std_to_wx (e.what()).data()));
-                       }
+                       load_film (wx_to_std (c->GetPath ()));
                }
  
                c->Destroy ();
  
        void file_save ()
        {
-               film->write_metadata ();
+               _film->write_metadata ();
        }
  
        void file_properties ()
        {
-               PropertiesDialog* d = new PropertiesDialog (this, film);
+               PropertiesDialog* d = new PropertiesDialog (this, _film);
                d->ShowModal ();
                d->Destroy ();
        }
+       void file_history (wxCommandEvent& event)
+       {
+               vector<boost::filesystem::path> history = Config::instance()->history ();
+               int n = event.GetId() - ID_file_history;
+               if (n >= 0 && n < static_cast<int> (history.size ())) {
+                       load_film (history[n]);
+               }
+       }
        
        void file_exit ()
        {
                double required;
                double available;
  
-               if (!film->should_be_enough_disk_space (required, available)) {
+               if (!_film->should_be_enough_disk_space (required, available)) {
                        if (!confirm_dialog (this, wxString::Format (_("The DCP for this film will take up about %.1f Gb, and the disk that you are using only has %.1f Gb available.  Do you want to continue anyway?"), required, available))) {
                                return;
                        }
                }
                
-               JobWrapper::make_dcp (this, film);
+               JobWrapper::make_dcp (this, _film);
        }
  
        void jobs_make_kdms ()
        {
-               if (!film) {
+               if (!_film) {
                        return;
                }
                
-               KDMDialog* d = new KDMDialog (this, film);
+               KDMDialog* d = new KDMDialog (this, _film);
                if (d->ShowModal () != wxID_OK) {
                        d->Destroy ();
                        return;
  
                try {
                        if (d->write_to ()) {
-                               write_kdm_files (film, d->screens (), d->cpl (), d->from (), d->until (), d->formulation (), d->directory ());
+                               write_kdm_files (_film, d->screens (), d->cpl (), d->from (), d->until (), d->formulation (), d->directory ());
                        } else {
                                JobManager::instance()->add (
-                                       shared_ptr<Job> (new SendKDMEmailJob (film, d->screens (), d->cpl (), d->from (), d->until (), d->formulation ()))
+                                       shared_ptr<Job> (new SendKDMEmailJob (_film, d->screens (), d->cpl (), d->from (), d->until (), d->formulation ()))
                                        );
                        }
 -              } catch (libdcp::NotEncryptedError& e) {
 +              } catch (dcp::NotEncryptedError& e) {
                        error_dialog (this, _("CPL's content is not encrypted."));
                } catch (exception& e) {
                        error_dialog (this, e.what ());
  
        void content_scale_to_fit_width ()
        {
 -              VideoContentList vc = _film_editor->selected_video_content ();
 +              VideoContentList vc = _film_editor->content_panel()->selected_video ();
                for (VideoContentList::iterator i = vc.begin(); i != vc.end(); ++i) {
                        (*i)->scale_and_crop_to_fit_width ();
                }
  
        void content_scale_to_fit_height ()
        {
 -              VideoContentList vc = _film_editor->selected_video_content ();
 +              VideoContentList vc = _film_editor->content_panel()->selected_video ();
                for (VideoContentList::iterator i = vc.begin(); i != vc.end(); ++i) {
                        (*i)->scale_and_crop_to_fit_height ();
                }
        
        void jobs_send_dcp_to_tms ()
        {
-               film->send_dcp_to_tms ();
+               _film->send_dcp_to_tms ();
        }
  
        void jobs_show_dcp ()
        {
  #ifdef __WXMSW__
-               string d = film->directory().string ();
+               string d = _film->directory().string ();
                wstring w;
                w.assign (d.begin(), d.end());
                ShellExecute (0, L"open", w.c_str(), 0, 0, SW_SHOWDEFAULT);
  #else
                int r = system ("which nautilus");
                if (WEXITSTATUS (r) == 0) {
-                       r = system (string ("nautilus " + film->directory().string()).c_str ());
+                       r = system (string ("nautilus " + _film->directory().string()).c_str ());
                        if (WEXITSTATUS (r)) {
                                error_dialog (this, _("Could not show DCP (could not run nautilus)"));
                        }
                } else {
                        int r = system ("which konqueror");
                        if (WEXITSTATUS (r) == 0) {
-                               r = system (string ("konqueror " + film->directory().string()).c_str ());
+                               r = system (string ("konqueror " + _film->directory().string()).c_str ());
                                if (WEXITSTATUS (r)) {
                                        error_dialog (this, _("Could not show DCP (could not run konqueror)"));
                                }
        void tools_hints ()
        {
                if (!_hints_dialog) {
-                       _hints_dialog = new HintsDialog (this, film);
+                       _hints_dialog = new HintsDialog (this, _film);
                }
  
                _hints_dialog->Show ();
                        ++i;
                }
                bool const dcp_creation = (i != jobs.end ()) && !(*i)->finished ();
-               bool const have_cpl = film && !film->cpls().empty ();
+               bool const have_cpl = _film && !_film->cpls().empty ();
 -              bool const have_selected_video_content = !_film_editor->selected_video_content().empty();
 +              bool const have_selected_video_content = !_film_editor->content_panel()->selected_video().empty();
                
                for (map<wxMenuItem*, int>::iterator j = menu_items.begin(); j != menu_items.end(); ++j) {
                        
                        bool enabled = true;
                        
-                       if ((j->second & NEEDS_FILM) && film == 0) {
+                       if ((j->second & NEEDS_FILM) && !_film) {
                                enabled = false;
                        }
                        
                        j->first->Enable (enabled);
                }
        }
+       void maybe_save_then_delete_film ()
+       {
+               if (!_film) {
+                       return;
+               }
+               
+               if (_film->dirty ()) {
+                       FilmChangedDialog d (_film->name ());
+                       switch (d.run ()) {
+                       case wxID_NO:
+                               break;
+                       case wxID_YES:
+                               _film->write_metadata ();
+                               break;
+                       }
+               }
+               
+               _film.reset ();
+       }
+       void add_item (wxMenu* menu, wxString text, int id, int sens)
+       {
+               wxMenuItem* item = menu->Append (id, text);
+               menu_items.insert (make_pair (item, sens));
+       }
+       
+       void setup_menu (wxMenuBar* m)
+       {
+               _file_menu = new wxMenu;
+               add_item (_file_menu, _("New..."), ID_file_new, ALWAYS);
+               add_item (_file_menu, _("&Open..."), ID_file_open, ALWAYS);
+               _file_menu->AppendSeparator ();
+               add_item (_file_menu, _("&Save"), ID_file_save, NEEDS_FILM);
+               _file_menu->AppendSeparator ();
+               add_item (_file_menu, _("&Properties..."), ID_file_properties, NEEDS_FILM);
+               _history_position = _file_menu->GetMenuItems().GetCount();
+ #ifndef __WXOSX__     
+               _file_menu->AppendSeparator ();
+ #endif
+       
+ #ifdef __WXOSX__      
+               add_item (_file_menu, _("&Exit"), wxID_EXIT, ALWAYS);
+ #else
+               add_item (_file_menu, _("&Quit"), wxID_EXIT, ALWAYS);
+ #endif        
+       
+ #ifdef __WXOSX__      
+               add_item (_file_menu, _("&Preferences..."), wxID_PREFERENCES, ALWAYS);
+ #else
+               wxMenu* edit = new wxMenu;
+               add_item (edit, _("&Preferences..."), wxID_PREFERENCES, ALWAYS);
+ #endif
+               wxMenu* content = new wxMenu;
+               add_item (content, _("Scale to fit &width"), ID_content_scale_to_fit_width, NEEDS_FILM | NEEDS_SELECTED_VIDEO_CONTENT);
+               add_item (content, _("Scale to fit &height"), ID_content_scale_to_fit_height, NEEDS_FILM | NEEDS_SELECTED_VIDEO_CONTENT);
+               
+               wxMenu* jobs_menu = new wxMenu;
+               add_item (jobs_menu, _("&Make DCP"), ID_jobs_make_dcp, NEEDS_FILM | NOT_DURING_DCP_CREATION);
+               add_item (jobs_menu, _("Make &KDMs..."), ID_jobs_make_kdms, NEEDS_FILM);
+               add_item (jobs_menu, _("&Send DCP to TMS"), ID_jobs_send_dcp_to_tms, NEEDS_FILM | NOT_DURING_DCP_CREATION | NEEDS_CPL);
+               add_item (jobs_menu, _("S&how DCP"), ID_jobs_show_dcp, NEEDS_FILM | NOT_DURING_DCP_CREATION | NEEDS_CPL);
+               wxMenu* tools = new wxMenu;
+               add_item (tools, _("Hints..."), ID_tools_hints, 0);
+               add_item (tools, _("Encoding servers..."), ID_tools_encoding_servers, 0);
+               add_item (tools, _("Check for updates"), ID_tools_check_for_updates, 0);
+               
+               wxMenu* help = new wxMenu;
+ #ifdef __WXOSX__      
+               add_item (help, _("About DCP-o-matic"), wxID_ABOUT, ALWAYS);
+ #else 
+               add_item (help, _("About"), wxID_ABOUT, ALWAYS);
+ #endif        
+               
+               m->Append (_file_menu, _("&File"));
+ #ifndef __WXOSX__     
+               m->Append (edit, _("&Edit"));
+ #endif
+               m->Append (content, _("&Content"));
+               m->Append (jobs_menu, _("&Jobs"));
+               m->Append (tools, _("&Tools"));
+               m->Append (help, _("&Help"));
+       }
+       void config_changed ()
+       {
+               for (int i = 0; i < _history_items; ++i) {
+                       delete _file_menu->Remove (ID_file_history + i);
+               }
+               if (_history_separator) {
+                       _file_menu->Remove (_history_separator);
+               }
+               delete _history_separator;
+               _history_separator = 0;
+               
+               int pos = _history_position;
+               
+               vector<boost::filesystem::path> history = Config::instance()->history ();
+               
+               if (!history.empty ()) {
+                       _history_separator = _file_menu->InsertSeparator (pos++);
+               }
+               
+               for (size_t i = 0; i < history.size(); ++i) {
+                       SafeStringStream s;
+                       if (i < 9) {
+                               s << "&" << (i + 1) << " ";
+                       }
+                       s << history[i].string();
+                       _file_menu->Insert (pos++, ID_file_history + i, std_to_wx (s.str ()));
+               }
+               _history_items = history.size ();
+       }
        
        FilmEditor* _film_editor;
        FilmViewer* _film_viewer;
        HintsDialog* _hints_dialog;
        ServersListDialog* _servers_list_dialog;
        wxPreferencesEditor* _config_dialog;
+       wxMenu* _file_menu;
+       shared_ptr<Film> _film;
+       int _history_items;
+       int _history_position;
+       wxMenuItem* _history_separator;
  };
  
  static const wxCmdLineEntryDesc command_line_description[] = {
        { wxCMD_LINE_NONE, "", "", "", wxCmdLineParamType (0), 0 }
  };
  
 +/** @class App
 + *  @brief The magic App class for wxWidgets.
 + */
  class App : public wxApp
  {
        bool OnInit ()
                */
                Config::drop ();
  
-               if (!film_to_load.empty() && boost::filesystem::is_directory (film_to_load)) {
+               _frame = new Frame (_("DCP-o-matic"));
+               SetTopWindow (_frame);
+               _frame->Maximize ();
+               _frame->Show ();
+               if (!_film_to_load.empty() && boost::filesystem::is_directory (_film_to_load)) {
                        try {
-                               load_film (film_to_load);
+                               _frame->load_film (_film_to_load);
                        } catch (exception& e) {
-                               error_dialog (0, std_to_wx (String::compose (wx_to_std (_("Could not load film %1 (%2)")), film_to_load, e.what())));
+                               error_dialog (0, std_to_wx (String::compose (wx_to_std (_("Could not load film %1 (%2)")), _film_to_load, e.what())));
                        }
                }
  
-               if (!film_to_create.empty ()) {
-                       film.reset (new Film (film_to_create));
-                       film->write_metadata ();
-                       film->set_name (boost::filesystem::path (film_to_create).filename().generic_string ());
-               }
-               if (!content_to_add.empty ()) {
-                       film->examine_and_add_content (content_factory (film, content_to_add));
+               if (!_film_to_create.empty ()) {
+                       _frame->new_film (_film_to_create);
+                       if (!_content_to_add.empty ()) {
+                               _frame->film()->examine_and_add_content (content_factory (_frame->film(), _content_to_add));
+                       }
                }
  
-               _frame = new Frame (_("DCP-o-matic"));
-               SetTopWindow (_frame);
-               _frame->Maximize ();
-               _frame->Show ();
                ui_signaller = new wxUISignaller (this);
                Bind (wxEVT_IDLE, boost::bind (&App::idle, this));
  
                _timer.reset (new wxTimer (this));
                _timer->Start (1000);
  
-               if (film) {
-                       check_film_state_version (film->state_version ());
-               }
                UpdateChecker::instance()->StateChanged.connect (boost::bind (&App::update_checker_state_changed, this));
                if (Config::instance()->check_for_updates ()) {
                        UpdateChecker::instance()->run ();
        {
                if (parser.GetParamCount() > 0) {
                        if (parser.Found (wxT ("new"))) {
-                               film_to_create = wx_to_std (parser.GetParam (0));
+                               _film_to_create = wx_to_std (parser.GetParam (0));
                        } else {
-                               film_to_load = wx_to_std (parser.GetParam (0));
+                               _film_to_load = wx_to_std (parser.GetParam (0));
                        }
                }
  
                wxString content;
                if (parser.Found (wxT ("content"), &content)) {
-                       content_to_add = wx_to_std (content);
+                       _content_to_add = wx_to_std (content);
                }
  
                return true;
  
        Frame* _frame;
        shared_ptr<wxTimer> _timer;
+       string _film_to_load;
+       string _film_to_create;
+       string _content_to_add;
  };
  
  IMPLEMENT_APP (App)
diff --combined src/wx/film_editor.cc
index 1ce4695d6bf83c2e00c09df358667e5551374da1,e2886b5f586a9138defe7901dd6b3167b7c8dede..7f9461d940fb4ba6f4035707fc1f930c214c62f9
  #include "lib/ffmpeg_content.h"
  #include "lib/sndfile_content.h"
  #include "lib/dcp_content_type.h"
 -#include "lib/sound_processor.h"
  #include "lib/scaler.h"
  #include "lib/playlist.h"
  #include "lib/content.h"
  #include "lib/content_factory.h"
 +#include "lib/dcp_content.h"
  #include "lib/safe_stringstream.h"
  #include "timecode.h"
  #include "wx_util.h"
  #include "film_editor.h"
 -#include "isdcf_metadata_dialog.h"
  #include "timeline_dialog.h"
  #include "timing_panel.h"
  #include "subtitle_panel.h"
  #include "audio_panel.h"
  #include "video_panel.h"
 +#include "content_panel.h"
 +#include "dcp_panel.h"
  
  using std::string;
  using std::cout;
@@@ -71,28 -70,342 +71,28 @@@ using boost::dynamic_pointer_cast
  using boost::lexical_cast;
  
  /** @param f Film to edit */
- FilmEditor::FilmEditor (shared_ptr<Film> f, wxWindow* parent)
+ FilmEditor::FilmEditor (wxWindow* parent)
        : wxPanel (parent)
 -      , _menu (this)
 -      , _generally_sensitive (true)
 -      , _timeline_dialog (0)
  {
        wxBoxSizer* s = new wxBoxSizer (wxVERTICAL);
  
        _main_notebook = new wxNotebook (this, wxID_ANY);
        s->Add (_main_notebook, 1);
  
 -      make_content_panel ();
 -      _main_notebook->AddPage (_content_panel, _("Content"), true);
 -      make_dcp_panel ();
 -      _main_notebook->AddPage (_dcp_panel, _("DCP"), false);
 +      _content_panel = new ContentPanel (_main_notebook, _film);
 +      _main_notebook->AddPage (_content_panel->panel (), _("Content"), true);
 +      _dcp_panel = new DCPPanel (_main_notebook, _film);
 +      _main_notebook->AddPage (_dcp_panel->panel (), _("DCP"), false);
        
-       set_film (f);
 -      connect_to_widgets ();
--
        JobManager::instance()->ActiveJobsChanged.connect (
                bind (&FilmEditor::active_jobs_changed, this, _1)
                );
  
 -      Config::instance()->Changed.connect (boost::bind (&FilmEditor::config_changed, this));
 -
+       set_film (shared_ptr<Film> ());
 -      SetSizerAndFit (s);
 -}
 -
 -void
 -FilmEditor::make_dcp_panel ()
 -{
 -      _dcp_panel = new wxPanel (_main_notebook);
 -      _dcp_sizer = new wxBoxSizer (wxVERTICAL);
 -      _dcp_panel->SetSizer (_dcp_sizer);
 -
 -      wxGridBagSizer* grid = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
 -      _dcp_sizer->Add (grid, 0, wxEXPAND | wxALL, 8);
 -
 -      int r = 0;
+       
 -      add_label_to_grid_bag_sizer (grid, _dcp_panel, _("Name"), true, wxGBPosition (r, 0));
 -      _name = new wxTextCtrl (_dcp_panel, wxID_ANY);
 -      grid->Add (_name, wxGBPosition(r, 1), wxDefaultSpan, wxEXPAND | wxLEFT | wxRIGHT);
 -      ++r;
 -      
 -      add_label_to_grid_bag_sizer (grid, _dcp_panel, _("DCP Name"), true, wxGBPosition (r, 0));
 -      _dcp_name = new wxStaticText (_dcp_panel, wxID_ANY, wxT (""));
 -      grid->Add (_dcp_name, wxGBPosition(r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
 -      ++r;
 -
 -      int flags = wxALIGN_CENTER_VERTICAL;
 -#ifdef __WXOSX__
 -      flags |= wxALIGN_RIGHT;
 -#endif        
 -
 -      _use_isdcf_name = new wxCheckBox (_dcp_panel, wxID_ANY, _("Use ISDCF name"));
 -      grid->Add (_use_isdcf_name, wxGBPosition (r, 0), wxDefaultSpan, flags);
 -      _edit_isdcf_button = new wxButton (_dcp_panel, wxID_ANY, _("Details..."));
 -      grid->Add (_edit_isdcf_button, wxGBPosition (r, 1), wxDefaultSpan);
 -      ++r;
 -
 -      add_label_to_grid_bag_sizer (grid, _dcp_panel, _("Container"), true, wxGBPosition (r, 0));
 -      _container = new wxChoice (_dcp_panel, wxID_ANY);
 -      grid->Add (_container, wxGBPosition (r, 1), wxDefaultSpan, wxEXPAND);
 -      ++r;
 -
 -      add_label_to_grid_bag_sizer (grid, _dcp_panel, _("Content Type"), true, wxGBPosition (r, 0));
 -      _dcp_content_type = new wxChoice (_dcp_panel, wxID_ANY);
 -      grid->Add (_dcp_content_type, wxGBPosition (r, 1));
 -      ++r;
 -
 -      {
 -              add_label_to_grid_bag_sizer (grid, _dcp_panel, _("Frame Rate"), true, wxGBPosition (r, 0));
 -              _frame_rate_sizer = new wxBoxSizer (wxHORIZONTAL);
 -              _frame_rate_choice = new wxChoice (_dcp_panel, wxID_ANY);
 -              _frame_rate_sizer->Add (_frame_rate_choice, 1, wxALIGN_CENTER_VERTICAL);
 -              _frame_rate_spin = new wxSpinCtrl (_dcp_panel, wxID_ANY);
 -              _frame_rate_sizer->Add (_frame_rate_spin, 1, wxALIGN_CENTER_VERTICAL);
 -              setup_frame_rate_widget ();
 -              _best_frame_rate = new wxButton (_dcp_panel, wxID_ANY, _("Use best"));
 -              _frame_rate_sizer->Add (_best_frame_rate, 1, wxALIGN_CENTER_VERTICAL | wxEXPAND);
 -              grid->Add (_frame_rate_sizer, wxGBPosition (r, 1));
 -      }
 -      ++r;
 -
 -      _signed = new wxCheckBox (_dcp_panel, wxID_ANY, _("Signed"));
 -      grid->Add (_signed, wxGBPosition (r, 0), wxGBSpan (1, 2));
 -      ++r;
 -      
 -      _encrypted = new wxCheckBox (_dcp_panel, wxID_ANY, _("Encrypted"));
 -      grid->Add (_encrypted, wxGBPosition (r, 0), wxGBSpan (1, 2));
 -      ++r;
 -
 -      add_label_to_grid_bag_sizer (grid, _dcp_panel, _("Audio channels"), true, wxGBPosition (r, 0));
 -      _audio_channels = new wxSpinCtrl (_dcp_panel, wxID_ANY);
 -      grid->Add (_audio_channels, wxGBPosition (r, 1));
 -      ++r;
 -
 -      _three_d = new wxCheckBox (_dcp_panel, wxID_ANY, _("3D"));
 -      grid->Add (_three_d, wxGBPosition (r, 0), wxGBSpan (1, 2));
 -      ++r;
 -
 -      add_label_to_grid_bag_sizer (grid, _dcp_panel, _("Resolution"), true, wxGBPosition (r, 0));
 -      _resolution = new wxChoice (_dcp_panel, wxID_ANY);
 -      grid->Add (_resolution, wxGBPosition (r, 1));
 -      ++r;
 -
 -      {
 -              add_label_to_grid_bag_sizer (grid, _dcp_panel, _("JPEG2000 bandwidth"), true, wxGBPosition (r, 0));
 -              wxSizer* s = new wxBoxSizer (wxHORIZONTAL);
 -              _j2k_bandwidth = new wxSpinCtrl (_dcp_panel, wxID_ANY);
 -              s->Add (_j2k_bandwidth, 1);
 -              add_label_to_sizer (s, _dcp_panel, _("Mbit/s"), false);
 -              grid->Add (s, wxGBPosition (r, 1));
 -      }
 -      ++r;
 -
 -      add_label_to_grid_bag_sizer (grid, _dcp_panel, _("Standard"), true, wxGBPosition (r, 0));
 -      _standard = new wxChoice (_dcp_panel, wxID_ANY);
 -      grid->Add (_standard, wxGBPosition (r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
 -      ++r;
 -
 -      add_label_to_grid_bag_sizer (grid, _dcp_panel, _("Scaler"), true, wxGBPosition (r, 0));
 -      _scaler = new wxChoice (_dcp_panel, wxID_ANY);
 -      grid->Add (_scaler, wxGBPosition (r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
 -      ++r;
 -
 -      vector<Scaler const *> const sc = Scaler::all ();
 -      for (vector<Scaler const *>::const_iterator i = sc.begin(); i != sc.end(); ++i) {
 -              _scaler->Append (std_to_wx ((*i)->name()));
 -      }
 -
 -      vector<Ratio const *> const ratio = Ratio::all ();
 -      for (vector<Ratio const *>::const_iterator i = ratio.begin(); i != ratio.end(); ++i) {
 -              _container->Append (std_to_wx ((*i)->nickname ()));
 -      }
 -
 -      vector<DCPContentType const *> const ct = DCPContentType::all ();
 -      for (vector<DCPContentType const *>::const_iterator i = ct.begin(); i != ct.end(); ++i) {
 -              _dcp_content_type->Append (std_to_wx ((*i)->pretty_name ()));
 -      }
 -
 -      list<int> const dfr = Config::instance()->allowed_dcp_frame_rates ();
 -      for (list<int>::const_iterator i = dfr.begin(); i != dfr.end(); ++i) {
 -              _frame_rate_choice->Append (std_to_wx (boost::lexical_cast<string> (*i)));
 -      }
 -
 -      _audio_channels->SetRange (0, MAX_DCP_AUDIO_CHANNELS);
 -      _j2k_bandwidth->SetRange (1, Config::instance()->maximum_j2k_bandwidth() / 1000000);
 -      _frame_rate_spin->SetRange (1, 480);
 -
 -      _resolution->Append (_("2K"));
 -      _resolution->Append (_("4K"));
 -
 -      _standard->Append (_("SMPTE"));
 -      _standard->Append (_("Interop"));
 -}
 -
 -void
 -FilmEditor::connect_to_widgets ()
 -{
 -      _name->Bind             (wxEVT_COMMAND_TEXT_UPDATED,          boost::bind (&FilmEditor::name_changed, this));
 -      _use_isdcf_name->Bind   (wxEVT_COMMAND_CHECKBOX_CLICKED,      boost::bind (&FilmEditor::use_isdcf_name_toggled, this));
 -      _edit_isdcf_button->Bind(wxEVT_COMMAND_BUTTON_CLICKED,        boost::bind (&FilmEditor::edit_isdcf_button_clicked, this));
 -      _container->Bind        (wxEVT_COMMAND_CHOICE_SELECTED,       boost::bind (&FilmEditor::container_changed, this));
 -      _content->Bind          (wxEVT_COMMAND_LIST_ITEM_SELECTED,    boost::bind (&FilmEditor::content_selection_changed, this));
 -      _content->Bind          (wxEVT_COMMAND_LIST_ITEM_DESELECTED,  boost::bind (&FilmEditor::content_selection_changed, this));
 -      _content->Bind          (wxEVT_COMMAND_LIST_ITEM_RIGHT_CLICK, boost::bind (&FilmEditor::content_right_click, this, _1));
 -      _content_add_file->Bind (wxEVT_COMMAND_BUTTON_CLICKED,        boost::bind (&FilmEditor::content_add_file_clicked, this));
 -      _content_add_folder->Bind (wxEVT_COMMAND_BUTTON_CLICKED,      boost::bind (&FilmEditor::content_add_folder_clicked, this));
 -      _content_remove->Bind   (wxEVT_COMMAND_BUTTON_CLICKED,        boost::bind (&FilmEditor::content_remove_clicked, this));
 -      _content_earlier->Bind  (wxEVT_COMMAND_BUTTON_CLICKED,        boost::bind (&FilmEditor::content_earlier_clicked, this));
 -      _content_later->Bind    (wxEVT_COMMAND_BUTTON_CLICKED,        boost::bind (&FilmEditor::content_later_clicked, this));
 -      _content_timeline->Bind (wxEVT_COMMAND_BUTTON_CLICKED,        boost::bind (&FilmEditor::content_timeline_clicked, this));
 -      _scaler->Bind           (wxEVT_COMMAND_CHOICE_SELECTED,       boost::bind (&FilmEditor::scaler_changed, this));
 -      _dcp_content_type->Bind (wxEVT_COMMAND_CHOICE_SELECTED,       boost::bind (&FilmEditor::dcp_content_type_changed, this));
 -      _frame_rate_choice->Bind(wxEVT_COMMAND_CHOICE_SELECTED,       boost::bind (&FilmEditor::frame_rate_choice_changed, this));
 -      _frame_rate_spin->Bind  (wxEVT_COMMAND_SPINCTRL_UPDATED,      boost::bind (&FilmEditor::frame_rate_spin_changed, this));
 -      _best_frame_rate->Bind  (wxEVT_COMMAND_BUTTON_CLICKED,        boost::bind (&FilmEditor::best_frame_rate_clicked, this));
 -      _signed->Bind           (wxEVT_COMMAND_CHECKBOX_CLICKED,      boost::bind (&FilmEditor::signed_toggled, this));
 -      _encrypted->Bind        (wxEVT_COMMAND_CHECKBOX_CLICKED,      boost::bind (&FilmEditor::encrypted_toggled, this));
 -      _audio_channels->Bind   (wxEVT_COMMAND_SPINCTRL_UPDATED,      boost::bind (&FilmEditor::audio_channels_changed, this));
 -      _j2k_bandwidth->Bind    (wxEVT_COMMAND_SPINCTRL_UPDATED,      boost::bind (&FilmEditor::j2k_bandwidth_changed, this));
 -      _resolution->Bind       (wxEVT_COMMAND_CHOICE_SELECTED,       boost::bind (&FilmEditor::resolution_changed, this));
 -      _sequence_video->Bind   (wxEVT_COMMAND_CHECKBOX_CLICKED,      boost::bind (&FilmEditor::sequence_video_changed, this));
 -      _three_d->Bind          (wxEVT_COMMAND_CHECKBOX_CLICKED,      boost::bind (&FilmEditor::three_d_changed, this));
 -      _standard->Bind         (wxEVT_COMMAND_CHOICE_SELECTED,       boost::bind (&FilmEditor::standard_changed, this));
 -}
 -
 -void
 -FilmEditor::make_content_panel ()
 -{
 -      _content_panel = new wxPanel (_main_notebook);
 -      _content_sizer = new wxBoxSizer (wxVERTICAL);
 -      _content_panel->SetSizer (_content_sizer);
 -
 -      {
 -              wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
 -              
 -              _content = new wxListCtrl (_content_panel, wxID_ANY, wxDefaultPosition, wxSize (320, 160), wxLC_REPORT | wxLC_NO_HEADER);
 -              s->Add (_content, 1, wxEXPAND | wxTOP | wxBOTTOM, 6);
 -
 -              _content->InsertColumn (0, wxT(""));
 -              _content->SetColumnWidth (0, 512);
 -
 -              wxBoxSizer* b = new wxBoxSizer (wxVERTICAL);
 -              _content_add_file = new wxButton (_content_panel, wxID_ANY, _("Add file(s)..."));
 -              b->Add (_content_add_file, 1, wxEXPAND | wxALL, DCPOMATIC_BUTTON_STACK_GAP);
 -              _content_add_folder = new wxButton (_content_panel, wxID_ANY, _("Add folder..."));
 -              b->Add (_content_add_folder, 1, wxEXPAND | wxALL, DCPOMATIC_BUTTON_STACK_GAP);
 -              _content_remove = new wxButton (_content_panel, wxID_ANY, _("Remove"));
 -              b->Add (_content_remove, 1, wxEXPAND | wxALL, DCPOMATIC_BUTTON_STACK_GAP);
 -              _content_earlier = new wxButton (_content_panel, wxID_ANY, _("Up"));
 -              b->Add (_content_earlier, 1, wxEXPAND | wxALL, DCPOMATIC_BUTTON_STACK_GAP);
 -              _content_later = new wxButton (_content_panel, wxID_ANY, _("Down"));
 -              b->Add (_content_later, 1, wxEXPAND | wxALL, DCPOMATIC_BUTTON_STACK_GAP);
 -              _content_timeline = new wxButton (_content_panel, wxID_ANY, _("Timeline..."));
 -              b->Add (_content_timeline, 1, wxEXPAND | wxALL, DCPOMATIC_BUTTON_STACK_GAP);
 -
 -              s->Add (b, 0, wxALL, 4);
 -
 -              _content_sizer->Add (s, 0, wxEXPAND | wxALL, 6);
 -      }
 -
 -      _sequence_video = new wxCheckBox (_content_panel, wxID_ANY, _("Keep video in sequence"));
 -      _content_sizer->Add (_sequence_video);
 -
 -      _content_notebook = new wxNotebook (_content_panel, wxID_ANY);
 -      _content_sizer->Add (_content_notebook, 1, wxEXPAND | wxTOP, 6);
 -
 -      _video_panel = new VideoPanel (this);
 -      _panels.push_back (_video_panel);
 -      _audio_panel = new AudioPanel (this);
 -      _panels.push_back (_audio_panel);
 -      _subtitle_panel = new SubtitlePanel (this);
 -      _panels.push_back (_subtitle_panel);
 -      _timing_panel = new TimingPanel (this);
 -      _panels.push_back (_timing_panel);
 -}
 -
 -/** Called when the name widget has been changed */
 -void
 -FilmEditor::name_changed ()
 -{
 -      if (!_film) {
 -              return;
 -      }
 -
 -      _film->set_name (string (_name->GetValue().mb_str()));
 -}
 -
 -void
 -FilmEditor::j2k_bandwidth_changed ()
 -{
 -      if (!_film) {
 -              return;
 -      }
 -      
 -      _film->set_j2k_bandwidth (_j2k_bandwidth->GetValue() * 1000000);
 -}
 -
 -void
 -FilmEditor::signed_toggled ()
 -{
 -      if (!_film) {
 -              return;
 -      }
 -
 -      _film->set_signed (_signed->GetValue ());
 -}
 -
 -void
 -FilmEditor::encrypted_toggled ()
 -{
 -      if (!_film) {
 -              return;
 -      }
 -
 -      _film->set_encrypted (_encrypted->GetValue ());
 -}
 -                             
 -/** Called when the frame rate choice widget has been changed */
 -void
 -FilmEditor::frame_rate_choice_changed ()
 -{
 -      if (!_film) {
 -              return;
 -      }
 -
 -      _film->set_video_frame_rate (
 -              boost::lexical_cast<int> (
 -                      wx_to_std (_frame_rate_choice->GetString (_frame_rate_choice->GetSelection ()))
 -                      )
 -              );
 -}
 -
 -/** Called when the frame rate spin widget has been changed */
 -void
 -FilmEditor::frame_rate_spin_changed ()
 -{
 -      if (!_film) {
 -              return;
 -      }
 -
 -      _film->set_video_frame_rate (_frame_rate_spin->GetValue ());
 -}
 -
 -void
 -FilmEditor::audio_channels_changed ()
 -{
 -      if (!_film) {
 -              return;
 -      }
 -
 -      _film->set_audio_channels (_audio_channels->GetValue ());
 -}
 -
 -void
 -FilmEditor::resolution_changed ()
 -{
 -      if (!_film) {
 -              return;
 -      }
 -
 -      _film->set_resolution (_resolution->GetSelection() == 0 ? RESOLUTION_2K : RESOLUTION_4K);
 +      SetSizerAndFit (s);
  }
  
 -void
 -FilmEditor::standard_changed ()
 -{
 -      if (!_film) {
 -              return;
 -      }
 -
 -      _film->set_interop (_standard->GetSelection() == 1);
 -}
  
  /** Called when the metadata stored in the Film object has changed;
   *  so that we can update the GUI.
@@@ -107,8 -420,96 +107,8 @@@ FilmEditor::film_changed (Film::Propert
                return;
        }
  
 -      SafeStringStream s;
 -
 -      for (list<FilmEditorPanel*>::iterator i = _panels.begin(); i != _panels.end(); ++i) {
 -              (*i)->film_changed (p);
 -      }
 -              
 -      switch (p) {
 -      case Film::NONE:
 -              break;
 -      case Film::CONTENT:
 -              setup_content ();
 -              break;
 -      case Film::CONTAINER:
 -              setup_container ();
 -              break;
 -      case Film::NAME:
 -              checked_set (_name, _film->name());
 -              setup_dcp_name ();
 -              break;
 -      case Film::WITH_SUBTITLES:
 -              setup_dcp_name ();
 -              break;
 -      case Film::DCP_CONTENT_TYPE:
 -              checked_set (_dcp_content_type, DCPContentType::as_index (_film->dcp_content_type ()));
 -              setup_dcp_name ();
 -              break;
 -      case Film::SCALER:
 -              checked_set (_scaler, Scaler::as_index (_film->scaler ()));
 -              break;
 -      case Film::SIGNED:
 -              checked_set (_signed, _film->is_signed ());
 -              break;
 -      case Film::ENCRYPTED:
 -              checked_set (_encrypted, _film->encrypted ());
 -              if (_film->encrypted ()) {
 -                      _film->set_signed (true);
 -                      _signed->Enable (false);
 -              } else {
 -                      _signed->Enable (_generally_sensitive);
 -              }
 -              break;
 -      case Film::RESOLUTION:
 -              checked_set (_resolution, _film->resolution() == RESOLUTION_2K ? 0 : 1);
 -              setup_dcp_name ();
 -              break;
 -      case Film::J2K_BANDWIDTH:
 -              checked_set (_j2k_bandwidth, _film->j2k_bandwidth() / 1000000);
 -              break;
 -      case Film::USE_ISDCF_NAME:
 -              checked_set (_use_isdcf_name, _film->use_isdcf_name ());
 -              setup_dcp_name ();
 -              break;
 -      case Film::ISDCF_METADATA:
 -              setup_dcp_name ();
 -              break;
 -      case Film::VIDEO_FRAME_RATE:
 -      {
 -              bool done = false;
 -              for (unsigned int i = 0; i < _frame_rate_choice->GetCount(); ++i) {
 -                      if (wx_to_std (_frame_rate_choice->GetString(i)) == boost::lexical_cast<string> (_film->video_frame_rate())) {
 -                              checked_set (_frame_rate_choice, i);
 -                              done = true;
 -                              break;
 -                      }
 -              }
 -
 -              if (!done) {
 -                      checked_set (_frame_rate_choice, -1);
 -              }
 -
 -              _frame_rate_spin->SetValue (_film->video_frame_rate ());
 -
 -              _best_frame_rate->Enable (_film->best_video_frame_rate () != _film->video_frame_rate ());
 -              break;
 -      }
 -      case Film::AUDIO_CHANNELS:
 -              checked_set (_audio_channels, _film->audio_channels ());
 -              setup_dcp_name ();
 -              break;
 -      case Film::SEQUENCE_VIDEO:
 -              checked_set (_sequence_video, _film->sequence_video ());
 -              break;
 -      case Film::THREE_D:
 -              checked_set (_three_d, _film->three_d ());
 -              setup_dcp_name ();
 -              break;
 -      case Film::INTEROP:
 -              checked_set (_standard, _film->interop() ? 1 : 0);
 -              break;
 -      }
 +      _content_panel->film_changed (p);
 +      _dcp_panel->film_changed (p);
  }
  
  void
@@@ -123,8 -524,67 +123,8 @@@ FilmEditor::film_content_changed (int p
                return;
        }
  
 -      for (list<FilmEditorPanel*>::iterator i = _panels.begin(); i != _panels.end(); ++i) {
 -              (*i)->film_content_changed (property);
 -      }
 -
 -      if (property == FFmpegContentProperty::AUDIO_STREAM) {
 -              setup_dcp_name ();
 -      } else if (property == ContentProperty::PATH) {
 -              setup_content ();
 -      } else if (property == ContentProperty::POSITION) {
 -              setup_content ();
 -      }
 -}
 -
 -void
 -FilmEditor::setup_container ()
 -{
 -      int n = 0;
 -      vector<Ratio const *> ratios = Ratio::all ();
 -      vector<Ratio const *>::iterator i = ratios.begin ();
 -      while (i != ratios.end() && *i != _film->container ()) {
 -              ++i;
 -              ++n;
 -      }
 -      
 -      if (i == ratios.end()) {
 -              checked_set (_container, -1);
 -      } else {
 -              checked_set (_container, n);
 -      }
 -      
 -      setup_dcp_name ();
 -}     
 -
 -/** Called when the container widget has been changed */
 -void
 -FilmEditor::container_changed ()
 -{
 -      if (!_film) {
 -              return;
 -      }
 -
 -      int const n = _container->GetSelection ();
 -      if (n >= 0) {
 -              vector<Ratio const *> ratios = Ratio::all ();
 -              assert (n < int (ratios.size()));
 -              _film->set_container (ratios[n]);
 -      }
 -}
 -
 -/** Called when the DCP content type widget has been changed */
 -void
 -FilmEditor::dcp_content_type_changed ()
 -{
 -      if (!_film) {
 -              return;
 -      }
 -
 -      int const n = _dcp_content_type->GetSelection ();
 -      if (n != wxNOT_FOUND) {
 -              _film->set_dcp_content_type (DCPContentType::from_index (n));
 -      }
 +      _content_panel->film_content_changed (property);
 +      _dcp_panel->film_content_changed (property);
  }
  
  /** Sets the Film that we are editing */
@@@ -139,9 -599,6 +139,9 @@@ FilmEditor::set_film (shared_ptr<Film> 
        
        _film = f;
  
 +      _content_panel->set_film (_film);
 +      _dcp_panel->set_film (_film);
 +
        if (_film) {
                _film->Changed.connect (bind (&FilmEditor::film_changed, this, _1));
                _film->ContentChanged.connect (bind (&FilmEditor::film_content_changed, this, _2));
                FileChanged ("");
        }
  
 -      film_changed (Film::NAME);
 -      film_changed (Film::USE_ISDCF_NAME);
 -      film_changed (Film::CONTENT);
 -      film_changed (Film::DCP_CONTENT_TYPE);
 -      film_changed (Film::CONTAINER);
 -      film_changed (Film::RESOLUTION);
 -      film_changed (Film::SCALER);
 -      film_changed (Film::WITH_SUBTITLES);
 -      film_changed (Film::SIGNED);
 -      film_changed (Film::ENCRYPTED);
 -      film_changed (Film::J2K_BANDWIDTH);
 -      film_changed (Film::ISDCF_METADATA);
 -      film_changed (Film::VIDEO_FRAME_RATE);
 -      film_changed (Film::AUDIO_CHANNELS);
 -      film_changed (Film::SEQUENCE_VIDEO);
 -      film_changed (Film::THREE_D);
 -      film_changed (Film::INTEROP);
 -
        if (!_film->content().empty ()) {
 -              set_selection (_film->content().front ());
 +              _content_panel->set_selection (_film->content().front ());
        }
 -
 -      content_selection_changed ();
  }
  
  void
  FilmEditor::set_general_sensitivity (bool s)
  {
 -      _generally_sensitive = s;
 -
 -      /* Stuff in the Content / DCP tabs */
 -      _name->Enable (s);
 -      _use_isdcf_name->Enable (s);
 -      _edit_isdcf_button->Enable (s);
 -      _content->Enable (s);
 -      _content_add_file->Enable (s);
 -      _content_add_folder->Enable (s);
 -      _content_remove->Enable (s);
 -      _content_earlier->Enable (s);
 -      _content_later->Enable (s);
 -      _content_timeline->Enable (s);
 -      _dcp_content_type->Enable (s);
 -
 -      bool si = s;
 -      if (_film && _film->encrypted ()) {
 -              si = false;
 -      }
 -      _signed->Enable (si);
 -      
 -      _encrypted->Enable (s);
 -      _frame_rate_choice->Enable (s);
 -      _frame_rate_spin->Enable (s);
 -      _audio_channels->Enable (s);
 -      _j2k_bandwidth->Enable (s);
 -      _container->Enable (s);
 -      _best_frame_rate->Enable (s && _film && _film->best_video_frame_rate () != _film->video_frame_rate ());
 -      _sequence_video->Enable (s);
 -      _resolution->Enable (s);
 -      _scaler->Enable (s);
 -      _three_d->Enable (s);
 -      _standard->Enable (s);
 -
 -      /* Set the panels in the content notebook */
 -      for (list<FilmEditorPanel*>::iterator i = _panels.begin(); i != _panels.end(); ++i) {
 -              (*i)->Enable (s);
 -      }
 -}
 -
 -/** Called when the scaler widget has been changed */
 -void
 -FilmEditor::scaler_changed ()
 -{
 -      if (!_film) {
 -              return;
 -      }
 -
 -      int const n = _scaler->GetSelection ();
 -      if (n >= 0) {
 -              _film->set_scaler (Scaler::from_index (n));
 -      }
 -}
 -
 -void
 -FilmEditor::use_isdcf_name_toggled ()
 -{
 -      if (!_film) {
 -              return;
 -      }
 -
 -      _film->set_use_isdcf_name (_use_isdcf_name->GetValue ());
 -}
 -
 -void
 -FilmEditor::edit_isdcf_button_clicked ()
 -{
 -      if (!_film) {
 -              return;
 -      }
 -
 -      ISDCFMetadataDialog* d = new ISDCFMetadataDialog (this, _film->isdcf_metadata ());
 -      d->ShowModal ();
 -      _film->set_isdcf_metadata (d->isdcf_metadata ());
 -      d->Destroy ();
 +      _content_panel->set_general_sensitivity (s);
 +      _dcp_panel->set_general_sensitivity (s);
  }
  
  void
@@@ -170,3 -720,343 +170,3 @@@ FilmEditor::active_jobs_changed (bool a
  {
        set_general_sensitivity (!a);
  }
 -
 -void
 -FilmEditor::setup_dcp_name ()
 -{
 -      string s = _film->dcp_name (true);
 -      if (s.length() > 28) {
 -              _dcp_name->SetLabel (std_to_wx (s.substr (0, 28)) + N_("..."));
 -              _dcp_name->SetToolTip (std_to_wx (s));
 -      } else {
 -              _dcp_name->SetLabel (std_to_wx (s));
 -      }
 -}
 -
 -void
 -FilmEditor::best_frame_rate_clicked ()
 -{
 -      if (!_film) {
 -              return;
 -      }
 -      
 -      _film->set_video_frame_rate (_film->best_video_frame_rate ());
 -}
 -
 -void
 -FilmEditor::setup_content ()
 -{
 -      string selected_summary;
 -      int const s = _content->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
 -      if (s != -1) {
 -              selected_summary = wx_to_std (_content->GetItemText (s));
 -      }
 -      
 -      _content->DeleteAllItems ();
 -
 -      ContentList content = _film->content ();
 -      sort (content.begin(), content.end(), ContentSorter ());
 -      
 -      for (ContentList::iterator i = content.begin(); i != content.end(); ++i) {
 -              int const t = _content->GetItemCount ();
 -              bool const valid = (*i)->paths_valid ();
 -
 -              string s = (*i)->summary ();
 -              if (!valid) {
 -                      s = _("MISSING: ") + s;
 -              }
 -
 -              _content->InsertItem (t, std_to_wx (s));
 -
 -              if ((*i)->summary() == selected_summary) {
 -                      _content->SetItemState (t, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED);
 -              }
 -
 -              if (!valid) {
 -                      _content->SetItemTextColour (t, *wxRED);
 -              }
 -      }
 -
 -      if (selected_summary.empty () && !content.empty ()) {
 -              /* Select the item of content if none was selected before */
 -              _content->SetItemState (0, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED);
 -      }
 -}
 -
 -void
 -FilmEditor::content_add_file_clicked ()
 -{
 -      /* The wxFD_CHANGE_DIR here prevents a `could not set working directory' error 123 on Windows when using
 -         non-Latin filenames or paths.
 -      */
 -      wxFileDialog* d = new wxFileDialog (this, _("Choose a file or files"), wxT (""), wxT (""), wxT ("*.*"), wxFD_MULTIPLE | wxFD_CHANGE_DIR);
 -      int const r = d->ShowModal ();
 -
 -      if (r != wxID_OK) {
 -              d->Destroy ();
 -              return;
 -      }
 -
 -      wxArrayString paths;
 -      d->GetPaths (paths);
 -
 -      /* XXX: check for lots of files here and do something */
 -
 -      for (unsigned int i = 0; i < paths.GetCount(); ++i) {
 -              _film->examine_and_add_content (content_factory (_film, wx_to_std (paths[i])));
 -      }
 -
 -      d->Destroy ();
 -}
 -
 -void
 -FilmEditor::content_add_folder_clicked ()
 -{
 -      wxDirDialog* d = new wxDirDialog (this, _("Choose a folder"), wxT (""), wxDD_DIR_MUST_EXIST);
 -      int const r = d->ShowModal ();
 -      d->Destroy ();
 -      
 -      if (r != wxID_OK) {
 -              return;
 -      }
 -
 -      shared_ptr<ImageContent> ic;
 -      
 -      try {
 -              ic.reset (new ImageContent (_film, boost::filesystem::path (wx_to_std (d->GetPath ()))));
 -      } catch (FileError& e) {
 -              error_dialog (this, std_to_wx (e.what ()));
 -              return;
 -      }
 -
 -      _film->examine_and_add_content (ic);
 -}
 -
 -void
 -FilmEditor::content_remove_clicked ()
 -{
 -      ContentList c = selected_content ();
 -      for (ContentList::iterator i = c.begin(); i != c.end(); ++i) {
 -              _film->remove_content (*i);
 -      }
 -
 -      content_selection_changed ();
 -}
 -
 -void
 -FilmEditor::content_selection_changed ()
 -{
 -      setup_content_sensitivity ();
 -
 -      for (list<FilmEditorPanel*>::iterator i = _panels.begin(); i != _panels.end(); ++i) {
 -              (*i)->content_selection_changed ();
 -      }
 -}
 -
 -/** Set up broad sensitivity based on the type of content that is selected */
 -void
 -FilmEditor::setup_content_sensitivity ()
 -{
 -      _content_add_file->Enable (_generally_sensitive);
 -      _content_add_folder->Enable (_generally_sensitive);
 -
 -      ContentList selection = selected_content ();
 -      VideoContentList video_selection = selected_video_content ();
 -      AudioContentList audio_selection = selected_audio_content ();
 -
 -      _content_remove->Enable   (!selection.empty() && _generally_sensitive);
 -      _content_earlier->Enable  (selection.size() == 1 && _generally_sensitive);
 -      _content_later->Enable    (selection.size() == 1 && _generally_sensitive);
 -      _content_timeline->Enable (!_film->content().empty() && _generally_sensitive);
 -
 -      _video_panel->Enable    (!video_selection.empty() && _generally_sensitive);
 -      _audio_panel->Enable    (!audio_selection.empty() && _generally_sensitive);
 -      _subtitle_panel->Enable (selection.size() == 1 && dynamic_pointer_cast<FFmpegContent> (selection.front()) && _generally_sensitive);
 -      _timing_panel->Enable   (!selection.empty() && _generally_sensitive);
 -}
 -
 -ContentList
 -FilmEditor::selected_content ()
 -{
 -      ContentList sel;
 -
 -      if (!_film) {
 -              return sel;
 -      }
 -
 -      /* The list was populated using a sorted content list, so we must sort it here too
 -         so that we can look up by index and get the right thing.
 -      */
 -      ContentList content = _film->content ();
 -      sort (content.begin(), content.end(), ContentSorter ());
 -      
 -      long int s = -1;
 -      while (true) {
 -              s = _content->GetNextItem (s, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
 -              if (s == -1) {
 -                      break;
 -              }
 -
 -              if (s < int (_film->content().size ())) {
 -                      sel.push_back (content[s]);
 -              }
 -      }
 -
 -      return sel;
 -}
 -
 -VideoContentList
 -FilmEditor::selected_video_content ()
 -{
 -      ContentList c = selected_content ();
 -      VideoContentList vc;
 -      
 -      for (ContentList::iterator i = c.begin(); i != c.end(); ++i) {
 -              shared_ptr<VideoContent> t = dynamic_pointer_cast<VideoContent> (*i);
 -              if (t) {
 -                      vc.push_back (t);
 -              }
 -      }
 -
 -      return vc;
 -}
 -
 -AudioContentList
 -FilmEditor::selected_audio_content ()
 -{
 -      ContentList c = selected_content ();
 -      AudioContentList ac;
 -      
 -      for (ContentList::iterator i = c.begin(); i != c.end(); ++i) {
 -              shared_ptr<AudioContent> t = dynamic_pointer_cast<AudioContent> (*i);
 -              if (t) {
 -                      ac.push_back (t);
 -              }
 -      }
 -
 -      return ac;
 -}
 -
 -SubtitleContentList
 -FilmEditor::selected_subtitle_content ()
 -{
 -      ContentList c = selected_content ();
 -      SubtitleContentList sc;
 -      
 -      for (ContentList::iterator i = c.begin(); i != c.end(); ++i) {
 -              shared_ptr<SubtitleContent> t = dynamic_pointer_cast<SubtitleContent> (*i);
 -              if (t) {
 -                      sc.push_back (t);
 -              }
 -      }
 -
 -      return sc;
 -}
 -
 -FFmpegContentList
 -FilmEditor::selected_ffmpeg_content ()
 -{
 -      ContentList c = selected_content ();
 -      FFmpegContentList sc;
 -      
 -      for (ContentList::iterator i = c.begin(); i != c.end(); ++i) {
 -              shared_ptr<FFmpegContent> t = dynamic_pointer_cast<FFmpegContent> (*i);
 -              if (t) {
 -                      sc.push_back (t);
 -              }
 -      }
 -
 -      return sc;
 -}
 -
 -void
 -FilmEditor::content_timeline_clicked ()
 -{
 -      if (_timeline_dialog) {
 -              _timeline_dialog->Destroy ();
 -              _timeline_dialog = 0;
 -      }
 -      
 -      _timeline_dialog = new TimelineDialog (this, _film);
 -      _timeline_dialog->Show ();
 -}
 -
 -void
 -FilmEditor::set_selection (weak_ptr<Content> wc)
 -{
 -      ContentList content = _film->content ();
 -      for (size_t i = 0; i < content.size(); ++i) {
 -              if (content[i] == wc.lock ()) {
 -                      _content->SetItemState (i, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED);
 -              } else {
 -                      _content->SetItemState (i, 0, wxLIST_STATE_SELECTED);
 -              }
 -      }
 -}
 -
 -void
 -FilmEditor::sequence_video_changed ()
 -{
 -      if (!_film) {
 -              return;
 -      }
 -      
 -      _film->set_sequence_video (_sequence_video->GetValue ());
 -}
 -
 -void
 -FilmEditor::content_right_click (wxListEvent& ev)
 -{
 -      _menu.popup (_film, selected_content (), ev.GetPoint ());
 -}
 -
 -void
 -FilmEditor::three_d_changed ()
 -{
 -      if (!_film) {
 -              return;
 -      }
 -
 -      _film->set_three_d (_three_d->GetValue ());
 -}
 -
 -void
 -FilmEditor::content_earlier_clicked ()
 -{
 -      ContentList sel = selected_content ();
 -      if (sel.size() == 1) {
 -              _film->move_content_earlier (sel.front ());
 -              content_selection_changed ();
 -      }
 -}
 -
 -void
 -FilmEditor::content_later_clicked ()
 -{
 -      ContentList sel = selected_content ();
 -      if (sel.size() == 1) {
 -              _film->move_content_later (sel.front ());
 -              content_selection_changed ();
 -      }
 -}
 -
 -void
 -FilmEditor::config_changed ()
 -{
 -      _j2k_bandwidth->SetRange (1, Config::instance()->maximum_j2k_bandwidth() / 1000000);
 -      setup_frame_rate_widget ();
 -}
 -
 -void
 -FilmEditor::setup_frame_rate_widget ()
 -{
 -      if (Config::instance()->allow_any_dcp_frame_rate ()) {
 -              _frame_rate_choice->Hide ();
 -              _frame_rate_spin->Show ();
 -      } else {
 -              _frame_rate_choice->Show ();
 -              _frame_rate_spin->Hide ();
 -      }
 -
 -      _frame_rate_sizer->Layout ();
 -}
diff --combined src/wx/film_editor.h
index 28baf410c6fe5f9d627de23fceaeee4d07fcc01c,6f39f2d68428d8ecc4fb0d391bbb3f316b129b8e..a198d7aa7d0ce221b0e246a13b3bd24bdd6667d6
   */
  
  #include <wx/wx.h>
 -#include <wx/spinctrl.h>
 -#include <wx/filepicker.h>
 -#include <wx/collpane.h>
  #include <boost/signals2.hpp>
  #include "lib/film.h"
 -#include "content_menu.h"
  
  class wxNotebook;
 -class wxListCtrl;
 -class wxListEvent;
 +class wxSpinCtrl;
  class Film;
 -class TimelineDialog;
  class Ratio;
 -class Timecode;
 -class FilmEditorPanel;
 -class SubtitleContent;
 +class ContentPanel;
 +class DCPPanel;
  
  /** @class FilmEditor
   *  @brief A wx widget to edit a film's metadata, and perform various functions.
  class FilmEditor : public wxPanel
  {
  public:
-       FilmEditor (boost::shared_ptr<Film>, wxWindow *);
+       FilmEditor (wxWindow *);
  
        void set_film (boost::shared_ptr<Film>);
 -      void set_selection (boost::weak_ptr<Content>);
  
        boost::signals2::signal<void (boost::filesystem::path)> FileChanged;
  
        /* Stuff for panels */
 -      
 -      wxNotebook* content_notebook () const {
 -              return _content_notebook;
 -      }
  
 +      ContentPanel* content_panel () const {
 +              return _content_panel;
 +      }
 +      
        boost::shared_ptr<Film> film () const {
                return _film;
        }
  
 -      ContentList selected_content ();
 -      VideoContentList selected_video_content ();
 -      AudioContentList selected_audio_content ();
 -      SubtitleContentList selected_subtitle_content ();
 -      FFmpegContentList selected_ffmpeg_content ();
 -      
  private:
 -      void make_dcp_panel ();
 -      void make_content_panel ();
 -      void connect_to_widgets ();
 -      
 -      /* Handle changes to the view */
 -      void name_changed ();
 -      void use_isdcf_name_toggled ();
 -      void edit_isdcf_button_clicked ();
 -      void content_selection_changed ();
 -      void content_add_file_clicked ();
 -      void content_add_folder_clicked ();
 -      void content_remove_clicked ();
 -      void content_earlier_clicked ();
 -      void content_later_clicked ();
 -      void container_changed ();
 -      void dcp_content_type_changed ();
 -      void scaler_changed ();
 -      void j2k_bandwidth_changed ();
 -      void frame_rate_choice_changed ();
 -      void frame_rate_spin_changed ();
 -      void best_frame_rate_clicked ();
 -      void content_timeline_clicked ();
 -      void audio_channels_changed ();
 -      void resolution_changed ();
 -      void sequence_video_changed ();
 -      void content_right_click (wxListEvent &);
 -      void three_d_changed ();
 -      void standard_changed ();
 -      void signed_toggled ();
 -      void encrypted_toggled ();
 -
        /* Handle changes to the model */
        void film_changed (Film::Property);
        void film_content_changed (int);
  
        void set_general_sensitivity (bool);
 -      void setup_dcp_name ();
 -      void setup_content ();
 -      void setup_container ();
 -      void setup_content_sensitivity ();
 -      void setup_frame_rate_widget ();
 -      
        void active_jobs_changed (bool);
 -      void config_changed ();
 -
 -      FilmEditorPanel* _video_panel;
 -      FilmEditorPanel* _audio_panel;
 -      FilmEditorPanel* _subtitle_panel;
 -      FilmEditorPanel* _timing_panel;
 -      std::list<FilmEditorPanel *> _panels;
  
        wxNotebook* _main_notebook;
 -      wxNotebook* _content_notebook;
 -      wxPanel* _dcp_panel;
 -      wxSizer* _dcp_sizer;
 -      wxPanel* _content_panel;
 -      wxSizer* _content_sizer;
 +      ContentPanel* _content_panel;
 +      DCPPanel* _dcp_panel;
  
        /** The film we are editing */
        boost::shared_ptr<Film> _film;
 -      wxTextCtrl* _name;
 -      wxStaticText* _dcp_name;
 -      wxCheckBox* _use_isdcf_name;
 -      wxChoice* _container;
 -      wxListCtrl* _content;
 -      wxButton* _content_add_file;
 -      wxButton* _content_add_folder;
 -      wxButton* _content_remove;
 -      wxButton* _content_earlier;
 -      wxButton* _content_later;
 -      wxButton* _content_timeline;
 -      wxCheckBox* _sequence_video;
 -      wxButton* _edit_isdcf_button;
 -      wxChoice* _scaler;
 -      wxSpinCtrl* _j2k_bandwidth;
 -      wxChoice* _dcp_content_type;
 -      wxChoice* _frame_rate_choice;
 -      wxSpinCtrl* _frame_rate_spin;
 -      wxSizer* _frame_rate_sizer;
 -      wxSpinCtrl* _audio_channels;
 -      wxButton* _best_frame_rate;
 -      wxCheckBox* _three_d;
 -      wxChoice* _resolution;
 -      wxChoice* _standard;
 -      wxCheckBox* _signed;
 -      wxCheckBox* _encrypted;
 -
 -      ContentMenu _menu;
 -
 -      std::vector<Ratio const *> _ratios;
 -
 -      bool _generally_sensitive;
 -      TimelineDialog* _timeline_dialog;
  };
diff --combined src/wx/film_viewer.cc
index 2416c6776841690fee63786d52b20725a578d8fd,595fd4720e413cb417761251f60f82dcbff4e8a9..ef5c78f24ae2b628751b493fb9a2f618c7ec047a
@@@ -24,7 -24,6 +24,7 @@@
  #include <iostream>
  #include <iomanip>
  #include <wx/tglbtn.h>
 +#include <dcp/exceptions.h>
  #include "lib/film.h"
  #include "lib/ratio.h"
  #include "lib/util.h"
  #include "lib/examine_content_job.h"
  #include "lib/filter.h"
  #include "lib/player.h"
 -#include "lib/player_video_frame.h"
 +#include "lib/player_video.h"
  #include "lib/video_content.h"
  #include "lib/video_decoder.h"
 +#include "lib/timer.h"
  #include "film_viewer.h"
  #include "wx_util.h"
  
@@@ -53,19 -51,18 +53,19 @@@ using std::make_pair
  using boost::shared_ptr;
  using boost::dynamic_pointer_cast;
  using boost::weak_ptr;
 -using libdcp::Size;
 +using dcp::Size;
  
- FilmViewer::FilmViewer (shared_ptr<Film> f, wxWindow* p)
+ FilmViewer::FilmViewer (wxWindow* p)
        : wxPanel (p)
        , _panel (new wxPanel (this))
 +      , _outline_content (new wxCheckBox (this, wxID_ANY, _("Outline content")))
        , _slider (new wxSlider (this, wxID_ANY, 0, 0, 4096))
        , _back_button (new wxButton (this, wxID_ANY, wxT("<")))
        , _forward_button (new wxButton (this, wxID_ANY, wxT(">")))
        , _frame_number (new wxStaticText (this, wxID_ANY, wxT("")))
        , _timecode (new wxStaticText (this, wxID_ANY, wxT("")))
        , _play_button (new wxToggleButton (this, wxID_ANY, _("Play")))
 -      , _got_frame (false)
 +      , _last_get_accurate (true)
  {
  #ifndef __WXOSX__
        _panel->SetDoubleBuffered (true);
@@@ -78,8 -75,6 +78,8 @@@
  
        _v_sizer->Add (_panel, 1, wxEXPAND);
  
 +      _v_sizer->Add (_outline_content, 0, wxALL, DCPOMATIC_SIZER_GAP);
 +
        wxBoxSizer* h_sizer = new wxBoxSizer (wxHORIZONTAL);
  
        wxBoxSizer* time_sizer = new wxBoxSizer (wxVERTICAL);
  
        _panel->Bind          (wxEVT_PAINT,                        boost::bind (&FilmViewer::paint_panel,     this));
        _panel->Bind          (wxEVT_SIZE,                         boost::bind (&FilmViewer::panel_sized,     this, _1));
 +      _outline_content->Bind(wxEVT_COMMAND_CHECKBOX_CLICKED,     boost::bind (&FilmViewer::refresh_panel,   this));
        _slider->Bind         (wxEVT_SCROLL_THUMBTRACK,            boost::bind (&FilmViewer::slider_moved,    this));
        _slider->Bind         (wxEVT_SCROLL_PAGEUP,                boost::bind (&FilmViewer::slider_moved,    this));
        _slider->Bind         (wxEVT_SCROLL_PAGEDOWN,              boost::bind (&FilmViewer::slider_moved,    this));
        _back_button->Bind    (wxEVT_COMMAND_BUTTON_CLICKED,       boost::bind (&FilmViewer::back_clicked,    this));
        _forward_button->Bind (wxEVT_COMMAND_BUTTON_CLICKED,       boost::bind (&FilmViewer::forward_clicked, this));
  
-       set_film (f);
+       set_film (shared_ptr<Film> ());
+       
        JobManager::instance()->ActiveJobsChanged.connect (
                bind (&FilmViewer::active_jobs_changed, this, _1)
                );
 +
 +      setup_sensitivity ();
  }
  
  void
@@@ -130,7 -122,7 +130,7 @@@ FilmViewer::set_film (shared_ptr<Film> 
        _frame.reset ();
        
        _slider->SetValue (0);
 -      set_position_text (0);
 +      set_position_text ();
        
        if (!_film) {
                return;
                return;
        }
        
 -      _player->disable_audio ();
 -      _player->Video.connect (boost::bind (&FilmViewer::process_video, this, _1, _3));
 -      _player->Changed.connect (boost::bind (&FilmViewer::player_changed, this, _1));
 +      _film_connection = _film->Changed.connect (boost::bind (&FilmViewer::film_changed, this, _1));
 +
 +      _player->set_approximate_size ();
 +      _player_connection = _player->Changed.connect (boost::bind (&FilmViewer::player_changed, this, _1));
  
        calculate_sizes ();
 -      fetch_next_frame ();
 +      get (_position, _last_get_accurate);
 +}
 +
 +void
 +FilmViewer::refresh_panel ()
 +{
 +      _panel->Refresh ();
 +      _panel->Update ();
  }
  
  void
 -FilmViewer::fetch_current_frame_again ()
 +FilmViewer::get (DCPTime p, bool accurate)
  {
        if (!_player) {
                return;
        }
  
 -      /* We could do this with a seek and a fetch_next_frame, but this is
 -         a shortcut to make it quicker.
 -      */
 -
 -      _got_frame = false;
 -      if (!_player->repeat_last_video ()) {
 -              fetch_next_frame ();
 +      list<shared_ptr<PlayerVideo> > pvf = _player->get_video (p, accurate);
 +      if (!pvf.empty ()) {
 +              try {
 +                      _frame = pvf.front()->image (true);
 +                      _frame = _frame->scale (_frame->size(), Scaler::from_id ("fastbilinear"), PIX_FMT_RGB24, false);
 +                      _position = pvf.front()->time ();
 +                      _inter_position = pvf.front()->inter_position ();
 +                      _inter_size = pvf.front()->inter_size ();
 +              } catch (dcp::DCPReadError& e) {
 +                      /* This can happen on the following sequence of events:
 +                       * - load encrypted DCP
 +                       * - add KDM
 +                       * - DCP is examined again, which sets its "playable" flag to 1
 +                       * - as a side effect of the exam, the viewer is updated using the old pieces
 +                       * - the DCPDecoder in the old piece gives us an encrypted frame
 +                       * - then, the pieces are re-made (but too late).
 +                       *
 +                       * I hope there's a better way to handle this ...
 +                       */
 +                      _frame.reset ();
 +                      _position = p;
 +              }
 +      } else {
 +              _frame.reset ();
 +              _position = p;
        }
 -      
 -      _panel->Refresh ();
 -      _panel->Update ();
 +
 +      set_position_text ();
 +      refresh_panel ();
 +
 +      _last_get_accurate = accurate;
  }
  
  void
  FilmViewer::timer ()
  {
 -      if (!_player) {
 -              return;
 -      }
 -      
 -      fetch_next_frame ();
 +      get (_position + DCPTime::from_frames (1, _film->video_frame_rate ()), true);
  
 -      Time const len = _film->length ();
 +      DCPTime const len = _film->length ();
  
 -      if (len) {
 -              int const new_slider_position = 4096 * _player->video_position() / len;
 +      if (len.get ()) {
 +              int const new_slider_position = 4096 * _position.get() / len.get();
                if (new_slider_position != _slider->GetValue()) {
                        _slider->SetValue (new_slider_position);
                }
@@@ -244,29 -212,22 +244,29 @@@ FilmViewer::paint_panel (
                dc.SetPen (p);
                dc.SetBrush (b);
                dc.DrawRectangle (0, _out_size.height, _panel_size.width, _panel_size.height - _out_size.height);
 -      }               
 -}
 +      }
  
 +      if (_outline_content->GetValue ()) {
 +              wxPen p (wxColour (255, 0, 0), 2);
 +              dc.SetPen (p);
 +              dc.SetBrush (*wxTRANSPARENT_BRUSH);
 +              dc.DrawRectangle (_inter_position.x, _inter_position.y, _inter_size.width, _inter_size.height);
 +      }
 +}
  
  void
  FilmViewer::slider_moved ()
  {
 -      if (_film && _player) {
 -              Time t = _slider->GetValue() * _film->length() / 4096;
 -              /* Ensure that we hit the end of the film at the end of the slider */
 -              if (t >= _film->length ()) {
 -                      t = _film->length() - _film->video_frames_to_time (1);
 -              }
 -              _player->seek (t, false);
 -              fetch_next_frame ();
 +      if (!_film) {
 +              return;
        }
 +
 +      DCPTime t (_slider->GetValue() * _film->length().get() / 4096);
 +      /* Ensure that we hit the end of the film at the end of the slider */
 +      if (t >= _film->length ()) {
 +              t = _film->length() - DCPTime::from_frames (1, _film->video_frame_rate ());
 +      }
 +      get (t, false);
  }
  
  void
@@@ -274,9 -235,8 +274,9 @@@ FilmViewer::panel_sized (wxSizeEvent& e
  {
        _panel_size.width = ev.GetSize().GetWidth();
        _panel_size.height = ev.GetSize().GetHeight();
 +
        calculate_sizes ();
 -      fetch_current_frame_again ();
 +      get (_position, _last_get_accurate);
  }
  
  void
@@@ -294,24 -254,17 +294,24 @@@ FilmViewer::calculate_sizes (
        if (panel_ratio < film_ratio) {
                /* panel is less widscreen than the film; clamp width */
                _out_size.width = _panel_size.width;
 -              _out_size.height = _out_size.width / film_ratio;
 +              _out_size.height = rint (_out_size.width / film_ratio);
        } else {
                /* panel is more widescreen than the film; clamp height */
                _out_size.height = _panel_size.height;
 -              _out_size.width = _out_size.height * film_ratio;
 +              _out_size.width = rint (_out_size.height * film_ratio);
        }
  
        /* Catch silly values */
        _out_size.width = max (64, _out_size.width);
        _out_size.height = max (64, _out_size.height);
  
 +      /* The player will round its image size down to the next lowest 4 pixels
 +         to speed up its scale, so do similar here to avoid black borders
 +         around things.  This is a bit of a hack.
 +      */
 +      _out_size.width &= ~3;
 +      _out_size.height &= ~3;
 +
        _player->set_video_container_size (_out_size);
  }
  
@@@ -336,7 -289,20 +336,7 @@@ FilmViewer::check_play_state (
  }
  
  void
 -FilmViewer::process_video (shared_ptr<PlayerVideoFrame> pvf, Time t)
 -{
 -      if (pvf->eyes() == EYES_RIGHT) {
 -              return;
 -      }
 -      
 -      _frame = pvf->image ();
 -      _got_frame = true;
 -
 -      set_position_text (t);
 -}
 -
 -void
 -FilmViewer::set_position_text (Time t)
 +FilmViewer::set_position_text ()
  {
        if (!_film) {
                _frame_number->SetLabel ("0");
                
        double const fps = _film->video_frame_rate ();
        /* Count frame number from 1 ... not sure if this is the best idea */
 -      _frame_number->SetLabel (wxString::Format (wxT("%d"), int (rint (t * fps / TIME_HZ)) + 1));
 +      _frame_number->SetLabel (wxString::Format (wxT("%d"), int (rint (_position.seconds() * fps)) + 1));
        
 -      double w = static_cast<double>(t) / TIME_HZ;
 +      double w = _position.seconds ();
        int const h = (w / 3600);
        w -= h * 3600;
        int const m = (w / 60);
        _timecode->SetLabel (wxString::Format (wxT("%02d:%02d:%02d.%02d"), h, m, s, f));
  }
  
 -/** Ask the player to emit its next frame, then update our display */
 -void
 -FilmViewer::fetch_next_frame ()
 -{
 -      /* Clear our frame in case we don't get a new one */
 -      _frame.reset ();
 -
 -      if (!_player) {
 -              return;
 -      }
 -
 -      _got_frame = false;
 -      
 -      try {
 -              while (!_got_frame && !_player->pass ()) {}
 -      } catch (DecodeError& e) {
 -              _play_button->SetValue (false);
 -              check_play_state ();
 -              error_dialog (this, wxString::Format (_("Could not decode video for view (%s)"), std_to_wx(e.what()).data()));
 -      } catch (OpenFileError& e) {
 -              /* There was a problem opening a content file; we'll let this slide as it
 -                 probably means a missing content file, which we're already taking care of.
 -              */
 -      }
 -
 -      _panel->Refresh ();
 -      _panel->Update ();
 -}
 -
  void
  FilmViewer::active_jobs_changed (bool a)
  {
  void
  FilmViewer::back_clicked ()
  {
 -      if (!_player) {
 -              return;
 +      DCPTime p = _position - DCPTime::from_frames (1, _film->video_frame_rate ());
 +      if (p < DCPTime ()) {
 +              p = DCPTime ();
        }
  
 -      /* Player::video_position is the time after the last frame that we received.
 -         We want to see the one before it, so we need to go back 2.
 -      */
 -
 -      Time p = _player->video_position() - _film->video_frames_to_time (2);
 -      if (p < 0) {
 -              p = 0;
 -      }
 -      
 -      _player->seek (p, true);
 -      fetch_next_frame ();
 +      get (p, true);
  }
  
  void
  FilmViewer::forward_clicked ()
  {
 -      if (!_player) {
 -              return;
 -      }
 -
 -      fetch_next_frame ();
 +      get (_position + DCPTime::from_frames (1, _film->video_frame_rate ()), true);
  }
  
  void
@@@ -404,27 -412,5 +404,27 @@@ FilmViewer::player_changed (bool freque
        }
  
        calculate_sizes ();
 -      fetch_current_frame_again ();
 +      get (_position, _last_get_accurate);
 +}
 +
 +void
 +FilmViewer::setup_sensitivity ()
 +{
 +      bool const c = _film && !_film->content().empty ();
 +      
 +      _slider->Enable (c);
 +      _back_button->Enable (c);
 +      _forward_button->Enable (c);
 +      _play_button->Enable (c);
 +      _outline_content->Enable (c);
 +      _frame_number->Enable (c);
 +      _timecode->Enable (c);
 +}
 +
 +void
 +FilmViewer::film_changed (Film::Property p)
 +{
 +      if (p == Film::CONTENT) {
 +              setup_sensitivity ();
 +      }
  }
diff --combined src/wx/film_viewer.h
index 0235d225fc030eb125b48b96fbb7a2bf6eb60b07,337b68446cf64bbd2b935ea6bcbda5cc11a12d77..e502c6f45cd2922e3d9fe90c32fe7553c67d83a6
@@@ -1,5 -1,5 +1,5 @@@
  /*
 -    Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
 +    Copyright (C) 2012-2014 Carl Hetherington <cth@carlh.net>
  
      This program is free software; you can redistribute it and/or modify
      it under the terms of the GNU General Public License as published by
@@@ -28,15 -28,28 +28,15 @@@ class wxToggleButton
  class FFmpegPlayer;
  class Image;
  class RGBPlusAlphaImage;
 -class PlayerVideoFrame;
 +class PlayerVideo;
  
  /** @class FilmViewer
   *  @brief A wx widget to view a preview of a Film.
 - *
 - *  The film takes the following path through the viewer:
 - *
 - *  1.        fetch_next_frame() asks our _player to decode some data.  If it does, process_video()
 - *    will be called.
 - *
 - *  2.        process_video() takes the image from the player (_frame).
 - *
 - *  3.        fetch_next_frame() calls _panel->Refresh() and _panel->Update() which results in
 - *    paint_panel() being called; this creates frame_bitmap from _frame and blits it to the display.
 - *
 - * fetch_current_frame_again() asks the player to re-emit its current frame on the next pass(), and then
 - * starts from step #1.
   */
  class FilmViewer : public wxPanel
  {
  public:
-       FilmViewer (boost::shared_ptr<Film>, wxWindow *);
+       FilmViewer (wxWindow *);
  
        void set_film (boost::shared_ptr<Film>);
  
@@@ -46,24 -59,22 +46,24 @@@ private
        void slider_moved ();
        void play_clicked ();
        void timer ();
 -      void process_video (boost::shared_ptr<PlayerVideoFrame>, Time);
        void calculate_sizes ();
        void check_play_state ();
 -      void fetch_current_frame_again ();
 -      void fetch_next_frame ();
        void active_jobs_changed (bool);
        void back_clicked ();
        void forward_clicked ();
        void player_changed (bool);
 -      void set_position_text (Time);
 +      void set_position_text ();
 +      void get (DCPTime, bool);
 +      void refresh_panel ();
 +      void setup_sensitivity ();
 +      void film_changed (Film::Property);
  
        boost::shared_ptr<Film> _film;
        boost::shared_ptr<Player> _player;
  
        wxSizer* _v_sizer;
        wxPanel* _panel;
 +      wxCheckBox* _outline_content;
        wxSlider* _slider;
        wxButton* _back_button;
        wxButton* _forward_button;
        wxTimer _timer;
  
        boost::shared_ptr<const Image> _frame;
 -      bool _got_frame;
 +      DCPTime _position;
 +      Position<int> _inter_position;
 +      dcp::Size _inter_size;
  
        /** Size of our output (including padding if we have any) */
 -      libdcp::Size _out_size;
 +      dcp::Size _out_size;
        /** Size of the panel that we have available */
 -      libdcp::Size _panel_size;
 +      dcp::Size _panel_size;
 +      /** true if the last call to ::get() was specified to be accurate;
 +       *  this is used so that when re-fetching the current frame we
 +       *  can get the same one that we got last time.
 +       */
 +      bool _last_get_accurate;
 +
 +      boost::signals2::scoped_connection _film_connection;
 +      boost::signals2::scoped_connection _player_connection;
  };