Fix crash.
[dcpomatic.git] / src / wx / control_film_viewer.cc
1 #include "control_film_viewer.h"
2 #include "film_viewer.h"
3 #include "wx_util.h"
4 #include "playhead_to_timecode_dialog.h"
5 #include "playhead_to_frame_dialog.h"
6 #include "lib/job_manager.h"
7 #include <wx/wx.h>
8 #include <wx/tglbtn.h>
9
10 using std::string;
11 using boost::optional;
12 using boost::shared_ptr;
13 using boost::weak_ptr;
14
15 /** @param outline_content true if viewer should present an "outline content" checkbox.
16  *  @param jump_to_selected true if viewer should present a "jump to selected" checkbox.
17  */
18 ControlFilmViewer::ControlFilmViewer (wxWindow* parent, bool outline_content, bool jump_to_selected)
19         : wxPanel (parent)
20         , _viewer (new FilmViewer(this))
21         , _slider_being_moved (false)
22         , _was_running_before_slider (false)
23         , _outline_content (0)
24         , _eye (0)
25         , _jump_to_selected (0)
26         , _slider (new wxSlider (this, wxID_ANY, 0, 0, 4096))
27         , _rewind_button (new wxButton (this, wxID_ANY, wxT("|<")))
28         , _back_button (new wxButton (this, wxID_ANY, wxT("<")))
29         , _forward_button (new wxButton (this, wxID_ANY, wxT(">")))
30         , _frame_number (new wxStaticText (this, wxID_ANY, wxT("")))
31         , _timecode (new wxStaticText (this, wxID_ANY, wxT("")))
32         , _play_button (new wxToggleButton (this, wxID_ANY, _("Play")))
33 {
34         _v_sizer = new wxBoxSizer (wxVERTICAL);
35         SetSizer (_v_sizer);
36         _v_sizer->Add (_viewer->panel(), 1, wxEXPAND);
37
38         wxBoxSizer* view_options = new wxBoxSizer (wxHORIZONTAL);
39         if (outline_content) {
40                 _outline_content = new wxCheckBox (this, wxID_ANY, _("Outline content"));
41                 view_options->Add (_outline_content, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, DCPOMATIC_SIZER_GAP);
42         }
43
44         _eye = new wxChoice (this, wxID_ANY);
45         _eye->Append (_("Left"));
46         _eye->Append (_("Right"));
47         _eye->SetSelection (0);
48         view_options->Add (_eye, 0, wxLEFT | wxRIGHT | wxALIGN_CENTER_VERTICAL, DCPOMATIC_SIZER_GAP);
49
50         if (jump_to_selected) {
51                 _jump_to_selected = new wxCheckBox (this, wxID_ANY, _("Jump to selected content"));
52                 view_options->Add (_jump_to_selected, 0, wxLEFT | wxRIGHT | wxALIGN_CENTER_VERTICAL, DCPOMATIC_SIZER_GAP);
53         }
54
55         _v_sizer->Add (view_options, 0, wxALL, DCPOMATIC_SIZER_GAP);
56
57         wxBoxSizer* h_sizer = new wxBoxSizer (wxHORIZONTAL);
58
59         wxBoxSizer* time_sizer = new wxBoxSizer (wxVERTICAL);
60         time_sizer->Add (_frame_number, 0, wxEXPAND);
61         time_sizer->Add (_timecode, 0, wxEXPAND);
62
63         h_sizer->Add (_rewind_button, 0, wxALL, 2);
64         h_sizer->Add (_back_button, 0, wxALL, 2);
65         h_sizer->Add (time_sizer, 0, wxEXPAND);
66         h_sizer->Add (_forward_button, 0, wxALL, 2);
67         h_sizer->Add (_play_button, 0, wxEXPAND);
68         h_sizer->Add (_slider, 1, wxEXPAND);
69
70         _v_sizer->Add (h_sizer, 0, wxEXPAND | wxALL, 6);
71
72         _frame_number->SetMinSize (wxSize (84, -1));
73         _rewind_button->SetMinSize (wxSize (32, -1));
74         _back_button->SetMinSize (wxSize (32, -1));
75         _forward_button->SetMinSize (wxSize (32, -1));
76
77         _eye->Bind (wxEVT_CHOICE, boost::bind (&ControlFilmViewer::eye_changed, this));
78         if (_outline_content) {
79                 _outline_content->Bind (wxEVT_CHECKBOX, boost::bind (&ControlFilmViewer::outline_content_changed, this));
80         }
81
82         _slider->Bind           (wxEVT_SCROLL_THUMBTRACK,   boost::bind (&ControlFilmViewer::slider_moved,    this, false));
83         _slider->Bind           (wxEVT_SCROLL_PAGEUP,       boost::bind (&ControlFilmViewer::slider_moved,    this, true));
84         _slider->Bind           (wxEVT_SCROLL_PAGEDOWN,     boost::bind (&ControlFilmViewer::slider_moved,    this, true));
85         _slider->Bind           (wxEVT_SCROLL_THUMBRELEASE, boost::bind (&ControlFilmViewer::slider_released, this));
86         _play_button->Bind      (wxEVT_TOGGLEBUTTON,        boost::bind (&ControlFilmViewer::play_clicked,    this));
87         _rewind_button->Bind    (wxEVT_LEFT_DOWN,           boost::bind (&ControlFilmViewer::rewind_clicked,  this, _1));
88         _back_button->Bind      (wxEVT_LEFT_DOWN,           boost::bind (&ControlFilmViewer::back_clicked,    this, _1));
89         _forward_button->Bind   (wxEVT_LEFT_DOWN,           boost::bind (&ControlFilmViewer::forward_clicked, this, _1));
90         _frame_number->Bind     (wxEVT_LEFT_DOWN,           boost::bind (&ControlFilmViewer::frame_number_clicked, this));
91         _timecode->Bind         (wxEVT_LEFT_DOWN,           boost::bind (&ControlFilmViewer::timecode_clicked, this));
92         if (_jump_to_selected) {
93                 _jump_to_selected->Bind (wxEVT_CHECKBOX, boost::bind (&ControlFilmViewer::jump_to_selected_clicked, this));
94                 _jump_to_selected->SetValue (Config::instance()->jump_to_selected ());
95         }
96
97         _viewer->ImageChanged.connect (boost::bind(&ControlFilmViewer::image_changed, this, _1));
98         _viewer->PositionChanged.connect (boost::bind(&ControlFilmViewer::position_changed, this));
99
100         set_film (shared_ptr<Film> ());
101
102         setup_sensitivity ();
103
104         JobManager::instance()->ActiveJobsChanged.connect (
105                 bind (&ControlFilmViewer::active_jobs_changed, this, _2)
106                 );
107 }
108
109 void
110 ControlFilmViewer::position_changed ()
111 {
112         update_position_label ();
113         update_position_slider ();
114 }
115
116 void
117 ControlFilmViewer::eye_changed ()
118 {
119         _viewer->set_eyes (_eye->GetSelection() == 0 ? EYES_LEFT : EYES_RIGHT);
120 }
121
122 void
123 ControlFilmViewer::outline_content_changed ()
124 {
125         _viewer->set_outline_content (_outline_content->GetValue());
126 }
127
128 void
129 ControlFilmViewer::film_change (ChangeType type, Film::Property p)
130 {
131         if (type != CHANGE_TYPE_DONE) {
132                 return;
133         }
134
135         if (p == Film::CONTENT || p == Film::THREE_D) {
136                 setup_sensitivity ();
137         }
138 }
139
140 void
141 ControlFilmViewer::image_changed (weak_ptr<PlayerVideo> pv)
142 {
143         ImageChanged (pv);
144 }
145
146 /** @param page true if this was a PAGEUP/PAGEDOWN event for which we won't receive a THUMBRELEASE */
147 void
148 ControlFilmViewer::slider_moved (bool page)
149 {
150         if (!_film) {
151                 return;
152         }
153
154         if (!page && !_slider_being_moved) {
155                 /* This is the first event of a drag; stop playback for the duration of the drag */
156                 _was_running_before_slider = stop ();
157                 _slider_being_moved = true;
158         }
159
160         DCPTime t (_slider->GetValue() * _film->length().get() / 4096);
161         t = t.round (_film->video_frame_rate());
162         /* Ensure that we hit the end of the film at the end of the slider */
163         if (t >= _film->length ()) {
164                 t = _film->length() - _viewer->one_video_frame();
165         }
166         _viewer->seek (t, false);
167         update_position_label ();
168 }
169
170 void
171 ControlFilmViewer::slider_released ()
172 {
173         if (_was_running_before_slider) {
174                 /* Restart after a drag */
175                 start ();
176         }
177         _slider_being_moved = false;
178 }
179
180 void
181 ControlFilmViewer::play_clicked ()
182 {
183         check_play_state ();
184 }
185
186 void
187 ControlFilmViewer::check_play_state ()
188 {
189         if (!_film || _film->video_frame_rate() == 0) {
190                 return;
191         }
192
193         if (_play_button->GetValue()) {
194                 start ();
195         } else {
196                 stop ();
197         }
198 }
199
200 void
201 ControlFilmViewer::update_position_slider ()
202 {
203         if (!_film) {
204                 _slider->SetValue (0);
205                 return;
206         }
207
208         DCPTime const len = _film->length ();
209
210         if (len.get ()) {
211                 int const new_slider_position = 4096 * _viewer->video_position().get() / len.get();
212                 if (new_slider_position != _slider->GetValue()) {
213                         _slider->SetValue (new_slider_position);
214                 }
215         }
216 }
217
218 void
219 ControlFilmViewer::update_position_label ()
220 {
221         if (!_film) {
222                 _frame_number->SetLabel ("0");
223                 _timecode->SetLabel ("0:0:0.0");
224                 return;
225         }
226
227         double const fps = _film->video_frame_rate ();
228         /* Count frame number from 1 ... not sure if this is the best idea */
229         _frame_number->SetLabel (wxString::Format (wxT("%ld"), lrint (_viewer->video_position().seconds() * fps) + 1));
230         _timecode->SetLabel (time_to_timecode (_viewer->video_position(), fps));
231 }
232
233 void
234 ControlFilmViewer::active_jobs_changed (optional<string> j)
235 {
236         /* examine content is the only job which stops the viewer working */
237         bool const a = !j || *j != "examine_content";
238         _slider->Enable (a);
239         _play_button->Enable (a);
240 }
241
242 DCPTime
243 ControlFilmViewer::nudge_amount (wxKeyboardState& ev)
244 {
245         DCPTime amount = _viewer->one_video_frame ();
246
247         if (ev.ShiftDown() && !ev.ControlDown()) {
248                 amount = DCPTime::from_seconds (1);
249         } else if (!ev.ShiftDown() && ev.ControlDown()) {
250                 amount = DCPTime::from_seconds (10);
251         } else if (ev.ShiftDown() && ev.ControlDown()) {
252                 amount = DCPTime::from_seconds (60);
253         }
254
255         return amount;
256 }
257
258 void
259 ControlFilmViewer::rewind_clicked (wxMouseEvent& ev)
260 {
261         _viewer->go_to (DCPTime());
262         ev.Skip();
263 }
264
265 void
266 ControlFilmViewer::back_frame ()
267 {
268         _viewer->move (-_viewer->one_video_frame());
269 }
270
271 void
272 ControlFilmViewer::forward_frame ()
273 {
274         _viewer->move (_viewer->one_video_frame());
275 }
276
277 void
278 ControlFilmViewer::back_clicked (wxKeyboardState& ev)
279 {
280         _viewer->move (-nudge_amount(ev));
281 }
282
283 void
284 ControlFilmViewer::forward_clicked (wxKeyboardState& ev)
285 {
286         _viewer->move (nudge_amount(ev));
287 }
288
289 void
290 ControlFilmViewer::setup_sensitivity ()
291 {
292         bool const c = _film && !_film->content().empty ();
293
294         _slider->Enable (c);
295         _rewind_button->Enable (c);
296         _back_button->Enable (c);
297         _forward_button->Enable (c);
298         _play_button->Enable (c);
299         if (_outline_content) {
300                 _outline_content->Enable (c);
301         }
302         _frame_number->Enable (c);
303         _timecode->Enable (c);
304         if (_jump_to_selected) {
305                 _jump_to_selected->Enable (c);
306         }
307
308         _eye->Enable (c && _film->three_d ());
309 }
310
311 void
312 ControlFilmViewer::timecode_clicked ()
313 {
314         PlayheadToTimecodeDialog* dialog = new PlayheadToTimecodeDialog (this, _film->video_frame_rate ());
315         if (dialog->ShowModal() == wxID_OK) {
316                 _viewer->go_to (dialog->get ());
317         }
318         dialog->Destroy ();
319 }
320
321 void
322 ControlFilmViewer::frame_number_clicked ()
323 {
324         PlayheadToFrameDialog* dialog = new PlayheadToFrameDialog (this, _film->video_frame_rate ());
325         if (dialog->ShowModal() == wxID_OK) {
326                 _viewer->go_to (dialog->get ());
327         }
328         dialog->Destroy ();
329 }
330
331 void
332 ControlFilmViewer::jump_to_selected_clicked ()
333 {
334         Config::instance()->set_jump_to_selected (_jump_to_selected->GetValue ());
335 }
336
337 void
338 ControlFilmViewer::set_film (shared_ptr<Film> film)
339 {
340         _viewer->set_film (film);
341
342         if (_film == film) {
343                 return;
344         }
345
346         _film = film;
347
348         setup_sensitivity ();
349
350         update_position_slider ();
351         update_position_label ();
352
353         _film->Change.connect (boost::bind (&ControlFilmViewer::film_change, this, _1, _2));
354 }
355
356 void
357 ControlFilmViewer::set_position (DCPTime p)
358 {
359         _viewer->set_position (p);
360 }
361
362 void
363 ControlFilmViewer::set_position (shared_ptr<Content> content, ContentTime t)
364 {
365         _viewer->set_position (content, t);
366 }
367
368 void
369 ControlFilmViewer::set_dcp_decode_reduction (boost::optional<int> reduction)
370 {
371         _viewer->set_dcp_decode_reduction (reduction);
372 }
373
374 void
375 ControlFilmViewer::show_closed_captions ()
376 {
377         _viewer->show_closed_captions ();
378 }
379
380 void
381 ControlFilmViewer::start ()
382 {
383         _play_button->SetValue (true);
384         _viewer->start ();
385 }
386
387 bool
388 ControlFilmViewer::stop ()
389 {
390         _play_button->SetValue (false);
391         return _viewer->stop ();
392 }
393
394 bool
395 ControlFilmViewer::playing () const
396 {
397         return _viewer->playing ();
398 }
399
400 void
401 ControlFilmViewer::slow_refresh ()
402 {
403         _viewer->slow_refresh ();
404 }
405
406 int
407 ControlFilmViewer::dropped () const
408 {
409         return _viewer->dropped ();
410 }
411
412 shared_ptr<Film>
413 ControlFilmViewer::film () const
414 {
415         return _film;
416 }
417
418 optional<int>
419 ControlFilmViewer::dcp_decode_reduction () const
420 {
421         return _viewer->dcp_decode_reduction ();
422 }
423
424 DCPTime
425 ControlFilmViewer::position () const
426 {
427         return _viewer->position ();
428 }
429
430 void
431 ControlFilmViewer::set_coalesce_player_changes (bool c)
432 {
433         _viewer->set_coalesce_player_changes (c);
434 }