Support CPL metadata.
authorCarl Hetherington <cth@carlh.net>
Fri, 4 Sep 2020 15:24:12 +0000 (17:24 +0200)
committerCarl Hetherington <cth@carlh.net>
Wed, 23 Sep 2020 09:38:43 +0000 (11:38 +0200)
19 files changed:
src/lib/copy_dcp_details_to_film.cc
src/lib/film.cc
src/lib/film.h
src/lib/util.cc
src/lib/writer.cc
src/wx/content_version_dialog.cc [new file with mode: 0644]
src/wx/content_version_dialog.h [new file with mode: 0644]
src/wx/dcp_panel.cc
src/wx/dcp_panel.h
src/wx/interop_metadata_dialog.cc [new file with mode: 0644]
src/wx/interop_metadata_dialog.h [new file with mode: 0644]
src/wx/language_tag_dialog.cc [new file with mode: 0644]
src/wx/language_tag_dialog.h [new file with mode: 0644]
src/wx/metadata_dialog.cc [deleted file]
src/wx/metadata_dialog.h [deleted file]
src/wx/smpte_metadata_dialog.cc [new file with mode: 0644]
src/wx/smpte_metadata_dialog.h [new file with mode: 0644]
src/wx/wscript
test/import_dcp_test.cc

index a009735fb7c32a02986b2b59f5f8a7a77718e635..d73ee8792a96f502a4ba09e6cf58ec1f55ef973a 100644 (file)
 #include <boost/shared_ptr.hpp>
 #include <map>
 
+
 using std::map;
 using std::string;
+using std::vector;
 using boost::shared_ptr;
 
+
 void
 copy_dcp_details_to_film (shared_ptr<const DCPContent> dcp, shared_ptr<Film> film)
 {
@@ -67,7 +70,10 @@ copy_dcp_details_to_film (shared_ptr<const DCPContent> dcp, shared_ptr<Film> fil
        }
 
        film->set_ratings (dcp->ratings());
-       film->set_content_version (dcp->content_version());
+
+       vector<string> cv;
+       cv.push_back (dcp->content_version());
+       film->set_content_versions (cv);
 }
 
 
index 867944689a029189e1b481a4ca57e7b5406fc0a8..79ee20cfb791f03b2b402bebb398013bbd4d8ac7 100644 (file)
@@ -165,6 +165,11 @@ Film::Film (optional<boost::filesystem::path> dir)
        , _user_explicit_video_frame_rate (false)
        , _user_explicit_container (false)
        , _user_explicit_resolution (false)
+       , _name_language (dcp::LanguageTag("en-US"))
+       , _release_territory (dcp::LanguageTag::RegionSubtag("US"))
+       , _version_number (1)
+       , _status (dcp::FINAL)
+       , _luminance (dcp::Luminance(4.5, dcp::Luminance::FOOT_LAMBERT))
        , _state_version (current_state_version)
        , _dirty (false)
        , _tolerant (false)
@@ -464,7 +469,18 @@ Film::metadata (bool with_content_paths) const
        BOOST_FOREACH (dcp::Rating i, _ratings) {
                i.as_xml (root->add_child("Rating"));
        }
-       root->add_child("ContentVersion")->add_child_text(_content_version);
+       BOOST_FOREACH (string i, _content_versions) {
+               root->add_child("ContentVersion")->add_child_text(i);
+       }
+       root->add_child("NameLanguage")->add_child_text(_name_language.to_string());
+       root->add_child("ReleaseTerritory")->add_child_text(_release_territory.subtag());
+       root->add_child("VersionNumber")->add_child_text(raw_convert<string>(_version_number));
+       root->add_child("Status")->add_child_text(dcp::status_to_string(_status));
+       root->add_child("Chain")->add_child_text(_chain);
+       root->add_child("Distributor")->add_child_text(_distributor);
+       root->add_child("Facility")->add_child_text(_facility);
+       root->add_child("LuminanceValue")->add_child_text(raw_convert<string>(_luminance.value()));
+       root->add_child("LuminanceUnit")->add_child_text(dcp::Luminance::unit_to_string(_luminance.unit()));
        root->add_child("UserExplicitContainer")->add_child_text(_user_explicit_container ? "1" : "0");
        root->add_child("UserExplicitResolution")->add_child_text(_user_explicit_resolution ? "1" : "0");
        _playlist->as_xml (root->add_child ("Playlist"), with_content_paths);
@@ -612,7 +628,35 @@ Film::read_metadata (optional<boost::filesystem::path> path)
                _ratings.push_back (dcp::Rating(i));
        }
 
-       _content_version = f.optional_string_child("ContentVersion").get_value_or("");
+       BOOST_FOREACH (cxml::ConstNodePtr i, f.node_children("ContentVersion")) {
+               _content_versions.push_back (i->content());
+       }
+
+       optional<string> name_language = f.optional_string_child("NameLanguage");
+       if (name_language) {
+               _name_language = dcp::LanguageTag (*name_language);
+       }
+       optional<string> release_territory = f.optional_string_child("ReleaseTerritory");
+       if (release_territory) {
+               _release_territory = dcp::LanguageTag::RegionSubtag (*release_territory);
+       }
+
+       _version_number = f.optional_number_child<int>("VersionNumber").get_value_or(0);
+
+       optional<string> status = f.optional_string_child("Status");
+       if (status) {
+               _status = dcp::string_to_status (*status);
+       }
+
+       _chain = f.optional_string_child("Chain").get_value_or("");
+       _distributor = f.optional_string_child("Distributor").get_value_or("");
+       _facility = f.optional_string_child("Facility").get_value_or("");
+
+       float value = f.optional_number_child<float>("LuminanceValue").get_value_or(4.5);
+       optional<string> unit = f.optional_string_child("LuminanceUnit");
+       if (unit) {
+               _luminance = dcp::Luminance (value, dcp::Luminance::string_to_unit(*unit));
+       }
 
        /* Disable guessing for files made in previous DCP-o-matic versions */
        _user_explicit_container = f.optional_bool_child("UserExplicitContainer").get_value_or(true);
@@ -1494,6 +1538,26 @@ Film::frame_size () const
        return fit_ratio_within (container()->ratio(), full_frame ());
 }
 
+
+/** @return Area of Film::frame_size() that contains picture rather than pillar/letterboxing */
+dcp::Size
+Film::active_area () const
+{
+       dcp::Size const frame = frame_size ();
+       dcp::Size active;
+
+       BOOST_FOREACH (shared_ptr<Content> i, content()) {
+               if (i->video) {
+                       dcp::Size s = i->video->scaled_size (frame);
+                       active.width = max(active.width, s.width);
+                       active.height = max(active.height, s.height);
+               }
+       }
+
+       return active;
+}
+
+
 /** @param recipient KDM recipient certificate.
  *  @param trusted_devices Certificate thumbprints of other trusted devices (can be empty).
  *  @param cpl_file CPL filename.
@@ -1865,12 +1929,77 @@ Film::set_ratings (vector<dcp::Rating> r)
 }
 
 void
-Film::set_content_version (string v)
+Film::set_content_versions (vector<string> v)
+{
+       ChangeSignaller<Film> ch (this, CONTENT_VERSIONS);
+       _content_versions = v;
+}
+
+
+void
+Film::set_name_language (dcp::LanguageTag lang)
+{
+       ChangeSignaller<Film> ch (this, NAME_LANGUAGE);
+       _name_language = lang;
+}
+
+
+void
+Film::set_release_territory (dcp::LanguageTag::RegionSubtag region)
 {
-       ChangeSignaller<Film> ch (this, CONTENT_VERSION);
-       _content_version = v;
+       ChangeSignaller<Film> ch (this, RELEASE_TERRITORY);
+       _release_territory = region;
 }
 
+
+void
+Film::set_status (dcp::Status s)
+{
+       ChangeSignaller<Film> ch (this, STATUS);
+       _status = s;
+}
+
+
+void
+Film::set_version_number (int v)
+{
+       ChangeSignaller<Film> ch (this, VERSION_NUMBER);
+       _version_number = v;
+}
+
+
+void
+Film::set_chain (string c)
+{
+       ChangeSignaller<Film> ch (this, CHAIN);
+       _chain = c;
+}
+
+
+void
+Film::set_distributor (string d)
+{
+       ChangeSignaller<Film> ch (this, DISTRIBUTOR);
+       _distributor = d;
+}
+
+
+void
+Film::set_luminance (dcp::Luminance l)
+{
+       ChangeSignaller<Film> ch (this, LUMINANCE);
+       _luminance = l;
+}
+
+
+void
+Film::set_facility (string f)
+{
+       ChangeSignaller<Film> ch (this, FACILITY);
+       _facility = f;
+}
+
+
 optional<DCPTime>
 Film::marker (dcp::Marker type) const
 {
index f5c20bccdbec79048285b82ab5ce4b87e802bfcd..1748057130ee8edbf8c1edfabe154873dd1303cc 100644 (file)
@@ -32,6 +32,7 @@
 #include "frame_rate_change.h"
 #include "signaller.h"
 #include "dcp_text_track.h"
+#include <dcp/language_tag.h>
 #include <dcp/key.h>
 #include <dcp/encrypted_kdm.h>
 #include <boost/signals2.hpp>
@@ -140,6 +141,7 @@ public:
 
        dcp::Size full_frame () const;
        dcp::Size frame_size () const;
+       dcp::Size active_area () const;
 
        std::vector<CPLSummary> cpls () const;
 
@@ -229,7 +231,15 @@ public:
                REENCODE_J2K,
                MARKERS,
                RATINGS,
-               CONTENT_VERSION
+               CONTENT_VERSIONS,
+               NAME_LANGUAGE,
+               RELEASE_TERRITORY,
+               VERSION_NUMBER,
+               STATUS,
+               CHAIN,
+               DISTRIBUTOR,
+               FACILITY,
+               LUMINANCE
        };
 
 
@@ -325,8 +335,40 @@ public:
                return _ratings;
        }
 
-       std::string content_version () const {
-               return _content_version;
+       std::vector<std::string> content_versions () const {
+               return _content_versions;
+       }
+
+       dcp::LanguageTag name_language () const {
+               return _name_language;
+       }
+
+       dcp::LanguageTag::RegionSubtag release_territory () const {
+               return _release_territory;
+       }
+
+       int version_number () const {
+               return _version_number;
+       }
+
+       dcp::Status status () const {
+               return _status;
+       }
+
+       std::string chain () const {
+               return _chain;
+       }
+
+       std::string distributor () const {
+               return _distributor;
+       }
+
+       std::string facility () const {
+               return _facility;
+       }
+
+       dcp::Luminance luminance () const {
+               return _luminance;
        }
 
        /* SET */
