wip: stuff.
authorCarl Hetherington <cth@carlh.net>
Sun, 3 May 2020 21:31:29 +0000 (23:31 +0200)
committerCarl Hetherington <cth@carlh.net>
Sun, 3 May 2020 21:32:50 +0000 (23:32 +0200)
24 files changed:
src/lib/config.cc
src/lib/config.h
src/lib/dkdm_recipient.cc [new file with mode: 0644]
src/lib/dkdm_recipient.h [new file with mode: 0644]
src/lib/kdm_recipient.h
src/lib/recipient_with_dkdm.cc [new file with mode: 0644]
src/lib/recipient_with_dkdm.h [new file with mode: 0644]
src/lib/send_dkdm_email_job.cc [new file with mode: 0644]
src/lib/send_dkdm_email_job.h [new file with mode: 0644]
src/lib/wscript
src/tools/dcpomatic.cc
src/wx/cinema_dialog.cc
src/wx/cinema_dialog.h
src/wx/dkdm_dialog.cc [new file with mode: 0644]
src/wx/dkdm_dialog.h [new file with mode: 0644]
src/wx/dkdm_output_panel.cc [new file with mode: 0644]
src/wx/dkdm_output_panel.h [new file with mode: 0644]
src/wx/recipient_dialog.cc [new file with mode: 0644]
src/wx/recipient_dialog.h [new file with mode: 0644]
src/wx/recipients_panel.cc [new file with mode: 0644]
src/wx/recipients_panel.h [new file with mode: 0644]
src/wx/wscript
src/wx/wx_util.cc
src/wx/wx_util.h

index 7364a122c4998c877a7970331840e240c50ec146..93ecc8e921c45a0f02bc4ec9ee5052d97a01f727 100644 (file)
@@ -1,5 +1,5 @@
 /*
-    Copyright (C) 2012-2019 Carl Hetherington <cth@carlh.net>
+    Copyright (C) 2012-2020 Carl Hetherington <cth@carlh.net>
 
     This file is part of DCP-o-matic.
 
@@ -26,6 +26,7 @@
 #include "dcp_content_type.h"
 #include "colour_conversion.h"
 #include "cinema.h"
+#include "dkdm_recipient.h"
 #include "util.h"
 #include "cross.h"
 #include "film.h"
@@ -127,10 +128,12 @@ Config::set_defaults ()
        _win32_console = false;
 #endif
        _cinemas_file = path ("cinemas.xml");
+       _dkdm_recipients_file = path ("dkdm_recipients.xml");
        _show_hints_before_make_dcp = true;
        _confirm_kdm_email = true;
        _kdm_container_name_format = dcp::NameFormat ("KDM %f %c");
        _kdm_filename_format = dcp::NameFormat ("KDM %f %c %s");
+       _dkdm_filename_format = dcp::NameFormat ("DKDM %f");
        _dcp_metadata_filename_format = dcp::NameFormat ("%t");
        _dcp_asset_filename_format = dcp::NameFormat ("%t");
        _jump_to_selected = true;
@@ -232,6 +235,7 @@ Config::backup ()
 
                boost::filesystem::copy_file(path("config.xml", false), path(String::compose("config.xml.%1", n), false));
                boost::filesystem::copy_file(path("cinemas.xml", false), path(String::compose("cinemas.xml.%1", n), false));
+               boost::filesystem::copy_file(path("dkdm_recipients.xml", false), path(String::compose("dkdm_recipients.xml.%1", n), false));
        } catch (...) {}
 }
 
@@ -333,6 +337,7 @@ try
        _default_audio_delay = f.optional_number_child<int>("DefaultAudioDelay").get_value_or (0);
        _default_interop = f.optional_bool_child("DefaultInterop").get_value_or (false);
        _default_kdm_directory = f.optional_string_child("DefaultKDMDirectory");
+       _default_dkdm_directory = f.optional_string_child("DefaultDKDMDirectory");
 
        /* Load any cinemas from config.xml */
        read_cinemas (f);
@@ -513,10 +518,12 @@ try
                }
        }
        _cinemas_file = f.optional_string_child("CinemasFile").get_value_or (path ("cinemas.xml").string ());
+       _dkdm_recipients_file = f.optional_string_child("DKDMRecipientsFile").get_value_or(path("dkdm_recipients.xml").string());
        _show_hints_before_make_dcp = f.optional_bool_child("ShowHintsBeforeMakeDCP").get_value_or (true);
        _confirm_kdm_email = f.optional_bool_child("ConfirmKDMEmail").get_value_or (true);
        _kdm_container_name_format = dcp::NameFormat (f.optional_string_child("KDMContainerNameFormat").get_value_or ("KDM %f %c"));
        _kdm_filename_format = dcp::NameFormat (f.optional_string_child("KDMFilenameFormat").get_value_or ("KDM %f %c %s"));
+       _dkdm_filename_format = dcp::NameFormat (f.optional_string_child("DKDMFilenameFormat").get_value_or("DKDM %f"));
        _dcp_metadata_filename_format = dcp::NameFormat (f.optional_string_child("DCPMetadataFilenameFormat").get_value_or ("%t"));
        _dcp_asset_filename_format = dcp::NameFormat (f.optional_string_child("DCPAssetFilenameFormat").get_value_or ("%t"));
        _jump_to_selected = f.optional_bool_child("JumpToSelected").get_value_or (true);
@@ -608,12 +615,17 @@ try
        _player_lock_file = f.optional_string_child("PlayerLockFile");
 #endif
 
-       /* Replace any cinemas from config.xml with those from the configured file */
        if (boost::filesystem::exists (_cinemas_file)) {
                cxml::Document f ("Cinemas");
                f.read_file (_cinemas_file);
                read_cinemas (f);
        }
+
+       if (boost::filesystem::exists (_dkdm_recipients_file)) {
+               cxml::Document f ("DKDMRecipients");
+               f.read_file (_dkdm_recipients_file);
+               read_dkdm_recipients (f);
+       }
 }
 catch (...) {
        if (have_existing ("config.xml")) {
@@ -647,6 +659,7 @@ Config::write () const
 {
        write_config ();
        write_cinemas ();
+       write_dkdm_recipients ();
 }
 
 void
@@ -746,6 +759,10 @@ Config::write_config () const
                /* [XML:opt] DefaultKDMDirectory Default directory to write KDMs to. */
                root->add_child("DefaultKDMDirectory")->add_child_text (_default_kdm_directory->string ());
        }
+       if (_default_dkdm_directory) {
+               /* [XML:opt] DefaultDKDMDirectory Default directory to write DKDMs to. */
+               root->add_child("DefaultDKDMDirectory")->add_child_text (_default_dkdm_directory->string());
+       }
        /* [XML] MailServer Hostname of SMTP server to use. */
        root->add_child("MailServer")->add_child_text (_mail_server);
        /* [XML] MailPort Port number to use on SMTP server. */
@@ -883,12 +900,16 @@ Config::write_config () const
 
        /* [XML] CinemasFile Filename of cinemas list file. */
        root->add_child("CinemasFile")->add_child_text (_cinemas_file.string());
+       /* [XML] DKDMRecipientsFile Filename of DKDM recipients list file. */
+       root->add_child("DKDMRecipientsFile")->add_child_text (_dkdm_recipients_file.string());
        /* [XML] ShowHintsBeforeMakeDCP 1 to show hints in the GUI before making a DCP, otherwise 0. */
        root->add_child("ShowHintsBeforeMakeDCP")->add_child_text (_show_hints_before_make_dcp ? "1" : "0");
        /* [XML] ConfirmKDMEmail 1 to confirm before sending KDM emails in the GUI, otherwise 0. */
        root->add_child("ConfirmKDMEmail")->add_child_text (_confirm_kdm_email ? "1" : "0");
        /* [XML] KDMFilenameFormat Format for KDM filenames. */
        root->add_child("KDMFilenameFormat")->add_child_text (_kdm_filename_format.specification ());
+       /* [XML] DKDMFilenameFormat Format for DKDM filenames. */
+       root->add_child("DKDMFilenameFormat")->add_child_text (_dkdm_filename_format.specification());
        /* [XML] KDMContainerNameFormat Format for KDM containers (directories or ZIP files). */
        root->add_child("KDMContainerNameFormat")->add_child_text (_kdm_container_name_format.specification ());
        /* [XML] DCPMetadataFilenameFormat Format for DCP metadata filenames. */
@@ -1080,28 +1101,45 @@ Config::write_config () const
        }
 }
 
+
+template <class T>
 void
