+ wxArrayTreeItemIds sel;
+ _dkdm->GetSelections (sel);
+ auto group = dynamic_pointer_cast<DKDMGroup>(selected_dkdm());
+ auto dkdm = dynamic_pointer_cast<DKDM>(selected_dkdm());
+ _create->Enable (!_screens->screens().empty() && sel.GetCount() > 0 && dkdm);
+ _remove_dkdm->Enable (sel.GetCount() > 0 && (!group || group->name() != "root"));
+ _export_dkdm->Enable (sel.GetCount() > 0 && dkdm);
+ }
+
+ void dkdm_selection_changed()
+ {
+ _selected_dkdm = selected_dkdm();
+ setup_sensitivity();
+ }
+
+ void dkdm_expanded(wxTreeEvent& ev)
+ {
+ if (_ignore_expand) {
+ return;
+ }
+
+ auto iter = _dkdm_id.find(ev.GetItem());
+ if (iter != _dkdm_id.end()) {
+ _expanded_dkdm_groups.insert(iter->second);
+ }
+ }
+
+ void dkdm_collapsed(wxTreeEvent& ev)
+ {
+ auto iter = _dkdm_id.find(ev.GetItem());
+ if (iter != _dkdm_id.end()) {
+ _expanded_dkdm_groups.erase(iter->second);
+ }
+ }
+
+ void dkdm_begin_drag (wxTreeEvent& ev)
+ {
+ ev.Allow ();
+ }
+
+ void dkdm_end_drag (wxTreeEvent& ev)
+ {
+ auto from = _dkdm_id.find (_dkdm->GetSelection ());
+ auto to = _dkdm_id.find (ev.GetItem ());
+ if (from == _dkdm_id.end() || to == _dkdm_id.end() || from->first == to->first) {
+ return;
+ }
+
+ auto group = dynamic_pointer_cast<DKDMGroup> (to->second);
+ if (!group) {
+ group = to->second->parent();
+ }
+
+ DCPOMATIC_ASSERT (group);
+ DCPOMATIC_ASSERT (from->second->parent ());
+
+ from->second->parent()->remove (from->second);
+ add_dkdm(from->second, group, dynamic_pointer_cast<DKDM>(to->second));
+
+ update_dkdm_view();
+ }
+
+ void add_dkdm_clicked ()
+ {
+ auto dialog = make_wx<FileDialog>(this, _("Select DKDM file"), wxT("XML files|*.xml|All files|*.*"), wxFD_MULTIPLE, "AddDKDMPath");
+ if (!dialog->show()) {
+ return;
+ }
+
+ for (auto path: dialog->paths()) {
+ add_dkdm(path);
+ }
+ }
+
+ void add_dkdm(boost::filesystem::path path)
+ {
+ auto chain = Config::instance()->decryption_chain();
+ DCPOMATIC_ASSERT (chain->key());
+
+ try {
+ dcp::EncryptedKDM ekdm(dcp::file_to_string(path, MAX_KDM_SIZE));
+ /* Decrypt the DKDM to make sure that we can */
+ dcp::DecryptedKDM dkdm(ekdm, chain->key().get());
+
+ auto new_dkdm = make_shared<DKDM>(ekdm);
+ auto group = dynamic_pointer_cast<DKDMGroup> (selected_dkdm());
+ if (!group) {
+ group = Config::instance()->dkdms ();
+ }
+ add_dkdm(new_dkdm, group);
+ } catch (dcp::KDMFormatError& e) {
+ error_dialog (
+ this,
+ _("Could not read file as a KDM. Perhaps it is badly formatted, or not a KDM at all."),
+ std_to_wx(e.what())
+ );
+ return;
+ } catch (dcp::KDMDecryptionError &) {
+ error_dialog (
+ this,
+ _("Could not decrypt the DKDM. Perhaps it was not created with the correct certificate.")
+ );
+ } catch (dcp::MiscError& e) {
+ error_dialog (
+ this,
+ _("Could not read file as a KDM. It is much too large. Make sure you are loading a DKDM (XML) file."),
+ std_to_wx(e.what())
+ );
+ }
+
+ update_dkdm_view();
+ }
+
+ void add_dkdm_folder_clicked ()
+ {
+ auto d = make_wx<NewDKDMFolderDialog>(this);
+ if (d->ShowModal() != wxID_OK) {
+ return;
+ }
+
+ auto new_dkdm = make_shared<DKDMGroup>(wx_to_std(d->get()));
+ auto parent = dynamic_pointer_cast<DKDMGroup>(selected_dkdm());
+ if (!parent) {
+ parent = Config::instance()->dkdms ();
+ }
+ add_dkdm(new_dkdm, parent);
+ update_dkdm_view();
+ }
+
+ void update_dkdm_view()
+ {
+ _dkdm->DeleteAllItems();
+ _dkdm_id.clear();
+ add_dkdm_to_view(Config::instance()->dkdms());
+ if (_selected_dkdm) {
+ auto selection_in_id_map = std::find_if(_dkdm_id.begin(), _dkdm_id.end(), [this](pair<wxTreeItemId, shared_ptr<DKDMBase>> const& entry) {
+ return entry.second == _selected_dkdm;
+ });
+ if (selection_in_id_map != _dkdm_id.end()) {
+ _dkdm->SelectItem(selection_in_id_map->first);
+ }
+ }
+ }
+
+ /** @return true if this thing or any of its children match a search string */
+ bool matches(shared_ptr<DKDMBase> base, string const& search)
+ {
+ if (search.empty()) {
+ return true;
+ }
+
+ auto name = base->name();
+ transform(name.begin(), name.end(), name.begin(), ::tolower);
+ if (name.find(search) != string::npos) {
+ return true;
+ }
+
+ auto group = dynamic_pointer_cast<DKDMGroup>(base);
+ if (!group) {
+ return false;
+ }
+
+ auto const children = group->children();
+ return std::any_of(children.begin(), children.end(), [this, search](shared_ptr<DKDMBase> child) {
+ return matches(child, search);
+ });
+ }
+
+ /** Add DKDMs to the view that match the current search */
+ void add_dkdm_to_view(shared_ptr<DKDMBase> base)
+ {
+ auto search = wx_to_std(_dkdm_search->GetValue());
+ transform(search.begin(), search.end(), search.begin(), ::tolower);
+
+ optional<wxTreeItemId> group_to_expand;
+
+ if (!base->parent()) {
+ /* This is the root group */
+ _dkdm_id[_dkdm->AddRoot("root")] = base;
+ } else {
+ /* Add base to the view */
+ wxTreeItemId added;
+ auto parent_id = dkdm_to_id(base->parent());
+ added = _dkdm->AppendItem(parent_id, std_to_wx(base->name()));
+ /* Expand the group (later) if it matches the search or it was manually expanded */
+ if (!search.empty() || _expanded_dkdm_groups.find(base) != _expanded_dkdm_groups.end()) {
+ group_to_expand = added;
+ }
+ _dkdm_id[added] = base;
+ }
+
+ /* Add children */
+ auto group = dynamic_pointer_cast<DKDMGroup>(base);
+ if (group) {
+ auto children = group->children();
+ children.sort(
+ [this](shared_ptr<DKDMBase> a, shared_ptr<DKDMBase> b) { return _collator.compare(a->name(), b->name()) < 0; }
+ );
+
+ for (auto i: children) {
+ if (matches(i, search)) {
+ add_dkdm_to_view(i);
+ }
+ }
+ }
+
+ if (group_to_expand) {
+ _ignore_expand = true;
+ _dkdm->Expand(*group_to_expand);
+ _ignore_expand = false;
+ }
+ }
+
+ /** @param group Group to add dkdm to */
+ void add_dkdm(shared_ptr<DKDMBase> dkdm, shared_ptr<DKDMGroup> group, shared_ptr<DKDM> previous = shared_ptr<DKDM>())
+ {
+ group->add (dkdm, previous);
+ /* We're messing with a Config-owned object here, so tell it that something has changed.
+ This isn't nice.
+ */
+ Config::instance()->changed ();
+ }
+
+ wxTreeItemId dkdm_to_id (shared_ptr<DKDMBase> dkdm)
+ {
+ for (auto const& i: _dkdm_id) {
+ if (i.second == dkdm) {
+ return i.first;
+ }
+ }
+ DCPOMATIC_ASSERT (false);
+ }
+
+ void remove_dkdm_clicked ()
+ {
+ auto removed = selected_dkdm ();
+ if (!removed) {
+ return;
+ }
+
+ if (NagDialog::maybe_nag (
+ this, Config::NAG_DELETE_DKDM,
+ _("You are about to remove a DKDM. This will make it impossible to decrypt the DCP that the DKDM was made for, and it cannot be undone. "
+ "Are you sure?"),
+ true)) {
+ return;
+ }
+
+ _dkdm->Delete (dkdm_to_id (removed));
+ auto dkdms = Config::instance()->dkdms ();
+ dkdms->remove (removed);
+ Config::instance()->changed ();
+ }
+
+ void export_dkdm_clicked ()
+ {
+ auto removed = selected_dkdm ();
+ if (!removed) {
+ return;
+ }
+
+ auto dkdm = dynamic_pointer_cast<DKDM>(removed);
+ if (!dkdm) {
+ return;
+ }
+
+ auto d = make_wx<wxFileDialog>(
+ this, _("Select DKDM File"), wxEmptyString, wxEmptyString, wxT("XML files (*.xml)|*.xml"),
+ wxFD_SAVE | wxFD_OVERWRITE_PROMPT
+ );
+
+ if (d->ShowModal() == wxID_OK) {
+ dkdm->dkdm().as_xml(wx_to_std(d->GetPath()));
+ }
+ }
+
+ void dkdm_search_changed()
+ {
+ update_dkdm_view();