@@ -360,7 +402,15 @@ public:
        void unset_marker (dcp::Marker type);
        void clear_markers ();
        void set_ratings (std::vector<dcp::Rating> r);
-       void set_content_version (std::string v);
+       void set_content_versions (std::vector<std::string> v);
+       void set_name_language (dcp::LanguageTag lang);
+       void set_release_territory (dcp::LanguageTag::RegionSubtag region);
+       void set_version_number (int v);
+       void set_status (dcp::Status s);
+       void set_chain (std::string c);
+       void set_facility (std::string f);
+       void set_distributor (std::string d);
+       void set_luminance (dcp::Luminance l);
 
        /** Emitted when some property has of the Film is about to change or has changed */
        mutable boost::signals2::signal<void (ChangeType, Property)> Change;
@@ -454,7 +504,15 @@ private:
        bool _user_explicit_resolution;
        std::map<dcp::Marker, dcpomatic::DCPTime> _markers;
        std::vector<dcp::Rating> _ratings;
-       std::string _content_version;
+       std::vector<std::string> _content_versions;
+       dcp::LanguageTag _name_language;
+       dcp::LanguageTag::RegionSubtag _release_territory;
+       int _version_number;
+       dcp::Status _status;
+       std::string _chain;
+       std::string _distributor;
+       std::string _facility;
+       dcp::Luminance _luminance;
 
        int _state_version;
 
index 7824e7fed739f75d51063298d99ffe61fafcbb28..32deac2e8f56fc824e42aaa05472c66b6acacca2 100644 (file)
@@ -833,6 +833,7 @@ audio_channel_types (list<int> mapped, int channels)
                        break;
                case dcp::HI:
                case dcp::VI:
+               case dcp::MOTION_DATA:
                case dcp::SYNC_SIGNAL:
                case dcp::SIGN_LANGUAGE:
                case dcp::UNUSED:
index 8cfd712a0d92cad3c8576caadb47676b408893fa..0b1d8e06d08d3604ec6848c8ff95e3344c2f577c 100644 (file)
@@ -562,12 +562,14 @@ Writer::finish ()
        pool.join_all ();
        service.stop ();
 
-       /* Add reels to CPL */
+       /* Add reels */
 
        BOOST_FOREACH (ReelWriter& i, _reels) {
                cpl->add (i.create_reel (_reel_assets, _fonts));
        }
 
+       /* Add metadata */
+
        string creator = Config::instance()->dcp_creator();
        if (creator.empty()) {
                creator = String::compose("DCP-o-matic %1 %2", dcpomatic_version, dcpomatic_git_commit);
@@ -580,11 +582,39 @@ Writer::finish ()
 
        cpl->set_ratings (_film->ratings());
 
-       dcp::ContentVersion cv = cpl->content_version ();
-       cv.label_text = _film->content_version();
-       cpl->set_content_version (cv);
+       vector<dcp::ContentVersion> cv;
+       BOOST_FOREACH (string i, _film->content_versions()) {
+               cv.push_back (dcp::ContentVersion(i));
+       }
+       cpl->set_content_versions (cv);
 
        cpl->set_full_content_title_text (_film->name());
+       cpl->set_full_content_title_text_language (_film->name_language());
+       cpl->set_release_territory (_film->release_territory());
+       cpl->set_version_number (_film->version_number());
+       cpl->set_status (_film->status());
+       cpl->set_chain (_film->chain());
+       cpl->set_distributor (_film->distributor());
+       cpl->set_facility (_film->facility());
+       cpl->set_luminance (_film->luminance());
+
+       list<int> ac = _film->mapped_audio_channels ();
+       dcp::MainSoundConfiguration::Field field = (
+               find(ac.begin(), ac.end(), static_cast<int>(dcp::BSL)) != ac.end() ||
+               find(ac.begin(), ac.end(), static_cast<int>(dcp::BSR)) != ac.end()
+               ) ? dcp::MainSoundConfiguration::SEVEN_POINT_ONE : dcp::MainSoundConfiguration::FIVE_POINT_ONE;
+
+       dcp::MainSoundConfiguration msc (field, _film->audio_channels());
+       BOOST_FOREACH (int i, ac) {
+               if (i < _film->audio_channels()) {
+                       msc.set_mapping (i, static_cast<dcp::Channel>(i));
+               }
+       }
+
+       cpl->set_main_sound_configuration (msc.to_string());
+       cpl->set_main_sound_sample_rate (_film->audio_frame_rate());
+       cpl->set_main_picture_stored_area (_film->frame_size());
+       cpl->set_main_picture_active_area (_film->active_area());
 
        shared_ptr<const dcp::CertificateChain> signer;
        signer = Config::instance()->signer_chain ();
diff --git a/src/wx/content_version_dialog.cc b/src/wx/content_version_dialog.cc
new file mode 100644 (file)
index 0000000..37c9cd4
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+    Copyright (C) 2019 Carl Hetherington <cth@carlh.net>
+
+    This file is part of DCP-o-matic.
+
+    DCP-o-matic is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    DCP-o-matic is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with DCP-o-matic.  If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "content_version_dialog.h"
+#include "wx_util.h"
+
+
+using std::string;
+
+
+ContentVersionDialog::ContentVersionDialog (wxWindow* parent)
+       : TableDialog (parent, _("Content version"), 2, 1, true)
+{
+       add (_("Content version"), true);
+       _version= new wxTextCtrl (this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(300, -1));
+       add (_version);
+
+       layout ();
+
+       _version->SetFocus ();
+}
+
+
+void
+ContentVersionDialog::set (string r)
+{
+       _version->SetValue (std_to_wx(r));
+}
+
+
+string
+ContentVersionDialog::get () const
+{
+       return wx_to_std(_version->GetValue());
+}
diff --git a/src/wx/content_version_dialog.h b/src/wx/content_version_dialog.h
new file mode 100644 (file)
index 0000000..8f37598
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+    Copyright (C) 2020 Carl Hetherington <cth@carlh.net>
+
+    This file is part of DCP-o-matic.
+
+    DCP-o-matic is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    DCP-o-matic is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with DCP-o-matic.  If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "table_dialog.h"
+#include <dcp/types.h>
+#include <string>
+
+
+class ContentVersionDialog : public TableDialog
+{
+public:
+       ContentVersionDialog (wxWindow* parent);
+
+       void set (std::string);
+       std::string get () const;
+
+private:
+       wxTextCtrl* _version;
+};
index 5046c51048ddfac577b977e70655609a6477e36d..4e023db325972c0c21fc486dd681e76c99708c13 100644 (file)
@@ -28,7 +28,8 @@
 #include "check_box.h"
 #include "dcpomatic_button.h"
 #include "markers_dialog.h"
-#include "metadata_dialog.h"
+#include "interop_metadata_dialog.h"
+#include "smpte_metadata_dialog.h"
 #include "lib/ratio.h"
 #include "lib/config.h"
 #include "lib/dcp_content_type.h"
@@ -67,7 +68,8 @@ using dcp::locale_convert;
 DCPPanel::DCPPanel (wxNotebook* n, shared_ptr<Film> film, weak_ptr<FilmViewer> viewer)
        : _audio_dialog (0)
        , _markers_dialog (0)
-       , _metadata_dialog (0)
+       , _interop_metadata_dialog (0)
+       , _smpte_metadata_dialog (0)
        , _film (film)
        , _viewer (viewer)
        , _generally_sensitive (true)
@@ -338,13 +340,23 @@ DCPPanel::markers_clicked ()
 void
 DCPPanel::metadata_clicked ()
 {
-       if (_metadata_dialog) {
-               _metadata_dialog->Destroy ();
-               _metadata_dialog = 0;
-       }
+       if (_film->interop()) {
+               if (_interop_metadata_dialog) {
+                       _interop_metadata_dialog->Destroy ();
+                       _interop_metadata_dialog = 0;
+               }
 
-       _metadata_dialog = new MetadataDialog (_panel, _film);
-       _metadata_dialog->Show ();
+               _interop_metadata_dialog = new InteropMetadataDialog (_panel, _film);
+               _interop_metadata_dialog->Show ();
+       } else {
+               if (_smpte_metadata_dialog) {
+                       _smpte_metadata_dialog->Destroy ();
+                       _smpte_metadata_dialog = 0;
+               }
+
+               _smpte_metadata_dialog = new SMPTEMetadataDialog (_panel, _film);
+               _smpte_metadata_dialog->Show ();
+       }
 }
 
 void
@@ -542,9 +554,13 @@ DCPPanel::set_film (shared_ptr<Film> film)
                _markers_dialog->Destroy ();
                _markers_dialog = 0;
        }
