Fix problems when adding the same content to a playback playlist twice.
[dcpomatic.git] / src / wx / controls.cc
1 /*
2     Copyright (C) 2018 Carl Hetherington <cth@carlh.net>
3
4     This file is part of DCP-o-matic.
5
6     DCP-o-matic is free software; you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation; either version 2 of the License, or
9     (at your option) any later version.
10
11     DCP-o-matic is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15
16     You should have received a copy of the GNU General Public License
17     along with DCP-o-matic.  If not, see <http://www.gnu.org/licenses/>.
18
19 */
20
21 #include "controls.h"
22 #include "film_viewer.h"
23 #include "wx_util.h"
24 #include "playhead_to_timecode_dialog.h"
25 #include "playhead_to_frame_dialog.h"
26 #include "lib/job_manager.h"
27 #include "lib/player_video.h"
28 #include "lib/dcp_content.h"
29 #include "lib/job.h"
30 #include "lib/examine_content_job.h"
31 #include "lib/content_factory.h"
32 #include "lib/cross.h"
33 #include <dcp/dcp.h>
34 #include <dcp/cpl.h>
35 #include <dcp/reel.h>
36 #include <dcp/reel_picture_asset.h>
37 #include <wx/wx.h>
38 #include <wx/tglbtn.h>
39 #include <wx/listctrl.h>
40 #include <wx/progdlg.h>
41
42 using std::string;
43 using std::list;
44 using std::make_pair;
45 using std::exception;
46 using boost::optional;
47 using boost::shared_ptr;
48 using boost::weak_ptr;
49 using boost::dynamic_pointer_cast;
50
51 Controls::Controls (wxWindow* parent, shared_ptr<FilmViewer> viewer, bool editor_controls)
52         : wxPanel (parent)
53         , _viewer (viewer)
54         , _slider_being_moved (false)
55         , _was_running_before_slider (false)
56         , _outline_content (0)
57         , _eye (0)
58         , _jump_to_selected (0)
59         , _slider (new wxSlider (this, wxID_ANY, 0, 0, 4096))
60         , _rewind_button (new wxButton (this, wxID_ANY, wxT("|<")))
61         , _back_button (new wxButton (this, wxID_ANY, wxT("<")))
62         , _forward_button (new wxButton (this, wxID_ANY, wxT(">")))
63         , _frame_number (new wxStaticText (this, wxID_ANY, wxT("")))
64         , _timecode (new wxStaticText (this, wxID_ANY, wxT("")))
65 #ifdef DCPOMATIC_VARIANT_SWAROOP
66         , _play_button (new wxButton(this, wxID_ANY, _("Play")))
67         , _pause_button (new wxButton(this, wxID_ANY, _("Pause")))
68         , _stop_button (new wxButton(this, wxID_ANY, _("Stop")))
69 #else
70         , _play_button (new wxToggleButton(this, wxID_ANY, _("Play")))
71 #endif
72 {
73         _v_sizer = new wxBoxSizer (wxVERTICAL);
74         SetSizer (_v_sizer);
75
76         wxBoxSizer* view_options = new wxBoxSizer (wxHORIZONTAL);
77         if (editor_controls) {
78                 _outline_content = new wxCheckBox (this, wxID_ANY, _("Outline content"));
79                 view_options->Add (_outline_content, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, DCPOMATIC_SIZER_GAP);
80                 _eye = new wxChoice (this, wxID_ANY);
81                 _eye->Append (_("Left"));
82                 _eye->Append (_("Right"));
83                 _eye->SetSelection (0);
84                 view_options->Add (_eye, 0, wxLEFT | wxRIGHT | wxALIGN_CENTER_VERTICAL, DCPOMATIC_SIZER_GAP);
85                 _jump_to_selected = new wxCheckBox (this, wxID_ANY, _("Jump to selected content"));
86                 view_options->Add (_jump_to_selected, 0, wxLEFT | wxRIGHT | wxALIGN_CENTER_VERTICAL, DCPOMATIC_SIZER_GAP);
87         }
88
89         _v_sizer->Add (view_options, 0, wxALL, DCPOMATIC_SIZER_GAP);
90
91         wxBoxSizer* left_sizer = new wxBoxSizer (wxVERTICAL);
92
93         _spl_view = new wxListCtrl (this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLC_REPORT | wxLC_NO_HEADER);
94         _spl_view->AppendColumn (wxT(""), wxLIST_FORMAT_LEFT, 740);
95         left_sizer->Add (_spl_view, 1, wxALL | wxEXPAND, DCPOMATIC_SIZER_GAP);
96
97         _content_view = new wxListCtrl (this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLC_REPORT | wxLC_NO_HEADER);
98         /* time */
99         _content_view->AppendColumn (wxT(""), wxLIST_FORMAT_LEFT, 80);
100         /* type */
101         _content_view->AppendColumn (wxT(""), wxLIST_FORMAT_LEFT, 80);
102         /* annotation text */
103         _content_view->AppendColumn (wxT(""), wxLIST_FORMAT_LEFT, 580);
104         left_sizer->Add (_content_view, 1, wxALL | wxEXPAND, DCPOMATIC_SIZER_GAP);
105
106         wxBoxSizer* e_sizer = new wxBoxSizer (wxHORIZONTAL);
107         e_sizer->Add (left_sizer, 1, wxALL | wxEXPAND, DCPOMATIC_SIZER_GAP);
108
109         _current_spl_view = new wxListCtrl (this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLC_REPORT | wxLC_NO_HEADER);
110         _current_spl_view->AppendColumn (wxT(""), wxLIST_FORMAT_LEFT, 80);
111         _current_spl_view->AppendColumn (wxT(""), wxLIST_FORMAT_LEFT, 80);
112         _current_spl_view->AppendColumn (wxT(""), wxLIST_FORMAT_LEFT, 580);
113         e_sizer->Add (_current_spl_view, 1, wxALL | wxEXPAND, DCPOMATIC_SIZER_GAP);
114
115         wxBoxSizer* buttons_sizer = new wxBoxSizer (wxVERTICAL);
116         _add_button = new wxButton(this, wxID_ANY, _("Add"));
117         buttons_sizer->Add (_add_button);
118         _save_button = new wxButton(this, wxID_ANY, _("Save..."));
119         buttons_sizer->Add (_save_button);
120         _load_button = new wxButton(this, wxID_ANY, _("Load..."));
121         buttons_sizer->Add (_load_button);
122         e_sizer->Add (buttons_sizer, 0, wxALL | wxEXPAND, DCPOMATIC_SIZER_GAP);
123
124         _v_sizer->Add (e_sizer, 1, wxEXPAND);
125
126         _log = new wxTextCtrl (this, wxID_ANY, wxT(""), wxDefaultPosition, wxSize(-1, 200), wxTE_READONLY | wxTE_MULTILINE);
127         _v_sizer->Add (_log, 0, wxALL | wxEXPAND, DCPOMATIC_SIZER_GAP);
128
129         _content_view->Show (false);
130         _spl_view->Show (false);
131         _current_spl_view->Show (false);
132         _add_button->Show (false);
133         _save_button->Show (false);
134         _load_button->Show (false);
135         _log->Show (false);
136
137         wxBoxSizer* h_sizer = new wxBoxSizer (wxHORIZONTAL);
138
139         wxBoxSizer* time_sizer = new wxBoxSizer (wxVERTICAL);
140         time_sizer->Add (_frame_number, 0, wxEXPAND);
141         time_sizer->Add (_timecode, 0, wxEXPAND);
142
143         h_sizer->Add (_rewind_button, 0, wxALL, 2);
144         h_sizer->Add (_back_button, 0, wxALL, 2);
145         h_sizer->Add (time_sizer, 0, wxEXPAND);
146         h_sizer->Add (_forward_button, 0, wxALL, 2);
147         h_sizer->Add (_play_button, 0, wxEXPAND);
148 #ifdef DCPOMATIC_VARIANT_SWAROOP
149         h_sizer->Add (_pause_button, 0, wxEXPAND);
150         h_sizer->Add (_stop_button, 0, wxEXPAND);
151 #endif
152         h_sizer->Add (_slider, 1, wxEXPAND);
153
154         _v_sizer->Add (h_sizer, 0, wxEXPAND | wxALL, 6);
155
156         _frame_number->SetMinSize (wxSize (84, -1));
157         _rewind_button->SetMinSize (wxSize (32, -1));
158         _back_button->SetMinSize (wxSize (32, -1));
159         _forward_button->SetMinSize (wxSize (32, -1));
160
161         if (_eye) {
162                 _eye->Bind (wxEVT_CHOICE, boost::bind (&Controls::eye_changed, this));
163         }
164         if (_outline_content) {
165                 _outline_content->Bind (wxEVT_CHECKBOX, boost::bind (&Controls::outline_content_changed, this));
166         }
167
168         _slider->Bind           (wxEVT_SCROLL_THUMBTRACK,    boost::bind(&Controls::slider_moved,    this, false));
169         _slider->Bind           (wxEVT_SCROLL_PAGEUP,        boost::bind(&Controls::slider_moved,    this, true));
170         _slider->Bind           (wxEVT_SCROLL_PAGEDOWN,      boost::bind(&Controls::slider_moved,    this, true));
171         _slider->Bind           (wxEVT_SCROLL_CHANGED,       boost::bind(&Controls::slider_released, this));
172 #ifdef DCPOMATIC_VARIANT_SWAROOP
173         _play_button->Bind      (wxEVT_BUTTON,               boost::bind(&Controls::play_clicked,    this));
174         _pause_button->Bind     (wxEVT_BUTTON,               boost::bind(&Controls::pause_clicked,   this));
175         _stop_button->Bind      (wxEVT_BUTTON,               boost::bind(&Controls::stop_clicked,    this));
176 #else
177         _play_button->Bind      (wxEVT_TOGGLEBUTTON,         boost::bind(&Controls::play_clicked,    this));
178 #endif
179         _rewind_button->Bind    (wxEVT_LEFT_DOWN,            boost::bind(&Controls::rewind_clicked,  this, _1));
180         _back_button->Bind      (wxEVT_LEFT_DOWN,            boost::bind(&Controls::back_clicked,    this, _1));
181         _forward_button->Bind   (wxEVT_LEFT_DOWN,            boost::bind(&Controls::forward_clicked, this, _1));
182         _frame_number->Bind     (wxEVT_LEFT_DOWN,            boost::bind(&Controls::frame_number_clicked, this));
183         _timecode->Bind         (wxEVT_LEFT_DOWN,            boost::bind(&Controls::timecode_clicked, this));
184         _content_view->Bind     (wxEVT_LIST_ITEM_SELECTED,   boost::bind(&Controls::setup_sensitivity, this));
185         _content_view->Bind     (wxEVT_LIST_ITEM_DESELECTED, boost::bind(&Controls::setup_sensitivity, this));
186         if (_jump_to_selected) {
187                 _jump_to_selected->Bind (wxEVT_CHECKBOX, boost::bind (&Controls::jump_to_selected_clicked, this));
188                 _jump_to_selected->SetValue (Config::instance()->jump_to_selected ());
189         }
190         _add_button->Bind       (wxEVT_BUTTON,              boost::bind(&Controls::add_clicked, this));
191         _save_button->Bind      (wxEVT_BUTTON,              boost::bind(&Controls::save_clicked, this));
192         _load_button->Bind      (wxEVT_BUTTON,              boost::bind(&Controls::load_clicked, this));
193
194         _viewer->PositionChanged.connect (boost::bind(&Controls::position_changed, this));
195         _viewer->Started.connect (boost::bind(&Controls::started, this));
196         _viewer->Stopped.connect (boost::bind(&Controls::stopped, this));
197         _viewer->FilmChanged.connect (boost::bind(&Controls::film_changed, this));
198         _viewer->ImageChanged.connect (boost::bind(&Controls::image_changed, this, _1));
199
200         film_changed ();
201
202         setup_sensitivity ();
203         update_content_directory ();
204         update_playlist_directory ();
205
206         JobManager::instance()->ActiveJobsChanged.connect (
207                 bind (&Controls::active_jobs_changed, this, _2)
208                 );
209
210         _config_changed_connection = Config::instance()->Changed.connect (bind(&Controls::config_changed, this, _1));
211         config_changed (Config::OTHER);
212 }
213
214 void
215 Controls::add_clicked ()
216 {
217         shared_ptr<Content> sel = selected_content()->clone();
218         DCPOMATIC_ASSERT (sel);
219         _film->examine_and_add_content (sel);
220         bool const ok = display_progress (_("DCP-o-matic"), _("Loading DCP"));
221         if (!ok || !report_errors_from_last_job(this)) {
222                 return;
223         }
224         if (_film->content().size() == 1) {
225                 /* Put 1 frame of black at the start so when we seek to 0 we don't see anything */
226                 sel->set_position (DCPTime::from_frames(1, _film->video_frame_rate()));
227         }
228         add_content_to_list (sel, _current_spl_view);
229         setup_sensitivity ();
230 }
231
232 void
233 Controls::save_clicked ()
234 {
235         wxFileDialog* d = new wxFileDialog (
236                 this, _("Select playlist file"), wxEmptyString, wxEmptyString, wxT ("XML files (*.xml)|*.xml"),
237                 wxFD_SAVE | wxFD_OVERWRITE_PROMPT
238                 );
239
240         if (d->ShowModal() == wxID_OK) {
241                 boost::filesystem::path p(wx_to_std(d->GetPath()));
242                 _film->set_name(p.stem().string());
243                 _film->write_metadata(p);
244         }
245
246         d->Destroy ();
247 }
248
249 void
250 Controls::load_clicked ()
251 {
252         wxFileDialog* d = new wxFileDialog (
253                 this, _("Select playlist file"), wxEmptyString, wxEmptyString, wxT ("XML files (*.xml)|*.xml")
254                 );
255
256         if (d->ShowModal() == wxID_OK) {
257                 _film->read_metadata (boost::filesystem::path(wx_to_std(d->GetPath())));
258                 _current_spl_view->DeleteAllItems ();
259                 BOOST_FOREACH (shared_ptr<Content> i, _film->content()) {
260                         shared_ptr<DCPContent> dcp = dynamic_pointer_cast<DCPContent>(i);
261                         add_content_to_list (dcp, _current_spl_view);
262                 }
263         }
264
265         d->Destroy ();
266 }
267
268 void
269 Controls::config_changed (int property)
270 {
271         if (property == Config::PLAYER_CONTENT_DIRECTORY) {
272                 update_content_directory ();
273         } else if (property == Config::PLAYER_PLAYLIST_DIRECTORY) {
274                 update_playlist_directory ();
275         } else {
276                 setup_sensitivity ();
277         }
278 }
279
280 void
281 Controls::started ()
282 {
283 #ifdef DCPOMATIC_VARIANT_SWAROOP
284         _play_button->Enable (false);
285         _pause_button->Enable (true);
286 #else
287         _play_button->SetValue (true);
288 #endif
289         setup_sensitivity ();
290 }
291
292 void
293 Controls::stopped ()
294 {
295 #ifdef DCPOMATIC_VARIANT_SWAROOP
296         _play_button->Enable (true);
297         _pause_button->Enable (false);
298 #else
299         _play_button->SetValue (false);
300 #endif
301         setup_sensitivity ();
302 }
303
304 void
305 Controls::position_changed ()
306 {
307         if (!_slider_being_moved) {
308                 update_position_label ();
309                 update_position_slider ();
310         }
311 }
312
313 void
314 Controls::eye_changed ()
315 {
316         _viewer->set_eyes (_eye->GetSelection() == 0 ? EYES_LEFT : EYES_RIGHT);
317 }
318
319 void
320 Controls::outline_content_changed ()
321 {
322         _viewer->set_outline_content (_outline_content->GetValue());
323 }
324
325 void
326 Controls::film_change (ChangeType type, Film::Property p)
327 {
328         if (type != CHANGE_TYPE_DONE) {
329                 return;
330         }
331
332         if (p == Film::CONTENT || p == Film::THREE_D) {
333                 setup_sensitivity ();
334         }
335 }
336
337 /** @param page true if this was a PAGEUP/PAGEDOWN event for which we won't receive a THUMBRELEASE */
338 void
339 Controls::slider_moved (bool page)
340 {
341         if (!_film) {
342                 return;
343         }
344
345         if (!page && !_slider_being_moved) {
346                 /* This is the first event of a drag; stop playback for the duration of the drag */
347                 _was_running_before_slider = _viewer->stop ();
348                 _slider_being_moved = true;
349         }
350
351         DCPTime t (_slider->GetValue() * _film->length().get() / 4096);
352         t = t.round (_film->video_frame_rate());
353         /* Ensure that we hit the end of the film at the end of the slider */
354         if (t >= _film->length ()) {
355                 t = _film->length() - _viewer->one_video_frame();
356         }
357         _viewer->seek (t, false);
358         update_position_label ();
359 }
360
361 void
362 Controls::slider_released ()
363 {
364         if (_was_running_before_slider) {
365                 /* Restart after a drag */
366                 _viewer->start ();
367         }
368         _slider_being_moved = false;
369 }
370
371 void
372 Controls::play_clicked ()
373 {
374 #ifdef DCPOMATIC_VARIANT_SWAROOP
375         _viewer->start ();
376 #else
377         check_play_state ();
378 #endif
379 }
380
381
382 #ifndef DCPOMATIC_VARIANT_SWAROOP
383 void
384 Controls::check_play_state ()
385 {
386         if (!_film || _film->video_frame_rate() == 0) {
387                 return;
388         }
389
390         if (_play_button->GetValue()) {
391                 _viewer->start ();
392         } else {
393                 _viewer->stop ();
394         }
395 }
396 #endif
397
398 void
399 Controls::update_position_slider ()
400 {
401         if (!_film) {
402                 _slider->SetValue (0);
403                 return;
404         }
405
406         DCPTime const len = _film->length ();
407
408         if (len.get ()) {
409                 int const new_slider_position = 4096 * _viewer->position().get() / len.get();
410                 if (new_slider_position != _slider->GetValue()) {
411                         _slider->SetValue (new_slider_position);
412                 }
413         }
414 }
415
416 void
417 Controls::update_position_label ()
418 {
419         if (!_film) {
420                 _frame_number->SetLabel ("0");
421                 _timecode->SetLabel ("0:0:0.0");
422                 return;
423         }
424
425         double const fps = _film->video_frame_rate ();
426         /* Count frame number from 1 ... not sure if this is the best idea */
427         _frame_number->SetLabel (wxString::Format (wxT("%ld"), lrint (_viewer->position().seconds() * fps) + 1));
428         _timecode->SetLabel (time_to_timecode (_viewer->position(), fps));
429 }
430
431 void
432 Controls::active_jobs_changed (optional<string> j)
433 {
434         _active_job = j;
435         setup_sensitivity ();
436 }
437
438 DCPTime
439 Controls::nudge_amount (wxKeyboardState& ev)
440 {
441         DCPTime amount = _viewer->one_video_frame ();
442
443         if (ev.ShiftDown() && !ev.ControlDown()) {
444                 amount = DCPTime::from_seconds (1);
445         } else if (!ev.ShiftDown() && ev.ControlDown()) {
446                 amount = DCPTime::from_seconds (10);
447         } else if (ev.ShiftDown() && ev.ControlDown()) {
448                 amount = DCPTime::from_seconds (60);
449         }
450
451         return amount;
452 }
453
454 void
455 Controls::rewind_clicked (wxMouseEvent& ev)
456 {
457         _viewer->seek (DCPTime(), true);
458         ev.Skip();
459 }
460
461 void
462 Controls::back_frame ()
463 {
464         _viewer->seek_by (-_viewer->one_video_frame(), true);
465 }
466
467 void
468 Controls::forward_frame ()
469 {
470         _viewer->seek_by (_viewer->one_video_frame(), true);
471 }
472
473 void
474 Controls::back_clicked (wxKeyboardState& ev)
475 {
476         _viewer->seek_by (-nudge_amount(ev), true);
477 }
478
479 void
480 Controls::forward_clicked (wxKeyboardState& ev)
481 {
482         _viewer->seek_by (nudge_amount(ev), true);
483 }
484
485 void
486 Controls::setup_sensitivity ()
487 {
488         /* examine content is the only job which stops the viewer working */
489         bool const active_job = _active_job && *_active_job != "examine_content";
490         bool const c = _film && !_film->content().empty() && !active_job;
491
492         _slider->Enable (c);
493         _rewind_button->Enable (c);
494         _back_button->Enable (c);
495         _forward_button->Enable (c);
496 #ifdef DCPOMATIC_VARIANT_SWAROOP
497         _play_button->Enable (c && !_viewer->playing());
498         _pause_button->Enable (c && (!_current_kind || _current_kind != dcp::ADVERTISEMENT) && _viewer->playing());
499         _stop_button->Enable (c && (!_current_kind || _current_kind != dcp::ADVERTISEMENT));
500         _slider->Enable (c && (!_current_kind || _current_kind != dcp::ADVERTISEMENT));
501 #else
502         _play_button->Enable (c);
503 #endif
504         if (_outline_content) {
505                 _outline_content->Enable (c);
506         }
507         _frame_number->Enable (c);
508         _timecode->Enable (c);
509         if (_jump_to_selected) {
510                 _jump_to_selected->Enable (c);
511         }
512
513         if (_eye) {
514                 _eye->Enable (c && _film->three_d ());
515         }
516
517         _add_button->Enable (Config::instance()->allow_spl_editing() && static_cast<bool>(selected_content()));
518         _save_button->Enable (Config::instance()->allow_spl_editing());
519 }
520
521 shared_ptr<Content>
522 Controls::selected_content () const
523 {
524         long int s = _content_view->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
525         if (s == -1) {
526                 return shared_ptr<Content>();
527         }
528
529         DCPOMATIC_ASSERT (s < int(_content.size()));
530         return _content[s];
531 }
532
533 void
534 Controls::timecode_clicked ()
535 {
536         PlayheadToTimecodeDialog* dialog = new PlayheadToTimecodeDialog (this, _film->video_frame_rate ());
537         if (dialog->ShowModal() == wxID_OK) {
538                 _viewer->seek (dialog->get(), true);
539         }
540         dialog->Destroy ();
541 }
542
543 void
544 Controls::frame_number_clicked ()
545 {
546         PlayheadToFrameDialog* dialog = new PlayheadToFrameDialog (this, _film->video_frame_rate ());
547         if (dialog->ShowModal() == wxID_OK) {
548                 _viewer->seek (dialog->get(), true);
549         }
550         dialog->Destroy ();
551 }
552
553 void
554 Controls::jump_to_selected_clicked ()
555 {
556         Config::instance()->set_jump_to_selected (_jump_to_selected->GetValue ());
557 }
558
559 void
560 Controls::film_changed ()
561 {
562         shared_ptr<Film> film = _viewer->film ();
563
564         if (_film == film) {
565                 return;
566         }
567
568         _film = film;
569
570         setup_sensitivity ();
571
572         update_position_slider ();
573         update_position_label ();
574
575         if (_film) {
576                 _film->Change.connect (boost::bind (&Controls::film_change, this, _1, _2));
577         }
578 }
579
580 shared_ptr<Film>
581 Controls::film () const
582 {
583         return _film;
584 }
585
586 void
587 Controls::show_extended_player_controls (bool s)
588 {
589         _content_view->Show (s);
590         _spl_view->Show (s);
591         if (s) {
592                 update_content_directory ();
593                 update_playlist_directory ();
594         }
595         _current_spl_view->Show (s);
596         _log->Show (s);
597         _add_button->Show (s);
598         _save_button->Show (s);
599         _load_button->Show (s);
600         _v_sizer->Layout ();
601 }
602
603 void
604 Controls::add_content_to_list (shared_ptr<Content> content, wxListCtrl* ctrl)
605 {
606         int const N = ctrl->GetItemCount();
607
608         wxListItem it;
609         it.SetId(N);
610         it.SetColumn(0);
611         DCPTime length = content->length_after_trim ();
612         int seconds = length.seconds();
613         int minutes = seconds / 60;
614         seconds -= minutes * 60;
615         int hours = minutes / 60;
616         minutes -= hours * 60;
617         it.SetText(wxString::Format("%02d:%02d:%02d", hours, minutes, seconds));
618         ctrl->InsertItem(it);
619
620         shared_ptr<DCPContent> dcp = dynamic_pointer_cast<DCPContent>(content);
621         if (dcp && dcp->content_kind()) {
622                 it.SetId(N);
623                 it.SetColumn(1);
624                 it.SetText(std_to_wx(dcp::content_kind_to_string(*dcp->content_kind())));
625                 ctrl->SetItem(it);
626         }
627
628         it.SetId(N);
629         it.SetColumn(2);
630         it.SetText(std_to_wx(content->summary()));
631         ctrl->SetItem(it);
632 }
633
634 void
635 Controls::add_playlist_to_list (shared_ptr<Film> film)
636 {
637         int const N = _spl_view->GetItemCount();
638
639         wxListItem it;
640         it.SetId(N);
641         it.SetColumn(0);
642         it.SetText (std_to_wx(film->name()));
643         _spl_view->InsertItem (it);
644 }
645
646 void
647 Controls::update_content_directory ()
648 {
649         if (!_content_view->IsShown()) {
650                 return;
651         }
652
653         using namespace boost::filesystem;
654
655         _content_view->DeleteAllItems ();
656         _content.clear ();
657         optional<path> dir = Config::instance()->player_content_directory();
658         if (!dir) {
659                 return;
660         }
661
662         wxProgressDialog progress (_("DCP-o-matic"), _("Reading content directory"));
663         JobManager* jm = JobManager::instance ();
664
665         for (directory_iterator i = directory_iterator(*dir); i != directory_iterator(); ++i) {
666                 try {
667                         shared_ptr<Content> content;
668                         if (is_directory(*i) && (is_regular_file(*i / "ASSETMAP") || is_regular_file(*i / "ASSETMAP.xml"))) {
669                                 content.reset (new DCPContent(_film, *i));
670                         } else if (i->path().extension() == ".mp4" || i->path().extension() == ".ecinema") {
671                                 content = content_factory(_film, *i).front();
672                         }
673
674                         if (content) {
675                                 jm->add (shared_ptr<Job>(new ExamineContentJob(_film, content)));
676                                 while (jm->work_to_do()) {
677                                         if (!progress.Pulse()) {
678                                                 /* user pressed cancel */
679                                                 BOOST_FOREACH (shared_ptr<Job> i, jm->get()) {
680                                                         i->cancel();
681                                                 }
682                                                 return;
683                                         }
684                                         dcpomatic_sleep (1);
685                                 }
686                                 if (report_errors_from_last_job (this)) {
687                                         add_content_to_list (content, _content_view);
688                                         _content.push_back (content);
689                                 }
690                         }
691                 } catch (boost::filesystem::filesystem_error& e) {
692                         /* Never mind */
693                 } catch (dcp::DCPReadError& e) {
694                         /* Never mind */
695                 }
696         }
697 }
698
699 void
700 Controls::update_playlist_directory ()
701 {
702         if (!_spl_view->IsShown()) {
703                 return;
704         }
705
706         using namespace boost::filesystem;
707
708         _spl_view->DeleteAllItems ();
709         optional<path> dir = Config::instance()->player_playlist_directory();
710         if (!dir) {
711                 return;
712         }
713
714         for (directory_iterator i = directory_iterator(*dir); i != directory_iterator(); ++i) {
715                 try {
716                         shared_ptr<Film> film (new Film(optional<path>()));
717                         film->read_metadata (i->path());
718                         _playlists.push_back (film);
719                         add_playlist_to_list (film);
720                 } catch (exception& e) {
721                         /* Never mind */
722                 }
723         }
724 }
725
726 #ifdef DCPOMATIC_VARIANT_SWAROOP
727 void
728 Controls::pause_clicked ()
729 {
730         _viewer->stop ();
731 }
732
733 void
734 Controls::stop_clicked ()
735 {
736         _viewer->stop ();
737         _viewer->seek (DCPTime(), true);
738 }
739 #endif
740
741 void
742 Controls::log (wxString s)
743 {
744         struct timeval time;
745         gettimeofday (&time, 0);
746         char buffer[64];
747         time_t const sec = time.tv_sec;
748         struct tm* t = localtime (&sec);
749         strftime (buffer, 64, "%c", t);
750         wxString ts = std_to_wx(string(buffer)) + N_(": ");
751         _log->SetValue(_log->GetValue() + ts + s + "\n");
752 }
753
754 void
755 Controls::image_changed (boost::weak_ptr<PlayerVideo> weak_pv)
756 {
757 #ifdef DCPOMATIC_VARIANT_SWAROOP
758         shared_ptr<PlayerVideo> pv = weak_pv.lock ();
759         if (!pv) {
760                 return;
761         }
762
763         shared_ptr<Content> c = pv->content().lock();
764         if (!c) {
765                 return;
766         }
767
768         shared_ptr<DCPContent> dc = dynamic_pointer_cast<DCPContent> (c);
769         if (!dc) {
770                 return;
771         }
772
773         if (!_current_kind || *_current_kind != dc->content_kind()) {
774                 _current_kind = dc->content_kind ();
775                 setup_sensitivity ();
776         }
777 #endif
778 }