-Config::write_cinemas () const
+write_list (string root_node, string version, string child_node, list<shared_ptr<T> > const& children, boost::filesystem::path file)
 {
        xmlpp::Document doc;
-       xmlpp::Element* root = doc.create_root_node ("Cinemas");
-       root->add_child("Version")->add_child_text ("1");
+       xmlpp::Element* root = doc.create_root_node(root_node);
+       root->add_child("Version")->add_child_text(version);
 
-       BOOST_FOREACH (shared_ptr<Cinema> i, _cinemas) {
-               i->as_xml (root->add_child ("Cinema"));
+       BOOST_FOREACH (shared_ptr<T> i, children) {
+               i->as_xml (root->add_child(child_node));
        }
 
        try {
-               doc.write_to_file_formatted (_cinemas_file.string() + ".tmp");
-               boost::filesystem::remove (_cinemas_file);
-               boost::filesystem::rename (_cinemas_file.string() + ".tmp", _cinemas_file);
+               doc.write_to_file_formatted (file.string() + ".tmp");
+               boost::filesystem::remove (file);
+               boost::filesystem::rename (file.string() + ".tmp", file);
        } catch (xmlpp::exception& e) {
                string s = e.what ();
                trim (s);
-               throw FileError (s, _cinemas_file);
+               throw FileError (s, file);
        }
 }
 
+
+void
+Config::write_cinemas () const
+{
+       write_list<Cinema>("Cinemas", "1", "Cinema", _cinemas, _cinemas_file);
+}
+
+
+void
+Config::write_dkdm_recipients () const
+{
+       write_list<DKDMRecipient>("DKDMRecipients", "1", "DKDMRecipient", _dkdm_recipients, _dkdm_recipients_file);
+}
+
+
 boost::filesystem::path
 Config::default_directory_or (boost::filesystem::path a) const
 {
@@ -1114,6 +1152,14 @@ Config::default_kdm_directory_or (boost::filesystem::path a) const
        return directory_or (_default_kdm_directory, a);
 }
 
+
+boost::filesystem::path
+Config::default_dkdm_directory_or (boost::filesystem::path a) const
+{
+       return directory_or (_default_dkdm_directory, a);
+}
+
+
 boost::filesystem::path
 Config::directory_or (optional<boost::filesystem::path> dir, boost::filesystem::path a) const
 {
@@ -1259,11 +1305,11 @@ Config::have_existing (string file)
        return boost::filesystem::exists (path (file, false));
 }
 
+
 void
 Config::read_cinemas (cxml::Document const & f)
 {
        _cinemas.clear ();
-       list<cxml::NodePtr> cin = f.node_children ("Cinema");
        BOOST_FOREACH (cxml::ConstNodePtr i, f.node_children("Cinema")) {
                /* Slightly grotty two-part construction of Cinema here so that we can use
                   shared_from_this.
@@ -1274,6 +1320,17 @@ Config::read_cinemas (cxml::Document const & f)
        }
 }
 
+
+void
+Config::read_dkdm_recipients (cxml::Document const & f)
+{
+       _dkdm_recipients.clear ();
+       BOOST_FOREACH (cxml::ConstNodePtr i, f.node_children("DKDMRecipient")) {
+               _dkdm_recipients.push_back (shared_ptr<DKDMRecipient>(new DKDMRecipient(i)));
+       }
+}
+
+
 void
 Config::set_cinemas_file (boost::filesystem::path file)
 {
@@ -1293,6 +1350,27 @@ Config::set_cinemas_file (boost::filesystem::path file)
        changed (OTHER);
 }
 
+
+void
+Config::set_dkdm_recipients_file (boost::filesystem::path file)
+{
+       if (file == _dkdm_recipients_file) {
+               return;
+       }
+
+       _dkdm_recipients_file = file;
+
+       if (boost::filesystem::exists (_dkdm_recipients_file)) {
+               /* Existing file; read it in */
+               cxml::Document f ("DKDMRecipients");
+               f.read_file (_dkdm_recipients_file);
+               read_dkdm_recipients (f);
+       }
+
+       changed (OTHER);
+}
+
+
 void
 Config::save_template (shared_ptr<const Film> film, string name) const
 {
index 7494074036c0fb01772d0c25cb4a0749a1b3ed50..a63061780e9e2808d8cc36a9e67dc1084fb06aca 100644 (file)
@@ -42,6 +42,7 @@ class CinemaSoundProcessor;
 class DCPContentType;
 class Ratio;
 class Cinema;
+class DKDMRecipient;
 class Film;
 class DKDMGroup;
 
@@ -69,13 +70,19 @@ public:
                return _default_kdm_directory;
        }
 
+       boost::optional<boost::filesystem::path> default_dkdm_directory () const {
+               return _default_dkdm_directory;
+       }
+
        boost::filesystem::path default_directory_or (boost::filesystem::path a) const;
        boost::filesystem::path default_kdm_directory_or (boost::filesystem::path a) const;
+       boost::filesystem::path default_dkdm_directory_or (boost::filesystem::path a) const;
 
        enum Property {
                USE_ANY_SERVERS,
                SERVERS,
                CINEMAS,
+               DKDM_RECIPIENTS,
                SOUND,
                SOUND_OUTPUT,
                INTERFACE_COMPLEXITY,
@@ -148,6 +155,10 @@ public:
                return _cinemas;
        }
 
+       std::list<boost::shared_ptr<DKDMRecipient> > dkdm_recipients () const {
+               return _dkdm_recipients;
+       }
+
        std::list<int> allowed_dcp_frame_rates () const {
                return _allowed_dcp_frame_rates;
        }
@@ -224,6 +235,14 @@ public:
                changed ();
        }
 
+       void set_default_dkdm_directory (boost::filesystem::path d) {
+               if (_default_dkdm_directory && _default_dkdm_directory.get() == d) {
+                       return;
+               }
+               _default_dkdm_directory = d;
+               changed ();
+       }
+
        std::string mail_server () const {
                return _mail_server;
        }
@@ -342,6 +361,10 @@ public:
                return _cinemas_file;
        }
 
+       boost::filesystem::path dkdm_recipients_file () const {
+               return _dkdm_recipients_file;
+       }
+
        bool show_hints_before_make_dcp () const {
                return _show_hints_before_make_dcp;
        }
@@ -358,6 +381,10 @@ public:
                return _kdm_filename_format;
        }
 
+       dcp::NameFormat dkdm_filename_format () const {
+               return _dkdm_filename_format;
+       }
+
        dcp::NameFormat dcp_metadata_filename_format () const {
                return _dcp_metadata_filename_format;
        }
@@ -617,6 +644,16 @@ public:
                changed (CINEMAS);
        }
 
+       void add_dkdm_recipient (boost::shared_ptr<DKDMRecipient> r) {
+               _dkdm_recipients.push_back (r);
+               changed (DKDM_RECIPIENTS);
+       }
+
+       void remove_dkdm_recipient (boost::shared_ptr<DKDMRecipient> r) {
+               _dkdm_recipients.remove (r);
+               changed (DKDM_RECIPIENTS);
+       }
+
        void set_allowed_dcp_frame_rates (std::list<int> const & r) {
                maybe_set (_allowed_dcp_frame_rates, r);
        }
@@ -814,6 +851,8 @@ public:
 
        void set_cinemas_file (boost::filesystem::path file);
 
+       void set_dkdm_recipients_file (boost::filesystem::path file);
+
        void set_show_hints_before_make_dcp (bool s) {
                maybe_set (_show_hints_before_make_dcp, s);
        }
@@ -859,6 +898,10 @@ public:
                maybe_set (_kdm_filename_format, n);
        }
 
+       void set_dkdm_filename_format (dcp::NameFormat n) {
+               maybe_set (_dkdm_filename_format, n);
+       }
+
        void set_dcp_metadata_filename_format (dcp::NameFormat n) {
                maybe_set (_dcp_metadata_filename_format, n);
        }
@@ -1111,6 +1154,7 @@ public:
        void write () const;
        void write_config () const;
        void write_cinemas () const;
+       void write_dkdm_recipients () const;
        void link (boost::filesystem::path new_file) const;
        void copy_and_link (boost::filesystem::path new_file) const;
        bool have_write_permission () const;
@@ -1136,6 +1180,7 @@ private:
        void set_notification_email_to_default ();
        void set_cover_sheet_to_default ();
        void read_cinemas (cxml::Document const & f);
+       void read_dkdm_recipients (cxml::Document const & f);
        boost::shared_ptr<dcp::CertificateChain> create_certificate_chain ();
        boost::filesystem::path directory_or (boost::optional<boost::filesystem::path> dir, boost::filesystem::path a) const;
        void add_to_history_internal (std::vector<boost::filesystem::path>& h, boost::filesystem::path p);
@@ -1213,8 +1258,10 @@ private:
            the home directory will be offered.
        */
        boost::optional<boost::filesystem::path> _default_kdm_directory;
+       boost::optional<boost::filesystem::path> _default_dkdm_directory;
        bool _default_upload_after_make_dcp;
        std::list<boost::shared_ptr<Cinema> > _cinemas;
+       std::list<boost::shared_ptr<DKDMRecipient> > _dkdm_recipients;
        std::string _mail_server;
        int _mail_port;
        EmailProtocol _mail_protocol;
@@ -1257,9 +1304,11 @@ private:
        std::vector<boost::filesystem::path> _player_history;
        boost::shared_ptr<DKDMGroup> _dkdms;
        boost::filesystem::path _cinemas_file;
+       boost::filesystem::path _dkdm_recipients_file;
        bool _show_hints_before_make_dcp;
        bool _confirm_kdm_email;
        dcp::NameFormat _kdm_filename_format;
+       dcp::NameFormat _dkdm_filename_format;
        dcp::NameFormat _kdm_container_name_format;
        dcp::NameFormat _dcp_metadata_filename_format;
        dcp::NameFormat _dcp_asset_filename_format;
diff --git a/src/lib/dkdm_recipient.cc b/src/lib/dkdm_recipient.cc
new file mode 100644 (file)
index 0000000..2cfecf3
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+    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 "dkdm_recipient.h"
+#include "dcpomatic_assert.h"
+#include <dcp/raw_convert.h>
+#include <libxml++/libxml++.h>
+#include <boost/foreach.hpp>
+
+using std::string;
+using dcp::raw_convert;
+
+DKDMRecipient::DKDMRecipient (cxml::ConstNodePtr node)
+       : KDMRecipient (node)
+{
+       BOOST_FOREACH (cxml::ConstNodePtr i, node->node_children("Email")) {
+               emails.push_back (i->content());
+       }
+
+       _utc_offset_hour = node->number_child<int>("UTCOffsetHour");
+       _utc_offset_minute = node->number_child<int>("UTCOffsetMinute");
+}
+
+
+void
+DKDMRecipient::as_xml (xmlpp::Element* parent) const
+{
+       KDMRecipient::as_xml (parent);
+
+       BOOST_FOREACH (string i, emails) {
+               parent->add_child("Email")->add_child_text (i);
+       }
+
+       parent->add_child("UTCOffsetHour")->add_child_text(raw_convert<string>(_utc_offset_hour));
+       parent->add_child("UTCOffsetMinute")->add_child_text(raw_convert<string>(_utc_offset_minute));
+}
+
+
+void
+DKDMRecipient::set_utc_offset_hour (int h)
+{
+       DCPOMATIC_ASSERT (h >= -11 && h <= 12);
+       _utc_offset_hour = h;
+}
+
+
+void
+DKDMRecipient::set_utc_offset_minute (int m)
+{
+       DCPOMATIC_ASSERT (m >= 0 && m <= 59);
+       _utc_offset_minute = m;
+}
diff --git a/src/lib/dkdm_recipient.h b/src/lib/dkdm_recipient.h
new file mode 100644 (file)
index 0000000..77ef4e2
--- /dev/null
@@ -0,0 +1,72 @@
+/*
+    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/>.
+
+*/
+
+#ifndef DCPOMATIC_DKDM_RECIPIENT_H
+#define DCPOMATIC_DKDM_RECIPIENT_H
+
+#include "kdm_recipient.h"
+#include <dcp/certificate.h>
+#include <libcxml/cxml.h>
+
+class DKDMRecipient : public KDMRecipient
+{
+public:
+       DKDMRecipient (
+               std::string const& name_,
+               std::string const& notes_,
+               boost::optional<dcp::Certificate> recipient_,
+               std::list<std::string> const& emails_,
+               int utc_offset_hour_,
+               int utc_offset_minute_)
+               : KDMRecipient (name_, notes_, recipient_)
+               , emails (emails_)
+               , _utc_offset_hour (utc_offset_hour_)
+               , _utc_offset_minute (utc_offset_minute_)
+       {}
+
+       explicit DKDMRecipient (cxml::ConstNodePtr node);
+
+       void as_xml (xmlpp::Element *) const;
+
+       void set_utc_offset_hour (int h);
+       void set_utc_offset_minute (int m);
+
+       int utc_offset_hour () const {
+               return _utc_offset_hour;
+       }
+
+       int utc_offset_minute () const {
+               return _utc_offset_minute;
+       }
+
+       std::list<std::string> emails;
+
+private:
+       /** Offset such that the equivalent time in UTC can be determined
+           by subtracting the offset from the local time.
+       */
+       int _utc_offset_hour;
+       /** Additional minutes to add to _utc_offset_hour if _utc_offset_hour is
+           positive, or to subtract if _utc_offset_hour is negative.
+       */
+       int _utc_offset_minute;
+};
+
+#endif
index ee4e8c39c8ff053f563ea3ea405febda6014a98e..c0533daeb07d2d6c3388c22fde2dea9189824145 100644 (file)
@@ -18,6 +18,9 @@
 
 */
 
