Tweak subtitle panel layout slightly.
[dcpomatic.git] / src / wx / subtitle_panel.cc
1 /*
2     Copyright (C) 2012-2015 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 "subtitle_panel.h"
21 #include "film_editor.h"
22 #include "wx_util.h"
23 #include "subtitle_view.h"
24 #include "content_panel.h"
25 #include "fonts_dialog.h"
26 #include "lib/ffmpeg_content.h"
27 #include "lib/subrip_content.h"
28 #include "lib/ffmpeg_subtitle_stream.h"
29 #include "lib/dcp_subtitle_content.h"
30 #include "lib/subrip_decoder.h"
31 #include "lib/dcp_subtitle_decoder.h"
32 #include "lib/dcp_content.h"
33 #include <wx/spinctrl.h>
34 #include <boost/lexical_cast.hpp>
35 #include <boost/foreach.hpp>
36
37 using std::vector;
38 using std::string;
39 using std::list;
40 using boost::shared_ptr;
41 using boost::lexical_cast;
42 using boost::dynamic_pointer_cast;
43
44 SubtitlePanel::SubtitlePanel (ContentPanel* p)
45         : ContentSubPanel (p, _("Subtitles"))
46         , _subtitle_view (0)
47         , _fonts_dialog (0)
48 {
49         wxGridBagSizer* grid = new wxGridBagSizer (DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
50         _sizer->Add (grid, 0, wxALL, 8);
51         int r = 0;
52
53         _reference = new wxCheckBox (this, wxID_ANY, _("Refer to existing DCP"));
54         grid->Add (_reference, wxGBPosition (r, 0), wxGBSpan (1, 2));
55         ++r;
56
57         _use = new wxCheckBox (this, wxID_ANY, _("Use subtitles"));
58         grid->Add (_use, wxGBPosition (r, 0), wxGBSpan (1, 2));
59         ++r;
60
61         _burn = new wxCheckBox (this, wxID_ANY, _("Burn subtitles into image"));
62         grid->Add (_burn, wxGBPosition (r, 0), wxGBSpan (1, 2));
63         ++r;
64
65         {
66                 add_label_to_grid_bag_sizer (grid, this, _("X Offset"), true, wxGBPosition (r, 0));
67                 wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
68                 _x_offset = new wxSpinCtrl (this);
69                 s->Add (_x_offset);
70                 add_label_to_sizer (s, this, _("%"), false);
71                 grid->Add (s, wxGBPosition (r, 1));
72                 ++r;
73         }
74
75         {
76                 add_label_to_grid_bag_sizer (grid, this, _("Y Offset"), true, wxGBPosition (r, 0));
77                 wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
78                 _y_offset = new wxSpinCtrl (this);
79                 s->Add (_y_offset);
80                 add_label_to_sizer (s, this, _("%"), false);
81                 grid->Add (s, wxGBPosition (r, 1));
82                 ++r;
83         }
84
85         {
86                 add_label_to_grid_bag_sizer (grid, this, _("X Scale"), true, wxGBPosition (r, 0));
87                 wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
88                 _x_scale = new wxSpinCtrl (this);
89                 s->Add (_x_scale);
90                 add_label_to_sizer (s, this, _("%"), false);
91                 grid->Add (s, wxGBPosition (r, 1));
92                 ++r;
93         }
94
95         {
96                 add_label_to_grid_bag_sizer (grid, this, _("Y Scale"), true, wxGBPosition (r, 0));
97                 wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
98                 _y_scale = new wxSpinCtrl (this);
99                 s->Add (_y_scale);
100                 add_label_to_sizer (s, this, _("%"), false);
101                 grid->Add (s, wxGBPosition (r, 1));
102                 ++r;
103         }
104
105         add_label_to_grid_bag_sizer (grid, this, _("Language"), true, wxGBPosition (r, 0));
106         _language = new wxTextCtrl (this, wxID_ANY);
107         grid->Add (_language, wxGBPosition (r, 1));
108         ++r;
109
110         add_label_to_grid_bag_sizer (grid, this, _("Stream"), true, wxGBPosition (r, 0));
111         _stream = new wxChoice (this, wxID_ANY);
112         grid->Add (_stream, wxGBPosition (r, 1));
113         ++r;
114
115         {
116                 wxBoxSizer* s = new wxBoxSizer (wxHORIZONTAL);
117
118                 _subtitle_view_button = new wxButton (this, wxID_ANY, _("View..."));
119                 s->Add (_subtitle_view_button, 1, wxALL, DCPOMATIC_SIZER_GAP);
120                 _fonts_dialog_button = new wxButton (this, wxID_ANY, _("Fonts..."));
121                 s->Add (_fonts_dialog_button, 1, wxALL, DCPOMATIC_SIZER_GAP);
122
123                 grid->Add (s, wxGBPosition (r, 0), wxGBSpan (1, 2));
124                 ++r;
125         }
126
127         _x_offset->SetRange (-100, 100);
128         _y_offset->SetRange (-100, 100);
129         _x_scale->SetRange (10, 1000);
130         _y_scale->SetRange (10, 1000);
131
132         _reference->Bind            (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&SubtitlePanel::reference_clicked, this));
133         _use->Bind                  (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&SubtitlePanel::use_toggled, this));
134         _burn->Bind                 (wxEVT_COMMAND_CHECKBOX_CLICKED, boost::bind (&SubtitlePanel::burn_toggled, this));
135         _x_offset->Bind             (wxEVT_COMMAND_SPINCTRL_UPDATED, boost::bind (&SubtitlePanel::x_offset_changed, this));
136         _y_offset->Bind             (wxEVT_COMMAND_SPINCTRL_UPDATED, boost::bind (&SubtitlePanel::y_offset_changed, this));
137         _x_scale->Bind              (wxEVT_COMMAND_SPINCTRL_UPDATED, boost::bind (&SubtitlePanel::x_scale_changed, this));
138         _y_scale->Bind              (wxEVT_COMMAND_SPINCTRL_UPDATED, boost::bind (&SubtitlePanel::y_scale_changed, this));
139         _language->Bind             (wxEVT_COMMAND_TEXT_UPDATED,     boost::bind (&SubtitlePanel::language_changed, this));
140         _stream->Bind               (wxEVT_COMMAND_CHOICE_SELECTED,  boost::bind (&SubtitlePanel::stream_changed, this));
141         _subtitle_view_button->Bind (wxEVT_COMMAND_BUTTON_CLICKED,   boost::bind (&SubtitlePanel::subtitle_view_clicked, this));
142         _fonts_dialog_button->Bind  (wxEVT_COMMAND_BUTTON_CLICKED,   boost::bind (&SubtitlePanel::fonts_dialog_clicked, this));
143 }
144
145 void
146 SubtitlePanel::film_changed (Film::Property property)
147 {
148         if (property == Film::CONTENT || property == Film::REEL_TYPE) {
149                 setup_sensitivity ();
150         }
151 }
152
153 void
154 SubtitlePanel::film_content_changed (int property)
155 {
156         FFmpegContentList fc = _parent->selected_ffmpeg ();
157         SubtitleContentList sc = _parent->selected_subtitle ();
158
159         shared_ptr<FFmpegContent> fcs;
160         if (fc.size() == 1) {
161                 fcs = fc.front ();
162         }
163
164         shared_ptr<SubtitleContent> scs;
165         if (sc.size() == 1) {
166                 scs = sc.front ();
167         }
168
169         if (property == FFmpegContentProperty::SUBTITLE_STREAMS) {
170                 _stream->Clear ();
171                 if (fcs) {
172                         vector<shared_ptr<FFmpegSubtitleStream> > s = fcs->subtitle_streams ();
173                         for (vector<shared_ptr<FFmpegSubtitleStream> >::iterator i = s.begin(); i != s.end(); ++i) {
174                                 _stream->Append (std_to_wx ((*i)->name), new wxStringClientData (std_to_wx ((*i)->identifier ())));
175                         }
176
177                         if (fcs->subtitle_stream()) {
178                                 checked_set (_stream, fcs->subtitle_stream()->identifier ());
179                         } else {
180                                 _stream->SetSelection (wxNOT_FOUND);
181                         }
182                 }
183                 setup_sensitivity ();
184         } else if (property == SubtitleContentProperty::USE_SUBTITLES) {
185                 checked_set (_use, scs ? scs->use_subtitles() : false);
186                 setup_sensitivity ();
187         } else if (property == SubtitleContentProperty::BURN_SUBTITLES) {
188                 checked_set (_burn, scs ? scs->burn_subtitles() : false);
189         } else if (property == SubtitleContentProperty::SUBTITLE_X_OFFSET) {
190                 checked_set (_x_offset, scs ? (scs->subtitle_x_offset() * 100) : 0);
191         } else if (property == SubtitleContentProperty::SUBTITLE_Y_OFFSET) {
192                 checked_set (_y_offset, scs ? (scs->subtitle_y_offset() * 100) : 0);
193         } else if (property == SubtitleContentProperty::SUBTITLE_X_SCALE) {
194                 checked_set (_x_scale, scs ? lrint (scs->subtitle_x_scale() * 100) : 100);
195         } else if (property == SubtitleContentProperty::SUBTITLE_Y_SCALE) {
196                 checked_set (_y_scale, scs ? lrint (scs->subtitle_y_scale() * 100) : 100);
197         } else if (property == SubtitleContentProperty::SUBTITLE_LANGUAGE) {
198                 checked_set (_language, scs ? scs->subtitle_language() : "");
199         } else if (property == DCPContentProperty::REFERENCE_SUBTITLE) {
200                 if (scs) {
201                         shared_ptr<DCPContent> dcp = dynamic_pointer_cast<DCPContent> (scs);
202                         checked_set (_reference, dcp ? dcp->reference_subtitle () : false);
203                 } else {
204                         checked_set (_reference, false);
205                 }
206
207                 setup_sensitivity ();
208         }
209 }
210
211 void
212 SubtitlePanel::use_toggled ()
213 {
214         BOOST_FOREACH (shared_ptr<SubtitleContent> i, _parent->selected_subtitle ()) {
215                 i->set_use_subtitles (_use->GetValue());
216         }
217 }
218
219 void
220 SubtitlePanel::burn_toggled ()
221 {
222         BOOST_FOREACH (shared_ptr<SubtitleContent> i, _parent->selected_subtitle ()) {
223                 i->set_burn_subtitles (_burn->GetValue());
224         }
225 }
226
227 void
228 SubtitlePanel::setup_sensitivity ()
229 {
230         int any_subs = 0;
231         int ffmpeg_subs = 0;
232         int subrip_or_dcp_subs = 0;
233         int image_subs = 0;
234         SubtitleContentList sel = _parent->selected_subtitle ();
235         BOOST_FOREACH (shared_ptr<SubtitleContent> i, sel) {
236                 shared_ptr<const FFmpegContent> fc = boost::dynamic_pointer_cast<const FFmpegContent> (i);
237                 shared_ptr<const SubRipContent> sc = boost::dynamic_pointer_cast<const SubRipContent> (i);
238                 shared_ptr<const DCPSubtitleContent> dsc = boost::dynamic_pointer_cast<const DCPSubtitleContent> (i);
239                 if (fc) {
240                         if (fc->has_subtitles ()) {
241                                 ++ffmpeg_subs;
242                                 ++any_subs;
243                         }
244                 } else if (sc || dsc) {
245                         ++subrip_or_dcp_subs;
246                         ++any_subs;
247                 } else {
248                         ++any_subs;
249                 }
250
251                 if (i->has_image_subtitles ()) {
252                         ++image_subs;
253                         /* We must burn image subtitles at the moment */
254                         i->set_burn_subtitles (true);
255                 }
256         }
257
258         shared_ptr<DCPContent> dcp;
259         if (sel.size() == 1) {
260                 dcp = dynamic_pointer_cast<DCPContent> (sel.front ());
261         }
262
263         list<string> why_not;
264         bool const can_reference = dcp && dcp->can_reference_subtitle (why_not);
265         _reference->Enable (can_reference);
266
267         wxString s;
268         if (!can_reference) {
269                 s = _("Cannot reference this DCP.  ");
270                 BOOST_FOREACH (string i, why_not) {
271                         s += std_to_wx(i) + wxT("  ");
272                 }
273         }
274         _reference->SetToolTip (s);
275
276         bool const reference = _reference->GetValue ();
277
278         _use->Enable (!reference && any_subs > 0);
279         bool const use = _use->GetValue ();
280         _burn->Enable (!reference && any_subs > 0 && use && image_subs == 0);
281         _x_offset->Enable (!reference && any_subs > 0 && use);
282         _y_offset->Enable (!reference && any_subs > 0 && use);
283         _x_scale->Enable (!reference && any_subs > 0 && use);
284         _y_scale->Enable (!reference && any_subs > 0 && use);
285         _language->Enable (!reference && any_subs > 0 && use);
286         _stream->Enable (!reference && ffmpeg_subs == 1);
287         _subtitle_view_button->Enable (!reference && subrip_or_dcp_subs == 1);
288         _fonts_dialog_button->Enable (!reference && subrip_or_dcp_subs == 1);
289 }
290
291 void
292 SubtitlePanel::stream_changed ()
293 {
294         FFmpegContentList fc = _parent->selected_ffmpeg ();
295         if (fc.size() != 1) {
296                 return;
297         }
298
299         shared_ptr<FFmpegContent> fcs = fc.front ();
300
301         vector<shared_ptr<FFmpegSubtitleStream> > a = fcs->subtitle_streams ();
302         vector<shared_ptr<FFmpegSubtitleStream> >::iterator i = a.begin ();
303         string const s = string_client_data (_stream->GetClientObject (_stream->GetSelection ()));
304         while (i != a.end() && (*i)->identifier () != s) {
305                 ++i;
306         }
307
308         if (i != a.end ()) {
309                 fcs->set_subtitle_stream (*i);
310         }
311 }
312
313 void
314 SubtitlePanel::x_offset_changed ()
315 {
316         BOOST_FOREACH (shared_ptr<SubtitleContent> i, _parent->selected_subtitle ()) {
317                 i->set_subtitle_x_offset (_x_offset->GetValue() / 100.0);
318         }
319 }
320
321 void
322 SubtitlePanel::y_offset_changed ()
323 {
324         BOOST_FOREACH (shared_ptr<SubtitleContent> i, _parent->selected_subtitle ()) {
325                 i->set_subtitle_y_offset (_y_offset->GetValue() / 100.0);
326         }
327 }
328
329 void
330 SubtitlePanel::x_scale_changed ()
331 {
332         SubtitleContentList c = _parent->selected_subtitle ();
333         if (c.size() == 1) {
334                 c.front()->set_subtitle_x_scale (_x_scale->GetValue() / 100.0);
335         }
336 }
337
338 void
339 SubtitlePanel::y_scale_changed ()
340 {
341         BOOST_FOREACH (shared_ptr<SubtitleContent> i, _parent->selected_subtitle ()) {
342                 i->set_subtitle_y_scale (_y_scale->GetValue() / 100.0);
343         }
344 }
345
346 void
347 SubtitlePanel::language_changed ()
348 {
349         BOOST_FOREACH (shared_ptr<SubtitleContent> i, _parent->selected_subtitle ()) {
350                 i->set_subtitle_language (wx_to_std (_language->GetValue()));
351         }
352 }
353
354 void
355 SubtitlePanel::content_selection_changed ()
356 {
357         film_content_changed (FFmpegContentProperty::SUBTITLE_STREAMS);
358         film_content_changed (SubtitleContentProperty::USE_SUBTITLES);
359         film_content_changed (SubtitleContentProperty::BURN_SUBTITLES);
360         film_content_changed (SubtitleContentProperty::SUBTITLE_X_OFFSET);
361         film_content_changed (SubtitleContentProperty::SUBTITLE_Y_OFFSET);
362         film_content_changed (SubtitleContentProperty::SUBTITLE_X_SCALE);
363         film_content_changed (SubtitleContentProperty::SUBTITLE_Y_SCALE);
364         film_content_changed (SubtitleContentProperty::SUBTITLE_LANGUAGE);
365         film_content_changed (SubtitleContentProperty::FONTS);
366         film_content_changed (DCPContentProperty::REFERENCE_SUBTITLE);
367 }
368
369 void
370 SubtitlePanel::subtitle_view_clicked ()
371 {
372         if (_subtitle_view) {
373                 _subtitle_view->Destroy ();
374                 _subtitle_view = 0;
375         }
376
377         SubtitleContentList c = _parent->selected_subtitle ();
378         DCPOMATIC_ASSERT (c.size() == 1);
379
380         shared_ptr<SubtitleDecoder> decoder;
381
382         shared_ptr<SubRipContent> sr = dynamic_pointer_cast<SubRipContent> (c.front ());
383         if (sr) {
384                 decoder.reset (new SubRipDecoder (sr));
385         }
386
387         shared_ptr<DCPSubtitleContent> dc = dynamic_pointer_cast<DCPSubtitleContent> (c.front ());
388         if (dc) {
389                 decoder.reset (new DCPSubtitleDecoder (dc));
390         }
391
392         if (decoder) {
393                 _subtitle_view = new SubtitleView (this, _parent->film(), decoder, c.front()->position ());
394                 _subtitle_view->Show ();
395         }
396 }
397
398 void
399 SubtitlePanel::fonts_dialog_clicked ()
400 {
401         if (_fonts_dialog) {
402                 _fonts_dialog->Destroy ();
403                 _fonts_dialog = 0;
404         }
405
406         SubtitleContentList c = _parent->selected_subtitle ();
407         DCPOMATIC_ASSERT (c.size() == 1);
408
409         _fonts_dialog = new FontsDialog (this, c.front ());
410         _fonts_dialog->Show ();
411 }
412
413 void
414 SubtitlePanel::reference_clicked ()
415 {
416         ContentList c = _parent->selected ();
417         if (c.size() != 1) {
418                 return;
419         }
420
421         shared_ptr<DCPContent> d = dynamic_pointer_cast<DCPContent> (c.front ());
422         if (!d) {
423                 return;
424         }
425
426         d->set_reference_subtitle (_reference->GetValue ());
427 }