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