Ubuntu 13.10 appears to have a broken GtkFileChooserButton in its GTK as well as...
[dcpomatic.git] / src / wx / video_panel.cc
1 /*
2     Copyright (C) 2012-2013 Carl Hetherington <cth@carlh.net>
3
4     This program is free software; you can redistribute it and/or modify
5     it under the terms of the GNU General Public License as published by
6     the Free Software Foundation; either version 2 of the License, or
7     (at your option) any later version.
8
9     This program is distributed in the hope that it will be useful,
10     but WITHOUT ANY WARRANTY; without even the implied warranty of
11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12     GNU General Public License for more details.
13
14     You should have received a copy of the GNU General Public License
15     along with this program; if not, write to the Free Software
16     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17
18 */
19
20 #include <wx/spinctrl.h>
21 #include "lib/ratio.h"
22 #include "lib/filter.h"
23 #include "lib/ffmpeg_content.h"
24 #include "lib/colour_conversion.h"
25 #include "lib/config.h"
26 #include "lib/util.h"
27 #include "filter_dialog.h"
28 #include "video_panel.h"
29 #include "wx_util.h"
30 #include "film_editor.h"
31 #include "content_colour_conversion_dialog.h"
32
33 using std::vector;
34 using std::string;
35 using std::pair;
36 using std::cout;
37 using std::list;
38 using boost::shared_ptr;
39 using boost::dynamic_pointer_cast;
40 using boost::bind;
41 using boost::optional;
42
43 VideoPanel::VideoPanel (FilmEditor* e)
44         : FilmEditorPanel (e, _("Video"))
45 {
46         wxGridBagSizer* grid = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
47         _sizer->Add (grid, 0, wxALL, 8);
48
49         int r = 0;
50
51         add_label_to_grid_bag_sizer (grid, this, _("Type"), true, wxGBPosition (r, 0));
52         _frame_type = new wxChoice (this, wxID_ANY);
53         grid->Add (_frame_type, wxGBPosition (r, 1));
54         ++r;
55         
56         add_label_to_grid_bag_sizer (grid, this, _("Left crop"), true, wxGBPosition (r, 0));
57         _left_crop = new wxSpinCtrl (this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1));
58         grid->Add (_left_crop, wxGBPosition (r, 1));
59         ++r;
60
61         add_label_to_grid_bag_sizer (grid, this, _("Right crop"), true, wxGBPosition (r, 0));
62         _right_crop = new wxSpinCtrl (this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1));
63         grid->Add (_right_crop, wxGBPosition (r, 1));
64         ++r;
65         
66         add_label_to_grid_bag_sizer (grid, this, _("Top crop"), true, wxGBPosition (r, 0));
67         _top_crop = new wxSpinCtrl (this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1));
68         grid->Add (_top_crop, wxGBPosition (r, 1));
69         ++r;
70         
71         add_label_to_grid_bag_sizer (grid, this, _("Bottom crop"), true, wxGBPosition (r, 0));
72         _bottom_crop = new wxSpinCtrl (this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize (64, -1));
73         grid->Add (_bottom_crop, wxGBPosition (r, 1));
74         ++r;
75
76         add_label_to_grid_bag_sizer (grid, this, _("Scale to"), true, wxGBPosition (r, 0));
77         _ratio = new wxChoice (this, wxID_ANY);
78         grid->Add (_ratio, wxGBPosition (r, 1));
79         ++r;
80
81         {
82                 add_label_to_grid_bag_sizer (grid, this, _("Filters"), true, wxGBPosition (r, 0));
83                 wxSizer* s = new wxBoxSizer (wxHORIZONTAL);
84
85                 wxClientDC dc (this);
86                 wxSize size = dc.GetTextExtent (wxT ("A quite long name"));
87                 size.SetHeight (-1);
88                 
89                 _filters = new wxStaticText (this, wxID_ANY, _("None"), wxDefaultPosition, size);
90                 s->Add (_filters, 1, wxEXPAND | wxALIGN_CENTER_VERTICAL | wxTOP | wxBOTTOM | wxRIGHT, 6);
91                 _filters_button = new wxButton (this, wxID_ANY, _("Edit..."));
92                 s->Add (_filters_button, 0, wxALIGN_CENTER_VERTICAL);
93                 grid->Add (s, wxGBPosition (r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
94         }
95         ++r;
96         
97         {
98                 add_label_to_grid_bag_sizer (grid, this, _("Colour conversion"), true, wxGBPosition (r, 0));
99                 wxSizer* s = new wxBoxSizer (wxHORIZONTAL);
100
101                 wxClientDC dc (this);
102                 wxSize size = dc.GetTextExtent (wxT ("A quite long name"));
103                 size.SetHeight (-1);
104                 
105                 _colour_conversion = new wxStaticText (this, wxID_ANY, wxT (""), wxDefaultPosition, size);
106
107                 s->Add (_colour_conversion, 1, wxEXPAND | wxALIGN_CENTER_VERTICAL | wxTOP | wxBOTTOM | wxRIGHT, 6);
108                 _colour_conversion_button = new wxButton (this, wxID_ANY, _("Edit..."));
109                 s->Add (_colour_conversion_button, 0, wxALIGN_CENTER_VERTICAL);
110                 grid->Add (s, wxGBPosition (r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
111         }
112         ++r;
113
114         _description = new wxStaticText (this, wxID_ANY, wxT ("\n \n \n \n \n"), wxDefaultPosition, wxDefaultSize);
115         grid->Add (_description, wxGBPosition (r, 0), wxGBSpan (1, 2), wxEXPAND | wxALIGN_CENTER_VERTICAL | wxALL, 6);
116         wxFont font = _description->GetFont();
117         font.SetStyle(wxFONTSTYLE_ITALIC);
118         font.SetPointSize(font.GetPointSize() - 1);
119         _description->SetFont(font);
120         ++r;
121
122         _left_crop->SetRange (0, 1024);
123         _top_crop->SetRange (0, 1024);
124         _right_crop->SetRange (0, 1024);
125         _bottom_crop->SetRange (0, 1024);
126
127         vector<Ratio const *> ratios = Ratio::all ();
128         _ratio->Clear ();
129         for (vector<Ratio const *>::iterator i = ratios.begin(); i != ratios.end(); ++i) {
130                 _ratio->Append (std_to_wx ((*i)->nickname ()));
131         }
132         _ratio->Append (_("No stretch"));
133
134         _frame_type->Append (_("2D"));
135         _frame_type->Append (_("3D left/right"));
136
137         _frame_type->Bind               (wxEVT_COMMAND_CHOICE_SELECTED,  boost::bind (&VideoPanel::frame_type_changed, this));
138         _left_crop->Bind                (wxEVT_COMMAND_SPINCTRL_UPDATED, boost::bind (&VideoPanel::left_crop_changed, this));
139         _right_crop->Bind               (wxEVT_COMMAND_SPINCTRL_UPDATED, boost::bind (&VideoPanel::right_crop_changed, this));
140         _top_crop->Bind                 (wxEVT_COMMAND_SPINCTRL_UPDATED, boost::bind (&VideoPanel::top_crop_changed, this));
141         _bottom_crop->Bind              (wxEVT_COMMAND_SPINCTRL_UPDATED, boost::bind (&VideoPanel::bottom_crop_changed, this));
142         _ratio->Bind                    (wxEVT_COMMAND_CHOICE_SELECTED,  boost::bind (&VideoPanel::ratio_changed, this));
143         _filters_button->Bind           (wxEVT_COMMAND_BUTTON_CLICKED,   boost::bind (&VideoPanel::edit_filters_clicked, this));
144         _colour_conversion_button->Bind (wxEVT_COMMAND_BUTTON_CLICKED,   boost::bind (&VideoPanel::edit_colour_conversion_clicked, this));
145 }
146
147
148 /** Called when the left crop widget has been changed */
149 void
150 VideoPanel::left_crop_changed ()
151 {
152         shared_ptr<VideoContent> c = _editor->selected_video_content ();
153         if (!c) {
154                 return;
155         }
156
157         c->set_left_crop (_left_crop->GetValue ());
158 }
159
160 /** Called when the right crop widget has been changed */
161 void
162 VideoPanel::right_crop_changed ()
163 {
164         shared_ptr<VideoContent> c = _editor->selected_video_content ();
165         if (!c) {
166                 return;
167         }
168
169         c->set_right_crop (_right_crop->GetValue ());
170 }
171
172 /** Called when the top crop widget has been changed */
173 void
174 VideoPanel::top_crop_changed ()
175 {
176         shared_ptr<VideoContent> c = _editor->selected_video_content ();
177         if (!c) {
178                 return;
179         }
180
181         c->set_top_crop (_top_crop->GetValue ());
182 }
183
184 /** Called when the bottom crop value has been changed */
185 void
186 VideoPanel::bottom_crop_changed ()
187 {
188         shared_ptr<VideoContent> c = _editor->selected_video_content ();
189         if (!c) {
190                 return;
191         }
192
193         c->set_bottom_crop (_bottom_crop->GetValue ());
194 }
195
196 void
197 VideoPanel::film_changed (Film::Property property)
198 {
199         switch (property) {
200         case Film::CONTAINER:
201         case Film::VIDEO_FRAME_RATE:
202                 setup_description ();
203                 break;
204         default:
205                 break;
206         }
207 }
208
209 void
210 VideoPanel::film_content_changed (shared_ptr<Content> c, int property)
211 {
212         shared_ptr<VideoContent> vc = dynamic_pointer_cast<VideoContent> (c);
213         shared_ptr<FFmpegContent> fc = dynamic_pointer_cast<FFmpegContent> (c);
214
215         if (property == VideoContentProperty::VIDEO_FRAME_TYPE) {
216                 checked_set (_frame_type, vc ? vc->video_frame_type () : VIDEO_FRAME_TYPE_2D);
217                 setup_description ();
218         } else if (property == VideoContentProperty::VIDEO_CROP) {
219                 checked_set (_left_crop,   vc ? vc->crop().left : 0);
220                 checked_set (_right_crop,  vc ? vc->crop().right        : 0);
221                 checked_set (_top_crop,    vc ? vc->crop().top  : 0);
222                 checked_set (_bottom_crop, vc ? vc->crop().bottom : 0);
223                 setup_description ();
224         } else if (property == VideoContentProperty::VIDEO_RATIO) {
225                 if (vc) {
226                         int n = 0;
227                         vector<Ratio const *> ratios = Ratio::all ();
228                         vector<Ratio const *>::iterator i = ratios.begin ();
229                         while (i != ratios.end() && *i != vc->ratio()) {
230                                 ++i;
231                                 ++n;
232                         }
233
234                         if (i == ratios.end()) {
235                                 checked_set (_ratio, ratios.size ());
236                         } else {
237                                 checked_set (_ratio, n);
238                         }
239                 } else {
240                         checked_set (_ratio, -1);
241                 }
242                 setup_description ();
243         } else if (property == VideoContentProperty::VIDEO_FRAME_RATE) {
244                 setup_description ();
245         } else if (property == VideoContentProperty::COLOUR_CONVERSION) {
246                 optional<size_t> preset = vc ? vc->colour_conversion().preset () : optional<size_t> ();
247                 vector<PresetColourConversion> cc = Config::instance()->colour_conversions ();
248                 _colour_conversion->SetLabel (preset ? std_to_wx (cc[preset.get()].name) : _("Custom"));
249         } else if (property == FFmpegContentProperty::FILTERS) {
250                 if (fc) {
251                         pair<string, string> p = Filter::ffmpeg_strings (fc->filters ());
252                         if (p.first.empty () && p.second.empty ()) {
253                                 _filters->SetLabel (_("None"));
254                         } else {
255                                 string const b = p.first + " " + p.second;
256                                 _filters->SetLabel (std_to_wx (b));
257                         }
258                 }
259         }
260 }
261
262 /** Called when the `Edit filters' button has been clicked */
263 void
264 VideoPanel::edit_filters_clicked ()
265 {
266         shared_ptr<Content> c = _editor->selected_content ();
267         if (!c) {
268                 return;
269         }
270
271         shared_ptr<FFmpegContent> fc = dynamic_pointer_cast<FFmpegContent> (c);
272         if (!fc) {
273                 return;
274         }
275         
276         FilterDialog* d = new FilterDialog (this, fc->filters());
277         d->ActiveChanged.connect (bind (&FFmpegContent::set_filters, fc, _1));
278         d->ShowModal ();
279         d->Destroy ();
280 }
281
282 void
283 VideoPanel::setup_description ()
284 {
285         shared_ptr<VideoContent> vc = _editor->selected_video_content ();
286         if (!vc) {
287                 _description->SetLabel ("");
288                 return;
289         }
290
291         wxString d;
292
293         int lines = 0;
294
295         if (vc->video_size().width && vc->video_size().height) {
296                 d << wxString::Format (
297                         _("Content video is %dx%d (%.2f:1)\n"),
298                         vc->video_size_after_3d_split().width,
299                         vc->video_size_after_3d_split().height,
300                         vc->video_size_after_3d_split().ratio ()
301                         );
302                 ++lines;
303         }
304
305         Crop const crop = vc->crop ();
306         if ((crop.left || crop.right || crop.top || crop.bottom) && vc->video_size() != libdcp::Size (0, 0)) {
307                 libdcp::Size cropped = vc->video_size_after_crop ();
308                 d << wxString::Format (
309                         _("Cropped to %dx%d (%.2f:1)\n"),
310                         cropped.width, cropped.height,
311                         cropped.ratio ()
312                         );
313                 ++lines;
314         }
315
316         Ratio const * ratio = vc->ratio ();
317         libdcp::Size container_size = fit_ratio_within (_editor->film()->container()->ratio (), _editor->film()->full_frame ());
318         float const ratio_value = ratio ? ratio->ratio() : vc->video_size_after_crop().ratio ();
319
320         /* We have a specified ratio to scale to */
321         libdcp::Size const scaled = fit_ratio_within (ratio_value, container_size);
322         
323         d << wxString::Format (
324                 _("Scaled to %dx%d (%.2f:1)\n"),
325                 scaled.width, scaled.height,
326                 scaled.ratio ()
327                 );
328         ++lines;
329         
330         if (scaled != container_size) {
331                 d << wxString::Format (
332                         _("Padded with black to %dx%d (%.2f:1)\n"),
333                         container_size.width, container_size.height,
334                         container_size.ratio ()
335                         );
336                 ++lines;
337         }
338
339         d << wxString::Format (_("Content frame rate %.4f\n"), vc->video_frame_rate ());
340         ++lines;
341         FrameRateConversion frc (vc->video_frame_rate(), _editor->film()->video_frame_rate ());
342         d << frc.description << "\n";
343         ++lines;
344
345         for (int i = lines; i < 6; ++i) {
346                 d << wxT ("\n ");
347         }
348
349         _description->SetLabel (d);
350         _sizer->Layout ();
351 }
352
353
354 void
355 VideoPanel::ratio_changed ()
356 {
357         if (!_editor->film ()) {
358                 return;
359         }
360
361         shared_ptr<VideoContent> vc = _editor->selected_video_content ();
362         
363         int const n = _ratio->GetSelection ();
364         if (n >= 0) {
365                 vector<Ratio const *> ratios = Ratio::all ();
366                 if (n < int (ratios.size ())) {
367                         vc->set_ratio (ratios[n]);
368                 } else {
369                         vc->set_ratio (0);
370                 }
371         }
372 }
373
374 void
375 VideoPanel::frame_type_changed ()
376 {
377         shared_ptr<VideoContent> vc = _editor->selected_video_content ();
378         if (vc) {
379                 vc->set_video_frame_type (static_cast<VideoFrameType> (_frame_type->GetSelection ()));
380         }
381 }
382
383 void
384 VideoPanel::edit_colour_conversion_clicked ()
385 {
386         shared_ptr<VideoContent> vc = _editor->selected_video_content ();
387         if (!vc) {
388                 return;
389         }
390
391         ColourConversion conversion = vc->colour_conversion ();
392         ContentColourConversionDialog* d = new ContentColourConversionDialog (this);
393         d->set (conversion);
394         d->ShowModal ();
395
396         vc->set_colour_conversion (d->get ());
397         d->Destroy ();
398 }