+#ifndef DCPOMATIC_KDM_RECIPIENT_H
+#define DCPOMATIC_KDM_RECIPIENT_H
+
 #include <dcp/certificate.h>
 #include <libcxml/cxml.h>
 #include <libxml++/libxml++.h>
@@ -41,3 +44,5 @@ public:
        std::string notes;
        boost::optional<dcp::Certificate> recipient;
 };
+
+#endif
diff --git a/src/lib/recipient_with_dkdm.cc b/src/lib/recipient_with_dkdm.cc
new file mode 100644 (file)
index 0000000..ebe966f
--- /dev/null
@@ -0,0 +1,149 @@
+/*
+    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 "recipient_with_dkdm.h"
+#include "config.h"
+#include "dcpomatic_log.h"
+#include "emailer.h"
+#include "exceptions.h"
+#include "zipper.h"
+#include "util.h"
+#include <boost/foreach.hpp>
+
+#include "i18n.h"
+
+using std::list;
+using std::vector;
+using std::string;
+using boost::shared_ptr;
+
+void
+RecipientWithDKDM::make_zip_file (boost::filesystem::path zip_file, dcp::NameFormat name_format, dcp::NameFormat::Map name_values) const
+{
+       Zipper zipper (zip_file);
+
+       name_values['i'] = kdm.cpl_id ();
+       string const name = careful_string_filter(name_format.get(name_values, ".xml"));
+       zipper.add (name, kdm.as_xml());
+
+       zipper.close ();
+}
+
+
+/** Email one ZIP file per recipient.
+ *  @param kdms KDMs to email.
+ *  @param filename_format Format of filenames to use.
+ *  @param name_values Values to substitute into \p container_name_format and \p filename_format.
+ *  @param cpl_name Name of the CPL that the KDMs are for.
+ */
+void
+RecipientWithDKDM::email (
+       vector<RecipientWithDKDM> kdms,
+       dcp::NameFormat filename_format,
+       dcp::NameFormat::Map name_values,
+       string cpl_name
+       )
+{
+       Config* config = Config::instance ();
+
+       if (config->mail_server().empty()) {
+               throw NetworkError (_("No mail server configured in preferences"));
+       }
+
+       BOOST_FOREACH (RecipientWithDKDM const & i, kdms) {
+
+               if (i.recipient->emails.empty()) {
+                       continue;
+               }
+
+               boost::filesystem::path zip_file = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path();
+               boost::filesystem::create_directories (zip_file);
+               zip_file /= filename_format.get(name_values, ".zip");
+               i.make_zip_file (zip_file, filename_format, name_values);
+
+               string subject = config->kdm_subject();
+               boost::algorithm::replace_all (subject, "$CPL_NAME", cpl_name);
+               boost::algorithm::replace_all (subject, "$START_TIME", name_values['b']);
+               boost::algorithm::replace_all (subject, "$END_TIME", name_values['e']);
+
+               string body = config->kdm_email().c_str();
+               boost::algorithm::replace_all (body, "$CPL_NAME", cpl_name);
+               boost::algorithm::replace_all (body, "$START_TIME", name_values['b']);
+               boost::algorithm::replace_all (body, "$END_TIME", name_values['e']);
+
+               Emailer email (config->kdm_from(), i.recipient->emails, subject, body);
+
+               BOOST_FOREACH (string i, config->kdm_cc()) {
+                       email.add_cc (i);
+               }
+               if (!config->kdm_bcc().empty ()) {
+                       email.add_bcc (config->kdm_bcc ());
+               }
+
+               email.add_attachment (zip_file, filename_format.get(name_values, ".zip"), "application/zip");
+
+               try {
+                       email.send (config->mail_server(), config->mail_port(), config->mail_protocol(), config->mail_user(), config->mail_password());
+               } catch (...) {
+                       boost::filesystem::remove (zip_file);
+                       dcpomatic_log->log ("Email content follows", LogEntry::TYPE_DEBUG_EMAIL);
+                       dcpomatic_log->log (email.email(), LogEntry::TYPE_DEBUG_EMAIL);
+                       dcpomatic_log->log ("Email session follows", LogEntry::TYPE_DEBUG_EMAIL);
+                       dcpomatic_log->log (email.notes(), LogEntry::TYPE_DEBUG_EMAIL);
+                       throw;
+               }
+
+               boost::filesystem::remove (zip_file);
+
+               dcpomatic_log->log ("Email content follows", LogEntry::TYPE_DEBUG_EMAIL);
+               dcpomatic_log->log (email.email(), LogEntry::TYPE_DEBUG_EMAIL);
+               dcpomatic_log->log ("Email session follows", LogEntry::TYPE_DEBUG_EMAIL);
+               dcpomatic_log->log (email.notes(), LogEntry::TYPE_DEBUG_EMAIL);
+       }
+}
+
+
+int
+RecipientWithDKDM::write_files (
+       vector<RecipientWithDKDM> kdms,
+       boost::filesystem::path directory,
+       dcp::NameFormat name_format,
+       dcp::NameFormat::Map name_values,
+       boost::function<bool (boost::filesystem::path)> confirm_overwrite
+       )
+{
+       int written = 0;
+
+       if (!boost::filesystem::exists (directory)) {
+               boost::filesystem::create_directories (directory);
+       }
+
+       /* Write KDMs to the specified directory */
+       BOOST_FOREACH (RecipientWithDKDM const& i, kdms) {
+               name_values['i'] = i.kdm.cpl_id ();
+               boost::filesystem::path out = directory / careful_string_filter(name_format.get(name_values, ".xml"));
+               if (!boost::filesystem::exists (out) || confirm_overwrite (out)) {
+                       i.kdm.as_xml (out);
+                       ++written;
+               }
+       }
+
+       return written;
+}
diff --git a/src/lib/recipient_with_dkdm.h b/src/lib/recipient_with_dkdm.h
new file mode 100644 (file)
index 0000000..8aed952
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+    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/>.
+
+*/
+
+#ifndef DCPOMATIC_RECIPIENT_WITH_DKDM_H
+#define DCPOMATIC_RECIPIENT_WITH_DKDM_H
+
+#include "dkdm_recipient.h"
+#include <dcp/encrypted_kdm.h>
+#include <dcp/name_format.h>
+
+class RecipientWithDKDM
+{
+public:
+       RecipientWithDKDM (boost::shared_ptr<DKDMRecipient> r, dcp::EncryptedKDM k)
+               : recipient (r)
+               , kdm (k)
+       {}
+
+       void make_zip_file (boost::filesystem::path zip_file, dcp::NameFormat name_format, dcp::NameFormat::Map name_values) const;
+
+       static int write_files (
+               std::vector<RecipientWithDKDM> kdms, boost::filesystem::path directory,
+               dcp::NameFormat name_format, dcp::NameFormat::Map name_values,
+               boost::function<bool (boost::filesystem::path)> confirm_overwrite
+               );
+
+       static void email (
+               std::vector<RecipientWithDKDM> kdms,
+               dcp::NameFormat filename_format,
+               dcp::NameFormat::Map name_values,
+               std::string cpl_name
+               );
+
+       boost::shared_ptr<DKDMRecipient> recipient;
+       dcp::EncryptedKDM kdm;
+};
+
+#endif
diff --git a/src/lib/send_dkdm_email_job.cc b/src/lib/send_dkdm_email_job.cc
new file mode 100644 (file)
index 0000000..54334aa
--- /dev/null
@@ -0,0 +1,88 @@
+/*
+    Copyright (C) 2013 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 "send_dkdm_email_job.h"
+#include "compose.hpp"
+#include "film.h"
+#include "cinema_kdms.h"
+#include <list>
+
+#include "i18n.h"
+
+using std::string;
+using std::list;
+using std::vector;
+using boost::shared_ptr;
+
+/** @param kdms KDMs to email.
+ *  @param container_name_format Format to ues for folders / ZIP files.
+ *  @param filename_format Format to use for filenames.
+ *  @param name_values Values to substitute into \p container_name_format and \p filename_format.
+ *  @param cpl_name Name of the CPL that the KDMs are for.
+ */
+SendDKDMEmailJob::SendDKDMEmailJob (
+       vector<RecipientWithDKDM> kdms,
+       dcp::NameFormat filename_format,
+       dcp::NameFormat::Map name_values,
+       string cpl_name
+       )
+       : Job (shared_ptr<Film>())
+       , _filename_format (filename_format)
+       , _name_values (name_values)
+       , _cpl_name (cpl_name)
+       , _kdms (kdms)
+{
+
+}
+
+
+SendDKDMEmailJob::~SendDKDMEmailJob ()
+{
+       stop_thread ();
+}
+
+
+string
+SendDKDMEmailJob::name () const
+{
+       dcp::NameFormat::Map::const_iterator i = _name_values.find ('f');
+       if (i == _name_values.end() || i->second.empty()) {
+               return _("Email DKDMs");
+       }
+
+       return String::compose (_("Email DKDMs for %1"), i->second);
+}
+
+
+string
+SendDKDMEmailJob::json_name () const
+{
+       return N_("send_dkdm_email");
+}
+
+
+void
+SendDKDMEmailJob::run ()
+{
+       set_progress_unknown ();
+       RecipientWithDKDM::email (_kdms, _filename_format, _name_values, _cpl_name);
+       set_progress (1);
+       set_state (FINISHED_OK);
+}
diff --git a/src/lib/send_dkdm_email_job.h b/src/lib/send_dkdm_email_job.h
new file mode 100644 (file)
index 0000000..2b47557
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+    Copyright (C) 2013-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 "job.h"
+#include "recipient_with_dkdm.h"
+#include <dcp/types.h>
+#include <dcp/name_format.h>
+#include <boost/filesystem.hpp>
+
+namespace dcpomatic {
+       class Screen;
+}
+
+class CinemaKDMs;
+class Log;
+
+class SendDKDMEmailJob : public Job
+{
+public:
+       SendDKDMEmailJob (
+               std::vector<RecipientWithDKDM> kdms,
+               dcp::NameFormat filename_format,
+               dcp::NameFormat::Map name_values,
+               std::string cpl_name
+               );
+       ~SendDKDMEmailJob ();
+
+       std::string name () const;
+       std::string json_name () const;
+       void run ();
+
+private:
+       dcp::NameFormat _filename_format;
+       dcp::NameFormat::Map _name_values;
+       std::string _cpl_name;
+       std::vector<RecipientWithDKDM> _kdms;
+};
index ce14532dd5364e993cc8229f772a5ee084a52e0e..1f9b1d1762f4d51f067ce155d843807595caa314 100644 (file)
@@ -78,6 +78,7 @@ sources = """
           decoder_part.cc
           decrypted_ecinema_kdm.cc
           digester.cc
+          dkdm_recipient.cc
           dkdm_wrapper.cc
           dolby_cp750.cc
           edid.cc
@@ -139,6 +140,7 @@ sources = """
           position_image.cc
           ratio.cc
           raw_image_proxy.cc
