From 2a1c9e9cfeb1049b1ea0e3b300029218f652f002 Mon Sep 17 00:00:00 2001 From: Carl Hetherington Date: Sun, 3 May 2020 23:31:29 +0200 Subject: [PATCH] wip: stuff. --- src/lib/config.cc | 102 ++++++++++++-- src/lib/config.h | 49 +++++++ src/lib/dkdm_recipient.cc | 69 +++++++++ src/lib/dkdm_recipient.h | 72 ++++++++++ src/lib/kdm_recipient.h | 5 + src/lib/recipient_with_dkdm.cc | 149 ++++++++++++++++++++ src/lib/recipient_with_dkdm.h | 55 ++++++++ src/lib/send_dkdm_email_job.cc | 88 ++++++++++++ src/lib/send_dkdm_email_job.h | 54 +++++++ src/lib/wscript | 3 + src/tools/dcpomatic.cc | 35 +++++ src/wx/cinema_dialog.cc | 33 +---- src/wx/cinema_dialog.h | 13 -- src/wx/dkdm_dialog.cc | 197 ++++++++++++++++++++++++++ src/wx/dkdm_dialog.h | 51 +++++++ src/wx/dkdm_output_panel.cc | 207 +++++++++++++++++++++++++++ src/wx/dkdm_output_panel.h | 62 ++++++++ src/wx/recipient_dialog.cc | 234 +++++++++++++++++++++++++++++++ src/wx/recipient_dialog.h | 68 +++++++++ src/wx/recipients_panel.cc | 249 +++++++++++++++++++++++++++++++++ src/wx/recipients_panel.h | 65 +++++++++ src/wx/wscript | 4 + src/wx/wx_util.cc | 42 +++++- src/wx/wx_util.h | 15 ++ 24 files changed, 1864 insertions(+), 57 deletions(-) create mode 100644 src/lib/dkdm_recipient.cc create mode 100644 src/lib/dkdm_recipient.h create mode 100644 src/lib/recipient_with_dkdm.cc create mode 100644 src/lib/recipient_with_dkdm.h create mode 100644 src/lib/send_dkdm_email_job.cc create mode 100644 src/lib/send_dkdm_email_job.h create mode 100644 src/wx/dkdm_dialog.cc create mode 100644 src/wx/dkdm_dialog.h create mode 100644 src/wx/dkdm_output_panel.cc create mode 100644 src/wx/dkdm_output_panel.h create mode 100644 src/wx/recipient_dialog.cc create mode 100644 src/wx/recipient_dialog.h create mode 100644 src/wx/recipients_panel.cc create mode 100644 src/wx/recipients_panel.h diff --git a/src/lib/config.cc b/src/lib/config.cc index 7364a122c..93ecc8e92 100644 --- a/src/lib/config.cc +++ b/src/lib/config.cc @@ -1,5 +1,5 @@ /* - Copyright (C) 2012-2019 Carl Hetherington + Copyright (C) 2012-2020 Carl Hetherington 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("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 void -Config::write_cinemas () const +write_list (string root_node, string version, string child_node, list > 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 i, _cinemas) { - i->as_xml (root->add_child ("Cinema")); + BOOST_FOREACH (shared_ptr 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("Cinemas", "1", "Cinema", _cinemas, _cinemas_file); +} + + +void +Config::write_dkdm_recipients () const +{ + write_list("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 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 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(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 film, string name) const { diff --git a/src/lib/config.h b/src/lib/config.h index 749407403..a63061780 100644 --- a/src/lib/config.h +++ b/src/lib/config.h @@ -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 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 > dkdm_recipients () const { + return _dkdm_recipients; + } + std::list 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 r) { + _dkdm_recipients.push_back (r); + changed (DKDM_RECIPIENTS); + } + + void remove_dkdm_recipient (boost::shared_ptr r) { + _dkdm_recipients.remove (r); + changed (DKDM_RECIPIENTS); + } + void set_allowed_dcp_frame_rates (std::list 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 create_certificate_chain (); boost::filesystem::path directory_or (boost::optional dir, boost::filesystem::path a) const; void add_to_history_internal (std::vector& h, boost::filesystem::path p); @@ -1213,8 +1258,10 @@ private: the home directory will be offered. */ boost::optional _default_kdm_directory; + boost::optional _default_dkdm_directory; bool _default_upload_after_make_dcp; std::list > _cinemas; + std::list > _dkdm_recipients; std::string _mail_server; int _mail_port; EmailProtocol _mail_protocol; @@ -1257,9 +1304,11 @@ private: std::vector _player_history; boost::shared_ptr _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 index 000000000..2cfecf316 --- /dev/null +++ b/src/lib/dkdm_recipient.cc @@ -0,0 +1,69 @@ +/* + Copyright (C) 2020 Carl Hetherington + + 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 . + +*/ + +#include "dkdm_recipient.h" +#include "dcpomatic_assert.h" +#include +#include +#include + +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("UTCOffsetHour"); + _utc_offset_minute = node->number_child("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(_utc_offset_hour)); + parent->add_child("UTCOffsetMinute")->add_child_text(raw_convert(_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 index 000000000..77ef4e2e5 --- /dev/null +++ b/src/lib/dkdm_recipient.h @@ -0,0 +1,72 @@ +/* + Copyright (C) 2020 Carl Hetherington + + 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 . + +*/ + +#ifndef DCPOMATIC_DKDM_RECIPIENT_H +#define DCPOMATIC_DKDM_RECIPIENT_H + +#include "kdm_recipient.h" +#include +#include + +class DKDMRecipient : public KDMRecipient +{ +public: + DKDMRecipient ( + std::string const& name_, + std::string const& notes_, + boost::optional recipient_, + std::list 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 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 diff --git a/src/lib/kdm_recipient.h b/src/lib/kdm_recipient.h index ee4e8c39c..c0533daeb 100644 --- a/src/lib/kdm_recipient.h +++ b/src/lib/kdm_recipient.h @@ -18,6 +18,9 @@ */ +#ifndef DCPOMATIC_KDM_RECIPIENT_H +#define DCPOMATIC_KDM_RECIPIENT_H + #include #include #include @@ -41,3 +44,5 @@ public: std::string notes; boost::optional recipient; }; + +#endif diff --git a/src/lib/recipient_with_dkdm.cc b/src/lib/recipient_with_dkdm.cc new file mode 100644 index 000000000..ebe966fe4 --- /dev/null +++ b/src/lib/recipient_with_dkdm.cc @@ -0,0 +1,149 @@ +/* + Copyright (C) 2020 Carl Hetherington + + 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 . + +*/ + +#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 + +#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 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 kdms, + boost::filesystem::path directory, + dcp::NameFormat name_format, + dcp::NameFormat::Map name_values, + boost::function 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 index 000000000..8aed95275 --- /dev/null +++ b/src/lib/recipient_with_dkdm.h @@ -0,0 +1,55 @@ +/* + Copyright (C) 2020 Carl Hetherington + + 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 . + +*/ + +#ifndef DCPOMATIC_RECIPIENT_WITH_DKDM_H +#define DCPOMATIC_RECIPIENT_WITH_DKDM_H + +#include "dkdm_recipient.h" +#include +#include + +class RecipientWithDKDM +{ +public: + RecipientWithDKDM (boost::shared_ptr 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 kdms, boost::filesystem::path directory, + dcp::NameFormat name_format, dcp::NameFormat::Map name_values, + boost::function confirm_overwrite + ); + + static void email ( + std::vector kdms, + dcp::NameFormat filename_format, + dcp::NameFormat::Map name_values, + std::string cpl_name + ); + + boost::shared_ptr 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 index 000000000..54334aa4a --- /dev/null +++ b/src/lib/send_dkdm_email_job.cc @@ -0,0 +1,88 @@ +/* + Copyright (C) 2013 Carl Hetherington + + 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 . + +*/ + +#include "send_dkdm_email_job.h" +#include "compose.hpp" +#include "film.h" +#include "cinema_kdms.h" +#include + +#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 kdms, + dcp::NameFormat filename_format, + dcp::NameFormat::Map name_values, + string cpl_name + ) + : Job (shared_ptr()) + , _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 index 000000000..2b475574e --- /dev/null +++ b/src/lib/send_dkdm_email_job.h @@ -0,0 +1,54 @@ +/* + Copyright (C) 2013-2020 Carl Hetherington + + 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 . + +*/ + +#include "job.h" +#include "recipient_with_dkdm.h" +#include +#include +#include + +namespace dcpomatic { + class Screen; +} + +class CinemaKDMs; +class Log; + +class SendDKDMEmailJob : public Job +{ +public: + SendDKDMEmailJob ( + std::vector 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 _kdms; +}; diff --git a/src/lib/wscript b/src/lib/wscript index ce14532dd..1f9b1d176 100644 --- a/src/lib/wscript +++ b/src/lib/wscript @@ -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 diff --git a/src/tools/dcpomatic.cc b/src/tools/dcpomatic.cc index ffc8a35fc..c5d6b669d 100644 --- a/src/tools/dcpomatic.cc +++ b/src/tools/dcpomatic.cc @@ -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 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; diff --git a/src/wx/cinema_dialog.cc b/src/wx/cinema_dialog.cc index ff5d1faf6..baaeb9388 100644 --- a/src/wx/cinema_dialog.cc +++ b/src/wx/cinema_dialog.cc @@ -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) { diff --git a/src/wx/cinema_dialog.h b/src/wx/cinema_dialog.h index 9cb0a65dc..4627a6b41 100644 --- a/src/wx/cinema_dialog.h +++ b/src/wx/cinema_dialog.h @@ -54,18 +54,5 @@ private: std::vector _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 _offsets; }; diff --git a/src/wx/dkdm_dialog.cc b/src/wx/dkdm_dialog.cc new file mode 100644 index 000000000..6339a66cf --- /dev/null +++ b/src/wx/dkdm_dialog.cc @@ -0,0 +1,197 @@ +/* + Copyright (C) 2012-2020 Carl Hetherington + + 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 . + +*/ + +#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 +#include +#include +#include +#include + +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 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 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 film = _film.lock (); + DCPOMATIC_ASSERT (film); + + vector recipient_kdms; + try { + BOOST_FOREACH (shared_ptr i, _recipients->recipients()) { + if (i->recipient) { + dcp::EncryptedKDM const kdm = film->make_kdm ( + i->recipient.get(), + vector(), + _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() + ); + + 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, 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 index 000000000..ba5a9818b --- /dev/null +++ b/src/wx/dkdm_dialog.h @@ -0,0 +1,51 @@ +/* + Copyright (C) 2012-2020 Carl Hetherington + + 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 . + +*/ + +#include "wx_util.h" +#include +#include +#include +#include +#include + +class RecipientsPanel; +class Film; +class KDMTimingPanel; +class DKDMOutputPanel; +class KDMCPLPanel; +struct CPLSummary; + +class DKDMDialog : public wxDialog +{ +public: + DKDMDialog (wxWindow *, boost::shared_ptr film); + +private: + void setup_sensitivity (); + void make_clicked (); + bool confirm_overwrite (boost::filesystem::path path); + + boost::weak_ptr _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 index 000000000..00e8e8463 --- /dev/null +++ b/src/wx/dkdm_output_panel.cc @@ -0,0 +1,207 @@ +/* + Copyright (C) 2015-2020 Carl Hetherington + + 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 . + +*/ + +#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 +#include +#ifdef DCPOMATIC_USE_OWN_PICKER +#include "dir_picker_ctrl.h" +#else +#include +#endif +#include + +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 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, int> +DKDMOutputPanel::make ( + vector recipient_kdms, string name, KDMTimingPanel* timing, function 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 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(), 0); + } + + Config::instance()->set_dkdm_filename_format (_filename_format->get()); + + int written = 0; + shared_ptr 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 index 000000000..cd2dfd600 --- /dev/null +++ b/src/wx/dkdm_output_panel.h @@ -0,0 +1,62 @@ +/* + Copyright (C) 2015-2020 Carl Hetherington + + 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 . + +*/ + +#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 +#include +#include + +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, int> make ( + std::vector recipient_kdms, + std::string name, + KDMTimingPanel* timing, + boost::function 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 index 000000000..4fc7189af --- /dev/null +++ b/src/wx/recipient_dialog.cc @@ -0,0 +1,234 @@ +/* + Copyright (C) 2012-2020 Carl Hetherington + + 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 . + +*/ + +#include "recipient_dialog.h" +#include "wx_util.h" +#include "static_text.h" +#include "lib/dcpomatic_assert.h" +#include "lib/util.h" +#include +#include +#include + +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 emails, + string notes, + int utc_offset_hour, + int utc_offset_minute, + optional 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 columns; + columns.push_back (EditableListColumn(wx_to_std(_("Address")))); + _email_list = new EditableList ( + 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 e) +{ + _emails = e; +} + +vector +RecipientDialog::get_emails () const +{ + return _emails; +} + +list +RecipientDialog::emails () const +{ + list 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 r) +{ + _recipient = r; + + if (_recipient) { + _recipient_thumbprint->SetLabel (std_to_wx(_recipient->thumbprint())); + _sizer->Layout (); + } +} + + +optional +RecipientDialog::recipient () const +{ + return _recipient; +} + + diff --git a/src/wx/recipient_dialog.h b/src/wx/recipient_dialog.h new file mode 100644 index 000000000..479715842 --- /dev/null +++ b/src/wx/recipient_dialog.h @@ -0,0 +1,68 @@ +/* + Copyright (C) 2012-2020 Carl Hetherington + + 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 . + +*/ + +#include "table_dialog.h" +#include "editable_list.h" +#include "email_dialog.h" +#include +#include +#include +#include + +class RecipientDialog : public wxDialog +{ +public: + RecipientDialog ( + wxWindow *, + wxString, + std::string name = "", + std::list emails = std::list(), + std::string notes = "", + int utc_offset_hour = 0, + int utc_offset_minute = 0, + boost::optional c = boost::optional () + ); + + std::string name () const; + std::list emails () const; + std::string notes () const; + int utc_offset_hour () const; + int utc_offset_minute () const; + boost::optional recipient () const; + +private: + std::vector get_emails () const; + void set_emails (std::vector); + void set_recipient (boost::optional); + void get_recipient_from_file (); + void load_recipient (boost::filesystem::path); + + wxGridBagSizer* _sizer; + wxTextCtrl* _name; + wxTextCtrl* _notes; + EditableList* _email_list; + std::vector _emails; + wxChoice* _utc_offset; + + std::vector _offsets; + boost::optional _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 index 000000000..3e52569aa --- /dev/null +++ b/src/wx/recipients_panel.cc @@ -0,0 +1,249 @@ +/* + Copyright (C) 2015-2020 Carl Hetherington + + 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 . + +*/ + +#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 + +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 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 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 > 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 > +RecipientsPanel::recipients () const +{ + list > 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 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 index 000000000..65ba6dd67 --- /dev/null +++ b/src/wx/recipients_panel.h @@ -0,0 +1,65 @@ +/* + Copyright (C) 2015-2020 Carl Hetherington + + 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 . + +*/ + +#include +#include +#include +#include +#include +#include +#include + +class DKDMRecipient; + +class RecipientsPanel : public wxPanel +{ +public: + explicit RecipientsPanel (wxWindow* parent); + ~RecipientsPanel (); + + std::list > recipients () const; + void setup_sensitivity (); + + boost::signals2::signal RecipientsChanged; + +private: + void add_recipients_from_config (); + void add_recipient (boost::shared_ptr); + 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 > RecipientMap; + + RecipientMap _recipients; + RecipientMap _selected; + + bool _ignore_selection_change; +}; diff --git a/src/wx/wscript b/src/wx/wscript index f78a8c617..dce83c5ed 100644 --- a/src/wx/wscript +++ b/src/wx/wscript @@ -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 diff --git a/src/wx/wx_util.cc b/src/wx/wx_util.cc index 28f79431a..9486b1935 100644 --- a/src/wx/wx_util.cc +++ b/src/wx/wx_util.cc @@ -1,5 +1,5 @@ /* - Copyright (C) 2012-2019 Carl Hetherington + Copyright (C) 2012-2020 Carl Hetherington 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& 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; +} + diff --git a/src/wx/wx_util.h b/src/wx/wx_util.h index af25ce0f5..b357ef55b 100644 --- a/src/wx/wx_util.h +++ b/src/wx/wx_util.h @@ -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 &); + 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); -- 2.30.2