Don't show an error when cancelling subtitle analysis.
[dcpomatic.git] / src / wx / text_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_panel.h"
24 #include "dcp_text_track_dialog.h"
25 #include "dcpomatic_button.h"
26 #include "dcpomatic_spin_ctrl.h"
27 #include "film_editor.h"
28 #include "film_viewer.h"
29 #include "fonts_dialog.h"
30 #include "language_tag_widget.h"
31 #include "static_text.h"
32 #include "subtitle_appearance_dialog.h"
33 #include "text_panel.h"
34 #include "text_view.h"
35 #include "wx_ptr.h"
36 #include "wx_util.h"
37 #include "lib/analyse_subtitles_job.h"
38 #include "lib/dcp_content.h"
39 #include "lib/dcp_subtitle_content.h"
40 #include "lib/dcp_subtitle_decoder.h"
41 #include "lib/decoder_factory.h"
42 #include "lib/ffmpeg_content.h"
43 #include "lib/ffmpeg_subtitle_stream.h"
44 #include "lib/job_manager.h"
45 #include "lib/scope_guard.h"
46 #include "lib/string_text_file_content.h"
47 #include "lib/string_text_file_decoder.h"
48 #include "lib/subtitle_analysis.h"
49 #include "lib/text_content.h"
50 #include <dcp/warnings.h>
51 LIBDCP_DISABLE_WARNINGS
52 #include <wx/spinctrl.h>
53 LIBDCP_ENABLE_WARNINGS
54
55
56 using std::cout;
57 using std::dynamic_pointer_cast;
58 using std::list;
59 using std::shared_ptr;
60 using std::string;
61 using std::vector;
62 using boost::bind;
63 using boost::optional;
64 #if BOOST_VERSION >= 106100
65 using namespace boost::placeholders;
66 #endif
67
68
69 /** @param t Original text type of the content, if known */
70 TextPanel::TextPanel (ContentPanel* p, TextType t)
71         : ContentSubPanel (p, std_to_wx(text_type_to_name(t)))
72         , _original_type (t)
73 {
74
75 }
76
77
78 void
79 TextPanel::create ()
80 {
81         wxString refer = _("Use this DCP's subtitle as OV and make VF");
82         if (_original_type == TextType::CLOSED_CAPTION) {
83                 refer = _("Use this DCP's closed caption as OV and make VF");
84         }
85
86         _reference = new CheckBox (this, refer);
87         _reference_note = new StaticText (this, wxT(""));
88         _reference_note->Wrap (200);
89         auto font = _reference_note->GetFont();
90         font.SetStyle(wxFONTSTYLE_ITALIC);
91         font.SetPointSize(font.GetPointSize() - 1);
92         _reference_note->SetFont(font);
93
94         _use = new CheckBox (this, _("Use as"));
95         _type = new wxChoice (this, wxID_ANY);
96         _type->Append (_("open subtitles"));
97         _type->Append (_("closed captions"));
98
99         _burn = new CheckBox (this, _("Burn subtitles into image"));
100
101         _offset_label = create_label (this, _("Offset"), true);
102         _x_offset_label = create_label (this, _("X"), true);
103         _x_offset = new SpinCtrl (this, DCPOMATIC_SPIN_CTRL_WIDTH);
104         _x_offset_pc_label = new StaticText (this, _("%"));
105         _y_offset_label = create_label (this, _("Y"), true);
106         _y_offset = new SpinCtrl (this, DCPOMATIC_SPIN_CTRL_WIDTH);
107         _y_offset_pc_label = new StaticText (this, _("%"));
108
109         _scale_label = create_label (this, _("Scale"), true);
110         _x_scale_label = create_label (this, _("X"), true);
111         _x_scale = new SpinCtrl (this, DCPOMATIC_SPIN_CTRL_WIDTH);
112         _x_scale_pc_label = new StaticText (this, _("%"));
113         _y_scale_label = create_label (this, S_("Coord|Y"), true);
114         _y_scale = new SpinCtrl (this, DCPOMATIC_SPIN_CTRL_WIDTH);
115         _y_scale_pc_label = new StaticText (this, _("%"));
116
117         _line_spacing_label = create_label (this, _("Line spacing"), true);
118         _line_spacing = new SpinCtrl (this, DCPOMATIC_SPIN_CTRL_WIDTH);
119         _line_spacing_pc_label = new StaticText (this, _("%"));
120
121         _stream_label = create_label (this, _("Stream"), true);
122         _stream = new wxChoice (this, wxID_ANY);
123
124         _text_view_button = new Button (this, _("View..."));
125         _fonts_dialog_button = new Button (this, _("Fonts..."));
126         _appearance_dialog_button = new Button (this, _("Appearance..."));
127
128         _x_offset->SetRange (-100, 100);
129         _y_offset->SetRange (-100, 100);
130         _x_scale->SetRange (0, 1000);
131         _y_scale->SetRange (0, 1000);
132         _line_spacing->SetRange (0, 1000);
133
134         _reference->bind(&TextPanel::reference_clicked, this);
135         _use->bind(&TextPanel::use_toggled, this);
136         _type->Bind                     (wxEVT_CHOICE,   boost::bind (&TextPanel::type_changed, this));
137         _burn->bind(&TextPanel::burn_toggled, this);
138         _x_offset->Bind                 (wxEVT_SPINCTRL, boost::bind (&TextPanel::x_offset_changed, this));
139         _y_offset->Bind                 (wxEVT_SPINCTRL, boost::bind (&TextPanel::y_offset_changed, this));
140         _x_scale->Bind                  (wxEVT_SPINCTRL, boost::bind (&TextPanel::x_scale_changed, this));
141         _y_scale->Bind                  (wxEVT_SPINCTRL, boost::bind (&TextPanel::y_scale_changed, this));
142         _line_spacing->Bind             (wxEVT_SPINCTRL, boost::bind (&TextPanel::line_spacing_changed, this));
143         _stream->Bind                   (wxEVT_CHOICE,   boost::bind (&TextPanel::stream_changed, this));
144         _text_view_button->Bind         (wxEVT_BUTTON,   boost::bind (&TextPanel::text_view_clicked, this));
145         _fonts_dialog_button->Bind      (wxEVT_BUTTON,   boost::bind (&TextPanel::fonts_dialog_clicked, this));
146         _appearance_dialog_button->Bind (wxEVT_BUTTON,   boost::bind (&TextPanel::appearance_dialog_clicked, this));
147
148         add_to_grid();
149         content_selection_changed ();
150
151         _sizer->Layout ();
152 }
153
154
155 void
156 TextPanel::setup_visibility ()
157 {
158         switch (current_type()) {
159         case TextType::OPEN_SUBTITLE:
160                 if (_dcp_track_label) {
161                         _dcp_track_label->Destroy ();
162                         _dcp_track_label = nullptr;
163                 }
164                 if (_dcp_track) {
165                         _dcp_track->Destroy ();
166                         _dcp_track = nullptr;
167                 }
168                 if (!_outline_subtitles) {
169                         _outline_subtitles = new CheckBox (this, _("Show subtitle area"));
170                         _outline_subtitles->bind(&TextPanel::outline_subtitles_changed, this);
171                         _grid->Add (_outline_subtitles, wxGBPosition(_outline_subtitles_row, 0), wxGBSpan(1, 2));
172                 }
173                 if (!_language) {
174                         _language_label = create_label (this, _("Language"), true);
175                         add_label_to_sizer (_grid, _language_label, true, wxGBPosition(_ccap_track_or_language_row, 0));
176                         _language_sizer = new wxBoxSizer (wxHORIZONTAL);
177                         _language = new LanguageTagWidget (this, _("Language of these subtitles"), boost::none, wxString("en-US-"));
178                         _language->Changed.connect (boost::bind(&TextPanel::language_changed, this));
179                         _language_sizer->Add (_language->sizer(), 1, wxRIGHT, DCPOMATIC_SIZER_GAP);
180                         _language_type = new wxChoice (this, wxID_ANY);
181                         /// TRANSLATORS: Main and Additional here are a choice for whether a set of subtitles is in the "main" language of the
182                         /// film or an "additional" language.
183                         _language_type->Append (_("Main"));
184                         _language_type->Append (_("Additional"));
185                         _language_type->Bind (wxEVT_CHOICE, boost::bind(&TextPanel::language_is_additional_changed, this));
186                         _language_sizer->Add (_language_type, 0, wxALIGN_CENTER_VERTICAL | wxTOP, DCPOMATIC_CHOICE_TOP_PAD);
187                         _grid->Add (_language_sizer, wxGBPosition(_ccap_track_or_language_row, 1), wxGBSpan(1, 2));
188                         film_content_changed (TextContentProperty::LANGUAGE);
189                         film_content_changed (TextContentProperty::LANGUAGE_IS_ADDITIONAL);
190                 }
191                 break;
192         case TextType::CLOSED_CAPTION:
193                 if (_language_label) {
194                         _language_label->Destroy ();
195                         _language_label = nullptr;
196                         _grid->Remove (_language->sizer());
197                         delete _language;
198                         _grid->Remove (_language_sizer);
199                         _language_sizer = nullptr;
200                         _language = nullptr;
201                         _language_type->Destroy ();
202                         _language_type = nullptr;
203                 }
204                 if (!_dcp_track_label) {
205                         _dcp_track_label = create_label (this, _("CCAP track"), true);
206                         add_label_to_sizer (_grid, _dcp_track_label, true, wxGBPosition(_ccap_track_or_language_row, 0));
207                 }
208                 if (!_dcp_track) {
209                         _dcp_track = new wxChoice (this, wxID_ANY);
210                         _dcp_track->Bind (wxEVT_CHOICE, boost::bind(&TextPanel::dcp_track_changed, this));
211                         _grid->Add (_dcp_track, wxGBPosition(_ccap_track_or_language_row, 1), wxDefaultSpan, wxEXPAND);
212                         update_dcp_tracks ();
213                         film_content_changed (TextContentProperty::DCP_TRACK);
214                 }
215                 if (_outline_subtitles) {
216                         _outline_subtitles->Destroy ();
217                         _outline_subtitles = nullptr;
218                         clear_outline_subtitles ();
219                 }
220                 break;
221         default:
222                 break;
223         }
224
225         _grid->Layout ();
226 }
227
228
229 void
230 TextPanel::add_to_grid ()
231 {
232         int r = 0;
233
234         auto reference_sizer = new wxBoxSizer (wxVERTICAL);
235         reference_sizer->Add (_reference, 0);
236         reference_sizer->Add (_reference_note, 0);
237         _grid->Add (reference_sizer, wxGBPosition(r, 0), wxGBSpan(1, 4));
238         ++r;
239
240         auto use = new wxBoxSizer (wxHORIZONTAL);
241         use->Add (_use, 0, wxEXPAND | wxRIGHT, DCPOMATIC_SIZER_GAP);
242         use->Add (_type, 1, wxEXPAND, 0);
243         _grid->Add (use, wxGBPosition (r, 0), wxGBSpan (1, 2));
244         ++r;
245
246         _grid->Add (_burn, wxGBPosition (r, 0), wxGBSpan (1, 2));
247         ++r;
248
249         _outline_subtitles_row = r;
250         ++r;
251
252         add_label_to_sizer (_grid, _offset_label, true, wxGBPosition (r, 0));
253         auto offset = new wxBoxSizer (wxHORIZONTAL);
254         add_label_to_sizer (offset, _x_offset_label, true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTER_VERTICAL);
255         offset->Add (_x_offset, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, DCPOMATIC_SIZER_X_GAP);
256         offset->Add (_x_offset_pc_label, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, DCPOMATIC_SIZER_X_GAP * 2);
257 #ifdef __WXGTK3__
258         _grid->Add (offset, wxGBPosition(r, 1));
259         ++r;
260         offset = new wxBoxSizer (wxHORIZONTAL);
261 #endif
262         add_label_to_sizer (offset, _y_offset_label, true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTER_VERTICAL);
263         offset->Add (_y_offset, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, DCPOMATIC_SIZER_X_GAP);
264         add_label_to_sizer (offset, _y_offset_pc_label, false, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL);
265         _grid->Add (offset, wxGBPosition (r, 1));
266         ++r;
267
268         add_label_to_sizer (_grid, _scale_label, true, wxGBPosition (r, 0));
269         auto scale = new wxBoxSizer (wxHORIZONTAL);
270         add_label_to_sizer (scale, _x_scale_label, true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTER_VERTICAL);
271         scale->Add (_x_scale, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, DCPOMATIC_SIZER_X_GAP);
272         scale->Add (_x_scale_pc_label, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, DCPOMATIC_SIZER_X_GAP * 2);
273 #ifdef __WXGTK3__
274         _grid->Add (scale, wxGBPosition(r, 1));
275         ++r;
276         scale = new wxBoxSizer (wxHORIZONTAL);
277 #endif
278         add_label_to_sizer (scale, _y_scale_label, true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTER_VERTICAL);
279         scale->Add (_y_scale, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, DCPOMATIC_SIZER_X_GAP);
280         add_label_to_sizer (scale, _y_scale_pc_label, false, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL);
281         _grid->Add (scale, wxGBPosition (r, 1));
282         ++r;
283
284         {
285                 add_label_to_sizer (_grid, _line_spacing_label, true, wxGBPosition (r, 0));
286                 auto s = new wxBoxSizer (wxHORIZONTAL);
287                 s->Add (_line_spacing);
288                 add_label_to_sizer (s, _line_spacing_pc_label, false, 0, wxLEFT | wxRIGHT | wxALIGN_CENTER_VERTICAL);
289                 _grid->Add (s, wxGBPosition (r, 1));
290                 ++r;
291         }
292
293         _ccap_track_or_language_row = r;
294         ++r;
295
296         add_label_to_sizer (_grid, _stream_label, true, wxGBPosition (r, 0));
297         _grid->Add (_stream, wxGBPosition (r, 1));
298         ++r;
299
300         {
301                 auto s = new wxBoxSizer (wxHORIZONTAL);
302
303                 s->Add (_text_view_button, 0, wxALL, DCPOMATIC_SIZER_GAP);
304                 s->Add (_fonts_dialog_button, 0, wxALL, DCPOMATIC_SIZER_GAP);
305                 s->Add (_appearance_dialog_button, 0, wxALL, DCPOMATIC_SIZER_GAP);
306
307                 _grid->Add (s, wxGBPosition(r, 0), wxGBSpan(1, 2));
308                 ++r;
309         }
310
311         setup_visibility ();
312 }
313
314
315 void
316 TextPanel::update_dcp_track_selection ()
317 {
318         DCPOMATIC_ASSERT (_dcp_track);
319
320         optional<DCPTextTrack> selected;
321         bool many = false;
322         for (auto i: _parent->selected_text()) {
323                 auto t = i->text_of_original_type(_original_type);
324                 if (t) {
325                         auto dt = t->dcp_track();
326                         if (dt && selected && *dt != *selected) {
327                                 many = true;
328                         } else if (!selected) {
329                                 selected = dt;
330                         }
331                 }
332         }
333
334         int n = 0;
335         for (auto i: _parent->film()->closed_caption_tracks()) {
336                 if (!many && selected && *selected == i) {
337                         _dcp_track->SetSelection (n);
338                 }
339                 ++n;
340         }
341
342         if (!selected || many) {
343                 _dcp_track->SetSelection (wxNOT_FOUND);
344         }
345 }
346
347
348 void
349 TextPanel::update_dcp_tracks ()
350 {
351         DCPOMATIC_ASSERT (_dcp_track);
352
353         _dcp_track->Clear ();
354         for (auto i: _parent->film()->closed_caption_tracks()) {
355                 /* XXX: don't display the "magic" track which has empty name and language;
356                    this is a nasty hack (see also Film::closed_caption_tracks)
357                 */
358                 if (!i.name.empty() || i.language) {
359                         _dcp_track->Append (std_to_wx(i.summary()));
360                 }
361         }
362
363         if (_parent->film()->closed_caption_tracks().size() < 6) {
364                 _dcp_track->Append (_("Add new..."));
365         }
366
367         update_dcp_track_selection ();
368 }
369
370
371 void
372 TextPanel::dcp_track_changed ()
373 {
374         optional<DCPTextTrack> track;
375
376         if (_dcp_track->GetSelection() == int(_dcp_track->GetCount()) - 1) {
377                 auto d = make_wx<DCPTextTrackDialog>(this);
378                 if (d->ShowModal() == wxID_OK) {
379                         track = d->get();
380                 }
381         } else {
382                 /* Find the DCPTextTrack that was selected */
383                 for (auto i: _parent->film()->closed_caption_tracks()) {
384                         if (i.summary() == wx_to_std(_dcp_track->GetStringSelection())) {
385                                 track = i;
386                         }
387                 }
388         }
389
390         if (track) {
391                 for (auto i: _parent->selected_text()) {
392                         auto t = i->text_of_original_type(_original_type);
393                         if (t && t->type() == TextType::CLOSED_CAPTION) {
394                                 t->set_dcp_track(*track);
395                         }
396                 }
397         }
398
399         update_dcp_tracks ();
400 }
401
402
403 void
404 TextPanel::film_changed (Film::Property property)
405 {
406         if (property == Film::Property::CONTENT || property == Film::Property::REEL_TYPE || property == Film::Property::INTEROP) {
407                 setup_sensitivity ();
408         }
409 }
410
411
412 void
413 TextPanel::film_content_changed (int property)
414 {
415         auto fc = _parent->selected_ffmpeg ();
416         auto sc = _parent->selected_text ();
417
418         shared_ptr<FFmpegContent> fcs;
419         if (fc.size() == 1) {
420                 fcs = fc.front ();
421         }
422
423         shared_ptr<Content> scs;
424         if (sc.size() == 1) {
425                 scs = sc.front ();
426         }
427
428         shared_ptr<TextContent> text;
429         if (scs) {
430                 text = scs->text_of_original_type(_original_type);
431         }
432
433         if (property == FFmpegContentProperty::SUBTITLE_STREAMS) {
434                 _stream->Clear ();
435                 if (fcs) {
436                         for (auto i: fcs->subtitle_streams()) {
437                                 _stream->Append (std_to_wx(i->name), new wxStringClientData(std_to_wx(i->identifier())));
438                         }
439
440                         if (fcs->subtitle_stream()) {
441                                 checked_set (_stream, fcs->subtitle_stream()->identifier ());
442                         } else {
443                                 _stream->SetSelection (wxNOT_FOUND);
444                         }
445                 }
446                 setup_sensitivity ();
447                 clear_outline_subtitles ();
448         } else if (property == TextContentProperty::USE) {
449                 checked_set (_use, text ? text->use() : false);
450                 setup_sensitivity ();
451                 clear_outline_subtitles ();
452         } else if (property == TextContentProperty::TYPE) {
453                 if (text) {
454                         switch (text->type()) {
455                         case TextType::OPEN_SUBTITLE:
456                                 _type->SetSelection (0);
457                                 break;
458                         case TextType::CLOSED_CAPTION:
459                                 _type->SetSelection (1);
460                                 break;
461                         default:
462                                 DCPOMATIC_ASSERT (false);
463                         }
464                 } else {
465                         _type->SetSelection (0);
466                 }
467                 setup_sensitivity ();
468                 setup_visibility ();
469         } else if (property == TextContentProperty::BURN) {
470                 checked_set (_burn, text ? text->burn() : false);
471         } else if (property == TextContentProperty::X_OFFSET) {
472                 checked_set (_x_offset, text ? lrint (text->x_offset() * 100) : 0);
473                 update_outline_subtitles_in_viewer ();
474         } else if (property == TextContentProperty::Y_OFFSET) {
475                 checked_set (_y_offset, text ? lrint (text->y_offset() * 100) : 0);
476                 update_outline_subtitles_in_viewer ();
477         } else if (property == TextContentProperty::X_SCALE) {
478                 checked_set (_x_scale, text ? lrint (text->x_scale() * 100) : 100);
479                 clear_outline_subtitles ();
480         } else if (property == TextContentProperty::Y_SCALE) {
481                 checked_set (_y_scale, text ? lrint (text->y_scale() * 100) : 100);
482                 clear_outline_subtitles ();
483         } else if (property == TextContentProperty::LINE_SPACING) {
484                 checked_set (_line_spacing, text ? lrint (text->line_spacing() * 100) : 100);
485                 clear_outline_subtitles ();
486         } else if (property == TextContentProperty::DCP_TRACK) {
487                 if (_dcp_track) {
488                         update_dcp_track_selection ();
489                 }
490         } else if (property == TextContentProperty::LANGUAGE) {
491                 if (_language) {
492                         _language->set (text ? text->language() : boost::none);
493                 }
494         } else if (property == TextContentProperty::LANGUAGE_IS_ADDITIONAL) {
495                 if (_language_type) {
496                         _language_type->SetSelection (text ? (text->language_is_additional() ? 1 : 0) : 0);
497                 }
498         } else if (property == DCPContentProperty::REFERENCE_TEXT) {
499                 if (scs) {
500                         auto dcp = dynamic_pointer_cast<DCPContent> (scs);
501                         checked_set (_reference, dcp ? dcp->reference_text(_original_type) : false);
502                 } else {
503                         checked_set (_reference, false);
504                 }
505
506                 setup_sensitivity ();
507         } else if (property == DCPContentProperty::TEXTS) {
508                 setup_sensitivity ();
509         } else if (property == ContentProperty::TRIM_START) {
510                 setup_sensitivity ();
511         }
512 }
513
514
515 void
516 TextPanel::use_toggled ()
517 {
518         for (auto i: _parent->selected_text()) {
519                 i->text_of_original_type(_original_type)->set_use (_use->GetValue());
520         }
521 }
522
523
524 /** @return the text type that is currently selected in the drop-down */
525 TextType
526 TextPanel::current_type () const
527 {
528         switch (_type->GetSelection()) {
529         case 0:
530                 return TextType::OPEN_SUBTITLE;
531         case 1:
532                 return TextType::CLOSED_CAPTION;
533         }
534
535         return TextType::UNKNOWN;
536 }
537
538
539 void
540 TextPanel::type_changed ()
541 {
542         for (auto i: _parent->selected_text()) {
543                 i->text_of_original_type(_original_type)->set_type (current_type ());
544         }
545
546         setup_visibility ();
547 }
548
549
550 void
551 TextPanel::burn_toggled ()
552 {
553         for (auto i: _parent->selected_text ()) {
554                 i->text_of_original_type(_original_type)->set_burn (_burn->GetValue());
555         }
556 }
557
558
559 void
560 TextPanel::setup_sensitivity ()
561 {
562         int any_subs = 0;
563         /* We currently assume that FFmpeg subtitles are bitmapped */
564         int ffmpeg_subs = 0;
565         /* DCP subs can't have their line spacing changed */
566         int dcp_subs = 0;
567         auto sel = _parent->selected_text ();
568         for (auto i: sel) {
569                 /* These are the content types that could include subtitles */
570                 auto fc = std::dynamic_pointer_cast<const FFmpegContent>(i);
571                 auto sc = std::dynamic_pointer_cast<const StringTextFileContent>(i);
572                 auto dc = std::dynamic_pointer_cast<const DCPContent>(i);
573                 auto dsc = std::dynamic_pointer_cast<const DCPSubtitleContent>(i);
574                 if (fc) {
575                         if (!fc->text.empty()) {
576                                 ++ffmpeg_subs;
577                                 ++any_subs;
578                         }
579                 } else if (dc || dsc) {
580                         ++dcp_subs;
581                         ++any_subs;
582                 } else if (sc) {
583                         /* XXX: in the future there could be bitmap subs from DCPs */
584                         ++any_subs;
585                 }
586         }
587
588         /* Decide whether we can reference these subs */
589
590         shared_ptr<DCPContent> dcp;
591         if (sel.size() == 1) {
592                 dcp = dynamic_pointer_cast<DCPContent>(sel.front());
593         }
594
595         string why_not;
596         bool const can_reference = dcp && dcp->can_reference_text (_parent->film(), _original_type, why_not);
597         wxString cannot;
598         if (why_not.empty()) {
599                 cannot = _("Cannot reference this DCP's subtitles or captions.");
600         } else {
601                 cannot = _("Cannot reference this DCP's subtitles or captions: ") + std_to_wx(why_not);
602         }
603         setup_refer_button (_reference, _reference_note, dcp, can_reference, cannot);
604
605         bool const reference = _reference->GetValue ();
606
607         auto const type = current_type ();
608
609         /* Set up _type */
610         _type->Clear ();
611         _type->Append (_("open subtitles"));
612         if (ffmpeg_subs == 0) {
613                 _type->Append (_("closed captions"));
614         }
615
616         switch (type) {
617         case TextType::OPEN_SUBTITLE:
618                 _type->SetSelection (0);
619                 break;
620         case TextType::CLOSED_CAPTION:
621                 if (_type->GetCount() > 1) {
622                         _type->SetSelection (1);
623                 }
624                 break;
625         default:
626                 break;
627         }
628
629         /* Set up sensitivity */
630         _use->Enable (!reference && any_subs > 0);
631         bool const use = _use->GetValue ();
632         if (_outline_subtitles) {
633                 _outline_subtitles->Enable (!_loading_analysis && any_subs && use && type == TextType::OPEN_SUBTITLE);
634         }
635         _type->Enable (!reference && any_subs > 0 && use);
636         _burn->Enable (!reference && any_subs > 0 && use && type == TextType::OPEN_SUBTITLE);
637         _x_offset->Enable (!reference && any_subs > 0 && use && type == TextType::OPEN_SUBTITLE);
638         _y_offset->Enable (!reference && any_subs > 0 && use && type == TextType::OPEN_SUBTITLE);
639         _x_scale->Enable (!reference && any_subs > 0 && use && type == TextType::OPEN_SUBTITLE);
640         _y_scale->Enable (!reference && any_subs > 0 && use && type == TextType::OPEN_SUBTITLE);
641         _line_spacing->Enable (!reference && use && type == TextType::OPEN_SUBTITLE && dcp_subs < any_subs);
642         _stream->Enable (!reference && ffmpeg_subs == 1);
643         /* Ideally we would check here to see if the FFmpeg content has "string" subs (i.e. not bitmaps) */
644         _text_view_button->Enable (!reference && any_subs > 0 && ffmpeg_subs == 0);
645         _fonts_dialog_button->Enable (!reference && any_subs > 0 && ffmpeg_subs == 0 && type == TextType::OPEN_SUBTITLE);
646         _appearance_dialog_button->Enable (!reference && any_subs > 0 && use && type == TextType::OPEN_SUBTITLE);
647 }
648
649
650 void
651 TextPanel::stream_changed ()
652 {
653         auto fc = _parent->selected_ffmpeg ();
654         if (fc.size() != 1) {
655                 return;
656         }
657
658         auto fcs = fc.front ();
659
660         auto a = fcs->subtitle_streams ();
661         auto i = a.begin ();
662         auto const s = string_client_data (_stream->GetClientObject(_stream->GetSelection()));
663         while (i != a.end() && (*i)->identifier () != s) {
664                 ++i;
665         }
666
667         if (i != a.end ()) {
668                 fcs->set_subtitle_stream (*i);
669         }
670 }
671
672
673 void
674 TextPanel::x_offset_changed ()
675 {
676         for (auto i: _parent->selected_text ()) {
677                 i->text_of_original_type(_original_type)->set_x_offset (_x_offset->GetValue() / 100.0);
678         }
679 }
680
681
682 void
683 TextPanel::y_offset_changed ()
684 {
685         for (auto i: _parent->selected_text ()) {
686                 i->text_of_original_type(_original_type)->set_y_offset (_y_offset->GetValue() / 100.0);
687         }
688 }
689
690
691 void
692 TextPanel::x_scale_changed ()
693 {
694         for (auto i: _parent->selected_text ()) {
695                 i->text_of_original_type(_original_type)->set_x_scale (_x_scale->GetValue() / 100.0);
696         }
697 }
698
699
700 void
701 TextPanel::y_scale_changed ()
702 {
703         for (auto i: _parent->selected_text ()) {
704                 i->text_of_original_type(_original_type)->set_y_scale (_y_scale->GetValue() / 100.0);
705         }
706 }
707
708
709 void
710 TextPanel::line_spacing_changed ()
711 {
712         for (auto i: _parent->selected_text ()) {
713                 i->text_of_original_type(_original_type)->set_line_spacing (_line_spacing->GetValue() / 100.0);
714         }
715 }
716
717
718 void
719 TextPanel::content_selection_changed ()
720 {
721         film_content_changed (FFmpegContentProperty::SUBTITLE_STREAMS);
722         film_content_changed (TextContentProperty::USE);
723         film_content_changed (TextContentProperty::BURN);
724         film_content_changed (TextContentProperty::X_OFFSET);
725         film_content_changed (TextContentProperty::Y_OFFSET);
726         film_content_changed (TextContentProperty::X_SCALE);
727         film_content_changed (TextContentProperty::Y_SCALE);
728         film_content_changed (TextContentProperty::LINE_SPACING);
729         film_content_changed (TextContentProperty::FONTS);
730         film_content_changed (TextContentProperty::TYPE);
731         film_content_changed (TextContentProperty::DCP_TRACK);
732         film_content_changed (TextContentProperty::LANGUAGE);
733         film_content_changed (TextContentProperty::LANGUAGE_IS_ADDITIONAL);
734         film_content_changed (DCPContentProperty::REFERENCE_TEXT);
735 }
736
737
738 void
739 TextPanel::text_view_clicked ()
740 {
741         auto c = _parent->selected_text ();
742         DCPOMATIC_ASSERT (c.size() == 1);
743
744         auto decoder = decoder_factory (_parent->film(), c.front(), false, false, shared_ptr<Decoder>());
745
746         if (decoder) {
747                 _text_view.reset(this, _parent->film(), c.front(), c.front()->text_of_original_type(_original_type), decoder, _parent->film_viewer());
748                 _text_view->Show ();
749         }
750 }
751
752
753 void
754 TextPanel::fonts_dialog_clicked ()
755 {
756         auto c = _parent->selected_text ();
757         DCPOMATIC_ASSERT (c.size() == 1);
758
759         _fonts_dialog.reset(this, c.front(), c.front()->text_of_original_type(_original_type));
760         _fonts_dialog->Show ();
761 }
762
763
764 void
765 TextPanel::reference_clicked ()
766 {
767         auto c = _parent->selected ();
768         if (c.size() != 1) {
769                 return;
770         }
771
772         auto d = dynamic_pointer_cast<DCPContent> (c.front ());
773         if (!d) {
774                 return;
775         }
776
777         d->set_reference_text (_original_type, _reference->GetValue ());
778 }
779
780
781 void
782 TextPanel::appearance_dialog_clicked ()
783 {
784         auto c = _parent->selected_text ();
785         DCPOMATIC_ASSERT (c.size() == 1);
786
787         SubtitleAppearanceDialog dialog(this, _parent->film(), c.front(), c.front()->text_of_original_type(_original_type));
788         if (dialog.ShowModal() == wxID_OK) {
789                 dialog.apply();
790         }
791 }
792
793
794 /** The user has clicked on the outline subtitles check box */
795 void
796 TextPanel::outline_subtitles_changed ()
797 {
798         if (_outline_subtitles->GetValue()) {
799                 _analysis_content = _parent->selected_text().front();
800                 try_to_load_analysis ();
801         } else {
802                 clear_outline_subtitles ();
803         }
804 }
805
806
807 void
808 TextPanel::try_to_load_analysis ()
809 {
810         if (_loading_analysis) {
811                 return;
812         }
813
814         _loading_analysis = true;
815         ScopeGuard sg = [this]() {
816                 _loading_analysis = false;
817                 setup_sensitivity();
818         };
819
820         setup_sensitivity ();
821         _analysis.reset ();
822
823         auto content = _analysis_content.lock ();
824         if (!content) {
825                 return;
826         }
827
828         auto const path = _parent->film()->subtitle_analysis_path(content);
829
830         if (!boost::filesystem::exists(path)) {
831                 for (auto i: JobManager::instance()->get()) {
832                         if (dynamic_pointer_cast<AnalyseSubtitlesJob>(i) && !i->finished()) {
833                                 i->cancel ();
834                         }
835                 }
836
837                 JobManager::instance()->analyse_subtitles (
838                         _parent->film(), content, _analysis_finished_connection, bind(&TextPanel::analysis_finished, this, _1)
839                         );
840                 return;
841         }
842
843         try {
844                 _analysis.reset (new SubtitleAnalysis(path));
845         } catch (OldFormatError& e) {
846                 /* An old analysis file: recreate it */
847                 JobManager::instance()->analyse_subtitles (
848                         _parent->film(), content, _analysis_finished_connection, bind(&TextPanel::analysis_finished, this, _1)
849                         );
850                 return;
851         }
852
853         update_outline_subtitles_in_viewer ();
854 }
855
856
857 void
858 TextPanel::update_outline_subtitles_in_viewer ()
859 {
860         auto& fv = _parent->film_viewer();
861
862         if (_analysis) {
863                 auto rect = _analysis->bounding_box ();
864                 if (rect) {
865                         auto content = _analysis_content.lock ();
866                         DCPOMATIC_ASSERT (content);
867                         rect->x += content->text.front()->x_offset() - _analysis->analysis_x_offset();
868                         rect->y += content->text.front()->y_offset() - _analysis->analysis_y_offset();
869                 }
870                 fv.set_outline_subtitles(rect);
871         } else {
872                 fv.set_outline_subtitles({});
873         }
874 }
875
876
877 /** Remove any current subtitle outline display */
878 void
879 TextPanel::clear_outline_subtitles ()
880 {
881         _analysis.reset ();
882         update_outline_subtitles_in_viewer ();
883         if (_outline_subtitles) {
884                 _outline_subtitles->SetValue (false);
885         }
886 }
887
888
889 void
890 TextPanel::analysis_finished(Job::Result result)
891 {
892         _loading_analysis = false;
893
894         auto content = _analysis_content.lock ();
895         if (!content || result == Job::Result::RESULT_CANCELLED) {
896                 clear_outline_subtitles();
897                 setup_sensitivity();
898                 return;
899         }
900
901         if (!boost::filesystem::exists(_parent->film()->subtitle_analysis_path(content))) {
902                 /* We analysed and still nothing showed up, so maybe it failed.  Give up. */
903                 error_dialog (_parent->window(), _("Could not analyse subtitles."));
904                 clear_outline_subtitles ();
905                 setup_sensitivity ();
906                 return;
907         }
908
909         try_to_load_analysis ();
910 }
911
912
913 void
914 TextPanel::language_changed ()
915 {
916         for (auto i: _parent->selected_text()) {
917                 auto t = i->text_of_original_type(_original_type);
918                 if (t) {
919                         t->set_language (_language->get());
920                 }
921         }
922 }
923
924
925 void
926 TextPanel::language_is_additional_changed ()
927 {
928         for (auto i: _parent->selected_text()) {
929                 auto t = i->text_of_original_type(_original_type);
930                 if (t) {
931                         t->set_language_is_additional (_language_type->GetSelection() == 1);
932                 }
933         }
934 }
935