Don't bother re-examining content if loading a KDM fails.
[dcpomatic.git] / src / wx / content_menu.cc
1 /*
2     Copyright (C) 2013-2015 Carl Hetherington <cth@carlh.net>
3
4     This program is free software; you can redistribute it and/or modify
5     it under the terms of the GNU General Public License as published by
6     the Free Software Foundation; either version 2 of the License, or
7     (at your option) any later version.
8
9     This program is distributed in the hope that it will be useful,
10     but WITHOUT ANY WARRANTY; without even the implied warranty of
11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12     GNU General Public License for more details.
13
14     You should have received a copy of the GNU General Public License
15     along with this program; if not, write to the Free Software
16     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17
18 */
19
20 #include "content_menu.h"
21 #include "repeat_dialog.h"
22 #include "wx_util.h"
23 #include "timeline_video_content_view.h"
24 #include "timeline_audio_content_view.h"
25 #include "content_properties_dialog.h"
26 #include "lib/playlist.h"
27 #include "lib/film.h"
28 #include "lib/image_content.h"
29 #include "lib/content_factory.h"
30 #include "lib/examine_content_job.h"
31 #include "lib/job_manager.h"
32 #include "lib/exceptions.h"
33 #include "lib/dcp_content.h"
34 #include <wx/wx.h>
35 #include <wx/dirdlg.h>
36
37 using std::cout;
38 using std::vector;
39 using std::exception;
40 using boost::shared_ptr;
41 using boost::weak_ptr;
42 using boost::dynamic_pointer_cast;
43
44 enum {
45         ID_repeat = 1,
46         ID_join,
47         ID_find_missing,
48         ID_properties,
49         ID_re_examine,
50         ID_kdm,
51         ID_remove
52 };
53
54 ContentMenu::ContentMenu (wxWindow* p)
55         : _menu (new wxMenu)
56         , _parent (p)
57 {
58         _repeat = _menu->Append (ID_repeat, _("Repeat..."));
59         _join = _menu->Append (ID_join, _("Join"));
60         _find_missing = _menu->Append (ID_find_missing, _("Find missing..."));
61         _properties = _menu->Append (ID_properties, _("Properties..."));
62         _re_examine = _menu->Append (ID_re_examine, _("Re-examine..."));
63         _kdm = _menu->Append (ID_kdm, _("Add KDM..."));
64         _menu->AppendSeparator ();
65         _remove = _menu->Append (ID_remove, _("Remove"));
66
67         _parent->Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&ContentMenu::repeat, this), ID_repeat);
68         _parent->Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&ContentMenu::join, this), ID_join);
69         _parent->Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&ContentMenu::find_missing, this), ID_find_missing);
70         _parent->Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&ContentMenu::properties, this), ID_properties);
71         _parent->Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&ContentMenu::re_examine, this), ID_re_examine);
72         _parent->Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&ContentMenu::kdm, this), ID_kdm);
73         _parent->Bind (wxEVT_COMMAND_MENU_SELECTED, boost::bind (&ContentMenu::remove, this), ID_remove);
74 }
75
76 ContentMenu::~ContentMenu ()
77 {
78         delete _menu;
79 }
80
81 void
82 ContentMenu::popup (weak_ptr<Film> film, ContentList c, TimelineContentViewList v, wxPoint p)
83 {
84         _film = film;
85         _content = c;
86         _views = v;
87         _repeat->Enable (!_content.empty ());
88
89         int n = 0;
90         for (ContentList::const_iterator i = _content.begin(); i != _content.end(); ++i) {
91                 if (dynamic_pointer_cast<FFmpegContent> (*i)) {
92                         ++n;
93                 }
94         }
95
96         _join->Enable (n > 1);
97
98         _find_missing->Enable (_content.size() == 1 && !_content.front()->paths_valid ());
99         _properties->Enable (_content.size() == 1);
100         _re_examine->Enable (!_content.empty ());
101
102         if (_content.size() == 1) {
103                 shared_ptr<DCPContent> dcp = dynamic_pointer_cast<DCPContent> (_content.front ());
104                 _kdm->Enable (dcp && dcp->encrypted ());
105         } else {
106                 _kdm->Enable (false);
107         }
108
109         _remove->Enable (!_content.empty ());
110         _parent->PopupMenu (_menu, p);
111 }
112
113 void
114 ContentMenu::repeat ()
115 {
116         if (_content.empty ()) {
117                 return;
118         }
119
120         RepeatDialog* d = new RepeatDialog (_parent);
121         if (d->ShowModal() != wxID_OK) {
122                 d->Destroy ();
123                 return;
124         }
125
126         shared_ptr<Film> film = _film.lock ();
127         if (!film) {
128                 return;
129         }
130
131         film->repeat_content (_content, d->number ());
132         d->Destroy ();
133
134         _content.clear ();
135         _views.clear ();
136 }
137
138 void
139 ContentMenu::join ()
140 {
141         vector<shared_ptr<Content> > fc;
142         for (ContentList::const_iterator i = _content.begin(); i != _content.end(); ++i) {
143                 shared_ptr<FFmpegContent> f = dynamic_pointer_cast<FFmpegContent> (*i);
144                 if (f) {
145                         fc.push_back (f);
146                 }
147         }
148
149         DCPOMATIC_ASSERT (fc.size() > 1);
150
151         shared_ptr<Film> film = _film.lock ();
152         if (!film) {
153                 return;
154         }
155
156         try {
157                 shared_ptr<FFmpegContent> joined (new FFmpegContent (film, fc));
158                 for (ContentList::const_iterator i = _content.begin(); i != _content.end(); ++i) {
159                         film->remove_content (*i);
160                 }
161                 film->add_content (joined);
162         } catch (JoinError& e) {
163                 error_dialog (_parent, std_to_wx (e.what ()));
164         }
165 }
166
167 void
168 ContentMenu::remove ()
169 {
170         if (_content.empty ()) {
171                 return;
172         }
173
174         shared_ptr<Film> film = _film.lock ();
175         if (!film) {
176                 return;
177         }
178
179         /* We are removing from the timeline if _views is not empty */
180         bool handled = false;
181         if (!_views.empty ()) {
182                 /* Special case: we only remove FFmpegContent if its video view is selected;
183                    if not, and its audio view is selected, we unmap the audio.
184                 */
185                 for (ContentList::iterator i = _content.begin(); i != _content.end(); ++i) {
186                         shared_ptr<FFmpegContent> fc = dynamic_pointer_cast<FFmpegContent> (*i);
187                         if (!fc) {
188                                 continue;
189                         }
190
191                         shared_ptr<TimelineVideoContentView> video;
192                         shared_ptr<TimelineAudioContentView> audio;
193
194                         for (TimelineContentViewList::iterator i = _views.begin(); i != _views.end(); ++i) {
195                                 shared_ptr<TimelineVideoContentView> v = dynamic_pointer_cast<TimelineVideoContentView> (*i);
196                                 shared_ptr<TimelineAudioContentView> a = dynamic_pointer_cast<TimelineAudioContentView> (*i);
197                                 if (v && v->content() == fc) {
198                                         video = v;
199                                 } else if (a && a->content() == fc) {
200                                         audio = a;
201                                 }
202                         }
203
204                         if (!video && audio) {
205                                 AudioMapping m = fc->audio_mapping ();
206                                 m.unmap_all ();
207                                 fc->set_audio_mapping (m);
208                                 handled = true;
209                         }
210                 }
211         }
212
213         if (!handled) {
214                 film->remove_content (_content);
215         }
216
217         _content.clear ();
218         _views.clear ();
219 }
220
221 void
222 ContentMenu::find_missing ()
223 {
224         if (_content.size() != 1) {
225                 return;
226         }
227
228         shared_ptr<const Film> film = _film.lock ();
229         if (!film) {
230                 return;
231         }
232
233         shared_ptr<Content> content;
234
235         /* XXX: a bit nasty */
236         shared_ptr<ImageContent> ic = dynamic_pointer_cast<ImageContent> (_content.front ());
237         if (ic && !ic->still ()) {
238                 wxDirDialog* d = new wxDirDialog (0, _("Choose a folder"), wxT (""), wxDD_DIR_MUST_EXIST);
239                 int const r = d->ShowModal ();
240                 if (r == wxID_OK) {
241                         content.reset (new ImageContent (film, boost::filesystem::path (wx_to_std (d->GetPath ()))));
242                 }
243                 d->Destroy ();
244         } else {
245                 wxFileDialog* d = new wxFileDialog (0, _("Choose a file"), wxT (""), wxT (""), wxT ("*.*"), wxFD_MULTIPLE);
246                 int const r = d->ShowModal ();
247                 if (r == wxID_OK) {
248                         content = content_factory (film, wx_to_std (d->GetPath ()));
249                 }
250                 d->Destroy ();
251         }
252
253         if (!content) {
254                 return;
255         }
256
257         shared_ptr<Job> j (new ExamineContentJob (film, content));
258
259         _job_connection = j->Finished.connect (
260                 bind (
261                         &ContentMenu::maybe_found_missing,
262                         this,
263                         boost::weak_ptr<Job> (j),
264                         boost::weak_ptr<Content> (_content.front ()),
265                         boost::weak_ptr<Content> (content)
266                         )
267                 );
268
269         JobManager::instance()->add (j);
270 }
271
272 void
273 ContentMenu::re_examine ()
274 {
275         shared_ptr<Film> film = _film.lock ();
276         if (!film) {
277                 return;
278         }
279
280         for (ContentList::iterator i = _content.begin(); i != _content.end(); ++i) {
281                 film->examine_content (*i);
282         }
283 }
284
285 void
286 ContentMenu::maybe_found_missing (weak_ptr<Job> j, weak_ptr<Content> oc, weak_ptr<Content> nc)
287 {
288         shared_ptr<Job> job = j.lock ();
289         if (!job || !job->finished_ok ()) {
290                 return;
291         }
292
293         shared_ptr<Content> old_content = oc.lock ();
294         shared_ptr<Content> new_content = nc.lock ();
295         DCPOMATIC_ASSERT (old_content);
296         DCPOMATIC_ASSERT (new_content);
297
298         if (new_content->digest() != old_content->digest()) {
299                 error_dialog (0, _("The content file(s) you specified are not the same as those that are missing.  Either try again with the correct content file or remove the missing content."));
300                 return;
301         }
302
303         old_content->set_path (new_content->path (0));
304 }
305
306 void
307 ContentMenu::kdm ()
308 {
309         DCPOMATIC_ASSERT (!_content.empty ());
310         shared_ptr<DCPContent> dcp = dynamic_pointer_cast<DCPContent> (_content.front ());
311         DCPOMATIC_ASSERT (dcp);
312
313         wxFileDialog* d = new wxFileDialog (_parent, _("Select KDM"));
314
315         if (d->ShowModal() == wxID_OK) {
316                 try {
317                         dcp->add_kdm (dcp::EncryptedKDM (dcp::file_to_string (wx_to_std (d->GetPath ()))));
318                 } catch (exception& e) {
319                         error_dialog (_parent, wxString::Format (_("Could not load KDM (%s)"), e.what ()));
320                         d->Destroy ();
321                         return;
322                 }
323
324                 shared_ptr<Film> film = _film.lock ();
325                 DCPOMATIC_ASSERT (film);
326                 film->examine_content (dcp);
327         }
328
329         d->Destroy ();
330 }
331
332 void
333 ContentMenu::properties ()
334 {
335         ContentPropertiesDialog* d = new ContentPropertiesDialog (_parent, _content.front ());
336         d->ShowModal ();
337         d->Destroy ();
338 }