2 Copyright (C) 2021-2022 Carl Hetherington <cth@carlh.net>
4 This file is part of DCP-o-matic.
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.
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.
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/>.
22 #include "film_viewer.h"
24 #include "markers_panel.h"
27 #include <dcp/warnings.h>
28 LIBDCP_DISABLE_WARNINGS
29 #include <wx/graphics.h>
30 #include <wx/tipwin.h>
31 LIBDCP_ENABLE_WARNINGS
32 #include <boost/bind/bind.hpp>
33 #include <boost/version.hpp>
36 using std::shared_ptr;
38 #if BOOST_VERSION >= 106100
39 using namespace boost::placeholders;
44 ID_move_marker_to_current_position,
47 /* Leave some space after this one as we use an ID for each marker type
48 * starting with ID_add_base.
54 static constexpr auto line_to_label_gap = 2;
57 MarkersPanel::MarkersPanel(wxWindow* parent, FilmViewer& viewer)
58 : wxPanel (parent, wxID_ANY, wxDefaultPosition, wxSize(-1, 16))
61 Bind (wxEVT_PAINT, boost::bind(&MarkersPanel::paint, this));
62 Bind (wxEVT_MOTION, boost::bind(&MarkersPanel::mouse_moved, this, _1));
64 Bind (wxEVT_LEFT_DOWN, boost::bind(&MarkersPanel::mouse_left_down, this));
65 Bind (wxEVT_RIGHT_DOWN, boost::bind(&MarkersPanel::mouse_right_down, this, _1));
67 Bind (wxEVT_MENU, boost::bind(&MarkersPanel::move_marker_to_current_position, this), ID_move_marker_to_current_position);
68 Bind (wxEVT_MENU, boost::bind(&MarkersPanel::remove_marker, this), ID_remove_marker);
69 Bind (wxEVT_MENU, boost::bind(&MarkersPanel::add_marker, this, _1), ID_add_base, ID_add_base + all_editable_markers().size());
74 MarkersPanel::set_film (weak_ptr<Film> weak_film)
77 auto film = weak_film.lock ();
79 film->Change.connect (boost::bind(&MarkersPanel::film_changed, this, _1, _2));
80 update_from_film (film);
86 MarkersPanel::film_changed(ChangeType type, FilmProperty property)
88 if (type != ChangeType::DONE) {
92 auto film = _film.lock();
97 if (property == FilmProperty::MARKERS || property == FilmProperty::CONTENT || property == FilmProperty::CONTENT_ORDER || property == FilmProperty::VIDEO_FRAME_RATE) {
98 update_from_film (film);
104 MarkersPanel::update_from_film (shared_ptr<Film> film)
107 for (auto const& marker: film->markers()) {
108 _markers[marker.first] = Marker(
110 marker.first == dcp::Marker::FFOC ||
111 marker.first == dcp::Marker::FFTC ||
112 marker.first == dcp::Marker::FFOI ||
113 marker.first == dcp::Marker::FFEC ||
114 marker.first == dcp::Marker::FFMC
123 MarkersPanel::position (dcpomatic::DCPTime time, int width) const
125 #ifdef DCPOMATIC_LINUX
126 /* Number of pixels between the left/right bounding box edge of a wxSlider
127 * and the start of the "track".
129 auto constexpr end_gap = 12;
131 auto constexpr end_gap = 0;
133 auto film = _film.lock ();
138 return end_gap + time.get() * (width - end_gap * 2) / film->length().get();
143 MarkersPanel::mouse_moved (wxMouseEvent& ev)
147 auto film = _film.lock ();
152 auto const panel_width = GetSize().GetWidth();
153 #if !defined(DCPOMATIC_LINUX)
154 auto const panel_height = GetSize().GetHeight();
155 auto const factor = GetContentScaleFactor();
158 auto const x = ev.GetPosition().x;
159 for (auto const& marker: _markers) {
160 auto const pos = position(marker.second.time, panel_width);
161 auto const width = marker.second.width ? marker.second.width : 4;
162 auto const x1 = marker.second.line_before_label ? pos : pos - width - line_to_label_gap;
163 auto const x2 = marker.second.line_before_label ? pos + width + line_to_label_gap : pos;
164 if (x1 <= x && x < x2) {
165 _over = marker.first;
166 /* Tooltips flicker really badly on Wayland for some reason, so only do this on Windows/macOS for now */
167 #if !defined(DCPOMATIC_LINUX)
169 auto mouse = ClientToScreen (ev.GetPosition());
170 auto rect = wxRect(mouse.x, mouse.y, width * factor, panel_height * factor);
171 auto hmsf = marker.second.time.split(film->video_frame_rate());
172 char timecode_buffer[64];
173 snprintf (timecode_buffer, sizeof(timecode_buffer), " %02d:%02d:%02d:%02d", hmsf.h, hmsf.m, hmsf.s, hmsf.f);
174 auto tip_text = dcp::marker_to_string(marker.first) + std::string(timecode_buffer);
175 _tip = new wxTipWindow (this, std_to_wx(tip_text), 100, &_tip, &rect);
184 MarkersPanel::paint ()
188 std::unique_ptr<wxGraphicsContext> gc(wxGraphicsContext::Create(dc));
193 gc->SetAntialiasMode (wxANTIALIAS_DEFAULT);
194 gc->SetPen (wxPen(wxColour(200, 0, 0)));
195 gc->SetFont (gc->CreateFont(*wxSMALL_FONT, wxColour(200, 0, 0)));
197 auto const panel_width = GetSize().GetWidth();
198 auto const panel_height = GetSize().GetHeight();
200 for (auto& marker: _markers) {
201 auto label = std_to_wx(dcp::marker_to_string(marker.first));
202 if (marker.second.width == 0) {
203 /* We don't know the width of this marker label yet, so calculate it now */
204 wxDouble width, height, descent, external_leading;
205 gc->GetTextExtent (label, &width, &height, &descent, &external_leading);
206 marker.second.width = width;
208 auto line = gc->CreatePath ();
209 auto const pos = position(marker.second.time, panel_width);
210 line.MoveToPoint (pos, 0);
211 line.AddLineToPoint (pos, panel_height);
212 gc->StrokePath (line);
213 if (marker.second.line_before_label) {
214 gc->DrawText (label, pos + line_to_label_gap, 0);
216 gc->DrawText (label, pos - line_to_label_gap - marker.second.width, 0);
223 MarkersPanel::mouse_left_down ()
226 _viewer.seek(_markers[*_over].time, true);
232 MarkersPanel::mouse_right_down (wxMouseEvent& ev)
236 menu.Append (ID_move_marker_to_current_position, wxString::Format(_("Move %s marker to current position"), wx_to_std(dcp::marker_to_string(*_over))));
237 menu.Append (ID_remove_marker, wxString::Format(_("Remove %s marker"), wx_to_std(dcp::marker_to_string(*_over))));
240 auto add_marker = new wxMenu ();
241 for (auto const& marker: all_editable_markers()) {
242 add_marker->Append (static_cast<int>(ID_add_base) + static_cast<int>(marker.second), marker.first);
244 menu.Append (ID_add_marker, _("Add or move marker to current position"), add_marker);
246 _menu_marker = _over;
247 PopupMenu (&menu, ev.GetPosition());
252 MarkersPanel::move_marker_to_current_position ()
254 auto film = _film.lock ();
255 if (!film || !_menu_marker) {
259 film->set_marker(*_menu_marker, _viewer.position());
264 MarkersPanel::remove_marker ()
266 auto film = _film.lock ();
267 if (!film || !_menu_marker) {
271 film->unset_marker(*_menu_marker);
276 MarkersPanel::add_marker (wxCommandEvent& ev)
278 auto film = _film.lock ();
283 auto marker = static_cast<dcp::Marker>(ev.GetId() - ID_add_base);
284 film->set_marker(marker, _viewer.position());