Hand-apply 6a3cd511559433554ab40ed72ff94b7d8dc2c5bd from master;
[dcpomatic.git] / src / wx / content_panel.cc
1 /*
2     Copyright (C) 2012-2014 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 <wx/wx.h>
21 #include <wx/notebook.h>
22 #include <wx/listctrl.h>
23 #include "lib/audio_content.h"
24 #include "lib/subtitle_content.h"
25 #include "lib/video_content.h"
26 #include "lib/ffmpeg_content.h"
27 #include "lib/content_factory.h"
28 #include "lib/image_content.h"
29 #include "lib/dcp_content.h"
30 #include "lib/playlist.h"
31 #include "content_panel.h"
32 #include "wx_util.h"
33 #include "video_panel.h"
34 #include "audio_panel.h"
35 #include "subtitle_panel.h"
36 #include "timing_panel.h"
37 #include "timeline_dialog.h"
38 #include "image_sequence_dialog.h"
39
40 using std::list;
41 using std::string;
42 using std::cout;
43 using boost::shared_ptr;
44 using boost::weak_ptr;
45 using boost::dynamic_pointer_cast;
46
47 ContentPanel::ContentPanel (wxNotebook* n, boost::shared_ptr<Film> f)
48         : _timeline_dialog (0)
49         , _film (f)
50         , _generally_sensitive (true)
51 {
52         _panel = new wxPanel (n);
53         _sizer = new wxBoxSizer (wxVERTICAL);
54         _panel->SetSizer (_sizer);
55
56         _menu = new ContentMenu (_panel);
57
58         {
59                 wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
60                 
61                 _content = new wxListCtrl (_panel, wxID_ANY, wxDefaultPosition, wxSize (320, 160), wxLC_REPORT | wxLC_NO_HEADER);
62                 s->Add (_content, 1, wxEXPAND | wxTOP | wxBOTTOM, 6);
63
64                 _content->InsertColumn (0, wxT(""));
65                 _content->SetColumnWidth (0, 512);
66
67                 wxBoxSizer* b = new wxBoxSizer (wxVERTICAL);
68                 
69                 _add_file = new wxButton (_panel, wxID_ANY, _("Add file(s)..."));
70                 _add_file->SetToolTip (_("Add video, image or sound files to the film."));
71                 b->Add (_add_file, 0, wxEXPAND | wxALL, DCPOMATIC_BUTTON_STACK_GAP);
72                 
73                 _add_folder = new wxButton (_panel, wxID_ANY, _("Add folder..."));
74                 _add_folder->SetToolTip (_("Add a folder of image files (which will be used as a moving image sequence) or a DCP."));
75                 b->Add (_add_folder, 1, wxEXPAND | wxALL, DCPOMATIC_BUTTON_STACK_GAP);
76                 
77                 _remove = new wxButton (_panel, wxID_ANY, _("Remove"));
78                 _remove->SetToolTip (_("Remove the selected piece of content from the film."));
79                 b->Add (_remove, 0, wxEXPAND | wxALL, DCPOMATIC_BUTTON_STACK_GAP);
80                 
81                 _earlier = new wxButton (_panel, wxID_ANY, _("Up"));
82                 _earlier->SetToolTip (_("Move the selected piece of content earlier in the film."));
83                 b->Add (_earlier, 0, wxEXPAND | wxALL, DCPOMATIC_BUTTON_STACK_GAP);
84                 
85                 _later = new wxButton (_panel, wxID_ANY, _("Down"));
86                 _later->SetToolTip (_("Move the selected piece of content later in the film."));
87                 b->Add (_later, 0, wxEXPAND | wxALL, DCPOMATIC_BUTTON_STACK_GAP);
88                 
89                 _timeline = new wxButton (_panel, wxID_ANY, _("Timeline..."));
90                 _timeline->SetToolTip (_("Open the timeline for the film."));
91                 b->Add (_timeline, 0, wxEXPAND | wxALL, DCPOMATIC_BUTTON_STACK_GAP);
92
93                 s->Add (b, 0, wxALL, 4);
94
95                 _sizer->Add (s, 0, wxEXPAND | wxALL, 6);
96         }
97
98         _notebook = new wxNotebook (_panel, wxID_ANY);
99         _sizer->Add (_notebook, 1, wxEXPAND | wxTOP, 6);
100
101         _video_panel = new VideoPanel (this);
102         _panels.push_back (_video_panel);
103         _audio_panel = new AudioPanel (this);
104         _panels.push_back (_audio_panel);
105         _subtitle_panel = new SubtitlePanel (this);
106         _panels.push_back (_subtitle_panel);
107         _timing_panel = new TimingPanel (this);
108         _panels.push_back (_timing_panel);
109
110         _content->Bind (wxEVT_COMMAND_LIST_ITEM_SELECTED, boost::bind (&ContentPanel::selection_changed, this));
111         _content->Bind (wxEVT_COMMAND_LIST_ITEM_DESELECTED, boost::bind (&ContentPanel::selection_changed, this));
112         _content->Bind (wxEVT_COMMAND_LIST_ITEM_RIGHT_CLICK, boost::bind (&ContentPanel::right_click, this, _1));
113         _content->Bind (wxEVT_DROP_FILES, boost::bind (&ContentPanel::files_dropped, this, _1));
114         _add_file->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&ContentPanel::add_file_clicked, this));
115         _add_folder->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&ContentPanel::add_folder_clicked, this));
116         _remove->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&ContentPanel::remove_clicked, this));
117         _earlier->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&ContentPanel::earlier_clicked, this));
118         _later->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&ContentPanel::later_clicked, this));
119         _timeline->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&ContentPanel::timeline_clicked, this));
120 }
121
122 ContentList
123 ContentPanel::selected ()
124 {
125         ContentList sel;
126         long int s = -1;
127         while (true) {
128                 s = _content->GetNextItem (s, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
129                 if (s == -1) {
130                         break;
131                 }
132
133                 if (s < int (_film->content().size ())) {
134                         sel.push_back (_film->content()[s]);
135                 }
136         }
137
138         return sel;
139 }
140
141 VideoContentList
142 ContentPanel::selected_video ()
143 {
144         ContentList c = selected ();
145         VideoContentList vc;
146         
147         for (ContentList::iterator i = c.begin(); i != c.end(); ++i) {
148                 shared_ptr<VideoContent> t = dynamic_pointer_cast<VideoContent> (*i);
149                 if (t) {
150                         vc.push_back (t);
151                 }
152         }
153
154         return vc;
155 }
156
157 AudioContentList
158 ContentPanel::selected_audio ()
159 {
160         ContentList c = selected ();
161         AudioContentList ac;
162         
163         for (ContentList::iterator i = c.begin(); i != c.end(); ++i) {
164                 shared_ptr<AudioContent> t = dynamic_pointer_cast<AudioContent> (*i);
165                 if (t) {
166                         ac.push_back (t);
167                 }
168         }
169
170         return ac;
171 }
172
173 SubtitleContentList
174 ContentPanel::selected_subtitle ()
175 {
176         ContentList c = selected ();
177         SubtitleContentList sc;
178         
179         for (ContentList::iterator i = c.begin(); i != c.end(); ++i) {
180                 shared_ptr<SubtitleContent> t = dynamic_pointer_cast<SubtitleContent> (*i);
181                 if (t) {
182                         sc.push_back (t);
183                 }
184         }
185
186         return sc;
187 }
188
189 FFmpegContentList
190 ContentPanel::selected_ffmpeg ()
191 {
192         ContentList c = selected ();
193         FFmpegContentList sc;
194         
195         for (ContentList::iterator i = c.begin(); i != c.end(); ++i) {
196                 shared_ptr<FFmpegContent> t = dynamic_pointer_cast<FFmpegContent> (*i);
197                 if (t) {
198                         sc.push_back (t);
199                 }
200         }
201
202         return sc;
203 }
204
205 void
206 ContentPanel::film_changed (Film::Property p)
207 {
208         switch (p) {
209         case Film::CONTENT:
210                 setup ();
211                 break;
212         default:
213                 break;
214         }
215
216         for (list<ContentSubPanel*>::iterator i = _panels.begin(); i != _panels.end(); ++i) {
217                 (*i)->film_changed (p);
218         }
219 }       
220
221 void
222 ContentPanel::selection_changed ()
223 {
224         setup_sensitivity ();
225
226         for (list<ContentSubPanel*>::iterator i = _panels.begin(); i != _panels.end(); ++i) {
227                 (*i)->content_selection_changed ();
228         }
229 }
230
231 void
232 ContentPanel::add_file_clicked ()
233 {
234         /* The wxFD_CHANGE_DIR here prevents a `could not set working directory' error 123 on Windows when using
235            non-Latin filenames or paths.
236         */
237         wxFileDialog* d = new wxFileDialog (_panel, _("Choose a file or files"), wxT (""), wxT (""), wxT ("*.*"), wxFD_MULTIPLE | wxFD_CHANGE_DIR);
238         int const r = d->ShowModal ();
239
240         if (r != wxID_OK) {
241                 d->Destroy ();
242                 return;
243         }
244
245         wxArrayString paths;
246         d->GetPaths (paths);
247
248         /* XXX: check for lots of files here and do something */
249
250         for (unsigned int i = 0; i < paths.GetCount(); ++i) {
251                 _film->examine_and_add_content (content_factory (_film, wx_to_std (paths[i])), true);
252         }
253
254         d->Destroy ();
255 }
256
257 void
258 ContentPanel::add_folder_clicked ()
259 {
260         wxDirDialog* d = new wxDirDialog (_panel, _("Choose a folder"), wxT (""), wxDD_DIR_MUST_EXIST);
261         int r = d->ShowModal ();
262         boost::filesystem::path const path (wx_to_std (d->GetPath ()));
263         d->Destroy ();
264         
265         if (r != wxID_OK) {
266                 return;
267         }
268
269         /* Guess if this is a DCP or a set of images: read the first ten filenames and if they
270            are all valid image files we assume it is a set of images.
271         */
272
273         bool is_dcp = false;
274         int read = 0;
275         for (boost::filesystem::directory_iterator i(path); i != boost::filesystem::directory_iterator() && read < 10; ++i, ++read) {
276                 if (!boost::filesystem::is_regular_file (i->path()) || !valid_image_file (i->path())) {
277                         is_dcp = true;
278                 }
279         }
280
281         if (is_dcp) {
282                 try {
283                         shared_ptr<DCPContent> content (new DCPContent (_film, path));
284                         _film->examine_and_add_content (content, true);
285                 } catch (...) {
286                         error_dialog (_panel, _("Could not find a DCP in that folder."));
287                 }
288         } else {
289                 
290                 ImageSequenceDialog* e = new ImageSequenceDialog (_panel);
291                 r = e->ShowModal ();
292                 float const frame_rate = e->frame_rate ();
293                 bool const digest = e->digest ();
294                 e->Destroy ();
295
296                 if (r != wxID_OK) {
297                         return;
298                 }
299
300                 shared_ptr<Content> content;
301                 
302                 try {
303                         shared_ptr<ImageContent> content (new ImageContent (_film, path));
304                         content->set_video_frame_rate (frame_rate);
305                         _film->examine_and_add_content (content, digest);
306                 } catch (...) {
307                         error_dialog (_panel, _("Could not find any images in that folder"));
308                         return;
309                 }
310         }
311 }
312
313 void
314 ContentPanel::remove_clicked ()
315 {
316         ContentList c = selected ();
317         if (c.size() == 1) {
318                 _film->remove_content (c.front ());
319         }
320
321         selection_changed ();
322 }
323
324 void
325 ContentPanel::timeline_clicked ()
326 {
327         if (_timeline_dialog) {
328                 _timeline_dialog->Destroy ();
329                 _timeline_dialog = 0;
330         }
331         
332         _timeline_dialog = new TimelineDialog (this, _film);
333         _timeline_dialog->Show ();
334 }
335
336 void
337 ContentPanel::right_click (wxListEvent& ev)
338 {
339         _menu->popup (_film, selected (), ev.GetPoint ());
340 }
341
342 /** Set up broad sensitivity based on the type of content that is selected */
343 void
344 ContentPanel::setup_sensitivity ()
345 {
346         _add_file->Enable (_generally_sensitive);
347         _add_folder->Enable (_generally_sensitive);
348
349         ContentList selection = selected ();
350         VideoContentList video_selection = selected_video ();
351         AudioContentList audio_selection = selected_audio ();
352
353         _remove->Enable   (selection.size() == 1 && _generally_sensitive);
354         _earlier->Enable  (selection.size() == 1 && _generally_sensitive);
355         _later->Enable    (selection.size() == 1 && _generally_sensitive);
356         _timeline->Enable (!_film->content().empty() && _generally_sensitive);
357
358         _video_panel->Enable    (video_selection.size() > 0 && _generally_sensitive);
359         _audio_panel->Enable    (audio_selection.size() > 0 && _generally_sensitive);
360         _subtitle_panel->Enable (selection.size() == 1 && dynamic_pointer_cast<SubtitleContent> (selection.front()) && _generally_sensitive);
361         _timing_panel->Enable   (selection.size() == 1 && _generally_sensitive);
362 }
363
364 void
365 ContentPanel::set_film (shared_ptr<Film> f)
366 {
367         _film = f;
368
369         film_changed (Film::CONTENT);
370         film_changed (Film::AUDIO_CHANNELS);
371         selection_changed ();
372 }
373
374 void
375 ContentPanel::set_general_sensitivity (bool s)
376 {
377         _generally_sensitive = s;
378
379         _content->Enable (s);
380         _add_file->Enable (s);
381         _add_folder->Enable (s);
382         _remove->Enable (s);
383         _earlier->Enable (s);
384         _later->Enable (s);
385         _timeline->Enable (s);
386
387         /* Set the panels in the content notebook */
388         for (list<ContentSubPanel*>::iterator i = _panels.begin(); i != _panels.end(); ++i) {
389                 (*i)->Enable (s);
390         }
391 }
392
393 void
394 ContentPanel::earlier_clicked ()
395 {
396         ContentList sel = selected ();
397         if (sel.size() == 1) {
398                 _film->move_content_earlier (sel.front ());
399                 selection_changed ();
400         }
401 }
402
403 void
404 ContentPanel::later_clicked ()
405 {
406         ContentList sel = selected ();
407         if (sel.size() == 1) {
408                 _film->move_content_later (sel.front ());
409                 selection_changed ();
410         }
411 }
412
413 void
414 ContentPanel::set_selection (weak_ptr<Content> wc)
415 {
416         ContentList content = _film->content ();
417         for (size_t i = 0; i < content.size(); ++i) {
418                 if (content[i] == wc.lock ()) {
419                         _content->SetItemState (i, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED);
420                 } else {
421                         _content->SetItemState (i, 0, wxLIST_STATE_SELECTED | wxLIST_STATE_FOCUSED);
422                 }
423         }
424 }
425
426 void
427 ContentPanel::film_content_changed (int property)
428 {
429         if (property == ContentProperty::PATH || property == ContentProperty::POSITION || property == DCPContentProperty::CAN_BE_PLAYED) {
430                 setup ();
431         }
432                 
433         for (list<ContentSubPanel*>::iterator i = _panels.begin(); i != _panels.end(); ++i) {
434                 (*i)->film_content_changed (property);
435         }
436 }
437
438 void
439 ContentPanel::setup ()
440 {
441         string selected_summary;
442         int const s = _content->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
443         if (s != -1) {
444                 selected_summary = wx_to_std (_content->GetItemText (s));
445         }
446         
447         _content->DeleteAllItems ();
448
449         ContentList content = _film->content ();
450         sort (content.begin(), content.end(), ContentSorter ());
451
452         for (ContentList::iterator i = content.begin(); i != content.end(); ++i) {
453                 int const t = _content->GetItemCount ();
454                 bool const valid = (*i)->paths_valid ();
455                 shared_ptr<DCPContent> dcp = dynamic_pointer_cast<DCPContent> (*i);
456                 bool const needs_kdm = dcp && !dcp->can_be_played ();
457
458                 string s = (*i)->summary ();
459                 
460                 if (!valid) {
461                         s = _("MISSING: ") + s;
462                 }
463
464                 if (needs_kdm) {
465                         s = _("NEEDS KDM: ") + s;
466                 }
467
468                 _content->InsertItem (t, std_to_wx (s));
469
470                 if ((*i)->summary() == selected_summary) {
471                         _content->SetItemState (t, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED);
472                 }
473
474                 if (!valid || needs_kdm) {
475                         _content->SetItemTextColour (t, *wxRED);
476                 }
477         }
478
479         if (selected_summary.empty () && !content.empty ()) {
480                 /* Select the item of content if none was selected before */
481                 _content->SetItemState (0, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED);
482         }
483 }
484
485 void
486 ContentPanel::files_dropped (wxDropFilesEvent& event)
487 {
488         if (!_film) {
489                 return;
490         }
491         
492         wxString* paths = event.GetFiles ();
493         for (int i = 0; i < event.GetNumberOfFiles(); i++) {
494                 _film->examine_and_add_content (content_factory (_film, wx_to_std (paths[i])), true);
495         }
496 }