From d394f2a171235fcbd5bdaf07c3b9b91529368538 Mon Sep 17 00:00:00 2001 From: Carl Hetherington Date: Mon, 10 Jan 2022 12:53:28 +0100 Subject: [PATCH] Basic display of markers above the playback timeline (#1921). --- src/wx/controls.cc | 11 +- src/wx/controls.h | 3 + src/wx/markers_panel.cc | 287 ++++++++++++++++++++++++++++++++++++++++ src/wx/markers_panel.h | 72 ++++++++++ src/wx/wscript | 1 + 5 files changed, 373 insertions(+), 1 deletion(-) create mode 100644 src/wx/markers_panel.cc create mode 100644 src/wx/markers_panel.h diff --git a/src/wx/controls.cc b/src/wx/controls.cc index ba0691268..a6f70c039 100644 --- a/src/wx/controls.cc +++ b/src/wx/controls.cc @@ -24,6 +24,7 @@ #include "controls.h" #include "dcpomatic_button.h" #include "film_viewer.h" +#include "markers_panel.h" #include "playhead_to_frame_dialog.h" #include "playhead_to_timecode_dialog.h" #include "static_text.h" @@ -65,6 +66,7 @@ using namespace dcpomatic; Controls::Controls (wxWindow* parent, shared_ptr viewer, bool editor_controls) : wxPanel (parent) + , _markers (new MarkersPanel(this, viewer)) , _slider (new wxSlider(this, wxID_ANY, 0, 0, 4096)) , _viewer (viewer) , _slider_being_moved (false) @@ -110,7 +112,12 @@ Controls::Controls (wxWindow* parent, shared_ptr viewer, bool editor _button_sizer = new wxBoxSizer (wxHORIZONTAL); h_sizer->Add (_button_sizer, 0, wxEXPAND); - h_sizer->Add (_slider, 1, wxEXPAND); + { + auto box = new wxBoxSizer (wxVERTICAL); + box->Add (_markers, 0, wxEXPAND); + box->Add (_slider, 0, wxEXPAND); + h_sizer->Add (box, 1, wxEXPAND); + } _v_sizer->Add (h_sizer, 0, wxEXPAND | wxALL, 6); @@ -469,6 +476,8 @@ Controls::set_film (shared_ptr film) _film = film; + _markers->set_film (_film); + if (_film) { _film_change_connection = _film->Change.connect (boost::bind(&Controls::film_change, this, _1, _2)); } diff --git a/src/wx/controls.h b/src/wx/controls.h index 0341da290..5f7dc387a 100644 --- a/src/wx/controls.h +++ b/src/wx/controls.h @@ -38,7 +38,9 @@ class Content; class ContentView; class Film; class FilmViewer; +class MarkersPanel; class PlayerVideo; + class wxListCtrl; class wxToggleButton; @@ -77,6 +79,7 @@ protected: wxSizer* _v_sizer; wxBoxSizer* _button_sizer; std::shared_ptr _film; + MarkersPanel* _markers; wxSlider* _slider; std::weak_ptr _viewer; boost::optional _active_job; diff --git a/src/wx/markers_panel.cc b/src/wx/markers_panel.cc new file mode 100644 index 000000000..1219da3f5 --- /dev/null +++ b/src/wx/markers_panel.cc @@ -0,0 +1,287 @@ +/* + Copyright (C) 2021-2022 Carl Hetherington + + This file is part of DCP-o-matic. + + DCP-o-matic is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + DCP-o-matic is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with DCP-o-matic. If not, see . + +*/ + + +#include "film_viewer.h" +#include "markers.h" +#include "markers_panel.h" +#include "wx_util.h" +#include +#include +#include +#include + + +using std::shared_ptr; +using std::weak_ptr; +#if BOOST_VERSION >= 106100 +using namespace boost::placeholders; +#endif + + +enum { + ID_move_marker_to_current_position, + ID_remove_marker, + ID_add_marker, + /* Leave some space after this one as we use an ID for each marker type + * starting with ID_add_base. + */ + ID_add_base, +}; + + +static constexpr auto line_to_label_gap = 2; + + +MarkersPanel::MarkersPanel (wxWindow* parent, weak_ptr viewer) + : wxPanel (parent, wxID_ANY, wxDefaultPosition, wxSize(-1, 16)) + , _viewer (viewer) +{ + Bind (wxEVT_PAINT, boost::bind(&MarkersPanel::paint, this)); + Bind (wxEVT_MOTION, boost::bind(&MarkersPanel::mouse_moved, this, _1)); + + Bind (wxEVT_LEFT_DOWN, boost::bind(&MarkersPanel::mouse_left_down, this)); + Bind (wxEVT_RIGHT_DOWN, boost::bind(&MarkersPanel::mouse_right_down, this, _1)); + + Bind (wxEVT_MENU, boost::bind(&MarkersPanel::move_marker_to_current_position, this), ID_move_marker_to_current_position); + Bind (wxEVT_MENU, boost::bind(&MarkersPanel::remove_marker, this), ID_remove_marker); + Bind (wxEVT_MENU, boost::bind(&MarkersPanel::add_marker, this, _1), ID_add_base, ID_add_base + all_markers().size()); +} + + +void +MarkersPanel::set_film (weak_ptr weak_film) +{ + _film = weak_film; + auto film = weak_film.lock (); + if (film) { + film->Change.connect (boost::bind(&MarkersPanel::film_changed, this, _1, _2)); + update_from_film (film); + } +} + + +void +MarkersPanel::film_changed (ChangeType type, Film::Property property) +{ + if (type != ChangeType::DONE) { + return; + } + + auto film = _film.lock(); + if (!film) { + return; + } + + if (property == Film::Property::MARKERS || property == Film::Property::CONTENT || property == Film::Property::CONTENT_ORDER || property == Film::Property::VIDEO_FRAME_RATE) { + update_from_film (film); + } +} + + +void +MarkersPanel::update_from_film (shared_ptr film) +{ + _markers.clear (); + for (auto const& marker: film->markers()) { + _markers[marker.first] = Marker( + marker.second, + marker.first == dcp::Marker::FFOC || + marker.first == dcp::Marker::FFTC || + marker.first == dcp::Marker::FFOI || + marker.first == dcp::Marker::FFEC || + marker.first == dcp::Marker::FFMC + ); + + } + Refresh (); +} + + +int +MarkersPanel::position (dcpomatic::DCPTime time, int width) const +{ +#ifdef DCPOMATIC_LINUX + /* Number of pixels between the left/right bounding box edge of a wxSlider + * and the start of the "track". + */ + auto constexpr end_gap = 12; +#else + auto constexpr end_gap = 0; +#endif + auto film = _film.lock (); + if (!film) { + return 0; + } + + return end_gap + time.get() * (width - end_gap * 2) / film->length().get(); +} + + +void +MarkersPanel::mouse_moved (wxMouseEvent& ev) +{ + _over = boost::none; + + auto film = _film.lock (); + if (!film) { + return; + } + + auto const panel_width = GetSize().GetWidth(); +#if !defined(DCPOMATIC_LINUX) + auto const panel_height = GetSize().GetHeight(); + auto const factor = GetContentScaleFactor(); +#endif + + auto const x = ev.GetPosition().x; + for (auto const& marker: _markers) { + auto const pos = position(marker.second.time, panel_width); + auto const width = marker.second.width ? marker.second.width : 4; + auto const x1 = marker.second.line_before_label ? pos : pos - width - line_to_label_gap; + auto const x2 = marker.second.line_before_label ? pos + width + line_to_label_gap : pos; + if (x1 <= x && x < x2) { + _over = marker.first; +/* Tooltips flicker really badly on Wayland for some reason, so only do this on Windows/macOS for now */ +#if !defined(DCPOMATIC_LINUX) + if (!_tip) { + auto mouse = ClientToScreen (ev.GetPosition()); + auto rect = wxRect(mouse.x, mouse.y, width * factor, panel_height * factor); + auto hmsf = marker.second.time.split(film->video_frame_rate()); + char timecode_buffer[64]; + snprintf (timecode_buffer, sizeof(timecode_buffer), " %02d:%02d:%02d:%02d", hmsf.h, hmsf.m, hmsf.s, hmsf.f); + auto tip_text = dcp::marker_to_string(marker.first) + std::string(timecode_buffer); + _tip = new wxTipWindow (this, std_to_wx(tip_text), 100, &_tip, &rect); + } +#endif + } + } +} + + +void +MarkersPanel::paint () +{ + wxPaintDC dc (this); + + std::unique_ptr gc(wxGraphicsContext::Create(dc)); + if (!gc) { + return; + } + + gc->SetAntialiasMode (wxANTIALIAS_DEFAULT); + gc->SetPen (wxPen(wxColour(200, 0, 0))); + gc->SetFont (gc->CreateFont(*wxSMALL_FONT, wxColour(200, 0, 0))); + + auto const panel_width = GetSize().GetWidth(); + auto const panel_height = GetSize().GetHeight(); + + for (auto& marker: _markers) { + auto label = std_to_wx(dcp::marker_to_string(marker.first)); + if (marker.second.width == 0) { + /* We don't know the width of this marker label yet, so calculate it now */ + wxDouble width, height, descent, external_leading; + gc->GetTextExtent (label, &width, &height, &descent, &external_leading); + marker.second.width = width; + } + auto line = gc->CreatePath (); + auto const pos = position(marker.second.time, panel_width); + line.MoveToPoint (pos, 0); + line.AddLineToPoint (pos, panel_height); + gc->StrokePath (line); + if (marker.second.line_before_label) { + gc->DrawText (label, pos + line_to_label_gap, 0); + } else { + gc->DrawText (label, pos - line_to_label_gap - marker.second.width, 0); + } + } +} + + +void +MarkersPanel::mouse_left_down () +{ + if (_over) { + auto viewer = _viewer.lock (); + DCPOMATIC_ASSERT (viewer); + viewer->seek (_markers[*_over].time, true); + } +} + + +void +MarkersPanel::mouse_right_down (wxMouseEvent& ev) +{ + wxMenu menu; + if (_over) { + menu.Append (ID_move_marker_to_current_position, wxString::Format(_("Move %s marker to current position"), wx_to_std(dcp::marker_to_string(*_over)))); + menu.Append (ID_remove_marker, wxString::Format(_("Remove %s marker"), wx_to_std(dcp::marker_to_string(*_over)))); + } + + auto add_marker = new wxMenu (); + for (auto const& marker: all_markers()) { + add_marker->Append (static_cast(ID_add_base) + static_cast(marker.second), marker.first); + } + menu.Append (ID_add_marker, _("Add or move marker to current position"), add_marker); + + _menu_marker = _over; + PopupMenu (&menu, ev.GetPosition()); +} + + +void +MarkersPanel::move_marker_to_current_position () +{ + auto film = _film.lock (); + auto viewer = _viewer.lock (); + if (!film || !viewer || !_menu_marker) { + return; + } + + film->set_marker (*_menu_marker, viewer->position()); +} + + +void +MarkersPanel::remove_marker () +{ + auto film = _film.lock (); + auto viewer = _viewer.lock (); + if (!film || !viewer || !_menu_marker) { + return; + } + + film->unset_marker (*_menu_marker); +} + + +void +MarkersPanel::add_marker (wxCommandEvent& ev) +{ + auto film = _film.lock (); + auto viewer = _viewer.lock (); + if (!film || !viewer) { + return; + } + + auto marker = static_cast(ev.GetId() - ID_add_base); + film->set_marker (marker, viewer->position()); +} + diff --git a/src/wx/markers_panel.h b/src/wx/markers_panel.h new file mode 100644 index 000000000..b88efa9a0 --- /dev/null +++ b/src/wx/markers_panel.h @@ -0,0 +1,72 @@ +/* + Copyright (C) 2021 Carl Hetherington + + This file is part of DCP-o-matic. + + DCP-o-matic is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + DCP-o-matic is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with DCP-o-matic. If not, see . + +*/ + + +#include "lib/dcpomatic_time.h" +#include "lib/film.h" +#include +#include + + +class wxTipWindow; + + +class MarkersPanel : public wxPanel +{ +public: + MarkersPanel (wxWindow* parent, std::weak_ptr viewer); + + void set_film (std::weak_ptr film); + +private: + void paint (); + void mouse_moved (wxMouseEvent& ev); + void mouse_left_down (); + void mouse_right_down (wxMouseEvent& ev); + int position (dcpomatic::DCPTime time, int width) const; + void move_marker_to_current_position (); + void remove_marker (); + void add_marker (wxCommandEvent& ev); + void film_changed (ChangeType type, Film::Property property); + void update_from_film (std::shared_ptr film); + + wxTipWindow* _tip = nullptr; + + class Marker { + public: + Marker () {} + + Marker (dcpomatic::DCPTime t, bool b) + : time (t) + , line_before_label (b) + {} + + dcpomatic::DCPTime time; + int width = 0; + bool line_before_label = false; + }; + + std::weak_ptr _film; + std::map _markers; + boost::optional _over; + std::weak_ptr _viewer; + boost::optional _menu_marker; +}; + diff --git a/src/wx/wscript b/src/wx/wscript index d2b4e58e2..276cfc6f0 100644 --- a/src/wx/wscript +++ b/src/wx/wscript @@ -99,6 +99,7 @@ sources = """ make_chain_dialog.cc markers.cc markers_dialog.cc + markers_panel.cc message_dialog.cc metadata_dialog.cc move_to_dialog.cc -- 2.30.2