+          recipient_with_dkdm.cc
           reel_writer.cc
           render_text.cc
           resampler.cc
@@ -166,6 +168,7 @@ sources = """
           transcode_job.cc
           trusted_device.cc
           types.cc
+          send_dkdm_email_job.cc
           signal_manager.cc
           stdout_log.cc
           update_checker.cc
index ffc8a35fc6d7af3503cb256b3c150a9d5aca9cce..c5d6b669d0be2dd3332f767bf293dad91221e72d 100644 (file)
@@ -33,6 +33,7 @@
 #include "wx/recreate_chain_dialog.h"
 #include "wx/about_dialog.h"
 #include "wx/kdm_dialog.h"
+#include "wx/dkdm_dialog.h"
 #include "wx/self_dkdm_dialog.h"
 #include "wx/servers_list_dialog.h"
 #include "wx/hints_dialog.h"
@@ -227,6 +228,7 @@ enum {
        ID_jobs_make_dcp,
        ID_jobs_make_dcp_batch,
        ID_jobs_make_kdms,
+       ID_jobs_make_dkdms,
        ID_jobs_make_self_dkdm,
        ID_jobs_export,
        ID_jobs_send_dcp_to_tms,
@@ -262,6 +264,7 @@ public:
                , _servers_list_dialog (0)
                , _config_dialog (0)
                , _kdm_dialog (0)
+               , _dkdm_dialog (0)
                , _templates_dialog (0)
                , _file_menu (0)
                , _history_items (0)
@@ -318,6 +321,7 @@ public:
                Bind (wxEVT_MENU, boost::bind (&DOMFrame::content_scale_to_fit_height, this), ID_content_scale_to_fit_height);
                Bind (wxEVT_MENU, boost::bind (&DOMFrame::jobs_make_dcp, this),           ID_jobs_make_dcp);
                Bind (wxEVT_MENU, boost::bind (&DOMFrame::jobs_make_kdms, this),          ID_jobs_make_kdms);
+               Bind (wxEVT_MENU, boost::bind (&DOMFrame::jobs_make_dkdms, this),         ID_jobs_make_dkdms);
                Bind (wxEVT_MENU, boost::bind (&DOMFrame::jobs_make_dcp_batch, this),     ID_jobs_make_dcp_batch);
                Bind (wxEVT_MENU, boost::bind (&DOMFrame::jobs_make_self_dkdm, this),     ID_jobs_make_self_dkdm);
                Bind (wxEVT_MENU, boost::bind (&DOMFrame::jobs_export, this),             ID_jobs_export);
@@ -797,6 +801,23 @@ private:
                _kdm_dialog->Show ();
        }
 
+
+       void jobs_make_dkdms ()
+       {
+               if (!_film) {
+                       return;
+               }
+
+               if (_dkdm_dialog) {
+                       _dkdm_dialog->Destroy ();
+                       _dkdm_dialog = 0;
+               }
+
+               _dkdm_dialog = new DKDMDialog (this, _film);
+               _dkdm_dialog->Show ();
+       }
+
+
        /** @return false if we succeeded, true if not */
        bool send_to_other_tool (int port, function<void(boost::filesystem::path)> start, string message)
        {
@@ -1314,6 +1335,7 @@ private:
                add_item (jobs_menu, _("Make DCP in &batch converter\tCtrl-B"), ID_jobs_make_dcp_batch, NEEDS_FILM | NOT_DURING_DCP_CREATION);
                jobs_menu->AppendSeparator ();
                add_item (jobs_menu, _("Make &KDMs...\tCtrl-K"), ID_jobs_make_kdms, NEEDS_FILM);
+               add_item (jobs_menu, _("Make DKDMs...\t"), ID_jobs_make_dkdms, NEEDS_FILM);
                add_item (jobs_menu, _("Make DKDM for DCP-o-matic..."), ID_jobs_make_self_dkdm, NEEDS_FILM | NEEDS_ENCRYPTION);
                jobs_menu->AppendSeparator ();
                add_item (jobs_menu, _("Export...\tCtrl-E"), ID_jobs_export, NEEDS_FILM);
@@ -1368,6 +1390,18 @@ private:
                                                )
                                        );
                        }
+               } else if (what == Config::DKDM_RECIPIENTS) {
+                       try {
+                               Config::instance()->write_dkdm_recipients();
+                       } catch (exception& e) {
+                               error_dialog (
+                                       this,
+                                       wxString::Format (
+                                               _("Could not write to DKDM recipients file at %s.  Your changes have not been saved."),
+                                               std_to_wx (Config::instance()->dkdm_recipients_file().string()).data()
+                                               )
+                                       );
+                       }
                } else {
                        try {
                                Config::instance()->write_config();
@@ -1485,6 +1519,7 @@ private:
        ServersListDialog* _servers_list_dialog;
        wxPreferencesEditor* _config_dialog;
        KDMDialog* _kdm_dialog;
+       DKDMDialog* _dkdm_dialog;
        TemplatesDialog* _templates_dialog;
        wxMenu* _file_menu;
        shared_ptr<Film> _film;
index ff5d1faf61976b43ed4d0bc36853cbfcf3eba458..baaeb9388ce0c111671749d3f70310a96661dc80 100644 (file)
@@ -83,37 +83,8 @@ CinemaDialog::CinemaDialog (wxWindow* parent, wxString title, string name, list<
                overall_sizer->Add (buttons, wxSizerFlags().Expand().DoubleBorder());
        }
 
-       _offsets.push_back (Offset (_("UTC-11"),  -11,  0));
-       _offsets.push_back (Offset (_("UTC-10"),  -10,  0));
-       _offsets.push_back (Offset (_("UTC-9"),    -9,  0));
-       _offsets.push_back (Offset (_("UTC-8"),    -8,  0));
-       _offsets.push_back (Offset (_("UTC-7"),    -7,  0));
-       _offsets.push_back (Offset (_("UTC-6"),    -6,  0));
-       _offsets.push_back (Offset (_("UTC-5"),    -5,  0));
-       _offsets.push_back (Offset (_("UTC-4:30"), -4, 30));
-       _offsets.push_back (Offset (_("UTC-4"),    -4,  0));
-       _offsets.push_back (Offset (_("UTC-3:30"), -3, 30));
-       _offsets.push_back (Offset (_("UTC-3"),    -3,  0));
-       _offsets.push_back (Offset (_("UTC-2"),    -2,  0));
-       _offsets.push_back (Offset (_("UTC-1"),    -1,  0));
-       _offsets.push_back (Offset (_("UTC")  ,     0,  0));
-       _offsets.push_back (Offset (_("UTC+1"),     1,  0));
-       _offsets.push_back (Offset (_("UTC+2"),     2,  0));
-       _offsets.push_back (Offset (_("UTC+3"),     3,  0));
-       _offsets.push_back (Offset (_("UTC+4"),     4,  0));
-       _offsets.push_back (Offset (_("UTC+5"),     5,  0));
-       _offsets.push_back (Offset (_("UTC+5:30"),  5, 30));
-       _offsets.push_back (Offset (_("UTC+6"),     6,  0));
-       _offsets.push_back (Offset (_("UTC+7"),     7,  0));
-       _offsets.push_back (Offset (_("UTC+8"),     8,  0));
-       _offsets.push_back (Offset (_("UTC+9"),     9,  0));
-       _offsets.push_back (Offset (_("UTC+9:30"),  9, 30));
-       _offsets.push_back (Offset (_("UTC+10"),   10,  0));
-       _offsets.push_back (Offset (_("UTC+11"),   11,  0));
-       _offsets.push_back (Offset (_("UTC+12"),   12,  0));
-
-       /* Default to UTC */
-       size_t sel = 13;
+       size_t sel = all_offsets (_offsets);
+
        for (size_t i = 0; i < _offsets.size(); ++i) {
                _utc_offset->Append (_offsets[i].name);
                if (_offsets[i].hour == utc_offset_hour && _offsets[i].minute == utc_offset_minute) {
index 9cb0a65dc151509c182b50df7752078ff3bf3195..4627a6b412ed1f200cf004a5f6e585ed718dc9cd 100644 (file)
@@ -54,18 +54,5 @@ private:
        std::vector<std::string> _emails;
        wxChoice* _utc_offset;
 
-       struct Offset
-       {
-               Offset (wxString n, int h, int m)
-                       : name (n)
-                       , hour (h)
-                       , minute (m)
-               {}
-
-               wxString name;
-               int hour;
-               int minute;
-       };
-
        std::vector<Offset> _offsets;
 };
diff --git a/src/wx/dkdm_dialog.cc b/src/wx/dkdm_dialog.cc
new file mode 100644 (file)
index 0000000..6339a66
--- /dev/null
@@ -0,0 +1,197 @@
+/*
+    Copyright (C) 2012-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 "dkdm_dialog.h"
+#include "wx_util.h"
+#include "recipients_panel.h"
+#include "kdm_timing_panel.h"
+#include "dkdm_output_panel.h"
+#include "kdm_cpl_panel.h"
+#include "confirm_kdm_email_dialog.h"
+#include "static_text.h"
+#include "dcpomatic_button.h"
+#include "lib/film.h"
+#include "lib/screen.h"
+#include "lib/screen_with_kdm.h"
+#include "lib/job_manager.h"
+#include "lib/cinema_kdms.h"
+#include "lib/config.h"
+#include "lib/cinema.h"
+#include "lib/dkdm_recipient.h"
+#include <libcxml/cxml.h>
+#include <dcp/exceptions.h>
+#include <wx/treectrl.h>
+#include <wx/listctrl.h>
+#include <iostream>
+
+using std::string;
+using std::exception;
+using std::map;
+using std::list;
+using std::pair;
+using std::cout;
+using std::vector;
+using std::make_pair;
+using std::runtime_error;
+using boost::shared_ptr;
+using boost::bind;
+using boost::optional;
+
+DKDMDialog::DKDMDialog (wxWindow* parent, shared_ptr<const Film> film)
+       : wxDialog (parent, wxID_ANY, _("Make DKDMs"))
+       , _film (film)
+{
+       /* Main sizers */
+       wxBoxSizer* horizontal = new wxBoxSizer (wxHORIZONTAL);
+       wxBoxSizer* left = new wxBoxSizer (wxVERTICAL);
+       wxBoxSizer* right = new wxBoxSizer (wxVERTICAL);
+
+       horizontal->Add (left, 1, wxEXPAND | wxRIGHT, DCPOMATIC_SIZER_X_GAP * 4);
+       horizontal->Add (right, 1, wxEXPAND);
+
+       /* Font for sub-headings */
+       wxFont subheading_font (*wxNORMAL_FONT);
+       subheading_font.SetWeight (wxFONTWEIGHT_BOLD);
+
+       /* Sub-heading: Recipients */
+       wxStaticText* h = new StaticText (this, _("Recipients"));
+       h->SetFont (subheading_font);
+       left->Add (h, 0, wxALIGN_CENTER_VERTICAL | wxBOTTOM, DCPOMATIC_SIZER_Y_GAP);
+       _recipients = new RecipientsPanel (this);
+       left->Add (_recipients, 1, wxEXPAND | wxBOTTOM, DCPOMATIC_SIZER_Y_GAP);
+
+       /* Sub-heading: Timing */
+       /// TRANSLATORS: translate the word "Timing" here; do not include the "KDM|" prefix
+       h = new StaticText (this, S_("KDM|Timing"));
+       h->SetFont (subheading_font);
+       right->Add (h, 0, wxALIGN_CENTER_VERTICAL, DCPOMATIC_SIZER_Y_GAP * 2);
+       _timing = new KDMTimingPanel (this);
+       right->Add (_timing);
+
+       /* Sub-heading: CPL */
+       h = new StaticText (this, _("CPL"));
+       h->SetFont (subheading_font);
+       right->Add (h, 0, wxALIGN_CENTER_VERTICAL, DCPOMATIC_SIZER_Y_GAP * 2);
+
+       vector<CPLSummary> cpls;
+       BOOST_FOREACH (CPLSummary const & i, film->cpls()) {
+               if (i.encrypted) {
+                       cpls.push_back (i);
+               }
+       }
+
+       _cpl = new KDMCPLPanel (this, cpls);
+       right->Add (_cpl, 0, wxEXPAND);
+
+       /* Sub-heading: Output */
+       h = new StaticText (this, _("Output"));
+       h->SetFont (subheading_font);
+       right->Add (h, 0, wxALIGN_CENTER_VERTICAL | wxTOP, DCPOMATIC_SIZER_Y_GAP * 2);
+       _output = new DKDMOutputPanel (this);
+       right->Add (_output, 0, wxEXPAND | wxTOP, DCPOMATIC_SIZER_GAP);
+
+       _make = new Button (this, _("Make KDMs"));
+       right->Add (_make, 0, wxTOP | wxBOTTOM, DCPOMATIC_SIZER_GAP);
+
+       /* Make an overall sizer to get a nice border */
+
+       wxBoxSizer* overall_sizer = new wxBoxSizer (wxVERTICAL);
+       overall_sizer->Add (horizontal, 0, wxEXPAND | wxTOP | wxLEFT | wxRIGHT, DCPOMATIC_DIALOG_BORDER);
+
+       /* Bind */
+
+       _recipients->RecipientsChanged.connect (boost::bind (&DKDMDialog::setup_sensitivity, this));
+       _timing->TimingChanged.connect (boost::bind (&DKDMDialog::setup_sensitivity, this));
+       _make->Bind (wxEVT_BUTTON, boost::bind (&DKDMDialog::make_clicked, this));
+
+       setup_sensitivity ();
+
+       SetSizer (overall_sizer);
+       overall_sizer->Layout ();
+       overall_sizer->SetSizeHints (this);
+}
+
+void
+DKDMDialog::setup_sensitivity ()
+{
+       _recipients->setup_sensitivity ();
+       _output->setup_sensitivity ();
+       _make->Enable (!_recipients->recipients().empty() && _timing->valid() && _cpl->has_selected());
+}
+
+bool
+DKDMDialog::confirm_overwrite (boost::filesystem::path path)
+{
+       return confirm_dialog (
+               this,
+               wxString::Format (_("File %s already exists.  Do you want to overwrite it?"), std_to_wx(path.string()).data())
+               );
+}
+
+void
+DKDMDialog::make_clicked ()
+{
+       shared_ptr<const Film> film = _film.lock ();
+       DCPOMATIC_ASSERT (film);
+
+       vector<RecipientWithDKDM> recipient_kdms;
+       try {
+               BOOST_FOREACH (shared_ptr<DKDMRecipient> i, _recipients->recipients()) {
+                       if (i->recipient) {
+                               dcp::EncryptedKDM const kdm = film->make_kdm (
+                                               i->recipient.get(),
+                                               vector<std::string>(),
+                                               _cpl->cpl(),
+                                               dcp::LocalTime(_timing->from(),  i->utc_offset_hour(), i->utc_offset_minute()),
+                                               dcp::LocalTime(_timing->until(), i->utc_offset_hour(), i->utc_offset_minute()),
+                                               dcp::MODIFIED_TRANSITIONAL_1,
+                                               true,
+                                               optional<int>()
+                                               );
+
+                               recipient_kdms.push_back (RecipientWithDKDM(i, kdm));
+                       }
+               }
+       } catch (dcp::BadKDMDateError& e) {
+               if (e.starts_too_early()) {
+                       error_dialog (this, _("The KDM start period is before (or close to) the start of the signing certificate's validity period.  Use a later start time for this KDM."));
+               } else {
+                       error_dialog (this, _("The KDM end period is after (or close to) the end of the signing certficates' validity period.  Either use an earlier end time for this KDM or re-create your signing certificates in the DCP-o-matic preferences window."));
+               }
+               return;
+       } catch (runtime_error& e) {
+               error_dialog (this, std_to_wx(e.what()));
+               return;
+       }
+
+       pair<shared_ptr<Job>, int> result = _output->make (recipient_kdms, film->name(), _timing, bind(&DKDMDialog::confirm_overwrite, this, _1));
+       if (result.first) {
+               JobManager::instance()->add (result.first);
+       }
+
+       if (result.second > 0) {
+               /* XXX: proper plural form support in wxWidgets? */
+               wxString s = result.second == 1 ? _("%d DKDM written to %s") : _("%d DKDMs written to %s");
+               message_dialog (
+                       this,
+                       wxString::Format (s, result.second, std_to_wx(_output->directory().string()).data())
+                       );
+       }
+}
diff --git a/src/wx/dkdm_dialog.h b/src/wx/dkdm_dialog.h
new file mode 100644 (file)
index 0000000..ba5a981
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+    Copyright (C) 2012-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 "wx_util.h"
+#include <dcp/types.h>
+#include <wx/wx.h>
+#include <boost/shared_ptr.hpp>
+#include <boost/date_time/posix_time/posix_time.hpp>
+#include <map>
+
+class RecipientsPanel;
+class Film;
+class KDMTimingPanel;
+class DKDMOutputPanel;
+class KDMCPLPanel;
+struct CPLSummary;
+
+class DKDMDialog : public wxDialog
+{
+public:
+       DKDMDialog (wxWindow *, boost::shared_ptr<const Film> film);
+
+private:
+       void setup_sensitivity ();
+       void make_clicked ();
+       bool confirm_overwrite (boost::filesystem::path path);
+
+       boost::weak_ptr<const Film> _film;
+       RecipientsPanel* _recipients;
+       KDMTimingPanel* _timing;
+       KDMCPLPanel* _cpl;
+       DKDMOutputPanel* _output;
+       wxButton* _make;
+};
diff --git a/src/wx/dkdm_output_panel.cc b/src/wx/dkdm_output_panel.cc
new file mode 100644 (file)
index 0000000..00e8e84
--- /dev/null
@@ -0,0 +1,207 @@
+/*
+    Copyright (C) 2015-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/config.h"
+#include "lib/cinema.h"
+#include "lib/cinema_kdms.h"
+#include "lib/send_dkdm_email_job.h"
+#include "lib/recipient_with_dkdm.h"
+#include "dkdm_output_panel.h"
+#include "kdm_timing_panel.h"
+#include "confirm_kdm_email_dialog.h"
+#include "wx_util.h"
+#include "kdm_advanced_dialog.h"
+#include "name_format_editor.h"
+#include "check_box.h"
+#include "dcpomatic_button.h"
+#include <dcp/exceptions.h>
+#include <dcp/types.h>
+#ifdef DCPOMATIC_USE_OWN_PICKER
+#include "dir_picker_ctrl.h"
+#else
+#include <wx/filepicker.h>
+#endif
+#include <wx/stdpaths.h>
+
+using std::pair;
+using std::string;
+using std::list;
+using std::exception;
+using std::make_pair;
+using std::vector;
+using boost::shared_ptr;
+using boost::function;
+
+DKDMOutputPanel::DKDMOutputPanel (wxWindow* parent)
+       : wxPanel (parent, wxID_ANY)
+{
+       wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, 0);
+
+       add_label_to_sizer (table, this, _("KDM type"), true);
+
+       add_label_to_sizer (table, this, _("Filename format"), true, 0, wxALIGN_TOP | wxTOP | wxLEFT | wxRIGHT);
+       dcp::NameFormat::Map titles;
+       titles['f'] = wx_to_std (_("film name"));
+       titles['b'] = wx_to_std (_("from date/time"));
+       titles['e'] = wx_to_std (_("to date/time"));
+       dcp::NameFormat::Map ex;
+       ex['s'] = "Screen 1";
+       ex['b'] = "2012/03/15 12:30";
+       ex['e'] = "2012/03/22 02:30";
+       _filename_format = new NameFormatEditor (this, Config::instance()->dkdm_filename_format(), titles, ex, ".xml");
+       table->Add (_filename_format->panel(), 1, wxEXPAND);
+
+       _write_to = new CheckBox (this, _("Write to"));
+       table->Add (_write_to, 1, wxEXPAND);
+
+#ifdef DCPOMATIC_USE_OWN_PICKER
+       _folder = new DirPickerCtrl (this);
+#else
+       _folder = new wxDirPickerCtrl (this, wxID_ANY, wxEmptyString, wxDirSelectorPromptStr, wxDefaultPosition, wxSize (300, -1));
+#endif
+
+       boost::optional<boost::filesystem::path> path = Config::instance()->default_dkdm_directory ();
+       if (path) {
+               _folder->SetPath (std_to_wx (path->string ()));
+       } else {
+               _folder->SetPath (wxStandardPaths::Get().GetDocumentsDir());
+       }
+
+       table->Add (_folder, 1, wxEXPAND);
+
+       _email = new CheckBox (this, _("Send by email"));
+       table->Add (_email, 1, wxEXPAND);
+       table->AddSpacer (0);
+
+       _write_to->SetValue (true);
+
+       _write_to->Bind (wxEVT_CHECKBOX, boost::bind (&DKDMOutputPanel::setup_sensitivity, this));
+       _email->Bind    (wxEVT_CHECKBOX, boost::bind (&DKDMOutputPanel::setup_sensitivity, this));
+
+       SetSizer (table);
+}
+
+
+void
+DKDMOutputPanel::setup_sensitivity ()
+{
+       _folder->Enable (_write_to->GetValue());
+}
+
+
+pair<shared_ptr<Job>, int>
+DKDMOutputPanel::make (
+       vector<RecipientWithDKDM> recipient_kdms, string name, KDMTimingPanel* timing, function<bool (boost::filesystem::path)> confirm_overwrite
+       )
+{
+       /* Decide whether to proceed */
+
+       bool proceed = true;
+
+       if (_email->GetValue ()) {
+
+               if (Config::instance()->mail_server().empty()) {
+                       proceed = false;
+                       error_dialog (this, _("You must set up a mail server in Preferences before you can send emails."));
+               }
+
+               bool recipients_with_no_email = false;
+               BOOST_FOREACH (RecipientWithDKDM const& i, recipient_kdms) {
+                       if (i.recipient->emails.empty ()) {
+                               recipients_with_no_email = true;
+                       }
+               }
+
+               if (proceed && recipients_with_no_email && !confirm_dialog (
+                           this,
+                           _("You have selected some recipients that have no configured email address.  Do you want to continue?")
+                           )) {
+                       proceed = false;
+               }
+
+               if (proceed && Config::instance()->confirm_kdm_email()) {
+                       list<string> emails;
+                       BOOST_FOREACH (RecipientWithDKDM i, recipient_kdms) {
+                               BOOST_FOREACH (string j, i.recipient->emails) {
+                                       emails.push_back (j);
+                               }
+                       }
+
+                       if (!emails.empty()) {
+                               ConfirmKDMEmailDialog* d = new ConfirmKDMEmailDialog (this, emails);
+                               if (d->ShowModal() == wxID_CANCEL) {
+                                       proceed = false;
+                               }
+                       }
+               }
+       }
+
+       if (!proceed) {
+               return make_pair (shared_ptr<Job>(), 0);
+       }
+
+       Config::instance()->set_dkdm_filename_format (_filename_format->get());
+
+       int written = 0;
+       shared_ptr<Job> job;
+
+       try {
+               dcp::NameFormat::Map name_values;
+               name_values['f'] = name;
+               name_values['b'] = dcp::LocalTime(timing->from()).date() + " " + dcp::LocalTime(timing->from()).time_of_day(false, false);
+               name_values['e'] = dcp::LocalTime(timing->until()).date() + " " + dcp::LocalTime(timing->until()).time_of_day(false, false);
+
+               written = RecipientWithDKDM::write_files (
+                               recipient_kdms,
+                               directory(),
+                               _filename_format->get(),
+                               name_values,
+                               confirm_overwrite
+                               );
+
+               if (_email->GetValue()) {
+                       job.reset (
+                               new SendDKDMEmailJob (
+                                       recipient_kdms,
+                                       _filename_format->get(),
+                                       name_values,
+                                       name
+                                       )
+                               );
+               }
+
+       } catch (dcp::NotEncryptedError& e) {
+               error_dialog (this, _("CPL's content is not encrypted."));
+       } catch (exception& e) {
+               error_dialog (this, std_to_wx(e.what()));
+       } catch (...) {
+               error_dialog (this, _("An unknown exception occurred."));
+       }
+
+       return make_pair (job, written);
+}
+
+
+boost::filesystem::path
+DKDMOutputPanel::directory () const
+{
+       return wx_to_std (_folder->GetPath());
+}
+
diff --git a/src/wx/dkdm_output_panel.h b/src/wx/dkdm_output_panel.h
new file mode 100644 (file)
index 0000000..cd2dfd6
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+    Copyright (C) 2015-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 "wx_util.h"
+#include "name_format_editor.h"
+#include "lib/screen_with_kdm.h"
+#include "lib/dkdm_recipient.h"
+#include "lib/recipient_with_dkdm.h"
+#include <dcp/types.h>
+#include <wx/wx.h>
+#include <boost/filesystem.hpp>
+
+class wxRadioButton;
+class wxDirPickerCtrl;
+class DirPickerCtrl;
+class KDMTimingPanel;
+class Job;
+class Log;
+
+class DKDMOutputPanel : public wxPanel
+{
+public:
+       DKDMOutputPanel (wxWindow* parent);
+
+       void setup_sensitivity ();
+
+       boost::filesystem::path directory () const;
+
+       std::pair<boost::shared_ptr<Job>, int> make (
+               std::vector<RecipientWithDKDM> recipient_kdms,
+               std::string name,
+               KDMTimingPanel* timing,
+               boost::function<bool (boost::filesystem::path)> confirm_overwrite
+               );
+
+private:
+       NameFormatEditor* _filename_format;
+       wxCheckBox* _write_to;
+#ifdef DCPOMATIC_USE_OWN_PICKER
+       DirPickerCtrl* _folder;
+#else
+       wxDirPickerCtrl* _folder;
+#endif
+       wxCheckBox* _email;
+};
diff --git a/src/wx/recipient_dialog.cc b/src/wx/recipient_dialog.cc
new file mode 100644 (file)
index 0000000..4fc7189
--- /dev/null
@@ -0,0 +1,234 @@
+/*
+    Copyright (C) 2012-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 "recipient_dialog.h"
+#include "wx_util.h"
+#include "static_text.h"
+#include "lib/dcpomatic_assert.h"
+#include "lib/util.h"
+#include <dcp/exceptions.h>
+#include <dcp/certificate_chain.h>
+#include <boost/foreach.hpp>
+
+using std::string;
+using std::vector;
+using std::copy;
+using std::back_inserter;
+using std::list;
+using std::cout;
+using boost::bind;
+using boost::optional;
+
+static string
+column (string s)
+{
+       return s;
+}
+
+RecipientDialog::RecipientDialog (
+               wxWindow* parent,
+               wxString title,
+               string name,
+               list<string> emails,
+               string notes,
+               int utc_offset_hour,
+               int utc_offset_minute,
+               optional<dcp::Certificate> recipient)
+       : wxDialog (parent, wxID_ANY, title)
+{
+       wxBoxSizer* overall_sizer = new wxBoxSizer (wxVERTICAL);
+       SetSizer (overall_sizer);
+
+       _sizer = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
+       int r = 0;
+
+       add_label_to_sizer (_sizer, this, _("Name"), true, wxGBPosition (r, 0));
+       _name = new wxTextCtrl (this, wxID_ANY, std_to_wx (name), wxDefaultPosition, wxSize (500, -1));
+       _sizer->Add (_name, wxGBPosition (r, 1));
+       ++r;
+
+       add_label_to_sizer (_sizer, this, _("UTC offset (time zone)"), true, wxGBPosition(r, 0));
+       _utc_offset = new wxChoice (this, wxID_ANY);
+       _sizer->Add (_utc_offset, wxGBPosition (r, 1));
+       ++r;
+
+       add_label_to_sizer (_sizer, this, _("Notes"), true, wxGBPosition(r, 0));
+       _notes = new wxTextCtrl (this, wxID_ANY, std_to_wx (notes), wxDefaultPosition, wxSize(500, -1));
+       _sizer->Add (_notes, wxGBPosition (r, 1));
+       ++r;
+
+       add_label_to_sizer (_sizer, this, _("Email addresses for DKDM delivery"), false, wxGBPosition(r, 0), wxGBSpan(1, 2));
+       ++r;
+
+       copy (emails.begin(), emails.end(), back_inserter(_emails));
+
+       vector<EditableListColumn> columns;
+       columns.push_back (EditableListColumn(wx_to_std(_("Address"))));
+       _email_list = new EditableList<string, EmailDialog> (
+               this, columns, bind(&RecipientDialog::get_emails, this), bind(&RecipientDialog::set_emails, this, _1), bind(&column, _1)
+               );
+
+       _sizer->Add (_email_list, wxGBPosition(r, 0), wxGBSpan(1, 2), wxEXPAND);
+       ++r;
+
+        wxClientDC dc (this);
+       wxFont font = _name->GetFont ();
+       font.SetFamily (wxFONTFAMILY_TELETYPE);
+       dc.SetFont (font);
+        wxSize size = dc.GetTextExtent (wxT ("1234567890123456789012345678"));
+        size.SetHeight (-1);
+
+       add_label_to_sizer (_sizer, this, _("Recipient certificate"), true, wxGBPosition (r, 0));
+       wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
+       _recipient_thumbprint = new StaticText (this, wxT(""), wxDefaultPosition, size);
+       _recipient_thumbprint->SetFont (font);
+       set_recipient (recipient);
+       _get_recipient = new Button (this, _("Get from file..."));
+       s->Add (_recipient_thumbprint, 1, wxLEFT | wxRIGHT | wxALIGN_CENTER_VERTICAL, DCPOMATIC_SIZER_X_GAP);
+       s->Add (_get_recipient, 0, wxLEFT | wxRIGHT | wxEXPAND, DCPOMATIC_SIZER_X_GAP);
+       _sizer->Add (s, wxGBPosition(r, 1));
+       ++r;
+
+       overall_sizer->Add (_sizer, 1, wxEXPAND | wxALL, DCPOMATIC_DIALOG_BORDER);
+
+       wxSizer* buttons = CreateSeparatedButtonSizer (wxOK | wxCANCEL);
+       if (buttons) {
+               overall_sizer->Add (buttons, wxSizerFlags().Expand().DoubleBorder());
+       }
+
+       size_t sel = all_offsets (_offsets);
+
+       for (size_t i = 0; i < _offsets.size(); ++i) {
+               _utc_offset->Append (_offsets[i].name);
+               if (_offsets[i].hour == utc_offset_hour && _offsets[i].minute == utc_offset_minute) {
+                       sel = i;
+               }
+       }
+
+       _utc_offset->SetSelection (sel);
+
+       _get_recipient->Bind (wxEVT_BUTTON, boost::bind (&RecipientDialog::get_recipient_from_file, this));
+
+       overall_sizer->Layout ();
+       overall_sizer->SetSizeHints (this);
+}
+
+string
+RecipientDialog::name () const
+{
+       return wx_to_std (_name->GetValue());
+}
+
+void
+RecipientDialog::set_emails (vector<string> e)
+{
+       _emails = e;
+}
+
+vector<string>
+RecipientDialog::get_emails () const
+{
+       return _emails;
+}
+
+list<string>
+RecipientDialog::emails () const
+{
+       list<string> e;
+       copy (_emails.begin(), _emails.end(), back_inserter(e));
+       return e;
+}
+
+int
+RecipientDialog::utc_offset_hour () const
+{
+       int const sel = _utc_offset->GetSelection();
+       if (sel < 0 || sel > int(_offsets.size())) {
+               return 0;
+       }
+
+       return _offsets[sel].hour;
+}
+
+int
+RecipientDialog::utc_offset_minute () const
+{
+       int const sel = _utc_offset->GetSelection();
+       if (sel < 0 || sel > int(_offsets.size())) {
+               return 0;
+       }
+
+       return _offsets[sel].minute;
+}
+
+string
+RecipientDialog::notes () const
+{
+       return wx_to_std (_notes->GetValue ());
+}
+
+
+void
+RecipientDialog::load_recipient (boost::filesystem::path file)
+{
+       try {
+               /* Load this as a chain, in case it is one, and then pick the leaf certificate */
+               dcp::CertificateChain c (dcp::file_to_string(file));
+               if (c.unordered().empty()) {
+                       error_dialog (this, _("Could not read certificate file."));
+                       return;
+               }
+               set_recipient (c.leaf());
+       } catch (dcp::MiscError& e) {
+               error_dialog (this, _("Could not read certificate file."), std_to_wx(e.what()));
+       }
+}
+
+
+void
+RecipientDialog::get_recipient_from_file ()
+{
+       wxFileDialog* d = new wxFileDialog (this, _("Select Certificate File"));
+       if (d->ShowModal() == wxID_OK) {
+               load_recipient (boost::filesystem::path(wx_to_std(d->GetPath())));
+       }
+       d->Destroy ();
+}
+
+
+void
+RecipientDialog::set_recipient (optional<dcp::Certificate> r)
+{
+       _recipient = r;
+
+       if (_recipient) {
+               _recipient_thumbprint->SetLabel (std_to_wx(_recipient->thumbprint()));
+               _sizer->Layout ();
+       }
+}
+
+
+optional<dcp::Certificate>
+RecipientDialog::recipient () const
+{
+       return _recipient;
+}
+
+
diff --git a/src/wx/recipient_dialog.h b/src/wx/recipient_dialog.h
new file mode 100644 (file)
index 0000000..4797158
--- /dev/null
@@ -0,0 +1,68 @@
+/*
+    Copyright (C) 2012-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 "editable_list.h"
+#include "email_dialog.h"
+#include <dcp/certificate.h>
+#include <wx/wx.h>
+#include <list>
+#include <vector>
+
+class RecipientDialog : public wxDialog
+{
+public:
+       RecipientDialog (
+               wxWindow *,
+               wxString,
+               std::string name = "",
+               std::list<std::string> emails = std::list<std::string>(),
+               std::string notes = "",
+               int utc_offset_hour = 0,
+               int utc_offset_minute = 0,
+               boost::optional<dcp::Certificate> c = boost::optional<dcp::Certificate> ()
+               );
+
+       std::string name () const;
+       std::list<std::string> emails () const;
+       std::string notes () const;
+       int utc_offset_hour () const;
+       int utc_offset_minute () const;
+       boost::optional<dcp::Certificate> recipient () const;
+
+private:
+       std::vector<std::string> get_emails () const;
+       void set_emails (std::vector<std::string>);
+       void set_recipient (boost::optional<dcp::Certificate>);
+       void get_recipient_from_file ();
+       void load_recipient (boost::filesystem::path);
+
+       wxGridBagSizer* _sizer;
+       wxTextCtrl* _name;
+       wxTextCtrl* _notes;
+       EditableList<std::string, EmailDialog>* _email_list;
+       std::vector<std::string> _emails;
+       wxChoice* _utc_offset;
+
+       std::vector<Offset> _offsets;
+       boost::optional<dcp::Certificate> _recipient;
+       wxStaticText* _recipient_thumbprint;
+       wxButton* _get_recipient;
+};
diff --git a/src/wx/recipients_panel.cc b/src/wx/recipients_panel.cc
new file mode 100644 (file)
index 0000000..3e52569
--- /dev/null
@@ -0,0 +1,249 @@
+/*
+    Copyright (C) 2015-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 "recipients_panel.h"
+#include "recipient_dialog.h"
+#include "wx_util.h"
+#include "dcpomatic_button.h"
+#include "lib/config.h"
+#include "lib/dkdm_recipient.h"
+#include <boost/foreach.hpp>
+
+using std::list;
+using std::pair;
+using std::cout;
+using std::map;
+using std::string;
+using std::make_pair;
+using boost::shared_ptr;
+using boost::optional;
+using namespace dcpomatic;
+
+RecipientsPanel::RecipientsPanel (wxWindow* parent)
+       : wxPanel (parent, wxID_ANY)
+       , _ignore_selection_change (false)
+{
+       wxBoxSizer* sizer = new wxBoxSizer (wxVERTICAL);
+
+       _search = new wxSearchCtrl (this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (200, -1));
+       _search->ShowCancelButton (true);
+       sizer->Add (_search, 0, wxBOTTOM, DCPOMATIC_SIZER_GAP);
+
+       wxBoxSizer* targets = new wxBoxSizer (wxHORIZONTAL);
+       _targets = new wxTreeCtrl (this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTR_HIDE_ROOT | wxTR_MULTIPLE | wxTR_HAS_BUTTONS | wxTR_LINES_AT_ROOT);
+       targets->Add (_targets, 1, wxEXPAND | wxRIGHT, DCPOMATIC_SIZER_GAP);
+
+       add_recipients_from_config ();
+
+       wxBoxSizer* target_buttons = new wxBoxSizer (wxVERTICAL);
+
+       _add_recipient = new Button (this, _("Add Recipient..."));
+       target_buttons->Add (_add_recipient, 1, wxEXPAND | wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
+       _edit_recipient = new Button (this, _("Edit Recipient..."));
+       target_buttons->Add (_edit_recipient, 1, wxEXPAND | wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
+       _remove_recipient = new Button (this, _("Remove Recipient"));
+       target_buttons->Add (_remove_recipient, 1, wxEXPAND | wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
+
+       targets->Add (target_buttons, 0, 0);
+
+       sizer->Add (targets, 1, wxEXPAND);
+
+       _search->Bind        (wxEVT_TEXT, boost::bind (&RecipientsPanel::search_changed, this));
+       _targets->Bind       (wxEVT_TREE_SEL_CHANGED, &RecipientsPanel::selection_changed_shim, this);
+
+       _add_recipient->Bind    (wxEVT_BUTTON, boost::bind (&RecipientsPanel::add_recipient_clicked, this));
+       _edit_recipient->Bind   (wxEVT_BUTTON, boost::bind (&RecipientsPanel::edit_recipient_clicked, this));
+       _remove_recipient->Bind (wxEVT_BUTTON, boost::bind (&RecipientsPanel::remove_recipient_clicked, this));
+
+       SetSizer (sizer);
+}
+
+
+RecipientsPanel::~RecipientsPanel ()
+{
+       _targets->Unbind (wxEVT_TREE_SEL_CHANGED, &RecipientsPanel::selection_changed_shim, this);
+}
+
+
+void
+RecipientsPanel::setup_sensitivity ()
+{
+       bool const s = _selected.size() == 1;
+
+       _edit_recipient->Enable (s);
+       _remove_recipient->Enable (_selected.size() >= 1);
+}
+
+
+void
+RecipientsPanel::add_recipient (shared_ptr<DKDMRecipient> r)
+{
+       string search = wx_to_std (_search->GetValue());
+       transform (search.begin(), search.end(), search.begin(), ::tolower);
+
+       if (!search.empty()) {
+               string name = r->name;
+               transform (name.begin(), name.end(), name.begin(), ::tolower);
+               if (name.find(search) == string::npos) {
+                       return;
+               }
+       }
+
+       _recipients[_targets->AppendItem(_root, std_to_wx(r->name))] = r;
+
+       _targets->SortChildren (_root);
+}
+
+
+void
+RecipientsPanel::add_recipient_clicked ()
+{
+       RecipientDialog* d = new RecipientDialog (GetParent(), _("Add Recipient"));
+       if (d->ShowModal() == wxID_OK) {
+               shared_ptr<DKDMRecipient> r (new DKDMRecipient(d->name(), d->notes(), d->recipient(), d->emails(), d->utc_offset_hour(), d->utc_offset_minute()));
+               Config::instance()->add_dkdm_recipient (r);
+               add_recipient (r);
+       }
+
+       d->Destroy ();
+}
+
+
+void
+RecipientsPanel::edit_recipient_clicked ()
+{
+       if (_selected.size() != 1) {
+               return;
+       }
+
+       pair<wxTreeItemId, shared_ptr<DKDMRecipient> > r = *_selected.begin();
+
+       RecipientDialog* d = new RecipientDialog (
+               GetParent(), _("Edit recipient"), r.second->name, r.second->emails, r.second->notes, r.second->utc_offset_hour(), r.second->utc_offset_minute(), r.second->recipient
+               );
+
+       if (d->ShowModal() == wxID_OK) {
+               r.second->name = d->name ();
+               r.second->emails = d->emails ();
+               r.second->notes = d->notes ();
+               r.second->set_utc_offset_hour (d->utc_offset_hour());
+               r.second->set_utc_offset_minute (d->utc_offset_minute());
+               r.second->recipient = d->recipient ();
+               _targets->SetItemText (r.first, std_to_wx(d->name()));
+               Config::instance()->changed (Config::DKDM_RECIPIENTS);
+       }
+
+       d->Destroy ();
+}
+
+
+void
+RecipientsPanel::remove_recipient_clicked ()
+{
+       for (RecipientMap::iterator i = _selected.begin(); i != _selected.end(); ++i) {
+               Config::instance()->remove_dkdm_recipient (i->second);
+               _targets->Delete (i->first);
+       }
+
+       selection_changed ();
+}
+
+
+list<shared_ptr<DKDMRecipient> >
+RecipientsPanel::recipients () const
+{
+       list<shared_ptr<DKDMRecipient> > s;
+
+       for (RecipientMap::const_iterator i = _selected.begin(); i != _selected.end(); ++i) {
+               s.push_back (i->second);
+       }
+
+       s.sort ();
+       s.unique ();
+
+       return s;
+}
+
+
+void
+RecipientsPanel::selection_changed_shim (wxTreeEvent &)
+{
+       selection_changed ();
+}
+
+
+void
+RecipientsPanel::selection_changed ()
+{
+       if (_ignore_selection_change) {
+               return;
+       }
+
+       wxArrayTreeItemIds s;
+       _targets->GetSelections (s);
+
+       _selected.clear ();
+
+       for (size_t i = 0; i < s.GetCount(); ++i) {
+               RecipientMap::const_iterator j = _recipients.find (s[i]);
+               if (j != _recipients.end()) {
+                       _selected[j->first] = j->second;
+               }
+       }
+
+       setup_sensitivity ();
+       RecipientsChanged ();
+}
+
+
+void
+RecipientsPanel::add_recipients_from_config ()
+{
+       _root = _targets->AddRoot ("Foo");
+
+       BOOST_FOREACH (shared_ptr<DKDMRecipient> i, Config::instance()->dkdm_recipients()) {
+               add_recipient (i);
+       }
+}
+
+
+void
+RecipientsPanel::search_changed ()
+{
+       _targets->DeleteAllItems ();
+       _recipients.clear ();
+
+       add_recipients_from_config ();
+
+       _ignore_selection_change = true;
+
+       for (RecipientMap::const_iterator i = _selected.begin(); i != _selected.end(); ++i) {
+               RecipientMap::const_iterator j = _recipients.begin ();
+               while (j != _recipients.end() && j->second != i->second) {
+                       ++j;
+               }
+
+               if (j != _recipients.end()) {
+                       _targets->SelectItem (j->first);
+               }
+       }
+
+       _ignore_selection_change = false;
+}
diff --git a/src/wx/recipients_panel.h b/src/wx/recipients_panel.h
new file mode 100644 (file)
index 0000000..65ba6dd
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+    Copyright (C) 2015-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 <wx/wx.h>
+#include <wx/srchctrl.h>
+#include <wx/treectrl.h>
+#include <boost/shared_ptr.hpp>
+#include <boost/signals2.hpp>
+#include <list>
+#include <map>
+
+class DKDMRecipient;
+
+class RecipientsPanel : public wxPanel
+{
+public:
+       explicit RecipientsPanel (wxWindow* parent);
+       ~RecipientsPanel ();
+
+       std::list<boost::shared_ptr<DKDMRecipient> > recipients () const;
+       void setup_sensitivity ();
+
+       boost::signals2::signal<void ()> RecipientsChanged;
+
+private:
+       void add_recipients_from_config ();
+       void add_recipient (boost::shared_ptr<DKDMRecipient>);
+       void add_recipient_clicked ();
+       void edit_recipient_clicked ();
+       void remove_recipient_clicked ();
+       void selection_changed_shim (wxTreeEvent &);
+       void selection_changed ();
+       void search_changed ();
+
+       wxSearchCtrl* _search;
+       wxTreeCtrl* _targets;
+       wxButton* _add_recipient;
+       wxButton* _edit_recipient;
+       wxButton* _remove_recipient;
+       wxTreeItemId _root;
+
+       typedef std::map<wxTreeItemId, boost::shared_ptr<DKDMRecipient> > RecipientMap;
+
+       RecipientMap _recipients;
+       RecipientMap _selected;
+
+       bool _ignore_selection_change;
+};
index f78a8c61762ba71fda5a59ebf19c63b2b42ad698..dce83c5ed6e2b598cd2369249e94a7d9eca96c49 100644 (file)
@@ -52,6 +52,8 @@ sources = """
           dcp_panel.cc
           dcpomatic_button.cc
           disk_warning_dialog.cc
