b81414bed9f72b37a0e638cf6545a19440685fae
[dcpomatic.git] / src / wx / closed_captions_dialog.cc
1 /*
2     Copyright (C) 2018 Carl Hetherington <cth@carlh.net>
3
4     This file is part of DCP-o-matic.
5
6     DCP-o-matic is free software; you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation; either version 2 of the License, or
9     (at your option) any later version.
10
11     DCP-o-matic is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15
16     You should have received a copy of the GNU General Public License
17     along with DCP-o-matic.  If not, see <http://www.gnu.org/licenses/>.
18
19 */
20
21 #include "closed_captions_dialog.h"
22 #include "lib/string_text.h"
23 #include "lib/butler.h"
24 #include <boost/bind.hpp>
25
26 using std::list;
27 using std::max;
28 using std::cout;
29 using std::pair;
30 using std::make_pair;
31 using boost::shared_ptr;
32 using boost::weak_ptr;
33 using boost::optional;
34
35 ClosedCaptionsDialog::ClosedCaptionsDialog (wxWindow* parent)
36         /* XXX: empirical and probably unhelpful default size here; needs to be related to font metrics */
37         : wxDialog (parent, wxID_ANY, _("Closed captions"), wxDefaultPosition, wxSize(640, (640 / 10) + 64),
38 #ifdef DCPOMATIC_OSX
39                 /* I can't get wxFRAME_FLOAT_ON_PARENT to work on OS X, and although wxSTAY_ON_TOP keeps
40                    the window above all others (and not just our own) it's better than nothing for now.
41                 */
42                 wxDEFAULT_FRAME_STYLE | wxRESIZE_BORDER | wxFULL_REPAINT_ON_RESIZE | wxSTAY_ON_TOP
43 #else
44                 wxDEFAULT_FRAME_STYLE | wxRESIZE_BORDER | wxFULL_REPAINT_ON_RESIZE | wxFRAME_FLOAT_ON_PARENT
45 #endif
46                 )
47         , _current_in_lines (false)
48 {
49         _lines.resize (CLOSED_CAPTION_LINES);
50         Bind (wxEVT_PAINT, boost::bind (&ClosedCaptionsDialog::paint, this));
51 }
52
53 void
54 ClosedCaptionsDialog::paint ()
55 {
56         wxPaintDC dc (this);
57         dc.SetBackground (*wxBLACK_BRUSH);
58         dc.Clear ();
59         dc.SetTextForeground (*wxWHITE);
60
61         /* Choose a font which fits vertically */
62         int const line_height = max (8, dc.GetSize().GetHeight() / CLOSED_CAPTION_LINES);
63         wxFont font (*wxNORMAL_FONT);
64         font.SetPixelSize (wxSize (0, line_height * 0.8));
65         dc.SetFont (font);
66
67         for (int i = 0; i < CLOSED_CAPTION_LINES; ++i) {
68                 wxString const good = _lines[i].Left (CLOSED_CAPTION_LENGTH);
69                 dc.DrawText (good, 8, line_height * i);
70                 if (_lines[i].Length() > CLOSED_CAPTION_LENGTH) {
71                         wxString const bad = _lines[i].Right (_lines[i].Length() - CLOSED_CAPTION_LENGTH);
72                         wxSize size = dc.GetTextExtent (good);
73                         dc.SetTextForeground (*wxRED);
74                         dc.DrawText (bad, 8 + size.GetWidth(), line_height * i);
75                         dc.SetTextForeground (*wxWHITE);
76                 }
77         }
78 }
79
80 class ClosedCaptionSorter
81 {
82 public:
83         bool operator() (StringText const & a, StringText const & b)
84         {
85                 return from_top(a) < from_top(b);
86         }
87
88 private:
89         float from_top (StringText const & c) const
90         {
91                 switch (c.v_align()) {
92                 case dcp::VALIGN_TOP:
93                         return c.v_position();
94                 case dcp::VALIGN_CENTER:
95                         return c.v_position() + 0.5;
96                 case dcp::VALIGN_BOTTOM:
97                         return 1.0 - c.v_position();
98                 }
99                 DCPOMATIC_ASSERT (false);
100                 return 0;
101         }
102 };
103
104 void
105 ClosedCaptionsDialog::update (DCPTime time)
106 {
107         if (_current_in_lines && _current->period.to > time) {
108                 /* Current one is fine */
109                 return;
110         }
111
112         if (_current && _current->period.to < time) {
113                 /* Current one has finished; clear out */
114                 for (int j = 0; j < CLOSED_CAPTION_LINES; ++j) {
115                         _lines[j] = "";
116                 }
117                 Refresh ();
118                 _current = optional<TextRingBuffers::Data>();
119         }
120
121         if (!_current) {
122                 /* We have no current one: get another */
123                 shared_ptr<Butler> butler = _butler.lock ();
124                 DCPOMATIC_ASSERT (butler);
125                 _current = butler->get_closed_caption ();
126                 _current_in_lines = false;
127         }
128
129         if (_current && _current->period.contains(time)) {
130                 /* We need to set this new one up */
131
132                 list<StringText> to_show = _current->text.string;
133
134                 for (int j = 0; j < CLOSED_CAPTION_LINES; ++j) {
135                         _lines[j] = "";
136                 }
137
138                 to_show.sort (ClosedCaptionSorter());
139
140                 list<StringText>::const_iterator j = to_show.begin();
141                 int k = 0;
142                 while (j != to_show.end() && k < CLOSED_CAPTION_LINES) {
143                         _lines[k] = j->text();
144                         ++j;
145                         ++k;
146                 }
147
148                 Refresh ();
149                 _current_in_lines = true;
150         }
151 }
152
153 void
154 ClosedCaptionsDialog::clear ()
155 {
156         _current = optional<TextRingBuffers::Data>();
157         _current_in_lines = false;
158         Refresh ();
159 }
160
161 void
162 ClosedCaptionsDialog::set_butler (weak_ptr<Butler> butler)
163 {
164         _butler = butler;
165 }