--- /dev/null
+/*
+ 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 "kdm_with_metadata.h"
+#include "film.h"
+#include <dcp/raw_convert.h>
+#include <boost/foreach.hpp>
+
+
+using std::string;
+using std::vector;
+using boost::shared_ptr;
+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* node) const
+{
+ KDMRecipient::as_xml (node);
+
+ BOOST_FOREACH (string i, emails) {
+ node->add_child("Email")->add_child_text(i);
+ }
+
+ node->add_child("UTCOffsetHour")->add_child_text(raw_convert<string>(utc_offset_hour));
+ node->add_child("UTCOffsetMinute")->add_child_text(raw_convert<string>(utc_offset_minute));
+}
+
+
+KDMWithMetadataPtr
+kdm_for_dkdm_recipient (
+ shared_ptr<const Film> film,
+ boost::filesystem::path cpl,
+ shared_ptr<DKDMRecipient> recipient,
+ boost::posix_time::ptime valid_from,
+ boost::posix_time::ptime valid_to
+ )
+{
+ if (!recipient->recipient) {
+ return KDMWithMetadataPtr();
+ }
+
+ dcp::LocalTime const begin(valid_from, recipient->utc_offset_hour, recipient->utc_offset_minute);
+ dcp::LocalTime const end (valid_to, recipient->utc_offset_hour, recipient->utc_offset_minute);
+
+ dcp::EncryptedKDM const kdm = film->make_kdm (
+ recipient->recipient.get(),
+ vector<string>(),
+ cpl,
+ begin,
+ end,
+ dcp::MODIFIED_TRANSITIONAL_1,
+ true,
+ 0
+ );
+
+ dcp::NameFormat::Map name_values;
+ name_values['f'] = film->name();
+ name_values['b'] = begin.date() + " " + begin.time_of_day(true, false);
+ name_values['e'] = end.date() + " " + end.time_of_day(true, false);
+ name_values['i'] = kdm.cpl_id();
+
+ return KDMWithMetadataPtr(new DCPKDMWithMetadata(name_values, 0, recipient->emails, kdm));
+}
+
--- /dev/null
+/*
+ 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 "kdm_recipient.h"
+#include "kdm_with_metadata.h"
+
+class Film;
+
+class DKDMRecipient : public KDMRecipient
+{
+public:
+ DKDMRecipient (
+ std::string const& name_,
+ std::string const& notes_,
+ boost::optional<dcp::Certificate> recipient_,
+ std::list<std::string> 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);
+
+ void as_xml (xmlpp::Element *) const;
+
+ std::list<std::string> emails;
+ int utc_offset_hour;
+ int utc_offset_minute;
+};
+
+
+KDMWithMetadataPtr
+kdm_for_dkdm_recipient (
+ boost::shared_ptr<const Film> film,
+ boost::filesystem::path cpl,
+ boost::shared_ptr<DKDMRecipient> recipient,
+ boost::posix_time::ptime valid_from,
+ boost::posix_time::ptime valid_to
+ );
+
--- /dev/null
+/*
+ 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/kdm_with_metadata.h"
+#include "lib/job_manager.h"
+#include "lib/config.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: Screens */
+ 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 DKDMs"));
+ 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);
+
+ list<KDMWithMetadataPtr> kdms;
+ try {
+ BOOST_FOREACH (shared_ptr<DKDMRecipient> i, _recipients->recipients()) {
+ KDMWithMetadataPtr p = kdm_for_dkdm_recipient (film, _cpl->cpl(), i, _timing->from(), _timing->until());
+ if (p) {
+ kdms.push_back (p);
+ }
+ }
+ } 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 (kdms, film->name(), 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())
+ );
+ }
+}
--- /dev/null
+/*
+ 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 <wx/wx.h>
+#include <boost/shared_ptr.hpp>
+#include <map>
+
+class Film;
+class ScreensPanel;
+class RecipientsPanel;
+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;
+};
--- /dev/null
+/*
+ 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/send_kdm_email_job.h"
+#include "dkdm_output_panel.h"
+#include "kdm_timing_panel.h"
+#include "confirm_kdm_email_dialog.h"
+#include "wx_util.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 boost::shared_ptr;
+using boost::function;
+
+
+DKDMOutputPanel::DKDMOutputPanel (wxWindow* parent)
+ : wxPanel (parent, wxID_ANY)
+{
+ wxFlexGridSizer* table = new wxFlexGridSizer (2, DCPOMATIC_SIZER_X_GAP, 0);
+ table->AddGrowableCol (1);
+
+ 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['f'] = "Bambi";
+ 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_kdm_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->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 (
+ list<KDMWithMetadataPtr> kdms, string name, 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 kdms_with_no_email = false;
+ BOOST_FOREACH (KDMWithMetadataPtr i, kdms) {
+ if (i->emails().empty()) {
+ kdms_with_no_email = true;
+ }
+ }
+
+ if (proceed && kdms_with_no_email && !confirm_dialog (
+ this,
+ _("You have selected some cinemas 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 (KDMWithMetadataPtr const& i, kdms) {
+ BOOST_FOREACH (string j, i->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 {
+ written = write_files (
+ kdms,
+ directory(),
+ _filename_format->get(),
+ confirm_overwrite
+ );
+
+ if (_email->GetValue ()) {
+ job.reset (
+ new SendKDMEmailJob (
+ kdms,
+ _filename_format->get(),
+ _filename_format->get(),
+ 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 ());
+}
--- /dev/null
+/*
+ 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/kdm_with_metadata.h"
+#include "wx_util.h"
+#include "name_format_editor.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::list<KDMWithMetadataPtr > kdms,
+ std::string name,
+ boost::function<bool (boost::filesystem::path)> confirm_overwrite
+ );
+
+private:
+ wxChoice* _type;
+ NameFormatEditor* _filename_format;
+ wxCheckBox* _write_to;
+#ifdef DCPOMATIC_USE_OWN_PICKER
+ DirPickerCtrl* _folder;
+#else
+ wxDirPickerCtrl* _folder;
+#endif
+ wxCheckBox* _email;
+};
--- /dev/null
+/*
+ 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 "file_dialog_wrapper.h"
+#include "static_text.h"
+#include "download_certificate_dialog.h"
+#include "table_dialog.h"
+#include "dcpomatic_button.h"
+#include "lib/compose.hpp"
+#include "lib/util.h"
+#include <dcp/exceptions.h>
+#include <dcp/certificate_chain.h>
+#include <wx/filepicker.h>
+#include <wx/validate.h>
+#include <iostream>
+
+using std::string;
+using std::cout;
+using std::vector;
+using std::list;
+using boost::optional;
+using boost::bind;
+
+
+static string
+column (string s)
+{
+ return s;
+}
+
+
+RecipientDialog::RecipientDialog (
+ wxWindow* parent, wxString title, string name, string notes, list<string> emails, int utc_offset_hour, int utc_offset_minute, optional<dcp::Certificate> recipient
+ )
+ : wxDialog (parent, wxID_ANY, title)
+ , _recipient (recipient)
+{
+ 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 (320, -1));
+ _sizer->Add (_name, 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 (320, -1));
+ _sizer->Add (_notes, 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, _("Email addresses for KDM 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_from_file = new Button (this, _("Get from file..."));
+ s->Add (_recipient_thumbprint, 1, wxLEFT | wxRIGHT | wxALIGN_CENTER_VERTICAL, DCPOMATIC_SIZER_X_GAP);
+ s->Add (_get_recipient_from_file, 0, wxLEFT | wxRIGHT | wxEXPAND, DCPOMATIC_SIZER_X_GAP);
+ _sizer->Add (s, wxGBPosition (r, 1));
+ ++r;
+
+ add_label_to_sizer (_sizer, this, _("Other trusted devices"), true, wxGBPosition (r, 0));
+ ++r;
+
+ _name->Bind (wxEVT_TEXT, boost::bind (&RecipientDialog::setup_sensitivity, this));
+ _get_recipient_from_file->Bind (wxEVT_BUTTON, boost::bind (&RecipientDialog::get_recipient_from_file, this));
+
+ overall_sizer->Add (_sizer, 1, wxEXPAND | wxALL, DCPOMATIC_DIALOG_BORDER);
+
+ wxSizer* buttons = CreateSeparatedButtonSizer (wxOK | wxCANCEL);
+ if (buttons) {
+ overall_sizer->Add (buttons, wxSizerFlags().Expand().DoubleBorder());
+ }
+
+ /* Default to UTC */
+ size_t sel = get_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);
+
+ overall_sizer->Layout ();
+ overall_sizer->SetSizeHints (this);
+
+ setup_sensitivity ();
+}
+
+
+string
+RecipientDialog::name () const
+{
+ return wx_to_std (_name->GetValue());
+}
+
+
+string
+RecipientDialog::notes () const
+{
+ return wx_to_std (_notes->GetValue());
+}
+
+
+optional<dcp::Certificate>
+RecipientDialog::recipient () const
+{
+ return _recipient;
+}
+
+
+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 ();
+
+ setup_sensitivity ();
+}
+
+
+void
+RecipientDialog::setup_sensitivity ()
+{
+ wxButton* ok = dynamic_cast<wxButton*> (FindWindowById (wxID_OK, this));
+ if (ok) {
+ ok->Enable (static_cast<bool>(_recipient) && !_name->GetValue().IsEmpty());
+ }
+}
+
+
+void
+RecipientDialog::set_recipient (optional<dcp::Certificate> r)
+{
+ _recipient = r;
+
+ if (_recipient) {
+ _recipient_thumbprint->SetLabel (std_to_wx (_recipient->thumbprint ()));
+ _sizer->Layout ();
+ }
+}
+
+
+vector<string>
+RecipientDialog::get_emails () const
+{
+ return _emails;
+}
+
+
+void
+RecipientDialog::set_emails (vector<string> e)
+{
+ _emails = e;
+}
+
+
+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;
+}
+
+
--- /dev/null
+/*
+ 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 "editable_list.h"
+#include "email_dialog.h"
+#include "wx_util.h"
+#include "lib/screen.h"
+#include <dcp/certificate.h>
+#include <wx/wx.h>
+#include <boost/shared_ptr.hpp>
+#include <boost/optional.hpp>
+
+class Progress;
+class TrustedDeviceDialog;
+
+class RecipientDialog : public wxDialog
+{
+public:
+ RecipientDialog (
+ wxWindow *,
+ wxString,
+ std::string name = "",
+ std::string notes = "",
+ std::list<std::string> emails = std::list<std::string>(),
+ int utc_offset_hour = 0,
+ int utc_offset_minute = 0,
+ boost::optional<dcp::Certificate> c = boost::optional<dcp::Certificate>()
+ );
+
+ std::string name () const;
+ std::string notes () const;
+ boost::optional<dcp::Certificate> recipient () const;
+ std::list<std::string> emails () const;
+ int utc_offset_hour () const;
+ int utc_offset_minute () const;
+
+private:
+ void get_recipient_from_file ();
+ void load_recipient (boost::filesystem::path);
+ void setup_sensitivity ();
+ void set_recipient (boost::optional<dcp::Certificate>);
+ std::vector<std::string> get_emails () const;
+ void set_emails (std::vector<std::string>);
+
+ wxGridBagSizer* _sizer;
+ wxTextCtrl* _name;
+ wxTextCtrl* _notes;
+ wxStaticText* _recipient_thumbprint;
+ wxButton* _get_recipient_from_file;
+ EditableList<std::string, EmailDialog>* _email_list;
+ std::vector<std::string> _emails;
+ wxChoice* _utc_offset;
+ std::vector<Offset> _offsets;
+
+ boost::optional<dcp::Certificate> _recipient;
+};
--- /dev/null
+/*
+ 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 "wx_util.h"
+#include "recipient_dialog.h"
+#include "dcpomatic_button.h"
+#include "lib/config.h"
+#include <boost/foreach.hpp>
+#include <list>
+#include <iostream>
+
+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 ();
+
+ wxBoxSizer* target_buttons = new wxBoxSizer (wxVERTICAL);
+
+ _add_recipient = new Button (this, _("Add..."));
+ target_buttons->Add (_add_recipient, 1, wxEXPAND | wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
+ _edit_recipient = new Button (this, _("Edit..."));
+ target_buttons->Add (_edit_recipient, 1, wxEXPAND | wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
+ _remove_recipient = new Button (this, _("Remove"));
+ 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 ()
+{
+ _edit_recipient->Enable (_selected.size() == 1);
+ _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> > c = *_selected.begin();
+
+ RecipientDialog* d = new RecipientDialog (
+ GetParent(), _("Edit recipient"), c.second->name, c.second->notes, c.second->emails, c.second->utc_offset_hour, c.second->utc_offset_minute, c.second->recipient
+ );
+
+ if (d->ShowModal () == wxID_OK) {
+ c.second->name = d->name ();
+ c.second->emails = d->emails ();
+ c.second->notes = d->notes ();
+ c.second->utc_offset_hour = d->utc_offset_hour ();
+ c.second->utc_offset_minute = d->utc_offset_minute ();
+ _targets->SetItemText (c.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> > r;
+
+ for (RecipientMap::const_iterator i = _selected.begin(); i != _selected.end(); ++i) {
+ r.push_back (i->second);
+ }
+
+ r.sort ();
+ r.unique ();
+
+ return r;
+}
+
+
+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 ()
+{
+ _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 ();
+
+ _ignore_selection_change = true;
+
+ for (RecipientMap::const_iterator i = _selected.begin(); i != _selected.end(); ++i) {
+ /* The wxTreeItemIds will now be different, so we must search by recipient */
+ 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;
+}
--- /dev/null
+/*
+ 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/dkdm_recipient.h"
+#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 ();
+
+ void setup_sensitivity ();
+
+ std::list<boost::shared_ptr<DKDMRecipient> > recipients () const;
+ boost::signals2::signal<void ()> RecipientsChanged;
+
+private:
+ void add_recipients ();
+ 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;
+};