+          dkdm_dialog.cc
+          dkdm_output_panel.cc
           drive_wipe_warning_dialog.cc
           email_dialog.cc
           image_sequence_dialog.cc
@@ -110,6 +112,8 @@ sources = """
           question_dialog.cc
           rating_dialog.cc
           qube_certificate_panel.cc
+          recipient_dialog.cc
+          recipients_panel.cc
           recreate_chain_dialog.cc
           repeat_dialog.cc
           report_problem_dialog.cc
index 28f79431a5147171626e1b45b90d51630b4420c9..9486b193548d7ed0d995ce44a1af38ac1e92d56b 100644 (file)
@@ -1,5 +1,5 @@
 /*
-    Copyright (C) 2012-2019 Carl Hetherington <cth@carlh.net>
+    Copyright (C) 2012-2020 Carl Hetherington <cth@carlh.net>
 
     This file is part of DCP-o-matic.
 
@@ -530,3 +530,43 @@ display_progress (wxString title, wxString task)
 
        return ok;
 }
+
+
+size_t
+all_offsets (vector<Offset>& offsets)
+{
+       size_t utc_index = 0;
+
+       offsets.push_back (Offset(_("UTC-11"),  -11,  0));
+       offsets.push_back (Offset(_("UTC-10"),  -10,  0));
+       offsets.push_back (Offset(_("UTC-9"),    -9,  0));
+       offsets.push_back (Offset(_("UTC-8"),    -8,  0));
+       offsets.push_back (Offset(_("UTC-7"),    -7,  0));
+       offsets.push_back (Offset(_("UTC-6"),    -6,  0));
+       offsets.push_back (Offset(_("UTC-5"),    -5,  0));
+       offsets.push_back (Offset(_("UTC-4:30"), -4, 30));
+       offsets.push_back (Offset(_("UTC-4"),    -4,  0));
+       offsets.push_back (Offset(_("UTC-3:30"), -3, 30));
+       offsets.push_back (Offset(_("UTC-3"),    -3,  0));
+       offsets.push_back (Offset(_("UTC-2"),    -2,  0));
+       offsets.push_back (Offset(_("UTC-1"),    -1,  0));
+       utc_index = offsets.size();
+       offsets.push_back (Offset(_("UTC")  ,     0,  0));
+       offsets.push_back (Offset(_("UTC+1"),     1,  0));
+       offsets.push_back (Offset(_("UTC+2"),     2,  0));
+       offsets.push_back (Offset(_("UTC+3"),     3,  0));
+       offsets.push_back (Offset(_("UTC+4"),     4,  0));
+       offsets.push_back (Offset(_("UTC+5"),     5,  0));
+       offsets.push_back (Offset(_("UTC+5:30"),  5, 30));
+       offsets.push_back (Offset(_("UTC+6"),     6,  0));
+       offsets.push_back (Offset(_("UTC+7"),     7,  0));
+       offsets.push_back (Offset(_("UTC+8"),     8,  0));
+       offsets.push_back (Offset(_("UTC+9"),     9,  0));
+       offsets.push_back (Offset(_("UTC+9:30"),  9, 30));
+       offsets.push_back (Offset(_("UTC+10"),   10,  0));
+       offsets.push_back (Offset(_("UTC+11"),   11,  0));
+       offsets.push_back (Offset(_("UTC+12"),   12,  0));
+
+       return utc_index;
+}
+
index af25ce0f5e1851ba1ca1fdcf086ff6564b3f10da..b357ef55b0f5d4c882d1c3d4e703cdda0cc2c6aa 100644 (file)
@@ -88,6 +88,21 @@ extern double calculate_mark_interval (double start);
 extern bool display_progress (wxString title, wxString task);
 extern bool report_errors_from_last_job (wxWindow* parent);
 
+struct Offset
+{
+       Offset (wxString n, int h, int m)
+         : name (n)
+         , hour (h)
+         , minute (m)
+       {}
+
+       wxString name;
+       int hour;
+       int minute;
+};
+
+extern size_t all_offsets(std::vector<Offset> &);
+
 extern void checked_set (FilePickerCtrl* widget, boost::filesystem::path value);
 extern void checked_set (wxDirPickerCtrl* widget, boost::filesystem::path value);
 extern void checked_set (wxSpinCtrl* widget, int value);