-       if (_metadata_dialog) {
-               _metadata_dialog->Destroy ();
-               _metadata_dialog = 0;
+       if (_interop_metadata_dialog) {
+               _interop_metadata_dialog->Destroy ();
+               _interop_metadata_dialog = 0;
+       }
+       if (_smpte_metadata_dialog) {
+               _smpte_metadata_dialog->Destroy ();
+               _smpte_metadata_dialog = 0;
        }
 
        _film = film;
index 7a7d138971b8cd1e5c5c3085f53c1e9d9bce1f37..465104c74bfa85c254b4a4669a5cd6bbbeb0fab4 100644 (file)
@@ -36,7 +36,8 @@ class wxGridBagSizer;
 
 class AudioDialog;
 class MarkersDialog;
-class MetadataDialog;
+class InteropMetadataDialog;
+class SMPTEMetadataDialog;
 class Film;
 class FilmViewer;
 class Ratio;
@@ -148,7 +149,8 @@ private:
 
        AudioDialog* _audio_dialog;
        MarkersDialog* _markers_dialog;
-       MetadataDialog* _metadata_dialog;
+       InteropMetadataDialog* _interop_metadata_dialog;
+       SMPTEMetadataDialog* _smpte_metadata_dialog;
 
        boost::shared_ptr<Film> _film;
        boost::weak_ptr<FilmViewer> _viewer;
diff --git a/src/wx/interop_metadata_dialog.cc b/src/wx/interop_metadata_dialog.cc
new file mode 100644 (file)
index 0000000..f91bdcf
--- /dev/null
@@ -0,0 +1,123 @@
+/*
+    Copyright (C) 2019 Carl Hetherington <cth@carlh.net>
+
+    This file is part of DCP-o-matic.
+
+    DCP-o-matic is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    DCP-o-matic is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with DCP-o-matic.  If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#include "interop_metadata_dialog.h"
+#include "editable_list.h"
+#include "rating_dialog.h"
+#include "lib/film.h"
+#include <dcp/types.h>
+#include <wx/gbsizer.h>
+
+using std::string;
+using std::vector;
+using boost::weak_ptr;
+using boost::shared_ptr;
+
+static string
+column (dcp::Rating r, int c)
+{
+       if (c == 0) {
+               return r.agency;
+       }
+
+       return r.label;
+}
+
+InteropMetadataDialog::InteropMetadataDialog (wxWindow* parent, weak_ptr<Film> film)
+       : wxDialog (parent, wxID_ANY, _("Metadata"))
+       , _film (film)
+{
+       wxBoxSizer* overall_sizer = new wxBoxSizer (wxVERTICAL);
+       SetSizer (overall_sizer);
+
+       wxFlexGridSizer* sizer = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
+       sizer->AddGrowableCol (1, 1);
+
+       {
+               int flags = wxALIGN_TOP | wxLEFT | wxRIGHT | wxTOP;
+#ifdef __WXOSX__
+               flags |= wxALIGN_RIGHT;
+#endif
+               wxStaticText* m = create_label (this, _("Ratings"), true);
+               sizer->Add (m, 0, flags, DCPOMATIC_SIZER_GAP);
+       }
+
+       vector<EditableListColumn> columns;
+       columns.push_back (EditableListColumn(_("Agency"), 200, true));
+       columns.push_back (EditableListColumn(_("Label"), 50, true));
+       _ratings = new EditableList<dcp::Rating, RatingDialog> (
+               this,
+               columns,
+               boost::bind(&InteropMetadataDialog::ratings, this),
+               boost::bind(&InteropMetadataDialog::set_ratings, this, _1),
+               boost::bind(&column, _1, _2),
+               true,
+               false
+               );
+       sizer->Add (_ratings, 1, wxEXPAND);
+
+       add_label_to_sizer (sizer, this, _("Content version"), true);
+       _content_version = new wxTextCtrl (this, wxID_ANY);
+       sizer->Add (_content_version, 1, wxEXPAND);
+
+       shared_ptr<Film> f = _film.lock();
+       DCPOMATIC_ASSERT (f);
+       vector<string> cv = f->content_versions();
+       _content_version->SetValue (std_to_wx(cv.empty() ? "" : cv[0]));
+
+       overall_sizer->Add (sizer, 1, wxEXPAND | wxALL, DCPOMATIC_DIALOG_BORDER);
+
+       wxSizer* buttons = CreateSeparatedButtonSizer (wxCLOSE);
+       if (buttons) {
+               overall_sizer->Add (buttons, wxSizerFlags().Expand().DoubleBorder());
+       }
+
+       overall_sizer->Layout ();
+       overall_sizer->SetSizeHints (this);
+
+       _content_version->Bind (wxEVT_TEXT, boost::bind(&InteropMetadataDialog::content_version_changed, this));
+       _content_version->SetFocus ();
+}
+
+vector<dcp::Rating>
+InteropMetadataDialog::ratings () const
+{
+       shared_ptr<Film> film = _film.lock ();
+       DCPOMATIC_ASSERT (film);
+       return film->ratings ();
+}
+
+void
+InteropMetadataDialog::set_ratings (vector<dcp::Rating> r)
+{
+       shared_ptr<Film> film = _film.lock ();
+       DCPOMATIC_ASSERT (film);
+       film->set_ratings (r);
+}
+
+void
+InteropMetadataDialog::content_version_changed ()
+{
+       shared_ptr<Film> film = _film.lock ();
+       DCPOMATIC_ASSERT (film);
+       vector<string> cv;
+       cv.push_back (wx_to_std(_content_version->GetValue()));
+       film->set_content_versions (cv);
+}
diff --git a/src/wx/interop_metadata_dialog.h b/src/wx/interop_metadata_dialog.h
new file mode 100644 (file)
index 0000000..43d028e
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+    Copyright (C) 2019 Carl Hetherington <cth@carlh.net>
+
+    This file is part of DCP-o-matic.
+
+    DCP-o-matic is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    DCP-o-matic is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with DCP-o-matic.  If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#include "editable_list.h"
+#include <dcp/types.h>
+#include <wx/wx.h>
+#include <boost/shared_ptr.hpp>
+#include <boost/weak_ptr.hpp>
+#include <vector>
+
+class Film;
+class RatingDialog;
+
+class InteropMetadataDialog : public wxDialog
+{
+public:
+       InteropMetadataDialog (wxWindow* parent, boost::weak_ptr<Film> film);
+
+private:
+       std::vector<dcp::Rating> ratings () const;
+       void set_ratings (std::vector<dcp::Rating> r);
+       void content_version_changed ();
+
+       boost::weak_ptr<Film> _film;
+       EditableList<dcp::Rating, RatingDialog>* _ratings;
+       wxTextCtrl* _content_version;
+};
diff --git a/src/wx/language_tag_dialog.cc b/src/wx/language_tag_dialog.cc
new file mode 100644 (file)
index 0000000..f4e54e9
--- /dev/null
@@ -0,0 +1,408 @@
+/*
+    Copyright (C) 2020 Carl Hetherington <cth@carlh.net>
+
+    This file is part of DCP-o-matic.
+
+    DCP-o-matic is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    DCP-o-matic is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with DCP-o-matic.  If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "lib/dcpomatic_assert.h"
+#include "language_tag_dialog.h"
+#include <dcp/language_tag.h>
+#include <wx/listctrl.h>
+#include <wx/srchctrl.h>
+#include <wx/wx.h>
+#include <boost/algorithm/string.hpp>
+#include <boost/bind.hpp>
+#include <boost/foreach.hpp>
+#include <boost/optional.hpp>
+#include <boost/shared_ptr.hpp>
+#include <boost/signals2.hpp>
+#include <iostream>
+#include <iterator>
+#include <string>
+#include <vector>
+
+
+using std::pair;
+using std::string;
+using std::vector;
+using boost::optional;
+using boost::shared_ptr;
+using boost::weak_ptr;
+
+
+class SubtagListCtrl : public wxListCtrl
+{
+public:
+       SubtagListCtrl (wxWindow* parent)
+               : wxListCtrl (parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLC_REPORT | wxLC_SINGLE_SEL | wxLC_NO_HEADER | wxLC_VIRTUAL)
+       {
+               AppendColumn ("", wxLIST_FORMAT_LEFT, 80);
+               AppendColumn ("", wxLIST_FORMAT_LEFT, 400);
+       }
+
+       void set (dcp::LanguageTag::SubtagType type, string search, optional<dcp::LanguageTag::SubtagData> subtag = optional<dcp::LanguageTag::SubtagData>())
+       {
+               _all_subtags = dcp::LanguageTag::get_all(type);
+               set_search (search);
+               if (subtag) {
+                       vector<dcp::LanguageTag::SubtagData>::iterator i = find(_matching_subtags.begin(), _matching_subtags.end(), *subtag);
+                       if (i != _matching_subtags.end()) {
+                               long item = std::distance(_matching_subtags.begin(), i);
+                               SetItemState (item, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED);
+                               EnsureVisible (item);
+                       }
+               }
+       }
+
+       void set_search (string search)
+       {
+               if (search == "") {
+                       _matching_subtags = _all_subtags;
+               } else {
+                       _matching_subtags.clear ();
+
+                       boost::algorithm::to_lower(search);
+                       BOOST_FOREACH (dcp::LanguageTag::SubtagData const& i, _all_subtags) {
+                               if (
+                                       (boost::algorithm::to_lower_copy(i.subtag).find(search) != string::npos) ||
+                                       (boost::algorithm::to_lower_copy(i.description).find(search) != string::npos)) {
+                                       _matching_subtags.push_back (i);
+                               }
+                       }
+               }
+
+               SetItemCount (_matching_subtags.size());
+               if (GetItemCount() > 0) {
+                       RefreshItems (0, GetItemCount() - 1);
+               }
+       }
+
+       optional<dcp::LanguageTag::SubtagData> selected_subtag () const
+       {
+               long int selected = GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
+               if (selected == -1) {
+                       return optional<dcp::LanguageTag::SubtagData>();
+               }
+
+               DCPOMATIC_ASSERT (static_cast<size_t>(selected) < _matching_subtags.size());
+               return _matching_subtags[selected];
+       }
+
+private:
+       wxString OnGetItemText (long item, long column) const
+       {
+               if (column == 0) {
+                       return _matching_subtags[item].subtag;
+               } else {
+                       return _matching_subtags[item].description;
+               }
+       }
+
+       std::vector<dcp::LanguageTag::SubtagData> _all_subtags;
+       std::vector<dcp::LanguageTag::SubtagData> _matching_subtags;
+};
+
+
+class LanguageSubtagPanel : public wxPanel
+{
+public:
+       LanguageSubtagPanel (wxWindow* parent)
+               : wxPanel (parent, wxID_ANY)
+       {
+#ifdef __WXGTK3__
+               int const height = 30;
+#else
+               int const height = -1;
+#endif
+
+               _search = new wxSearchCtrl (this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(200, height));
+               _list = new SubtagListCtrl (this);
+
+               wxBoxSizer* sizer = new wxBoxSizer (wxVERTICAL);
+               sizer->Add (_search, 0, wxALL, 8);
+               sizer->Add (_list, 1, wxALL, 8);
+               SetSizer (sizer);
+
+               _search->Bind (wxEVT_TEXT, boost::bind(&LanguageSubtagPanel::search_changed, this));
+               _list->Bind (wxEVT_LIST_ITEM_SELECTED, boost::bind(&LanguageSubtagPanel::selection_changed, this));
+               _list->Bind (wxEVT_LIST_ITEM_DESELECTED, boost::bind(&LanguageSubtagPanel::selection_changed, this));
+       }
+
+       void set (dcp::LanguageTag::SubtagType type, string search, optional<dcp::LanguageTag::SubtagData> subtag = optional<dcp::LanguageTag::SubtagData>())
+       {
+               _list->set (type, search, subtag);
+               _search->SetValue (wxString(search));
+       }
+
+       optional<dcp::LanguageTag::RegionSubtag> get () const
+       {
+               if (!_list->selected_subtag()) {
+                       return optional<dcp::LanguageTag::RegionSubtag>();
+               }
+
+               return dcp::LanguageTag::RegionSubtag(_list->selected_subtag()->subtag);
+       }
+
+       boost::signals2::signal<void (optional<dcp::LanguageTag::SubtagData>)> SelectionChanged;
+       boost::signals2::signal<void (string)> SearchChanged;
+
+private:
+       void search_changed ()
+       {
+               _list->set_search (_search->GetValue().ToStdString());
+               SearchChanged (_search->GetValue().ToStdString());
+       }
+
+       void selection_changed ()
+       {
+               SelectionChanged (_list->selected_subtag());
+       }
+
+       wxSearchCtrl* _search;
+       SubtagListCtrl* _list;
+};
+
+
+LanguageTagDialog::LanguageTagDialog (wxWindow* parent, dcp::LanguageTag tag)
+       : wxDialog (parent, wxID_ANY, "Language Tag", wxDefaultPosition, wxSize(-1, 500))
+{
+       _current_tag_list = new wxListCtrl (this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLC_REPORT | wxLC_SINGLE_SEL | wxLC_NO_HEADER);
+       _current_tag_list->AppendColumn ("", wxLIST_FORMAT_LEFT, 200);
+       _current_tag_list->AppendColumn ("", wxLIST_FORMAT_LEFT, 400);
+
+       wxBoxSizer* button_sizer = new wxBoxSizer (wxVERTICAL);
+       _add_script = new wxButton(this, wxID_ANY, "Add script");
+       button_sizer->Add (_add_script, 0, wxTOP | wxBOTTOM | wxEXPAND, 2);
+       _add_region = new wxButton(this, wxID_ANY, "Add region");
+       button_sizer->Add (_add_region, 0, wxTOP | wxBOTTOM | wxEXPAND, 2);
+       _add_variant = new wxButton(this, wxID_ANY, "Add variant");
+       button_sizer->Add (_add_variant, 0, wxTOP | wxBOTTOM | wxEXPAND, 2);
+       _add_external = new wxButton(this, wxID_ANY, "Add external");
+       button_sizer->Add (_add_external, 0, wxTOP | wxBOTTOM | wxEXPAND, 2);
+
+       _choose_subtag_panel = new LanguageSubtagPanel (this);
+       _choose_subtag_panel->set (dcp::LanguageTag::LANGUAGE, "");
+
+       wxBoxSizer* ltor_sizer = new wxBoxSizer (wxHORIZONTAL);
+       ltor_sizer->Add (_current_tag_list, 1, wxALL, 8);
+       ltor_sizer->Add (button_sizer, 0, wxALL, 8);
+       ltor_sizer->Add (_choose_subtag_panel, 1, wxALL, 8);
+
+       wxBoxSizer* overall_sizer = new wxBoxSizer (wxVERTICAL);
+       overall_sizer->Add (ltor_sizer, 0);
+
+       wxSizer* buttons = CreateSeparatedButtonSizer (wxOK);
+       if (buttons) {
+               overall_sizer->Add (buttons, wxSizerFlags().Expand().DoubleBorder());
+       }
+
+       SetSizerAndFit (overall_sizer);
+
+       bool have_language = false;
+       vector<pair<dcp::LanguageTag::SubtagType, dcp::LanguageTag::SubtagData> > subtags = tag.subtags();
+       for (vector<pair<dcp::LanguageTag::SubtagType, dcp::LanguageTag::SubtagData> >::const_iterator i = subtags.begin(); i != subtags.end(); ++i) {
+               add_to_current_tag (i->first, i->second);
+               if (i->first == dcp::LanguageTag::LANGUAGE) {
+                       have_language = true;
+               }
+       }
+
+       if (!have_language) {
+               add_to_current_tag (dcp::LanguageTag::LANGUAGE, dcp::LanguageTag::SubtagData("en", "English"));
+       }
+
+       _add_script->Bind (wxEVT_BUTTON, boost::bind(&LanguageTagDialog::add_to_current_tag, this, dcp::LanguageTag::SCRIPT, boost::optional<dcp::LanguageTag::SubtagData>()));
+       _add_region->Bind (wxEVT_BUTTON, boost::bind(&LanguageTagDialog::add_to_current_tag, this, dcp::LanguageTag::REGION, boost::optional<dcp::LanguageTag::SubtagData>()));
+       _add_variant->Bind (wxEVT_BUTTON, boost::bind(&LanguageTagDialog::add_to_current_tag, this, dcp::LanguageTag::VARIANT, boost::optional<dcp::LanguageTag::SubtagData>()));
+       _add_external->Bind (wxEVT_BUTTON, boost::bind(&LanguageTagDialog::add_to_current_tag, this, dcp::LanguageTag::EXTLANG, boost::optional<dcp::LanguageTag::SubtagData>()));
+       _choose_subtag_panel->SelectionChanged.connect(bind(&LanguageTagDialog::chosen_subtag_changed, this, _1));
+       _choose_subtag_panel->SearchChanged.connect(bind(&LanguageTagDialog::search_changed, this, _1));
+       _current_tag_list->Bind (wxEVT_LIST_ITEM_SELECTED, boost::bind(&LanguageTagDialog::current_tag_selection_changed, this));
+       _current_tag_list->Bind (wxEVT_LIST_ITEM_DESELECTED, boost::bind(&LanguageTagDialog::current_tag_selection_changed, this));
+}
+
+
+dcp::LanguageTag LanguageTagDialog::get () const
+{
+       dcp::LanguageTag tag;
+
+       vector<dcp::LanguageTag::VariantSubtag> variants;
+       vector<dcp::LanguageTag::ExtlangSubtag> extlangs;
+
+       BOOST_FOREACH (Subtag i, _current_tag_subtags) {
+               if (!i.subtag) {
+                       continue;
+               }
+               switch (i.type) {
+                       case dcp::LanguageTag::LANGUAGE:
+                               tag.set_language (i.subtag->subtag);
+                               break;
+                       case dcp::LanguageTag::SCRIPT:
+                               tag.set_script (i.subtag->subtag);
+                               break;
+                       case dcp::LanguageTag::REGION:
+                               tag.set_region (i.subtag->subtag);
+                               break;
+                       case dcp::LanguageTag::VARIANT:
+                               variants.push_back (i.subtag->subtag);
+                               break;
+                       case dcp::LanguageTag::EXTLANG:
+                               extlangs.push_back (i.subtag->subtag);
+                               break;
+               }
+       }
+
+       tag.set_variants (variants);
+       tag.set_extlangs (extlangs);
+       return tag;
+}
+
+
+string LanguageTagDialog::subtag_type_name (dcp::LanguageTag::SubtagType type)
+{
+       switch (type) {
+               case dcp::LanguageTag::LANGUAGE:
+                       return "Language";
+               case dcp::LanguageTag::SCRIPT:
+                       return "Script";
+               case dcp::LanguageTag::REGION:
+                       return "Region";
+               case dcp::LanguageTag::VARIANT:
+                       return "Variant";
+               case dcp::LanguageTag::EXTLANG:
+                       return "External";
+       }
+
+       return "";
+}
+
+
+void
+LanguageTagDialog::search_changed (string search)
+{
+       long int selected = _current_tag_list->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
+       if (selected >= 0) {
+               _current_tag_subtags[selected].last_search = search;
+       }
+}
+
+
+void
+LanguageTagDialog::add_to_current_tag (dcp::LanguageTag::SubtagType type, optional<dcp::LanguageTag::SubtagData> subtag)
+{
+       _current_tag_subtags.push_back (Subtag(type, subtag));
+       wxListItem it;
+       it.SetId (_current_tag_list->GetItemCount());
+       it.SetColumn (0);
+       it.SetText (subtag_type_name(type));
+       _current_tag_list->InsertItem (it);
+       it.SetColumn (1);
+       if (subtag) {
+               it.SetText (subtag->description);
+       } else {
+               it.SetText ("Select...");
+       }
+       _current_tag_list->SetItem (it);
+       _current_tag_list->SetItemState (_current_tag_list->GetItemCount() - 1, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED);
+       _choose_subtag_panel->set (type, "");
+       setup_sensitivity ();
+       current_tag_selection_changed ();
+}
+
+
+void
+LanguageTagDialog::current_tag_selection_changed ()
+{
+       long int selected = _current_tag_list->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
+       if (selected >= 0) {
+               _choose_subtag_panel->Enable (true);
+               _choose_subtag_panel->set (_current_tag_subtags[selected].type, _current_tag_subtags[selected].last_search, _current_tag_subtags[selected].subtag);
+       } else {
+               _choose_subtag_panel->Enable (false);
+       }
+}
+
+
+void
+LanguageTagDialog::chosen_subtag_changed (optional<dcp::LanguageTag::SubtagData> selection)
+{
+       if (!selection) {
+               return;
+       }
+
+       long int selected = _current_tag_list->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
+       if (selected >= 0) {
+               _current_tag_subtags[selected].subtag = *selection;
+               _current_tag_list->SetItem (selected, 0, subtag_type_name(_current_tag_subtags[selected].type));
+               _current_tag_list->SetItem (selected, 1, selection->description);
+       }
+}
+
+void
+LanguageTagDialog::setup_sensitivity ()
+{
+       _add_script->Enable ();
+       _add_region->Enable ();
+       _add_variant->Enable ();
+       _add_external->Enable ();
+       BOOST_FOREACH (Subtag const& i, _current_tag_subtags) {
+               switch (i.type) {
+                       case dcp::LanguageTag::SCRIPT:
+                               _add_script->Enable (false);
+                               break;
+                       case dcp::LanguageTag::REGION:
+                               _add_region->Enable (false);
+                               break;
+                       case dcp::LanguageTag::VARIANT:
+                               _add_variant->Enable (false);
+                               break;
+                       case dcp::LanguageTag::EXTLANG:
+                               _add_external->Enable (false);
+                               break;
+                       default:
+                               break;
+               }
+       }
+}
+
+
+RegionSubtagDialog::RegionSubtagDialog (wxWindow* parent, dcp::LanguageTag::RegionSubtag region)
+       : wxDialog (parent, wxID_ANY, _("Region"), wxDefaultPosition, wxSize(-1, 500))
+       , _panel (new LanguageSubtagPanel (this))
+{
+       wxBoxSizer* sizer = new wxBoxSizer (wxVERTICAL);
+       sizer->Add (_panel, 1);
+
+       wxSizer* buttons = CreateSeparatedButtonSizer (wxOK);
+       if (buttons) {
+               sizer->Add (buttons, wxSizerFlags().Expand().DoubleBorder());
+       }
+
+       SetSizer (sizer);
+
+       _panel->set (dcp::LanguageTag::REGION, "", *dcp::LanguageTag::get_subtag_data(region));
+}
+
+
+optional<dcp::LanguageTag::RegionSubtag>
+RegionSubtagDialog::get () const
+{
+       return _panel->get ();
+}
+
+
diff --git a/src/wx/language_tag_dialog.h b/src/wx/language_tag_dialog.h
new file mode 100644 (file)
index 0000000..3fc5251
--- /dev/null
@@ -0,0 +1,81 @@
+/*
+    Copyright (C) 2020 Carl Hetherington <cth@carlh.net>
+
+    This file is part of DCP-o-matic.
+
+    DCP-o-matic is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    DCP-o-matic is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with DCP-o-matic.  If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include <dcp/language_tag.h>
+#include <wx/wx.h>
+
+
+class wxListCtrl;
+class LanguageSubtagPanel;
+
+
+class LanguageTagDialog : public wxDialog
+{
+public:
+       class Subtag
+       {
+       public:
+               Subtag (dcp::LanguageTag::SubtagType type_, boost::optional<dcp::LanguageTag::SubtagData> subtag_)
+                       : type (type_)
+                       , subtag (subtag_)
+               {}
+
+               dcp::LanguageTag::SubtagType type;
+               boost::optional<dcp::LanguageTag::SubtagData> subtag;
+               std::string last_search;
+       };
+
+       LanguageTagDialog (wxWindow* parent, dcp::LanguageTag tag);
+
+       dcp::LanguageTag get () const;
+
+
+private:
+
+       std::string subtag_type_name (dcp::LanguageTag::SubtagType type);
+       void search_changed (std::string search);
+       void add_to_current_tag (dcp::LanguageTag::SubtagType type, boost::optional<dcp::LanguageTag::SubtagData> subtag);
+       void current_tag_selection_changed ();
+       void chosen_subtag_changed (boost::optional<dcp::LanguageTag::SubtagData> selection);
+       void setup_sensitivity ();
+
+       std::vector<Subtag> _current_tag_subtags;
+       wxListCtrl* _current_tag_list;
+       LanguageSubtagPanel* _choose_subtag_panel;
+       wxButton* _add_script;
+       wxButton* _add_region;
+       wxButton* _add_variant;
+       wxButton* _add_external;
+};
+
+
+
+class RegionSubtagDialog : public wxDialog
+{
+public:
+       RegionSubtagDialog (wxWindow* parent, dcp::LanguageTag::RegionSubtag region);
+
+       boost::optional<dcp::LanguageTag::RegionSubtag> get () const;
+
+private:
+       LanguageSubtagPanel* _panel;
+};
+
diff --git a/src/wx/metadata_dialog.cc b/src/wx/metadata_dialog.cc
deleted file mode 100644 (file)
index 2398ce2..0000000
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
-    Copyright (C) 2019 Carl Hetherington <cth@carlh.net>
-
-    This file is part of DCP-o-matic.
-
-    DCP-o-matic is free software; you can redistribute it and/or modify
-    it under the terms of the GNU General Public License as published by
-    the Free Software Foundation; either version 2 of the License, or
-    (at your option) any later version.
-
-    DCP-o-matic is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-    GNU General Public License for more details.
-
-    You should have received a copy of the GNU General Public License
-    along with DCP-o-matic.  If not, see <http://www.gnu.org/licenses/>.
-
-*/
-
-#include "metadata_dialog.h"
-#include "editable_list.h"
-#include "rating_dialog.h"
-#include "lib/film.h"
-#include <dcp/types.h>
-#include <wx/gbsizer.h>
-
-using std::string;
-using std::vector;
-using boost::weak_ptr;
-using boost::shared_ptr;
-
-static string
-column (dcp::Rating r, int c)
-{
-       if (c == 0) {
-               return r.agency;
-       }
-
-       return r.label;
-}
-
-MetadataDialog::MetadataDialog (wxWindow* parent, weak_ptr<Film> film)
-       : wxDialog (parent, wxID_ANY, _("Metadata"))
-       , _film (film)
-{
-       wxBoxSizer* overall_sizer = new wxBoxSizer (wxVERTICAL);
-       SetSizer (overall_sizer);
-
-       wxFlexGridSizer* sizer = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
-       sizer->AddGrowableCol (1, 1);
-
-       {
-               int flags = wxALIGN_TOP | wxLEFT | wxRIGHT | wxTOP;
-#ifdef __WXOSX__
-               flags |= wxALIGN_RIGHT;
-#endif
-               wxStaticText* m = create_label (this, _("Ratings"), true);
-               sizer->Add (m, 0, flags, DCPOMATIC_SIZER_GAP);
-       }
-
-       vector<EditableListColumn> columns;
-       columns.push_back (EditableListColumn(_("Agency"), 200, true));
-       columns.push_back (EditableListColumn(_("Label"), 50, true));
-       _ratings = new EditableList<dcp::Rating, RatingDialog> (
-               this,
-               columns,
-               boost::bind(&MetadataDialog::ratings, this),
-               boost::bind(&MetadataDialog::set_ratings, this, _1),
-               boost::bind(&column, _1, _2),
-               true,
-               false
-               );
-       sizer->Add (_ratings, 1, wxEXPAND);
-
-       add_label_to_sizer (sizer, this, _("Content version"), true);
-       _content_version = new wxTextCtrl (this, wxID_ANY);
-       sizer->Add (_content_version, 1, wxEXPAND);
-
-       shared_ptr<Film> f = _film.lock();
-       DCPOMATIC_ASSERT (f);
-       _content_version->SetValue (std_to_wx(f->content_version()));
-
-       overall_sizer->Add (sizer, 1, wxEXPAND | wxALL, DCPOMATIC_DIALOG_BORDER);
-
-       wxSizer* buttons = CreateSeparatedButtonSizer (wxCLOSE);
-       if (buttons) {
-               overall_sizer->Add (buttons, wxSizerFlags().Expand().DoubleBorder());
-       }
-
-       overall_sizer->Layout ();
-       overall_sizer->SetSizeHints (this);
-
-       _content_version->Bind (wxEVT_TEXT, boost::bind(&MetadataDialog::content_version_changed, this));
-       _content_version->SetFocus ();
-}
-
-vector<dcp::Rating>
-MetadataDialog::ratings () const
-{
-       shared_ptr<Film> film = _film.lock ();
-       DCPOMATIC_ASSERT (film);
-       return film->ratings ();
-}
-
-void
-MetadataDialog::set_ratings (vector<dcp::Rating> r)
-{
-       shared_ptr<Film> film = _film.lock ();
-       DCPOMATIC_ASSERT (film);
-       film->set_ratings (r);
-}
-
-void
-MetadataDialog::content_version_changed ()
-{
-       shared_ptr<Film> film = _film.lock ();
-       DCPOMATIC_ASSERT (film);
-       film->set_content_version (wx_to_std(_content_version->GetValue()));
-}
diff --git a/src/wx/metadata_dialog.h b/src/wx/metadata_dialog.h
deleted file mode 100644 (file)
index 892aa89..0000000
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
-    Copyright (C) 2019 Carl Hetherington <cth@carlh.net>
-
-    This file is part of DCP-o-matic.
-
-    DCP-o-matic is free software; you can redistribute it and/or modify
-    it under the terms of the GNU General Public License as published by
-    the Free Software Foundation; either version 2 of the License, or
-    (at your option) any later version.
-
-    DCP-o-matic is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-    GNU General Public License for more details.
-
-    You should have received a copy of the GNU General Public License
-    along with DCP-o-matic.  If not, see <http://www.gnu.org/licenses/>.
-
-*/
-
-#include "editable_list.h"
-#include <dcp/types.h>
-#include <wx/wx.h>
-#include <boost/shared_ptr.hpp>
-#include <boost/weak_ptr.hpp>
-#include <vector>
-
-class Film;
-class RatingDialog;
-
-class MetadataDialog : public wxDialog
-{
-public:
-       MetadataDialog (wxWindow* parent, boost::weak_ptr<Film> film);
-
-private:
-       std::vector<dcp::Rating> ratings () const;
-       void set_ratings (std::vector<dcp::Rating> r);
-       void content_version_changed ();
-
-       boost::weak_ptr<Film> _film;
-       EditableList<dcp::Rating, RatingDialog>* _ratings;
-       wxTextCtrl* _content_version;
-};
diff --git a/src/wx/smpte_metadata_dialog.cc b/src/wx/smpte_metadata_dialog.cc
new file mode 100644 (file)
index 0000000..6c13083
--- /dev/null
@@ -0,0 +1,374 @@
+/*
+    Copyright (C) 2019-2020 Carl Hetherington <cth@carlh.net>
+
+    This file is part of DCP-o-matic.
+
+    DCP-o-matic is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    DCP-o-matic is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with DCP-o-matic.  If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#include "content_version_dialog.h"
+#include "editable_list.h"
+#include "language_tag_dialog.h"
+#include "smpte_metadata_dialog.h"
+#include "rating_dialog.h"
+#include "lib/film.h"
+#include <dcp/types.h>
+#include <wx/gbsizer.h>
+#include <wx/spinctrl.h>
+
+using std::string;
+using std::vector;
+using boost::optional;
+using boost::shared_ptr;
+using boost::weak_ptr;
+
+
+static string
+ratings_column (dcp::Rating r, int c)
+{
+       if (c == 0) {
+               return r.agency;
+       }
+
+       return r.label;
+}
+
+
+static string
+content_versions_column (string v, int)
+{
+       return v;
+}
+
+
+SMPTEMetadataDialog::SMPTEMetadataDialog (wxWindow* parent, weak_ptr<Film> weak_film)
+       : wxDialog (parent, wxID_ANY, _("Metadata"))
+       , _film (weak_film)
+{
+       wxBoxSizer* overall_sizer = new wxBoxSizer (wxVERTICAL);
+       SetSizer (overall_sizer);
+
+       wxFlexGridSizer* sizer = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
+       sizer->AddGrowableCol (1, 1);
+
+       wxButton* edit_name_language = 0;
+       Button* edit_release_territory = 0;
+
+       add_label_to_sizer(sizer, this, _("Title language"), true);
+       {
+               wxSizer* s = new wxBoxSizer (wxHORIZONTAL);
+               _name_language = new wxStaticText (this, wxID_ANY, wxT(""));
+               _name_language->SetToolTip (wxString::Format(_("The language that the film's title (\"%s\") is in"), std_to_wx(film()->name())));
+               s->Add (_name_language, 1, wxLEFT | wxALIGN_CENTER_VERTICAL, DCPOMATIC_SIZER_X_GAP);
+               edit_name_language = new Button (this, _("Edit..."));
+               s->Add (edit_name_language, 0, wxLEFT, DCPOMATIC_SIZER_GAP);
+               sizer->Add (s, 0, wxEXPAND);
+       }
+
+       add_label_to_sizer (sizer, this, _("Release territory"), true);
+       {
+               wxSizer* s = new wxBoxSizer (wxHORIZONTAL);
+               _release_territory = new wxStaticText (this, wxID_ANY, wxT(""));
+               s->Add (_release_territory, 1, wxLEFT | wxALIGN_CENTER_VERTICAL, DCPOMATIC_SIZER_X_GAP);
+               edit_release_territory = new Button (this, _("Edit..."));
+               s->Add (edit_release_territory, 0, wxLEFT, DCPOMATIC_SIZER_GAP);
+               sizer->Add (s, 0, wxEXPAND);
+       }
+
+       add_label_to_sizer (sizer, this, _("Version number"), true);
+       _version_number = new wxSpinCtrl (this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 1, 1000);
+       sizer->Add (_version_number, 0);
+
+       add_label_to_sizer (sizer, this, _("Status"), true);
+       _status = new wxChoice (this, wxID_ANY);
+       sizer->Add (_status, 0);
+
+       add_label_to_sizer (sizer, this, _("Chain"), true);
+       _chain = new wxTextCtrl (this, wxID_ANY);
+       sizer->Add (_chain, 1, wxEXPAND);
+
+       add_label_to_sizer (sizer, this, _("Distributor"), true);
+       _distributor = new wxTextCtrl (this, wxID_ANY);
+       sizer->Add (_distributor, 1, wxEXPAND);
+
+       add_label_to_sizer (sizer, this, _("Facility"), true);
+       _facility = new wxTextCtrl (this, wxID_ANY);
+       sizer->Add (_facility, 1, wxEXPAND);
+
+       add_label_to_sizer (sizer, this, _("Luminance"), true);
+       {
+               wxSizer* s = new wxBoxSizer (wxHORIZONTAL);
+               _luminance_value = new wxSpinCtrlDouble (this, wxID_ANY);
+               _luminance_value->SetDigits (1);
+               _luminance_value->SetIncrement (0.1);
+               s->Add (_luminance_value, 0);
+               _luminance_unit = new wxChoice (this, wxID_ANY);
+               s->Add (_luminance_unit, 0, wxLEFT, DCPOMATIC_SIZER_X_GAP);
+               sizer->Add (s, 1, wxEXPAND);
+       }
+
+       {
+               int flags = wxALIGN_TOP | wxLEFT | wxRIGHT | wxTOP;
+#ifdef __WXOSX__
+               flags |= wxALIGN_RIGHT;
+#endif
+               wxStaticText* m = create_label (this, _("Ratings"), true);
+               sizer->Add (m, 0, flags, DCPOMATIC_SIZER_GAP);
+       }
+
+       vector<EditableListColumn> columns;
+       columns.push_back (EditableListColumn("Agency", 200, true));
+       columns.push_back (EditableListColumn("Label", 50, true));
+       _ratings = new EditableList<dcp::Rating, RatingDialog> (
+               this,
+               columns,
+               boost::bind(&SMPTEMetadataDialog::ratings, this),
+               boost::bind(&SMPTEMetadataDialog::set_ratings, this, _1),
+               boost::bind(&ratings_column, _1, _2),
+               true,
+               false
+               );
+       sizer->Add (_ratings, 1, wxEXPAND);
+
+       {
+               int flags = wxALIGN_TOP | wxLEFT | wxRIGHT | wxTOP;
+#ifdef __WXOSX__
+               flags |= wxALIGN_RIGHT;
+#endif
+               wxStaticText* m = create_label (this, _("Content versions"), true);
+               sizer->Add (m, 0, flags, DCPOMATIC_SIZER_GAP);
+       }
+
+       columns.clear ();
+       columns.push_back (EditableListColumn("Version", 350, true));
+       _content_versions = new EditableList<string, ContentVersionDialog> (
+               this,
+               columns,
+               boost::bind(&SMPTEMetadataDialog::content_versions, this),
+               boost::bind(&SMPTEMetadataDialog::set_content_versions, this, _1),
+               boost::bind(&content_versions_column, _1, _2),
+               true,
+               false
+               );
+       sizer->Add (_content_versions, 1, wxEXPAND);
+
+       overall_sizer->Add (sizer, 1, wxEXPAND | wxALL, DCPOMATIC_DIALOG_BORDER);
+
+       wxSizer* buttons = CreateSeparatedButtonSizer (wxCLOSE);
+       if (buttons) {
+               overall_sizer->Add (buttons, wxSizerFlags().Expand().DoubleBorder());
+       }
+
+       overall_sizer->Layout ();
+       overall_sizer->SetSizeHints (this);
+
+       _status->Append (_("Temporary"));
+       _status->Append (_("Pre-release"));
+       _status->Append (_("Final"));
+
+       _luminance_unit->Append (_("candela per m²"));
+       _luminance_unit->Append (_("foot lambert"));
+
+       edit_name_language->Bind (wxEVT_BUTTON, boost::bind(&SMPTEMetadataDialog::edit_name_language, this));
+       edit_release_territory->Bind (wxEVT_BUTTON, boost::bind(&SMPTEMetadataDialog::edit_release_territory, this));
+       _version_number->Bind (wxEVT_SPINCTRL, boost::bind(&SMPTEMetadataDialog::version_number_changed, this));
+       _status->Bind (wxEVT_CHOICE, boost::bind(&SMPTEMetadataDialog::status_changed, this));
+       _chain->Bind (wxEVT_TEXT, boost::bind(&SMPTEMetadataDialog::chain_changed, this));
+       _distributor->Bind (wxEVT_TEXT, boost::bind(&SMPTEMetadataDialog::distributor_changed, this));
+       _facility->Bind (wxEVT_TEXT, boost::bind(&SMPTEMetadataDialog::facility_changed, this));
+       _luminance_value->Bind (wxEVT_SPINCTRLDOUBLE, boost::bind(&SMPTEMetadataDialog::luminance_changed, this));
+       _luminance_unit->Bind (wxEVT_CHOICE, boost::bind(&SMPTEMetadataDialog::luminance_changed, this));
+
+       _version_number->SetFocus ();
+
+       _film_changed_connection = film()->Change.connect(boost::bind(&SMPTEMetadataDialog::film_changed, this, _1, _2));
+
+       film_changed (CHANGE_TYPE_DONE, Film::NAME_LANGUAGE);
+       film_changed (CHANGE_TYPE_DONE, Film::RELEASE_TERRITORY);
+       film_changed (CHANGE_TYPE_DONE, Film::VERSION_NUMBER);
+       film_changed (CHANGE_TYPE_DONE, Film::STATUS);
+       film_changed (CHANGE_TYPE_DONE, Film::CHAIN);
+       film_changed (CHANGE_TYPE_DONE, Film::DISTRIBUTOR);
+       film_changed (CHANGE_TYPE_DONE, Film::FACILITY);
+       film_changed (CHANGE_TYPE_DONE, Film::CONTENT_VERSIONS);
+       film_changed (CHANGE_TYPE_DONE, Film::LUMINANCE);
+}
+
+
+void
+SMPTEMetadataDialog::film_changed (ChangeType type, Film::Property property)
+{
+       if (type != CHANGE_TYPE_DONE || film()->interop()) {
+               return;
+       }
+
+       if (property == Film::NAME_LANGUAGE) {
+               checked_set (_name_language, std_to_wx(film()->name_language().to_string()));
+       } else if (property == Film::RELEASE_TERRITORY) {
+               checked_set (_release_territory, std_to_wx(*dcp::LanguageTag::get_subtag_description(dcp::LanguageTag::REGION, film()->release_territory().subtag())));
+       } else if (property == Film::VERSION_NUMBER) {
+               checked_set (_version_number, film()->version_number());
+       } else if (property == Film::STATUS) {
+               switch (film()->status()) {
+               case dcp::TEMP:
+                       checked_set (_status, 0);
+                       break;
+               case dcp::PRE:
+                       checked_set (_status, 1);
+                       break;
+               case dcp::FINAL:
+                       checked_set (_status, 2);
+                       break;
+               }
+       } else if (property == Film::CHAIN) {
+               checked_set (_chain, film()->chain());
+       } else if (property == Film::DISTRIBUTOR) {
+               checked_set (_distributor, film()->distributor());
+       } else if (property == Film::FACILITY) {
+               checked_set (_facility, film()->facility());
+       } else if (property == Film::LUMINANCE) {
+               checked_set (_luminance_value, film()->luminance().value());
+               switch (film()->luminance().unit()) {
+               case dcp::Luminance::CANDELA_PER_SQUARE_METRE:
+                       checked_set (_luminance_unit, 0);
+                       break;
+               case dcp::Luminance::FOOT_LAMBERT:
+                       checked_set (_luminance_unit, 1);
+                       break;
+               }
+       }
+}
+
+
+vector<dcp::Rating>
+SMPTEMetadataDialog::ratings () const
+{
+       return film()->ratings ();
+}
+
+
+void
+SMPTEMetadataDialog::set_ratings (vector<dcp::Rating> r)
+{
+       film()->set_ratings (r);
+}
+
+
+vector<string>
+SMPTEMetadataDialog::content_versions () const
+{
+       return film()->content_versions ();
+}
+
+
+void
+SMPTEMetadataDialog::set_content_versions (vector<string> cv)
+{
+       film()->set_content_versions (cv);
+}
+
+
+void
+SMPTEMetadataDialog::edit_name_language ()
+{
+       LanguageTagDialog* d = new LanguageTagDialog(this, film()->name_language());
+       d->ShowModal ();
+       film()->set_name_language (d->get());
+       d->Destroy ();
+}
+
+
+void
+SMPTEMetadataDialog::edit_release_territory ()
+{
+       RegionSubtagDialog* d = new RegionSubtagDialog(this, film()->release_territory());
+       d->ShowModal ();
+       optional<dcp::LanguageTag::RegionSubtag> tag = d->get();
+       if (tag) {
+               film()->set_release_territory (*tag);
+       }
+       d->Destroy ();
+}
+
+
+shared_ptr<Film>
+SMPTEMetadataDialog::film () const
+{
+       shared_ptr<Film> film = _film.lock ();
+       DCPOMATIC_ASSERT (film);
+       return film;
+}
+
+
+void
+SMPTEMetadataDialog::version_number_changed ()
+{
+       film()->set_version_number (_version_number->GetValue());
+}
+
+
+void
+SMPTEMetadataDialog::status_changed ()
+{
+       switch (_status->GetSelection()) {
+       case 0:
+               film()->set_status (dcp::TEMP);
+               break;
+       case 1:
+               film()->set_status (dcp::PRE);
+               break;
+       case 2:
+               film()->set_status (dcp::FINAL);
+               break;
+       }
+}
+
+
+void
+SMPTEMetadataDialog::chain_changed ()
+{
+       film()->set_chain (wx_to_std(_chain->GetValue()));
+}
+
+
+void
+SMPTEMetadataDialog::distributor_changed ()
+{
+       film()->set_distributor (wx_to_std(_distributor->GetValue()));
+}
+
+
+void
+SMPTEMetadataDialog::facility_changed ()
+{
+       film()->set_facility (wx_to_std(_facility->GetValue()));
+}
+
+
+void
+SMPTEMetadataDialog::luminance_changed ()
+{
+       dcp::Luminance::Unit unit;
+       switch (_luminance_unit->GetSelection()) {
+       case 0:
+               unit = dcp::Luminance::CANDELA_PER_SQUARE_METRE;
+               break;
+       case 1:
+               unit = dcp::Luminance::FOOT_LAMBERT;
+               break;
+       }
+
+       film()->set_luminance (dcp::Luminance(_luminance_value->GetValue(), unit));
+}
diff --git a/src/wx/smpte_metadata_dialog.h b/src/wx/smpte_metadata_dialog.h
new file mode 100644 (file)
index 0000000..260d54d
--- /dev/null
@@ -0,0 +1,70 @@
+/*
+    Copyright (C) 2019-2020 Carl Hetherington <cth@carlh.net>
+
+    This file is part of DCP-o-matic.
+
+    DCP-o-matic is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    DCP-o-matic is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with DCP-o-matic.  If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#include "editable_list.h"
+#include "lib/film.h"
+#include <dcp/types.h>
+#include <wx/wx.h>
+#include <boost/shared_ptr.hpp>
+#include <boost/weak_ptr.hpp>
+#include <vector>
+
+
+class Film;
+class RatingDialog;
+class ContentVersionDialog;
+
+
+class SMPTEMetadataDialog : public wxDialog
+{
+public:
+       SMPTEMetadataDialog (wxWindow* parent, boost::weak_ptr<Film> film);
+
+private:
+       std::vector<dcp::Rating> ratings () const;
+       void set_ratings (std::vector<dcp::Rating> r);
+       std::vector<std::string> content_versions () const;
+       void set_content_versions (std::vector<std::string> v);
+       void edit_name_language ();
+       void edit_release_territory ();
+       void version_number_changed ();
+       void status_changed ();
+       void chain_changed ();
+       void distributor_changed ();
+       void facility_changed ();
+       void luminance_changed ();
+       void film_changed (ChangeType type, Film::Property property);
+       boost::shared_ptr<Film> film () const;
+
+       boost::weak_ptr<Film> _film;
+       wxStaticText* _name_language;
+       wxStaticText* _release_territory;
+       wxSpinCtrl* _version_number;
+       wxChoice* _status;
+       wxTextCtrl* _chain;
+       wxTextCtrl* _distributor;
+       wxTextCtrl* _facility;
+       wxSpinCtrlDouble* _luminance_value;
+       wxChoice* _luminance_unit;
+       EditableList<dcp::Rating, RatingDialog>* _ratings;
+       EditableList<std::string, ContentVersionDialog>* _content_versions;
+
+       boost::signals2::scoped_connection _film_changed_connection;
+};
index e260667bcd6e91b06f8f2ed68662abb6df1e5da3..d72c209401ec756c64ce52787535d4768402a057 100644 (file)
@@ -46,6 +46,7 @@ sources = """
           content_panel.cc
           content_properties_dialog.cc
           content_sub_panel.cc
+          content_version_dialog.cc
           content_view.cc
           controls.cc
           closed_captions_dialog.cc
@@ -82,6 +83,7 @@ sources = """
           html_dialog.cc
           initial_setup_dialog.cc
           instant_i18n_dialog.cc
+          interop_metadata_dialog.cc
           i18n_hook.cc
           job_view.cc
           job_view_dialog.cc
@@ -91,10 +93,10 @@ sources = """
           kdm_dialog.cc
           kdm_output_panel.cc
           kdm_timing_panel.cc
+          language_tag_dialog.cc
           make_chain_dialog.cc
           markers_dialog.cc
           message_dialog.cc
-          metadata_dialog.cc
           monitor_dialog.cc
           move_to_dialog.cc
           nag_dialog.cc
@@ -128,6 +130,7 @@ sources = """
           server_dialog.cc
           servers_list_dialog.cc
           simple_video_view.cc
+          smpte_metadata_dialog.cc
           standard_controls.cc
           static_text.cc
           subtitle_appearance_dialog.cc
index 83dd0c6de043b8b35803e6b65987e3d1109fae38..07116c1e26f0e0f058702a1b1760f527dc2ca740 100644 (file)
@@ -170,7 +170,9 @@ BOOST_AUTO_TEST_CASE (import_dcp_metadata_test)
        ratings.push_back (dcp::Rating("MPAA", "NC-17"));
        film->set_ratings (ratings);
 
-       film->set_content_version ("Fred");
+       vector<string> cv;
+       cv.push_back ("Fred");
+       film->set_content_versions (cv);
 
        film->make_dcp ();
        BOOST_REQUIRE (!wait_for_jobs());