Hide some more warnings.
[dcpomatic.git] / src / wx / markers_panel.cc
1 /*
2     Copyright (C) 2021-2022 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 "film_viewer.h"
23 #include "markers.h"
24 #include "markers_panel.h"
25 #include "wx_util.h"
26 #include <dcp/warnings.h>
27 LIBDCP_DISABLE_WARNINGS
28 #include <wx/graphics.h>
29 #include <wx/tipwin.h>
30 LIBDCP_ENABLE_WARNINGS
31 #include <boost/bind/bind.hpp>
32 #include <boost/version.hpp>
33
34
35 using std::shared_ptr;
36 using std::weak_ptr;
37 #if BOOST_VERSION >= 106100
38 using namespace boost::placeholders;
39 #endif
40
41
42 enum {
43         ID_move_marker_to_current_position,
44         ID_remove_marker,
45         ID_add_marker,
46         /* Leave some space after this one as we use an ID for each marker type
47          * starting with ID_add_base.
48          */
49         ID_add_base,
50 };
51
52
53 static constexpr auto line_to_label_gap = 2;
54
55
56 MarkersPanel::MarkersPanel (wxWindow* parent, weak_ptr<FilmViewer> viewer)
57         : wxPanel (parent, wxID_ANY, wxDefaultPosition, wxSize(-1, 16))
58         , _viewer (viewer)
59 {
60         Bind (wxEVT_PAINT, boost::bind(&MarkersPanel::paint, this));
61         Bind (wxEVT_MOTION, boost::bind(&MarkersPanel::mouse_moved, this, _1));
62
63         Bind (wxEVT_LEFT_DOWN, boost::bind(&MarkersPanel::mouse_left_down, this));
64         Bind (wxEVT_RIGHT_DOWN, boost::bind(&MarkersPanel::mouse_right_down, this, _1));
65
66         Bind (wxEVT_MENU, boost::bind(&MarkersPanel::move_marker_to_current_position, this), ID_move_marker_to_current_position);
67         Bind (wxEVT_MENU, boost::bind(&MarkersPanel::remove_marker, this), ID_remove_marker);
68         Bind (wxEVT_MENU, boost::bind(&MarkersPanel::add_marker, this, _1), ID_add_base, ID_add_base + all_markers().size());
69 }
70
71
72 void
73 MarkersPanel::set_film (weak_ptr<Film> weak_film)
74 {
75         _film = weak_film;
76         auto film = weak_film.lock ();
77         if (film) {
78                 film->Change.connect (boost::bind(&MarkersPanel::film_changed, this, _1, _2));
79                 update_from_film (film);
80         }
81 }
82
83
84 void
85 MarkersPanel::film_changed (ChangeType type, Film::Property property)
86 {
87         if (type != ChangeType::DONE) {
88                 return;
89         }
90
91         auto film = _film.lock();
92         if (!film) {
93                 return;
94         }
95
96         if (property == Film::Property::MARKERS || property == Film::Property::CONTENT || property == Film::Property::CONTENT_ORDER || property == Film::Property::VIDEO_FRAME_RATE) {
97                 update_from_film (film);
98         }
99 }
100
101
102 void
103 MarkersPanel::update_from_film (shared_ptr<Film> film)
104 {
105         _markers.clear ();
106         for (auto const& marker: film->markers()) {
107                 _markers[marker.first] = Marker(
108                         marker.second,
109                         marker.first == dcp::Marker::FFOC ||
110                         marker.first == dcp::Marker::FFTC ||
111                         marker.first == dcp::Marker::FFOI ||
112                         marker.first == dcp::Marker::FFEC ||
113                         marker.first == dcp::Marker::FFMC
114                         );
115
116         }
117         Refresh ();
118 }
119
120
121 int
122 MarkersPanel::position (dcpomatic::DCPTime time, int width) const
123 {
124 #ifdef DCPOMATIC_LINUX
125         /* Number of pixels between the left/right bounding box edge of a wxSlider
126          * and the start of the "track".
127          */
128         auto constexpr end_gap = 12;
129 #else
130         auto constexpr end_gap = 0;
131 #endif
132         auto film = _film.lock ();
133         if (!film) {
134                 return 0;
135         }
136
137         return end_gap + time.get() * (width - end_gap * 2) / film->length().get();
138 }
139
140
141 void
142 MarkersPanel::mouse_moved (wxMouseEvent& ev)
143 {
144         _over = boost::none;
145
146         auto film = _film.lock ();
147         if (!film) {
148                 return;
149         }
150
151         auto const panel_width = GetSize().GetWidth();
152 #if !defined(DCPOMATIC_LINUX)
153         auto const panel_height = GetSize().GetHeight();
154         auto const factor = GetContentScaleFactor();
155 #endif
156
157         auto const x = ev.GetPosition().x;
158         for (auto const& marker: _markers) {
159                 auto const pos = position(marker.second.time, panel_width);
160                 auto const width = marker.second.width ? marker.second.width : 4;
161                 auto const x1 = marker.second.line_before_label ? pos : pos - width - line_to_label_gap;
162                 auto const x2 = marker.second.line_before_label ? pos + width + line_to_label_gap : pos;
163                 if (x1 <= x && x < x2) {
164                         _over = marker.first;
165 /* Tooltips flicker really badly on Wayland for some reason, so only do this on Windows/macOS for now */
166 #if !defined(DCPOMATIC_LINUX)
167                         if (!_tip) {
168                                 auto mouse = ClientToScreen (ev.GetPosition());
169                                 auto rect = wxRect(mouse.x, mouse.y, width * factor, panel_height * factor);
170                                 auto hmsf = marker.second.time.split(film->video_frame_rate());
171                                 char timecode_buffer[64];
172                                 snprintf (timecode_buffer, sizeof(timecode_buffer), " %02d:%02d:%02d:%02d", hmsf.h, hmsf.m, hmsf.s, hmsf.f);
173                                 auto tip_text = dcp::marker_to_string(marker.first) + std::string(timecode_buffer);
174                                 _tip = new wxTipWindow (this, std_to_wx(tip_text), 100, &_tip, &rect);
175                         }
176 #endif
177                 }
178         }
179 }
180
181
182 void
183 MarkersPanel::paint ()
184 {
185         wxPaintDC dc (this);
186
187         std::unique_ptr<wxGraphicsContext> gc(wxGraphicsContext::Create(dc));
188         if (!gc) {
189                 return;
190         }
191
192         gc->SetAntialiasMode (wxANTIALIAS_DEFAULT);
193         gc->SetPen (wxPen(wxColour(200, 0, 0)));
194         gc->SetFont (gc->CreateFont(*wxSMALL_FONT, wxColour(200, 0, 0)));
195
196         auto const panel_width = GetSize().GetWidth();
197         auto const panel_height = GetSize().GetHeight();
198
199         for (auto& marker: _markers) {
200                 auto label = std_to_wx(dcp::marker_to_string(marker.first));
201                 if (marker.second.width == 0) {
202                         /* We don't know the width of this marker label yet, so calculate it now */
203                         wxDouble width, height, descent, external_leading;
204                         gc->GetTextExtent (label, &width, &height, &descent, &external_leading);
205                         marker.second.width = width;
206                 }
207                 auto line = gc->CreatePath ();
208                 auto const pos = position(marker.second.time, panel_width);
209                 line.MoveToPoint (pos, 0);
210                 line.AddLineToPoint (pos, panel_height);
211                 gc->StrokePath (line);
212                 if (marker.second.line_before_label) {
213                         gc->DrawText (label, pos + line_to_label_gap, 0);
214                 } else {
215                         gc->DrawText (label, pos - line_to_label_gap - marker.second.width, 0);
216                 }
217         }
218 }
219
220
221 void
222 MarkersPanel::mouse_left_down ()
223 {
224         if (_over) {
225                 auto viewer = _viewer.lock ();
226                 DCPOMATIC_ASSERT (viewer);
227                 viewer->seek (_markers[*_over].time, true);
228         }
229 }
230
231
232 void
233 MarkersPanel::mouse_right_down (wxMouseEvent& ev)
234 {
235         wxMenu menu;
236         if (_over) {
237                 menu.Append (ID_move_marker_to_current_position, wxString::Format(_("Move %s marker to current position"), wx_to_std(dcp::marker_to_string(*_over))));
238                 menu.Append (ID_remove_marker, wxString::Format(_("Remove %s marker"), wx_to_std(dcp::marker_to_string(*_over))));
239         }
240
241         auto add_marker = new wxMenu ();
242         for (auto const& marker: all_markers()) {
243                 add_marker->Append (static_cast<int>(ID_add_base) + static_cast<int>(marker.second), marker.first);
244         }
245         menu.Append (ID_add_marker, _("Add or move marker to current position"), add_marker);
246
247         _menu_marker = _over;
248         PopupMenu (&menu, ev.GetPosition());
249 }
250
251
252 void
253 MarkersPanel::move_marker_to_current_position ()
254 {
255         auto film = _film.lock ();
256         auto viewer = _viewer.lock ();
257         if (!film || !viewer || !_menu_marker) {
258                 return;
259         }
260
261         film->set_marker (*_menu_marker, viewer->position());
262 }
263
264
265 void
266 MarkersPanel::remove_marker ()
267 {
268         auto film = _film.lock ();
269         auto viewer = _viewer.lock ();
270         if (!film || !viewer || !_menu_marker) {
271                 return;
272         }
273
274         film->unset_marker (*_menu_marker);
275 }
276
277
278 void
279 MarkersPanel::add_marker (wxCommandEvent& ev)
280 {
281         auto film = _film.lock ();
282         auto viewer = _viewer.lock ();
283         if (!film || !viewer) {
284                 return;
285         }
286
287         auto marker = static_cast<dcp::Marker>(ev.GetId() - ID_add_base);
288         film->set_marker (marker, viewer->position());
289 }
290