Missed update to private test repo version.
[dcpomatic.git] / src / wx / video_panel.cc
1 /*
2     Copyright (C) 2012-2021 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
22 #include "check_box.h"
23 #include "content_colour_conversion_dialog.h"
24 #include "content_panel.h"
25 #include "content_widget.h"
26 #include "custom_scale_dialog.h"
27 #include "dcpomatic_button.h"
28 #include "filter_dialog.h"
29 #include "static_text.h"
30 #include "video_panel.h"
31 #include "wx_util.h"
32 #include "lib/colour_conversion.h"
33 #include "lib/config.h"
34 #include "lib/dcp_content.h"
35 #include "lib/ffmpeg_content.h"
36 #include "lib/film.h"
37 #include "lib/filter.h"
38 #include "lib/frame_rate_change.h"
39 #include "lib/ratio.h"
40 #include "lib/util.h"
41 #include "lib/video_content.h"
42 #include <dcp/warnings.h>
43 LIBDCP_DISABLE_WARNINGS
44 #include <wx/spinctrl.h>
45 #include <wx/tglbtn.h>
46 LIBDCP_ENABLE_WARNINGS
47 #include <boost/functional/hash.hpp>
48 #include <boost/unordered_set.hpp>
49 #include <set>
50 #include <iostream>
51
52
53 using std::vector;
54 using std::string;
55 using std::pair;
56 using std::cout;
57 using std::list;
58 using std::set;
59 using std::shared_ptr;
60 using std::dynamic_pointer_cast;
61 using boost::bind;
62 using boost::optional;
63 using namespace dcpomatic;
64 #if BOOST_VERSION >= 106100
65 using namespace boost::placeholders;
66 #endif
67
68
69 VideoPanel::VideoPanel (ContentPanel* p)
70         : ContentSubPanel (p, _("Video"))
71 {
72
73 }
74
75
76 void
77 VideoPanel::create ()
78 {
79         _reference = new CheckBox (this, _("Use this DCP's video as OV and make VF"));
80         _reference_note = new StaticText (this, wxT(""));
81         _reference_note->Wrap (200);
82         auto font = _reference_note->GetFont();
83         font.SetStyle(wxFONTSTYLE_ITALIC);
84         font.SetPointSize(font.GetPointSize() - 1);
85         _reference_note->SetFont(font);
86
87         _type_label = create_label (this, _("Type"), true);
88         _frame_type = new ContentChoice<VideoContent, VideoFrameType> (
89                 this,
90                 new wxChoice (this, wxID_ANY),
91                 VideoContentProperty::FRAME_TYPE,
92                 &Content::video,
93                 boost::mem_fn (&VideoContent::frame_type),
94                 boost::mem_fn (&VideoContent::set_frame_type),
95                 &caster<int, VideoFrameType>,
96                 &caster<VideoFrameType, int>
97                 );
98
99         _crop_label = create_label (this, _("Crop"), true);
100
101 #if defined(__WXGTK3__)
102         int const crop_width = 128;
103         int const link_width = 32;
104         int const link_height = 64;
105 #elif defined(__WXGTK20__)
106         int const crop_width = 56;
107         int const link_width = 24;
108         int const link_height = 32;
109 #elif defined(DCPOMATIC_OSX)
110         int const crop_width = 56;
111         int const link_width = 8 + 15 / dpi_scale_factor(this);
112         int const link_height = 28;
113 #else
114         int const crop_width = 56;
115         int const link_width = 22;
116         int const link_height = 28;
117 #endif
118
119         _left_crop_label = create_label (this, _("Left"), true);
120         _left_crop = new ContentSpinCtrl<VideoContent> (
121                 this,
122                 new wxSpinCtrl (this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(crop_width, -1)),
123                 VideoContentProperty::CROP,
124                 &Content::video,
125                 boost::mem_fn (&VideoContent::requested_left_crop),
126                 boost::mem_fn (&VideoContent::set_left_crop),
127                 boost::bind (&VideoPanel::left_crop_changed, this)
128                 );
129
130         auto const link_path = bitmap_path(gui_is_dark() ? "link_white.png" : "link_black.png");
131
132         _left_right_link = new wxToggleButton (this, wxID_ANY, wxT(""), wxDefaultPosition, wxSize(link_width, link_height));
133         _left_right_link->SetBitmap(wxBitmap(link_path, wxBITMAP_TYPE_PNG));
134
135         _right_crop_label = create_label (this, _("Right"), true);
136         _right_crop = new ContentSpinCtrl<VideoContent> (
137                 this,
138                 new wxSpinCtrl (this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(crop_width, -1)),
139                 VideoContentProperty::CROP,
140                 &Content::video,
141                 boost::mem_fn (&VideoContent::requested_right_crop),
142                 boost::mem_fn (&VideoContent::set_right_crop),
143                 boost::bind (&VideoPanel::right_crop_changed, this)
144                 );
145
146         _top_crop_label = create_label (this, _("Top"), true);
147         _top_crop = new ContentSpinCtrl<VideoContent> (
148                 this,
149                 new wxSpinCtrl (this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(crop_width, -1)),
150                 VideoContentProperty::CROP,
151                 &Content::video,
152                 boost::mem_fn (&VideoContent::requested_top_crop),
153                 boost::mem_fn (&VideoContent::set_top_crop),
154                 boost::bind (&VideoPanel::top_crop_changed, this)
155                 );
156
157         _top_bottom_link = new wxToggleButton (this, wxID_ANY, wxT(""), wxDefaultPosition, wxSize(link_width, link_height));
158         _top_bottom_link->SetBitmap(wxBitmap(link_path, wxBITMAP_TYPE_PNG));
159
160         _bottom_crop_label = create_label (this, _("Bottom"), true);
161         _bottom_crop = new ContentSpinCtrl<VideoContent> (
162                 this,
163                 new wxSpinCtrl (this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(crop_width, -1)),
164                 VideoContentProperty::CROP,
165                 &Content::video,
166                 boost::mem_fn (&VideoContent::requested_bottom_crop),
167                 boost::mem_fn (&VideoContent::set_bottom_crop),
168                 boost::bind (&VideoPanel::bottom_crop_changed, this)
169                 );
170
171         _fade_in_label = create_label (this, _("Fade in"), true);
172         _fade_in = new Timecode<ContentTime> (this);
173
174         _fade_out_label = create_label (this, _("Fade out"), true);
175         _fade_out = new Timecode<ContentTime> (this);
176
177         wxClientDC dc (this);
178         auto size = dc.GetTextExtent (wxT ("A quite long name"));
179 #ifdef __WXGTK3__
180         size.SetWidth (size.GetWidth() + 64);
181 #endif
182         size.SetHeight (-1);
183
184         _scale_label = create_label (this, _("Scale"), true);
185         _scale_fit = new wxRadioButton (this, wxID_ANY, _("to fit DCP"));
186         _scale_custom = new wxRadioButton (this, wxID_ANY, _("custom"));
187         _scale_custom_edit = new Button (this, _("Edit..."), wxDefaultPosition, small_button_size(this, _("Edit...")));
188
189         _colour_conversion_label = create_label (this, _("Colour"), true);
190         _colour_conversion = new wxChoice (this, wxID_ANY, wxDefaultPosition, size);
191         _colour_conversion->Append (_("None"));
192         for (auto const& i: PresetColourConversion::all()) {
193                 _colour_conversion->Append (std_to_wx (i.name));
194         }
195
196         /// TRANSLATORS: translate the word "Custom" here; do not include the "Colour|" prefix
197         _colour_conversion->Append (S_("Colour|Custom"));
198         _edit_colour_conversion_button = new Button (this, _("Edit..."), wxDefaultPosition, small_button_size(this, _("Edit...")));
199
200         _range_label = create_label (this, _("Range"), true);
201         _range = new wxChoice (this, wxID_ANY);
202         _range->Append (_("Full (JPEG, 0-255)"));
203         _range->Append (_("Video (MPEG, 16-235)"));
204
205         _description = new StaticText (this, wxT ("\n \n \n \n \n"), wxDefaultPosition, wxDefaultSize);
206         _description->SetFont(font);
207
208         _left_crop->wrapped()->SetRange (0, 4096);
209         _top_crop->wrapped()->SetRange (0, 4096);
210         _right_crop->wrapped()->SetRange (0, 4096);
211         _bottom_crop->wrapped()->SetRange (0, 4096);
212
213         _frame_type->wrapped()->Append (_("2D"));
214         _frame_type->wrapped()->Append (_("3D"));
215         _frame_type->wrapped()->Append (_("3D left/right"));
216         _frame_type->wrapped()->Append (_("3D top/bottom"));
217         _frame_type->wrapped()->Append (_("3D alternate"));
218         _frame_type->wrapped()->Append (_("3D left only"));
219         _frame_type->wrapped()->Append (_("3D right only"));
220
221         content_selection_changed ();
222
223         _fade_in->Changed.connect (boost::bind (&VideoPanel::fade_in_changed, this));
224         _fade_out->Changed.connect (boost::bind (&VideoPanel::fade_out_changed, this));
225
226         _reference->bind(&VideoPanel::reference_clicked, this);
227         _scale_fit->Bind                     (wxEVT_RADIOBUTTON, boost::bind (&VideoPanel::scale_fit_clicked, this));
228         _scale_custom->Bind                  (wxEVT_RADIOBUTTON, boost::bind (&VideoPanel::scale_custom_clicked, this));
229         _scale_custom_edit->Bind             (wxEVT_BUTTON,   boost::bind (&VideoPanel::scale_custom_edit_clicked, this));
230         _colour_conversion->Bind             (wxEVT_CHOICE,   boost::bind (&VideoPanel::colour_conversion_changed, this));
231         _range->Bind                         (wxEVT_CHOICE,   boost::bind (&VideoPanel::range_changed, this));
232         _edit_colour_conversion_button->Bind (wxEVT_BUTTON,   boost::bind (&VideoPanel::edit_colour_conversion_clicked, this));
233         _left_right_link->Bind               (wxEVT_TOGGLEBUTTON, boost::bind(&VideoPanel::left_right_link_clicked, this));
234         _top_bottom_link->Bind               (wxEVT_TOGGLEBUTTON, boost::bind(&VideoPanel::top_bottom_link_clicked, this));
235
236         add_to_grid ();
237
238         _sizer->Layout ();
239 }
240
241
242 void
243 VideoPanel::add_to_grid ()
244 {
245         int r = 0;
246
247         auto reference_sizer = new wxBoxSizer (wxVERTICAL);
248         reference_sizer->Add (_reference, 0);
249         reference_sizer->Add (_reference_note, 0);
250         _grid->Add (reference_sizer, wxGBPosition(r, 0), wxGBSpan(1, 3));
251         ++r;
252
253         add_label_to_sizer (_grid, _type_label, true, wxGBPosition(r, 0));
254         _frame_type->add (_grid, wxGBPosition(r, 1), wxGBSpan(1, 2));
255         ++r;
256
257         int cr = 0;
258         auto crop = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
259
260         add_label_to_sizer (crop, _left_crop_label, true, wxGBPosition (cr, 0));
261         _left_crop->add (crop, wxGBPosition(cr, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
262 #ifdef __WXGTK3__
263         crop->Add (_left_right_link, wxGBPosition(cr, 2), wxGBSpan(2, 1));
264         ++cr;
265         add_label_to_sizer (crop, _right_crop_label, true, wxGBPosition(cr, 0));
266         _right_crop->add (crop, wxGBPosition(cr, 1));
267 #else
268         crop->Add (_left_right_link, wxGBPosition(cr, 2), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
269         add_label_to_sizer (crop, _right_crop_label, true, wxGBPosition (cr, 3));
270         _right_crop->add (crop, wxGBPosition (cr, 4), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
271 #endif
272         ++cr;
273         add_label_to_sizer (crop, _top_crop_label, true, wxGBPosition (cr, 0));
274         _top_crop->add (crop, wxGBPosition (cr, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
275 #ifdef __WXGTK3__
276         crop->Add (_top_bottom_link, wxGBPosition(cr, 2), wxGBSpan(2, 1));
277         ++cr;
278         add_label_to_sizer (crop, _bottom_crop_label, true, wxGBPosition(cr, 0));
279         _bottom_crop->add (crop, wxGBPosition(cr, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
280 #else
281         crop->Add (_top_bottom_link, wxGBPosition(cr, 2), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
282         add_label_to_sizer (crop, _bottom_crop_label, true, wxGBPosition (cr, 3));
283         _bottom_crop->add (crop, wxGBPosition (cr, 4), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
284 #endif
285         add_label_to_sizer (_grid, _crop_label, true, wxGBPosition(r, 0));
286         _grid->Add (crop, wxGBPosition(r, 1));
287         ++r;
288
289         add_label_to_sizer (_grid, _fade_in_label, true, wxGBPosition (r, 0));
290         _grid->Add (_fade_in, wxGBPosition (r, 1), wxGBSpan (1, 3));
291         ++r;
292
293         add_label_to_sizer (_grid, _fade_out_label, true, wxGBPosition (r, 0));
294         _grid->Add (_fade_out, wxGBPosition (r, 1), wxGBSpan (1, 3));
295         ++r;
296
297         add_label_to_sizer (_grid, _scale_label, true, wxGBPosition (r, 0));
298         {
299                 auto v = new wxBoxSizer (wxVERTICAL);
300                 v->Add (_scale_fit, 0, wxBOTTOM, 4);
301                 auto h = new wxBoxSizer (wxHORIZONTAL);
302                 h->Add (_scale_custom, 1, wxRIGHT | wxALIGN_CENTER_VERTICAL, 6);
303                 h->Add (_scale_custom_edit, 0, wxALIGN_CENTER_VERTICAL);
304                 v->Add (h, 0);
305                 _grid->Add (v, wxGBPosition(r, 1));
306         }
307         ++r;
308
309         add_label_to_sizer (_grid, _colour_conversion_label, true, wxGBPosition(r, 0));
310         {
311                 auto s = new wxBoxSizer (wxHORIZONTAL);
312                 s->Add (_colour_conversion, 1, wxALIGN_CENTER_VERTICAL | wxTOP | wxBOTTOM | wxRIGHT, 6);
313                 s->Add (_edit_colour_conversion_button, 0, wxALIGN_CENTER_VERTICAL);
314                 _grid->Add (s, wxGBPosition (r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
315         }
316         ++r;
317
318         add_label_to_sizer (_grid, _range_label, true, wxGBPosition(r, 0));
319         _grid->Add (_range, wxGBPosition(r, 1), wxGBSpan(1, 2), wxALIGN_CENTER_VERTICAL);
320         ++r;
321
322         _grid->Add (_description, wxGBPosition (r, 0), wxGBSpan (1, 4), wxEXPAND | wxALIGN_CENTER_VERTICAL, 6);
323         ++r;
324 }
325
326
327 void
328 VideoPanel::range_changed ()
329 {
330         auto vc = _parent->selected_video ();
331         if (vc.size() != 1) {
332                 return;
333         }
334
335         switch (_range->GetSelection()) {
336         case 0:
337                 vc.front()->video->set_range (VideoRange::FULL);
338                 break;
339         case 1:
340                 vc.front()->video->set_range (VideoRange::VIDEO);
341                 break;
342         default:
343                 DCPOMATIC_ASSERT (false);
344         }
345 }
346
347
348 void
349 VideoPanel::film_changed(FilmProperty property)
350 {
351         switch (property) {
352         case FilmProperty::VIDEO_FRAME_RATE:
353         case FilmProperty::CONTAINER:
354         case FilmProperty::RESOLUTION:
355                 setup_description ();
356                 setup_sensitivity ();
357                 break;
358         case FilmProperty::REEL_TYPE:
359         case FilmProperty::INTEROP:
360                 setup_sensitivity ();
361                 break;
362         default:
363                 break;
364         }
365 }
366
367
368 std::size_t
369 hash_value (boost::optional<ColourConversion> const & c)
370 {
371         boost::hash<string> hasher;
372         if (!c) {
373                 return hasher ("none");
374         }
375         return hasher (c->identifier());
376 }
377
378
379 void
380 VideoPanel::film_content_changed (int property)
381 {
382         auto vc = _parent->selected_video ();
383         shared_ptr<Content> vcs;
384         shared_ptr<FFmpegContent> fcs;
385         if (!vc.empty()) {
386                 vcs = vc.front ();
387                 fcs = dynamic_pointer_cast<FFmpegContent> (vcs);
388         }
389
390         if (property == ContentProperty::VIDEO_FRAME_RATE ||
391             property == VideoContentProperty::FRAME_TYPE ||
392             property == VideoContentProperty::CROP ||
393             property == VideoContentProperty::CUSTOM_RATIO ||
394             property == VideoContentProperty::CUSTOM_SIZE) {
395                 setup_description ();
396         } else if (property == VideoContentProperty::COLOUR_CONVERSION) {
397                 boost::unordered_set<optional<ColourConversion>> check;
398                 for (auto i: vc) {
399                         check.insert (i->video->colour_conversion());
400                 }
401
402                 /* Remove any "Many" entry that we might have added previously.  There should
403                  * be entries for each preset plus one for "None" and one for "Custom".
404                  */
405                 auto cc = PresetColourConversion::all ();
406                 if (_colour_conversion->GetCount() > cc.size() + 2) {
407                         _colour_conversion->Delete (_colour_conversion->GetCount() - 1);
408                 }
409
410                 if (check.size() == 1) {
411                         if (vcs && vcs->video->colour_conversion ()) {
412                                 auto preset = vcs->video->colour_conversion().get().preset();
413                                 if (preset) {
414                                         checked_set (_colour_conversion, preset.get() + 1);
415                                 } else {
416                                         checked_set (_colour_conversion, cc.size() + 1);
417                                 }
418                         } else {
419                                 checked_set (_colour_conversion, 0);
420                         }
421                 } else if (check.size() > 1) {
422                         /* Add a "many" entry and select it as an indication that multiple different
423                          * colour conversions are present in the selection.
424                          */
425                         _colour_conversion->Append (_("Many"));
426                         checked_set (_colour_conversion, _colour_conversion->GetCount() - 1);
427                 }
428
429                 setup_sensitivity ();
430
431         } else if (property == VideoContentProperty::USE) {
432                 setup_sensitivity ();
433         } else if (property == VideoContentProperty::FADE_IN) {
434                 set<Frame> check;
435                 for (auto i: vc) {
436                         check.insert (i->video->fade_in ());
437                 }
438
439                 if (check.size() == 1) {
440                         _fade_in->set (
441                                 ContentTime::from_frames (vc.front()->video->fade_in(), vc.front()->active_video_frame_rate(_parent->film())),
442                                 vc.front()->active_video_frame_rate(_parent->film())
443                                 );
444                 } else {
445                         _fade_in->clear ();
446                 }
447         } else if (property == VideoContentProperty::FADE_OUT) {
448                 set<Frame> check;
449                 for (auto i: vc) {
450                         check.insert (i->video->fade_out ());
451                 }
452
453                 if (check.size() == 1) {
454                         _fade_out->set (
455                                 ContentTime::from_frames (vc.front()->video->fade_out(), vc.front()->active_video_frame_rate(_parent->film())),
456                                 vc.front()->active_video_frame_rate(_parent->film())
457                                 );
458                 } else {
459                         _fade_out->clear ();
460                 }
461         } else if (property == DCPContentProperty::REFERENCE_VIDEO) {
462                 if (vc.size() == 1) {
463                         shared_ptr<DCPContent> dcp = dynamic_pointer_cast<DCPContent> (vc.front ());
464                         checked_set (_reference, dcp ? dcp->reference_video () : false);
465                 } else {
466                         checked_set (_reference, false);
467                 }
468
469                 setup_sensitivity ();
470         } else if (property == VideoContentProperty::RANGE) {
471                 if (vcs) {
472                         checked_set (_range, vcs->video->range() == VideoRange::FULL ? 0 : 1);
473                 } else {
474                         checked_set (_range, 0);
475                 }
476
477                 setup_sensitivity ();
478         } else if (property == VideoContentProperty::CUSTOM_RATIO || property == VideoContentProperty::CUSTOM_SIZE) {
479                 set<Frame> check;
480                 for (auto i: vc) {
481                         check.insert (i->video->custom_ratio() || i->video->custom_size());
482                 }
483
484                 if (check.size() == 1) {
485                         checked_set (_scale_fit, !vc.front()->video->custom_ratio() && !vc.front()->video->custom_size());
486                         checked_set (_scale_custom, vc.front()->video->custom_ratio() || vc.front()->video->custom_size());
487                 } else {
488                         checked_set (_scale_fit, true);
489                         checked_set (_scale_custom, false);
490                 }
491                 setup_sensitivity ();
492         }
493 }
494
495
496 void
497 VideoPanel::setup_description ()
498 {
499         auto vc = _parent->selected_video ();
500         if (vc.empty ()) {
501                 checked_set (_description, wxT (""));
502                 return;
503         } else if (vc.size() > 1) {
504                 checked_set (_description, _("Multiple content selected"));
505                 return;
506         }
507
508         auto d = vc.front()->video->processing_description(_parent->film());
509         size_t lines = count (d.begin(), d.end(), '\n');
510
511         for (int i = lines; i < 6; ++i) {
512                 d += "\n ";
513         }
514
515         checked_set (_description, d);
516         layout ();
517 }
518
519
520 void
521 VideoPanel::colour_conversion_changed ()
522 {
523         auto vc = _parent->selected_video ();
524
525         int const s = _colour_conversion->GetSelection ();
526         auto all = PresetColourConversion::all ();
527
528         if (s == int(all.size() + 1)) {
529                 edit_colour_conversion_clicked ();
530         } else {
531                 for (auto i: _parent->selected_video()) {
532                         if (s == 0) {
533                                 i->video->unset_colour_conversion ();
534                         } else if (s != int(all.size() + 2)) {
535                                 i->video->set_colour_conversion (all[s - 1].conversion);
536                         }
537                 }
538         }
539 }
540
541
542 void
543 VideoPanel::edit_colour_conversion_clicked ()
544 {
545         auto vc = _parent->selected_video ();
546
547         ContentColourConversionDialog dialog(this, vc.front()->video->yuv());
548         dialog.set(vc.front()->video->colour_conversion().get_value_or(PresetColourConversion::all().front().conversion));
549         if (dialog.ShowModal() == wxID_OK) {
550                 for (auto i: vc) {
551                         i->video->set_colour_conversion(dialog.get());
552                 }
553         } else {
554                 /* Reset the colour conversion choice */
555                 film_content_changed (VideoContentProperty::COLOUR_CONVERSION);
556         }
557 }
558
559
560 void
561 VideoPanel::content_selection_changed ()
562 {
563         auto video_sel = _parent->selected_video ();
564
565         _frame_type->set_content (video_sel);
566         _left_crop->set_content (video_sel);
567         _right_crop->set_content (video_sel);
568         _top_crop->set_content (video_sel);
569         _bottom_crop->set_content (video_sel);
570
571         film_content_changed (ContentProperty::VIDEO_FRAME_RATE);
572         film_content_changed (VideoContentProperty::CROP);
573         film_content_changed (VideoContentProperty::COLOUR_CONVERSION);
574         film_content_changed (VideoContentProperty::FADE_IN);
575         film_content_changed (VideoContentProperty::FADE_OUT);
576         film_content_changed (VideoContentProperty::RANGE);
577         film_content_changed (VideoContentProperty::USE);
578         film_content_changed (VideoContentProperty::CUSTOM_RATIO);
579         film_content_changed (VideoContentProperty::CUSTOM_SIZE);
580         film_content_changed (FFmpegContentProperty::FILTERS);
581         film_content_changed (DCPContentProperty::REFERENCE_VIDEO);
582
583         setup_sensitivity ();
584 }
585
586
587 void
588 VideoPanel::setup_sensitivity ()
589 {
590         auto sel = _parent->selected ();
591
592         shared_ptr<DCPContent> dcp;
593         if (sel.size() == 1) {
594                 dcp = dynamic_pointer_cast<DCPContent> (sel.front ());
595         }
596
597         string why_not;
598         bool const can_reference = dcp && dcp->can_reference_video (_parent->film(), why_not);
599         wxString cannot;
600         if (why_not.empty()) {
601                 cannot = _("Cannot reference this DCP's video.");
602         } else {
603                 cannot = _("Cannot reference this DCP's video: ") + std_to_wx(why_not);
604         }
605         setup_refer_button (_reference, _reference_note, dcp, can_reference, cannot);
606
607         bool any_use = false;
608         for (auto i: _parent->selected_video()) {
609                 if (i->video && i->video->use()) {
610                         any_use = true;
611                 }
612         }
613
614         bool const enable = !_reference->GetValue() && any_use;
615
616         if (!enable) {
617                 _frame_type->wrapped()->Enable (false);
618                 _left_crop->wrapped()->Enable (false);
619                 _right_crop->wrapped()->Enable (false);
620                 _top_crop->wrapped()->Enable (false);
621                 _bottom_crop->wrapped()->Enable (false);
622                 _fade_in->Enable (false);
623                 _fade_out->Enable (false);
624                 _scale_fit->Enable (false);
625                 _scale_custom->Enable (false);
626                 _scale_custom_edit->Enable (false);
627                 _description->Enable (false);
628                 _colour_conversion->Enable (false);
629                 _range->Enable (false);
630         } else {
631                 auto video_sel = _parent->selected_video ();
632                 auto ffmpeg_sel = _parent->selected_ffmpeg ();
633                 bool const single = video_sel.size() == 1;
634
635                 _frame_type->wrapped()->Enable (true);
636                 _left_crop->wrapped()->Enable (true);
637                 _right_crop->wrapped()->Enable (true);
638                 _top_crop->wrapped()->Enable (true);
639                 _bottom_crop->wrapped()->Enable (true);
640                 _fade_in->Enable (!video_sel.empty ());
641                 _fade_out->Enable (!video_sel.empty ());
642                 _scale_fit->Enable (true);
643                 _scale_custom->Enable (true);
644                 _scale_custom_edit->Enable (_scale_custom->GetValue());
645                 _description->Enable (true);
646                 _colour_conversion->Enable (!video_sel.empty());
647                 _range->Enable (single && !video_sel.empty() && !dcp);
648         }
649
650         auto vc = _parent->selected_video ();
651         shared_ptr<Content> vcs;
652         if (!vc.empty ()) {
653                 vcs = vc.front ();
654         }
655
656         if (vcs && vcs->video->colour_conversion ()) {
657                 _edit_colour_conversion_button->Enable (!vcs->video->colour_conversion().get().preset());
658         } else {
659                 _edit_colour_conversion_button->Enable (false);
660         }
661 }
662
663
664 void
665 VideoPanel::fade_in_changed ()
666 {
667         auto const hmsf = _fade_in->get();
668         for (auto i: _parent->selected_video()) {
669                 auto const vfr = i->active_video_frame_rate(_parent->film());
670                 i->video->set_fade_in (dcpomatic::ContentTime(hmsf, vfr).frames_round(vfr));
671         }
672 }
673
674
675 void
676 VideoPanel::fade_out_changed ()
677 {
678         auto const hmsf = _fade_out->get();
679         for (auto i: _parent->selected_video()) {
680                 auto const vfr = i->active_video_frame_rate (_parent->film());
681                 i->video->set_fade_out (dcpomatic::ContentTime(hmsf, vfr).frames_round(vfr));
682         }
683 }
684
685
686 void
687 VideoPanel::reference_clicked ()
688 {
689         auto c = _parent->selected ();
690         if (c.size() != 1) {
691                 return;
692         }
693
694         auto d = dynamic_pointer_cast<DCPContent> (c.front ());
695         if (!d) {
696                 return;
697         }
698
699         d->set_reference_video (_reference->GetValue ());
700 }
701
702
703 void
704 VideoPanel::scale_fit_clicked ()
705 {
706         for (auto i: _parent->selected_video()) {
707                 i->video->set_custom_ratio (optional<float>());
708                 i->video->set_custom_size (optional<dcp::Size>());
709         }
710
711         setup_sensitivity ();
712 }
713
714
715 void
716 VideoPanel::scale_custom_clicked ()
717 {
718         if (!scale_custom_edit_clicked()) {
719                 _scale_fit->SetValue (true);
720         }
721
722         setup_sensitivity ();
723 }
724
725
726 bool
727 VideoPanel::scale_custom_edit_clicked ()
728 {
729         auto vc = _parent->selected_video().front()->video;
730         auto size = vc->size();
731         DCPOMATIC_ASSERT(size);
732
733         CustomScaleDialog dialog(this, *size, _parent->film()->frame_size(), vc->custom_ratio(), vc->custom_size());
734         if (dialog.ShowModal() != wxID_OK) {
735                 return false;
736         }
737
738         for (auto i: _parent->selected_video()) {
739                 i->video->set_custom_ratio(dialog.custom_ratio());
740                 i->video->set_custom_size(dialog.custom_size());
741         }
742
743         return true;
744 }
745
746
747 void
748 VideoPanel::left_right_link_clicked ()
749 {
750         if (_left_changed_last) {
751                 left_crop_changed ();
752         } else {
753                 right_crop_changed ();
754         }
755 }
756
757
758 void
759 VideoPanel::top_bottom_link_clicked ()
760 {
761         if (_top_changed_last) {
762                 top_crop_changed ();
763         } else {
764                 bottom_crop_changed ();
765         }
766 }
767
768
769 void
770 VideoPanel::left_crop_changed ()
771 {
772         _left_changed_last = true;
773         if (_left_right_link->GetValue()) {
774                 for (auto const& i: _parent->selected_video()) {
775                         i->video->set_right_crop (i->video->requested_left_crop());
776                 }
777         }
778 }
779
780
781 void
782 VideoPanel::right_crop_changed ()
783 {
784         _left_changed_last = false;
785         if (_left_right_link->GetValue()) {
786                 for (auto const& i: _parent->selected_video()) {
787                         i->video->set_left_crop (i->video->requested_right_crop());
788                 }
789         }
790 }
791
792
793 void
794 VideoPanel::top_crop_changed ()
795 {
796         _top_changed_last = true;
797         if (_top_bottom_link->GetValue()) {
798                 for (auto i: _parent->selected_video()) {
799                         i->video->set_bottom_crop (i->video->requested_top_crop());
800                 }
801         }
802 }
803
804
805 void
806 VideoPanel::bottom_crop_changed ()
807 {
808         _top_changed_last = false;
809         if (_top_bottom_link->GetValue()) {
810                 for (auto i: _parent->selected_video()) {
811                         i->video->set_top_crop (i->video->requested_bottom_crop());
812                 }
813         }
814 }
815
816