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