Add swaroop-profile start/stop/pause buttons.
[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 <wx/wx.h>
28 #include <wx/tglbtn.h>
29 #include <wx/listctrl.h>
30
31 using std::string;
32 using boost::optional;
33 using boost::shared_ptr;
34 using boost::weak_ptr;
35
36 /** @param outline_content true if viewer should present an "outline content" checkbox.
37  *  @param jump_to_selected true if viewer should present a "jump to selected" checkbox.
38  */
39 Controls::Controls (wxWindow* parent, shared_ptr<FilmViewer> viewer, bool outline_content, bool jump_to_selected, bool eye, bool dcp_directory)
40         : wxPanel (parent)
41         , _viewer (viewer)
42         , _slider_being_moved (false)
43         , _was_running_before_slider (false)
44         , _outline_content (0)
45         , _eye (0)
46         , _jump_to_selected (0)
47         , _slider (new wxSlider (this, wxID_ANY, 0, 0, 4096))
48         , _rewind_button (new wxButton (this, wxID_ANY, wxT("|<")))
49         , _back_button (new wxButton (this, wxID_ANY, wxT("<")))
50         , _forward_button (new wxButton (this, wxID_ANY, wxT(">")))
51         , _frame_number (new wxStaticText (this, wxID_ANY, wxT("")))
52         , _timecode (new wxStaticText (this, wxID_ANY, wxT("")))
53 #ifdef DCPOMATIC_VARIANT_SWAROOP
54         , _play_button (new wxButton(this, wxID_ANY, _("Play")))
55         , _pause_button (new wxButton(this, wxID_ANY, _("Pause")))
56         , _stop_button (new wxButton(this, wxID_ANY, _("Stop")))
57 #else
58         , _play_button (new wxToggleButton(this, wxID_ANY, _("Play")))
59 #endif
60 {
61         _v_sizer = new wxBoxSizer (wxVERTICAL);
62         SetSizer (_v_sizer);
63
64         wxBoxSizer* view_options = new wxBoxSizer (wxHORIZONTAL);
65         if (outline_content) {
66                 _outline_content = new wxCheckBox (this, wxID_ANY, _("Outline content"));
67                 view_options->Add (_outline_content, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, DCPOMATIC_SIZER_GAP);
68         }
69
70         if (eye) {
71                 _eye = new wxChoice (this, wxID_ANY);
72                 _eye->Append (_("Left"));
73                 _eye->Append (_("Right"));
74                 _eye->SetSelection (0);
75                 view_options->Add (_eye, 0, wxLEFT | wxRIGHT | wxALIGN_CENTER_VERTICAL, DCPOMATIC_SIZER_GAP);
76         }
77
78         if (jump_to_selected) {
79                 _jump_to_selected = new wxCheckBox (this, wxID_ANY, _("Jump to selected content"));
80                 view_options->Add (_jump_to_selected, 0, wxLEFT | wxRIGHT | wxALIGN_CENTER_VERTICAL, DCPOMATIC_SIZER_GAP);
81         }
82
83         _v_sizer->Add (view_options, 0, wxALL, DCPOMATIC_SIZER_GAP);
84
85         _dcp_directory = new wxListCtrl (this, wxID_ANY, wxDefaultPosition, wxSize(600, -1), wxLC_REPORT | wxLC_NO_HEADER);
86         _dcp_directory->AppendColumn (wxT(""), wxLIST_FORMAT_LEFT, 580);
87         _v_sizer->Add (_dcp_directory, 0, wxALL, DCPOMATIC_SIZER_GAP);
88         _dcp_directory->Show (dcp_directory);
89
90         wxBoxSizer* h_sizer = new wxBoxSizer (wxHORIZONTAL);
91
92         wxBoxSizer* time_sizer = new wxBoxSizer (wxVERTICAL);
93         time_sizer->Add (_frame_number, 0, wxEXPAND);
94         time_sizer->Add (_timecode, 0, wxEXPAND);
95
96         h_sizer->Add (_rewind_button, 0, wxALL, 2);
97         h_sizer->Add (_back_button, 0, wxALL, 2);
98         h_sizer->Add (time_sizer, 0, wxEXPAND);
99         h_sizer->Add (_forward_button, 0, wxALL, 2);
100         h_sizer->Add (_play_button, 0, wxEXPAND);
101 #ifdef DCPOMATIC_VARIANT_SWAROOP
102         h_sizer->Add (_pause_button, 0, wxEXPAND);
103         h_sizer->Add (_stop_button, 0, wxEXPAND);
104 #endif
105         h_sizer->Add (_slider, 1, wxEXPAND);
106
107         _v_sizer->Add (h_sizer, 0, wxEXPAND | wxALL, 6);
108
109         _frame_number->SetMinSize (wxSize (84, -1));
110         _rewind_button->SetMinSize (wxSize (32, -1));
111         _back_button->SetMinSize (wxSize (32, -1));
112         _forward_button->SetMinSize (wxSize (32, -1));
113
114         if (_eye) {
115                 _eye->Bind (wxEVT_CHOICE, boost::bind (&Controls::eye_changed, this));
116         }
117         if (_outline_content) {
118                 _outline_content->Bind (wxEVT_CHECKBOX, boost::bind (&Controls::outline_content_changed, this));
119         }
120
121         _slider->Bind           (wxEVT_SCROLL_THUMBTRACK,   boost::bind (&Controls::slider_moved,    this, false));
122         _slider->Bind           (wxEVT_SCROLL_PAGEUP,       boost::bind (&Controls::slider_moved,    this, true));
123         _slider->Bind           (wxEVT_SCROLL_PAGEDOWN,     boost::bind (&Controls::slider_moved,    this, true));
124         _slider->Bind           (wxEVT_SCROLL_CHANGED,      boost::bind (&Controls::slider_released, this));
125 #ifdef DCPOMATIC_VARIANT_SWAROOP
126         _play_button->Bind      (wxEVT_BUTTON,              boost::bind (&Controls::play_clicked,    this));
127         _pause_button->Bind     (wxEVT_BUTTON,              boost::bind (&Controls::pause_clicked,   this));
128         _stop_button->Bind      (wxEVT_BUTTON,              boost::bind (&Controls::stop_clicked,    this));
129 #else
130         _play_button->Bind      (wxEVT_TOGGLEBUTTON,        boost::bind (&Controls::play_clicked,    this));
131 #endif
132         _rewind_button->Bind    (wxEVT_LEFT_DOWN,           boost::bind (&Controls::rewind_clicked,  this, _1));
133         _back_button->Bind      (wxEVT_LEFT_DOWN,           boost::bind (&Controls::back_clicked,    this, _1));
134         _forward_button->Bind   (wxEVT_LEFT_DOWN,           boost::bind (&Controls::forward_clicked, this, _1));
135         _frame_number->Bind     (wxEVT_LEFT_DOWN,           boost::bind (&Controls::frame_number_clicked, this));
136         _timecode->Bind         (wxEVT_LEFT_DOWN,           boost::bind (&Controls::timecode_clicked, this));
137         _dcp_directory->Bind    (wxEVT_LIST_ITEM_SELECTED,  boost::bind (&Controls::dcp_directory_selected, this));
138         if (_jump_to_selected) {
139                 _jump_to_selected->Bind (wxEVT_CHECKBOX, boost::bind (&Controls::jump_to_selected_clicked, this));
140                 _jump_to_selected->SetValue (Config::instance()->jump_to_selected ());
141         }
142
143         _viewer->PositionChanged.connect (boost::bind(&Controls::position_changed, this));
144         _viewer->Started.connect (boost::bind(&Controls::started, this));
145         _viewer->Stopped.connect (boost::bind(&Controls::stopped, this));
146         _viewer->FilmChanged.connect (boost::bind(&Controls::film_changed, this));
147
148         film_changed ();
149
150         setup_sensitivity ();
151         update_dcp_directory ();
152
153         JobManager::instance()->ActiveJobsChanged.connect (
154                 bind (&Controls::active_jobs_changed, this, _2)
155                 );
156
157         _config_changed_connection = Config::instance()->Changed.connect (bind(&Controls::config_changed, this, _1));
158 }
159
160 void
161 Controls::config_changed (int property)
162 {
163         if (property == Config::PLAYER_DCP_DIRECTORY) {
164                 update_dcp_directory ();
165         }
166 }
167
168 void
169 Controls::started ()
170 {
171 #ifdef DCPOMATIC_VARIANT_SWAROOP
172         _play_button->Enable (false);
173         _pause_button->Enable (true);
174 #else
175         _play_button->SetValue (true);
176 #endif
177         setup_sensitivity ();
178 }
179
180 void
181 Controls::stopped ()
182 {
183 #ifdef DCPOMATIC_VARIANT_SWAROOP
184         _play_button->Enable (true);
185         _pause_button->Enable (false);
186 #else
187         _play_button->SetValue (false);
188 #endif
189         setup_sensitivity ();
190 }
191
192 void
193 Controls::position_changed ()
194 {
195         if (!_slider_being_moved) {
196                 update_position_label ();
197                 update_position_slider ();
198         }
199 }
200
201 void
202 Controls::eye_changed ()
203 {
204         _viewer->set_eyes (_eye->GetSelection() == 0 ? EYES_LEFT : EYES_RIGHT);
205 }
206
207 void
208 Controls::outline_content_changed ()
209 {
210         _viewer->set_outline_content (_outline_content->GetValue());
211 }
212
213 void
214 Controls::film_change (ChangeType type, Film::Property p)
215 {
216         if (type != CHANGE_TYPE_DONE) {
217                 return;
218         }
219
220         if (p == Film::CONTENT || p == Film::THREE_D) {
221                 setup_sensitivity ();
222         }
223 }
224
225 /** @param page true if this was a PAGEUP/PAGEDOWN event for which we won't receive a THUMBRELEASE */
226 void
227 Controls::slider_moved (bool page)
228 {
229         if (!_film) {
230                 return;
231         }
232
233         if (!page && !_slider_being_moved) {
234                 /* This is the first event of a drag; stop playback for the duration of the drag */
235                 _was_running_before_slider = _viewer->stop ();
236                 _slider_being_moved = true;
237         }
238
239         DCPTime t (_slider->GetValue() * _film->length().get() / 4096);
240         t = t.round (_film->video_frame_rate());
241         /* Ensure that we hit the end of the film at the end of the slider */
242         if (t >= _film->length ()) {
243                 t = _film->length() - _viewer->one_video_frame();
244         }
245         _viewer->seek (t, false);
246         update_position_label ();
247 }
248
249 void
250 Controls::slider_released ()
251 {
252         if (_was_running_before_slider) {
253                 /* Restart after a drag */
254                 _viewer->start ();
255         }
256         _slider_being_moved = false;
257 }
258
259 void
260 Controls::play_clicked ()
261 {
262 #ifdef DCPOMATIC_VARIANT_SWAROOP
263         _viewer->start ();
264 #else
265         check_play_state ();
266 #endif
267 }
268
269
270 #ifndef DCPOMATIC_VARIANT_SWAROOP
271 void
272 Controls::check_play_state ()
273 {
274         if (!_film || _film->video_frame_rate() == 0) {
275                 return;
276         }
277
278         if (_play_button->GetValue()) {
279                 _viewer->start ();
280         } else {
281                 _viewer->stop ();
282         }
283 }
284 #endif
285
286 void
287 Controls::update_position_slider ()
288 {
289         if (!_film) {
290                 _slider->SetValue (0);
291                 return;
292         }
293
294         DCPTime const len = _film->length ();
295
296         if (len.get ()) {
297                 int const new_slider_position = 4096 * _viewer->position().get() / len.get();
298                 if (new_slider_position != _slider->GetValue()) {
299                         _slider->SetValue (new_slider_position);
300                 }
301         }
302 }
303
304 void
305 Controls::update_position_label ()
306 {
307         if (!_film) {
308                 _frame_number->SetLabel ("0");
309                 _timecode->SetLabel ("0:0:0.0");
310                 return;
311         }
312
313         double const fps = _film->video_frame_rate ();
314         /* Count frame number from 1 ... not sure if this is the best idea */
315         _frame_number->SetLabel (wxString::Format (wxT("%ld"), lrint (_viewer->position().seconds() * fps) + 1));
316         _timecode->SetLabel (time_to_timecode (_viewer->position(), fps));
317 }
318
319 void
320 Controls::active_jobs_changed (optional<string> j)
321 {
322         _active_job = j;
323         setup_sensitivity ();
324 }
325
326 DCPTime
327 Controls::nudge_amount (wxKeyboardState& ev)
328 {
329         DCPTime amount = _viewer->one_video_frame ();
330
331         if (ev.ShiftDown() && !ev.ControlDown()) {
332                 amount = DCPTime::from_seconds (1);
333         } else if (!ev.ShiftDown() && ev.ControlDown()) {
334                 amount = DCPTime::from_seconds (10);
335         } else if (ev.ShiftDown() && ev.ControlDown()) {
336                 amount = DCPTime::from_seconds (60);
337         }
338
339         return amount;
340 }
341
342 void
343 Controls::rewind_clicked (wxMouseEvent& ev)
344 {
345         _viewer->seek (DCPTime(), true);
346         ev.Skip();
347 }
348
349 void
350 Controls::back_frame ()
351 {
352         _viewer->seek_by (-_viewer->one_video_frame(), true);
353 }
354
355 void
356 Controls::forward_frame ()
357 {
358         _viewer->seek_by (_viewer->one_video_frame(), true);
359 }
360
361 void
362 Controls::back_clicked (wxKeyboardState& ev)
363 {
364         _viewer->seek_by (-nudge_amount(ev), true);
365 }
366
367 void
368 Controls::forward_clicked (wxKeyboardState& ev)
369 {
370         _viewer->seek_by (nudge_amount(ev), true);
371 }
372
373 void
374 Controls::setup_sensitivity ()
375 {
376         /* examine content is the only job which stops the viewer working */
377         bool const c = _film && !_film->content().empty() && (!_active_job || *_active_job != "examine_content");
378
379         _slider->Enable (c);
380         _rewind_button->Enable (c);
381         _back_button->Enable (c);
382         _forward_button->Enable (c);
383 #ifdef DCPOMATIC_VARIANT_SWAROOP
384         _play_button->Enable (c && !_viewer->playing());
385         _pause_button->Enable (c && _viewer->playing());
386         _stop_button->Enable (c);
387 #else
388         _play_button->Enable (c);
389 #endif
390         if (_outline_content) {
391                 _outline_content->Enable (c);
392         }
393         _frame_number->Enable (c);
394         _timecode->Enable (c);
395         if (_jump_to_selected) {
396                 _jump_to_selected->Enable (c);
397         }
398
399         if (_eye) {
400                 _eye->Enable (c && _film->three_d ());
401         }
402 }
403
404 void
405 Controls::timecode_clicked ()
406 {
407         PlayheadToTimecodeDialog* dialog = new PlayheadToTimecodeDialog (this, _film->video_frame_rate ());
408         if (dialog->ShowModal() == wxID_OK) {
409                 _viewer->seek (dialog->get(), true);
410         }
411         dialog->Destroy ();
412 }
413
414 void
415 Controls::frame_number_clicked ()
416 {
417         PlayheadToFrameDialog* dialog = new PlayheadToFrameDialog (this, _film->video_frame_rate ());
418         if (dialog->ShowModal() == wxID_OK) {
419                 _viewer->seek (dialog->get(), true);
420         }
421         dialog->Destroy ();
422 }
423
424 void
425 Controls::jump_to_selected_clicked ()
426 {
427         Config::instance()->set_jump_to_selected (_jump_to_selected->GetValue ());
428 }
429
430 void
431 Controls::film_changed ()
432 {
433         shared_ptr<Film> film = _viewer->film ();
434
435         if (_film == film) {
436                 return;
437         }
438
439         _film = film;
440
441         setup_sensitivity ();
442
443         update_position_slider ();
444         update_position_label ();
445
446         if (_film) {
447                 _film->Change.connect (boost::bind (&Controls::film_change, this, _1, _2));
448         }
449 }
450
451 shared_ptr<Film>
452 Controls::film () const
453 {
454         return _film;
455 }
456
457 void
458 Controls::show_dcp_directory (bool s)
459 {
460         _dcp_directory->Show (s);
461 }
462
463 void
464 Controls::update_dcp_directory ()
465 {
466         using namespace boost::filesystem;
467
468         _dcp_directory->DeleteAllItems ();
469         _dcp_directories.clear ();
470         optional<path> dir = Config::instance()->player_dcp_directory();
471         if (!dir) {
472                 return;
473         }
474
475         for (directory_iterator i = directory_iterator(*dir); i != directory_iterator(); ++i) {
476                 try {
477                         if (is_directory(*i) && (is_regular_file(*i / "ASSETMAP") || is_regular_file(*i / "ASSETMAP.xml"))) {
478                                 string const x = i->path().string().substr(dir->string().length() + 1);
479                                 _dcp_directory->InsertItem(_dcp_directory->GetItemCount(), std_to_wx(x));
480                                 _dcp_directories.push_back(x);
481                         }
482                 } catch (boost::filesystem::filesystem_error& e) {
483                         /* Never mind */
484                 }
485         }
486 }
487
488 void
489 Controls::dcp_directory_selected ()
490 {
491         long int s = _dcp_directory->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
492         if (s == -1) {
493                 return;
494         }
495
496         DCPOMATIC_ASSERT (s < int(_dcp_directories.size()));
497         DCPDirectorySelected (*Config::instance()->player_dcp_directory() / _dcp_directories[s]);
498 }
499
500 #ifdef DCPOMATIC_VARIANT_SWAROOP
501 void
502 Controls::pause_clicked ()
503 {
504         _viewer->stop ();
505 }
506
507 void
508 Controls::stop_clicked ()
509 {
510         _viewer->stop ();
511         DCPEjected ();
512 }
513 #endif