Basic dual-screen mode for player.
[dcpomatic.git] / src / wx / controls.cc
1 #include "controls.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 Controls::Controls (wxWindow* parent, shared_ptr<FilmViewer> viewer, bool outline_content, bool jump_to_selected, bool eye)
19         : wxPanel (parent)
20         , _viewer (viewer)
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
37         wxBoxSizer* view_options = new wxBoxSizer (wxHORIZONTAL);
38         if (outline_content) {
39                 _outline_content = new wxCheckBox (this, wxID_ANY, _("Outline content"));
40                 view_options->Add (_outline_content, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, DCPOMATIC_SIZER_GAP);
41         }
42
43         if (eye) {
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
51         if (jump_to_selected) {
52                 _jump_to_selected = new wxCheckBox (this, wxID_ANY, _("Jump to selected content"));
53                 view_options->Add (_jump_to_selected, 0, wxLEFT | wxRIGHT | wxALIGN_CENTER_VERTICAL, DCPOMATIC_SIZER_GAP);
54         }
55
56         _v_sizer->Add (view_options, 0, wxALL, DCPOMATIC_SIZER_GAP);
57
58         wxBoxSizer* h_sizer = new wxBoxSizer (wxHORIZONTAL);
59
60         wxBoxSizer* time_sizer = new wxBoxSizer (wxVERTICAL);
61         time_sizer->Add (_frame_number, 0, wxEXPAND);
62         time_sizer->Add (_timecode, 0, wxEXPAND);
63
64         h_sizer->Add (_rewind_button, 0, wxALL, 2);
65         h_sizer->Add (_back_button, 0, wxALL, 2);
66         h_sizer->Add (time_sizer, 0, wxEXPAND);
67         h_sizer->Add (_forward_button, 0, wxALL, 2);
68         h_sizer->Add (_play_button, 0, wxEXPAND);
69         h_sizer->Add (_slider, 1, wxEXPAND);
70
71         _v_sizer->Add (h_sizer, 0, wxEXPAND | wxALL, 6);
72
73         _frame_number->SetMinSize (wxSize (84, -1));
74         _rewind_button->SetMinSize (wxSize (32, -1));
75         _back_button->SetMinSize (wxSize (32, -1));
76         _forward_button->SetMinSize (wxSize (32, -1));
77
78         if (_eye) {
79                 _eye->Bind (wxEVT_CHOICE, boost::bind (&Controls::eye_changed, this));
80         }
81         if (_outline_content) {
82                 _outline_content->Bind (wxEVT_CHECKBOX, boost::bind (&Controls::outline_content_changed, this));
83         }
84
85         _slider->Bind           (wxEVT_SCROLL_THUMBTRACK,   boost::bind (&Controls::slider_moved,    this, false));
86         _slider->Bind           (wxEVT_SCROLL_PAGEUP,       boost::bind (&Controls::slider_moved,    this, true));
87         _slider->Bind           (wxEVT_SCROLL_PAGEDOWN,     boost::bind (&Controls::slider_moved,    this, true));
88         _slider->Bind           (wxEVT_SCROLL_THUMBRELEASE, boost::bind (&Controls::slider_released, this));
89         _play_button->Bind      (wxEVT_TOGGLEBUTTON,        boost::bind (&Controls::play_clicked,    this));
90         _rewind_button->Bind    (wxEVT_LEFT_DOWN,           boost::bind (&Controls::rewind_clicked,  this, _1));
91         _back_button->Bind      (wxEVT_LEFT_DOWN,           boost::bind (&Controls::back_clicked,    this, _1));
92         _forward_button->Bind   (wxEVT_LEFT_DOWN,           boost::bind (&Controls::forward_clicked, this, _1));
93         _frame_number->Bind     (wxEVT_LEFT_DOWN,           boost::bind (&Controls::frame_number_clicked, this));
94         _timecode->Bind         (wxEVT_LEFT_DOWN,           boost::bind (&Controls::timecode_clicked, this));
95         if (_jump_to_selected) {
96                 _jump_to_selected->Bind (wxEVT_CHECKBOX, boost::bind (&Controls::jump_to_selected_clicked, this));
97                 _jump_to_selected->SetValue (Config::instance()->jump_to_selected ());
98         }
99
100         _viewer->PositionChanged.connect (boost::bind(&Controls::position_changed, this));
101         _viewer->Started.connect (boost::bind(&Controls::started, this));
102         _viewer->Stopped.connect (boost::bind(&Controls::stopped, this));
103         _viewer->FilmChanged.connect (boost::bind(&Controls::film_changed, this));
104
105         film_changed ();
106
107         setup_sensitivity ();
108
109         JobManager::instance()->ActiveJobsChanged.connect (
110                 bind (&Controls::active_jobs_changed, this, _2)
111                 );
112 }
113
114 void
115 Controls::started ()
116 {
117         _play_button->SetValue (true);
118 }
119
120 void
121 Controls::stopped ()
122 {
123         _play_button->SetValue (false);
124 }
125
126 void
127 Controls::position_changed ()
128 {
129         if (!_slider_being_moved) {
130                 update_position_label ();
131                 update_position_slider ();
132         }
133 }
134
135 void
136 Controls::eye_changed ()
137 {
138         _viewer->set_eyes (_eye->GetSelection() == 0 ? EYES_LEFT : EYES_RIGHT);
139 }
140
141 void
142 Controls::outline_content_changed ()
143 {
144         _viewer->set_outline_content (_outline_content->GetValue());
145 }
146
147 void
148 Controls::film_change (ChangeType type, Film::Property p)
149 {
150         if (type != CHANGE_TYPE_DONE) {
151                 return;
152         }
153
154         if (p == Film::CONTENT || p == Film::THREE_D) {
155                 setup_sensitivity ();
156         }
157 }
158
159 /** @param page true if this was a PAGEUP/PAGEDOWN event for which we won't receive a THUMBRELEASE */
160 void
161 Controls::slider_moved (bool page)
162 {
163         if (!_film) {
164                 return;
165         }
166
167         if (!page && !_slider_being_moved) {
168                 /* This is the first event of a drag; stop playback for the duration of the drag */
169                 _was_running_before_slider = _viewer->stop ();
170                 _slider_being_moved = true;
171         }
172
173         DCPTime t (_slider->GetValue() * _film->length().get() / 4096);
174         t = t.round (_film->video_frame_rate());
175         /* Ensure that we hit the end of the film at the end of the slider */
176         if (t >= _film->length ()) {
177                 t = _film->length() - _viewer->one_video_frame();
178         }
179         _viewer->seek (t, false);
180         update_position_label ();
181 }
182
183 void
184 Controls::slider_released ()
185 {
186         if (_was_running_before_slider) {
187                 /* Restart after a drag */
188                 _viewer->start ();
189         }
190         _slider_being_moved = false;
191 }
192
193 void
194 Controls::play_clicked ()
195 {
196         check_play_state ();
197 }
198
199 void
200 Controls::check_play_state ()
201 {
202         if (!_film || _film->video_frame_rate() == 0) {
203                 return;
204         }
205
206         if (_play_button->GetValue()) {
207                 _viewer->start ();
208         } else {
209                 _viewer->stop ();
210         }
211 }
212
213 void
214 Controls::update_position_slider ()
215 {
216         if (!_film) {
217                 _slider->SetValue (0);
218                 return;
219         }
220
221         DCPTime const len = _film->length ();
222
223         if (len.get ()) {
224                 int const new_slider_position = 4096 * _viewer->position().get() / len.get();
225                 if (new_slider_position != _slider->GetValue()) {
226                         _slider->SetValue (new_slider_position);
227                 }
228         }
229 }
230
231 void
232 Controls::update_position_label ()
233 {
234         if (!_film) {
235                 _frame_number->SetLabel ("0");
236                 _timecode->SetLabel ("0:0:0.0");
237                 return;
238         }
239
240         double const fps = _film->video_frame_rate ();
241         /* Count frame number from 1 ... not sure if this is the best idea */
242         _frame_number->SetLabel (wxString::Format (wxT("%ld"), lrint (_viewer->position().seconds() * fps) + 1));
243         _timecode->SetLabel (time_to_timecode (_viewer->position(), fps));
244 }
245
246 void
247 Controls::active_jobs_changed (optional<string> j)
248 {
249         /* examine content is the only job which stops the viewer working */
250         bool const a = !j || *j != "examine_content";
251         _slider->Enable (a);
252         _play_button->Enable (a);
253 }
254
255 DCPTime
256 Controls::nudge_amount (wxKeyboardState& ev)
257 {
258         DCPTime amount = _viewer->one_video_frame ();
259
260         if (ev.ShiftDown() && !ev.ControlDown()) {
261                 amount = DCPTime::from_seconds (1);
262         } else if (!ev.ShiftDown() && ev.ControlDown()) {
263                 amount = DCPTime::from_seconds (10);
264         } else if (ev.ShiftDown() && ev.ControlDown()) {
265                 amount = DCPTime::from_seconds (60);
266         }
267
268         return amount;
269 }
270
271 void
272 Controls::rewind_clicked (wxMouseEvent& ev)
273 {
274         _viewer->seek (DCPTime(), true);
275         ev.Skip();
276 }
277
278 void
279 Controls::back_frame ()
280 {
281         _viewer->seek_by (-_viewer->one_video_frame(), true);
282 }
283
284 void
285 Controls::forward_frame ()
286 {
287         _viewer->seek_by (_viewer->one_video_frame(), true);
288 }
289
290 void
291 Controls::back_clicked (wxKeyboardState& ev)
292 {
293         _viewer->seek_by (-nudge_amount(ev), true);
294 }
295
296 void
297 Controls::forward_clicked (wxKeyboardState& ev)
298 {
299         _viewer->seek_by (nudge_amount(ev), true);
300 }
301
302 void
303 Controls::setup_sensitivity ()
304 {
305         bool const c = _film && !_film->content().empty ();
306
307         _slider->Enable (c);
308         _rewind_button->Enable (c);
309         _back_button->Enable (c);
310         _forward_button->Enable (c);
311         _play_button->Enable (c);
312         if (_outline_content) {
313                 _outline_content->Enable (c);
314         }
315         _frame_number->Enable (c);
316         _timecode->Enable (c);
317         if (_jump_to_selected) {
318                 _jump_to_selected->Enable (c);
319         }
320
321         if (_eye) {
322                 _eye->Enable (c && _film->three_d ());
323         }
324 }
325
326 void
327 Controls::timecode_clicked ()
328 {
329         PlayheadToTimecodeDialog* dialog = new PlayheadToTimecodeDialog (this, _film->video_frame_rate ());
330         if (dialog->ShowModal() == wxID_OK) {
331                 _viewer->seek (dialog->get(), true);
332         }
333         dialog->Destroy ();
334 }
335
336 void
337 Controls::frame_number_clicked ()
338 {
339         PlayheadToFrameDialog* dialog = new PlayheadToFrameDialog (this, _film->video_frame_rate ());
340         if (dialog->ShowModal() == wxID_OK) {
341                 _viewer->seek (dialog->get(), true);
342         }
343         dialog->Destroy ();
344 }
345
346 void
347 Controls::jump_to_selected_clicked ()
348 {
349         Config::instance()->set_jump_to_selected (_jump_to_selected->GetValue ());
350 }
351
352 void
353 Controls::film_changed ()
354 {
355         shared_ptr<Film> film = _viewer->film ();
356
357         if (_film == film) {
358                 return;
359         }
360
361         _film = film;
362
363         setup_sensitivity ();
364
365         update_position_slider ();
366         update_position_label ();
367
368         _film->Change.connect (boost::bind (&Controls::film_change, this, _1, _2));
369 }
370
371 shared_ptr<Film>
372 Controls::film () const
373 {
374         return _film;
375 }