summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/lib/layout_markers.cc158
-rw-r--r--src/lib/layout_markers.h93
-rw-r--r--src/lib/wscript1
-rw-r--r--src/wx/markers.cc20
-rw-r--r--src/wx/markers_panel.cc189
-rw-r--r--src/wx/markers_panel.h25
-rw-r--r--test/layout_markers_test.cc154
-rw-r--r--test/wscript1
8 files changed, 532 insertions, 109 deletions
diff --git a/src/lib/layout_markers.cc b/src/lib/layout_markers.cc
new file mode 100644
index 000000000..7b16b07f1
--- /dev/null
+++ b/src/lib/layout_markers.cc
@@ -0,0 +1,158 @@
+/*
+ Copyright (C) 2025 Carl Hetherington <cth@carlh.net>
+
+ 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 <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "dcpomatic_time.h"
+#include "layout_markers.h"
+#include <dcp/types.h>
+#include <functional>
+#include <iostream>
+#include <map>
+#include <string>
+#include <vector>
+
+#include "i18n.h"
+
+
+using std::function;
+using std::map;
+using std::max;
+using std::min;
+using std::pair;
+using std::string;
+using std::vector;
+using namespace std::placeholders;
+
+
+vector<MarkerLayoutComponent>
+layout_markers(
+ map<dcp::Marker, dcpomatic::DCPTime> const& markers,
+ int width_in_pixels,
+ dcpomatic::DCPTime width_in_time,
+ int label_to_end_gap,
+ int outside_label_gap,
+ function<int (string)> text_width
+)
+{
+ float const pixels_per_unit_time = static_cast<float>(width_in_pixels) / width_in_time.get();
+ auto pixels_between = [&](dcpomatic::DCPTime t1, dcpomatic::DCPTime t2) {
+ return pixels_per_unit_time * t2.get() - pixels_per_unit_time * t1.get();
+ };
+
+ vector<AllocationRow> allocations;
+
+ auto allocate = [&](int x1, int x2) -> int {
+ int index = 0;
+ for (auto& row: allocations) {
+ if (row.allocate(x1, x2)) {
+ return index;
+ }
+ ++index;
+ }
+
+ auto row = AllocationRow();
+ row.allocate(x1, x2);
+ allocations.push_back(row);
+ return index;
+ };
+
+ vector<MarkerLayoutComponent> components;
+ auto layout_pair = [&](string name, pair<dcp::Marker, dcpomatic::DCPTime> start, pair<dcp::Marker, dcpomatic::DCPTime> end) {
+ auto const width = text_width(name);
+ int const x1 = floor(pixels_per_unit_time * start.second.get());
+ int const x2 = ceil(pixels_per_unit_time * end.second.get());
+
+ int label_x = 0;
+ int y = 0;
+
+ if (pixels_between(start.second, end.second) <= text_width(name)) {
+ if (x1 > width_in_pixels / 2) {
+ label_x = x1 - outside_label_gap - width;
+ y = allocate(x1 - outside_label_gap - width, x2);
+ } else {
+ label_x = x2 + outside_label_gap;
+ y = allocate(x1, x2 + outside_label_gap + width);
+ }
+ } else {
+ label_x = (x1 + x2 - width) / 2;
+ y = allocate(x1, x2);
+ }
+
+ components.push_back(MarkerLayoutComponent{MarkerLayoutComponent::Type::LEFT, x1, y, start.first, start.second});
+ components.push_back(MarkerLayoutComponent{MarkerLayoutComponent::Type::RIGHT, x2, y, end.first, end.second});
+ components.push_back(MarkerLayoutComponent{MarkerLayoutComponent::Type::LABEL, label_x, width, y, name});
+ components.push_back(MarkerLayoutComponent{MarkerLayoutComponent::Type::LINE, x1, x2, y});
+ };
+
+ auto layout_left = [&](string name, pair<dcp::Marker, dcpomatic::DCPTime> marker) {
+ int const x1 = floor(pixels_per_unit_time * marker.second.get());
+ auto const width = text_width(name);
+ auto const y = allocate(x1, x1 + label_to_end_gap + width);
+ components.push_back(MarkerLayoutComponent{MarkerLayoutComponent::Type::LEFT, x1, y, marker.first, marker.second});
+ components.push_back(MarkerLayoutComponent{MarkerLayoutComponent::Type::LABEL, x1 + label_to_end_gap, width, y, name});
+ components.push_back(MarkerLayoutComponent{MarkerLayoutComponent::Type::LINE, x1, x1 + label_to_end_gap, y});
+ };
+
+ auto layout_right = [&](string name, pair<dcp::Marker, dcpomatic::DCPTime> marker) {
+ int const x2 = floor(pixels_per_unit_time * marker.second.get());
+ auto const width = text_width(name);
+ auto const y = allocate(x2 - label_to_end_gap - width, x2);
+ components.push_back(MarkerLayoutComponent{MarkerLayoutComponent::Type::RIGHT, x2, y, marker.first, marker.second});
+ components.push_back(MarkerLayoutComponent{MarkerLayoutComponent::Type::LABEL, x2 - label_to_end_gap - width, width, y, name});
+ components.push_back(MarkerLayoutComponent{MarkerLayoutComponent::Type::LINE, x2 - label_to_end_gap, x2, y});
+ };
+
+ auto check_pair = [&](string name, dcp::Marker a, dcp::Marker b) {
+ auto fa = markers.find(a);
+ auto fb = markers.find(b);
+ if (fa != markers.end() && fb != markers.end()) {
+ layout_pair(name, *fa, *fb);
+ } else if (fa != markers.end()) {
+ layout_left(name, *fa);
+ } else if (fb != markers.end()) {
+ layout_right(name, *fb);
+ }
+ };
+
+ check_pair(_("RB"), dcp::Marker::FFOB, dcp::Marker::LFOB);
+ check_pair(_("TC"), dcp::Marker::FFTC, dcp::Marker::LFTC);
+ check_pair(_("IN"), dcp::Marker::FFOI, dcp::Marker::LFOI);
+ check_pair(_("EC"), dcp::Marker::FFEC, dcp::Marker::LFEC);
+ check_pair(_("MC"), dcp::Marker::FFMC, dcp::Marker::LFMC);
+
+ return components;
+}
+
+
+bool
+AllocationRow::allocate(int x1, int x2)
+{
+ auto overlaps = [](pair<int, int> a, pair<int, int> b) {
+ return max(a.first, b.first) <= min(a.second, b.second);
+ };
+
+ if (std::none_of(_allocated.begin(), _allocated.end(), std::bind(overlaps, pair{x1, x2}, _1))) {
+ _allocated.push_back({x1, x2});
+ return true;
+ }
+
+ return false;
+}
+
diff --git a/src/lib/layout_markers.h b/src/lib/layout_markers.h
new file mode 100644
index 000000000..5c615f20e
--- /dev/null
+++ b/src/lib/layout_markers.h
@@ -0,0 +1,93 @@
+/*
+ Copyright (C) 2025 Carl Hetherington <cth@carlh.net>
+
+ 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 <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "dcpomatic_time.h"
+#include <dcp/types.h>
+#include <string>
+#include <utility>
+#include <vector>
+
+
+class MarkerLayoutComponent
+{
+public:
+ enum class Type {
+ LEFT,
+ RIGHT,
+ LINE,
+ LABEL
+ };
+
+ MarkerLayoutComponent(Type type_, int x1_, int y_, dcp::Marker marker_, dcpomatic::DCPTime t1_)
+ : type(type_)
+ , x1(x1_)
+ , y(y_)
+ , marker(marker_)
+ , t1(t1_)
+ {}
+
+ MarkerLayoutComponent(Type type_, int x1_, int x2_, int y_)
+ : type(type_)
+ , x1(x1_)
+ , x2(x2_)
+ , y(y_)
+ {}
+
+ MarkerLayoutComponent(Type type_, int x1_, int width, int y_, std::string text_)
+ : type(type_)
+ , x1(x1_)
+ , x2(x1_ + width)
+ , y(y_)
+ , text(text_)
+ {}
+
+ Type type = Type::LINE;
+ int x1 = 0;
+ int x2 = 0;
+ int y = 0;
+ boost::optional<dcp::Marker> marker;
+ dcpomatic::DCPTime t1;
+ std::string text;
+};
+
+
+class AllocationRow
+{
+public:
+ AllocationRow() = default;
+
+ /** @return true if allocation succeded, otherwise false */
+ bool allocate(int x1, int x2);
+
+private:
+ std::vector<std::pair<int, int>> _allocated;
+};
+
+
+std::vector<MarkerLayoutComponent> layout_markers(
+ std::map<dcp::Marker, dcpomatic::DCPTime> const& markers,
+ int width_in_pixels,
+ dcpomatic::DCPTime width_in_time,
+ int label_to_end_gap,
+ int outside_label_gap,
+ std::function<int (std::string)> text_width
+);
+
diff --git a/src/lib/wscript b/src/lib/wscript
index ce93b5824..2e7b0339c 100644
--- a/src/lib/wscript
+++ b/src/lib/wscript
@@ -157,6 +157,7 @@ sources = """
kdm_recipient.cc
kdm_with_metadata.cc
kdm_util.cc
+ layout_markers.cc
log.cc
log_entry.cc
make_dcp.cc
diff --git a/src/wx/markers.cc b/src/wx/markers.cc
index a2ef1e9e7..97e6ac41b 100644
--- a/src/wx/markers.cc
+++ b/src/wx/markers.cc
@@ -30,16 +30,16 @@ vector<pair<wxString, dcp::Marker>>
all_editable_markers()
{
return vector<pair<wxString, dcp::Marker>>{
- { _("First frame of ratings band"), dcp::Marker::FFOB },
- { _("Last frame of ratings band"), dcp::Marker::LFOB },
- { _("First frame of title credits"), dcp::Marker::FFTC },
- { _("Last frame of title credits"), dcp::Marker::LFTC },
- { _("First frame of intermission"), dcp::Marker::FFOI },
- { _("Last frame of intermission"), dcp::Marker::LFOI },
- { _("First frame of end credits"), dcp::Marker::FFEC },
- { _("Last frame of end credits"), dcp::Marker::LFEC },
- { _("First frame of moving credits"), dcp::Marker::FFMC },
- { _("Last frame of moving credits"), dcp::Marker::LFMC }
+ { _("First frame of ratings band (FFOB)"), dcp::Marker::FFOB },
+ { _("Last frame of ratings band (LFOB)"), dcp::Marker::LFOB },
+ { _("First frame of title credits (FFTC)"), dcp::Marker::FFTC },
+ { _("Last frame of title credits (LFTC)"), dcp::Marker::LFTC },
+ { _("First frame of intermission (FFOI)"), dcp::Marker::FFOI },
+ { _("Last frame of intermission (LFOI)"), dcp::Marker::LFOI },
+ { _("First frame of end credits (FFEC)"), dcp::Marker::FFEC },
+ { _("Last frame of end credits (LFEC)"), dcp::Marker::LFEC },
+ { _("First frame of moving credits (FFMC)"), dcp::Marker::FFMC },
+ { _("Last frame of moving credits (LFMC)"), dcp::Marker::LFMC }
};
}
diff --git a/src/wx/markers_panel.cc b/src/wx/markers_panel.cc
index 3a5e8c30b..675aa5e77 100644
--- a/src/wx/markers_panel.cc
+++ b/src/wx/markers_panel.cc
@@ -34,7 +34,10 @@ LIBDCP_ENABLE_WARNINGS
#include <boost/version.hpp>
+using std::make_pair;
+using std::pair;
using std::shared_ptr;
+using std::vector;
using std::weak_ptr;
#if BOOST_VERSION >= 106100
using namespace boost::placeholders;
@@ -56,11 +59,12 @@ static constexpr auto line_to_label_gap = 2;
MarkersPanel::MarkersPanel(wxWindow* parent, FilmViewer& viewer)
- : wxPanel(parent, wxID_ANY, wxDefaultPosition, wxSize(-1, 16))
+ : wxPanel(parent, wxID_ANY, wxDefaultPosition, wxSize(-1, 40))
, _viewer(viewer)
{
Bind(wxEVT_PAINT, boost::bind(&MarkersPanel::paint, this));
Bind(wxEVT_MOTION, boost::bind(&MarkersPanel::mouse_moved, this, _1));
+ Bind(wxEVT_SIZE, boost::bind(&MarkersPanel::size, this));
Bind(wxEVT_LEFT_DOWN, boost::bind(&MarkersPanel::mouse_left_down, this));
Bind(wxEVT_RIGHT_DOWN, boost::bind(&MarkersPanel::mouse_right_down, this, _1));
@@ -72,13 +76,19 @@ MarkersPanel::MarkersPanel(wxWindow* parent, FilmViewer& viewer)
void
+MarkersPanel::size()
+{
+ layout();
+}
+
+
+void
MarkersPanel::set_film(weak_ptr<Film> weak_film)
{
_film = weak_film;
- auto film = weak_film.lock();
- if (film) {
+ if (auto film = weak_film.lock()) {
film->Change.connect(boost::bind(&MarkersPanel::film_changed, this, _1, _2));
- update_from_film(film);
+ layout();
}
}
@@ -96,54 +106,52 @@ MarkersPanel::film_changed(ChangeType type, FilmProperty property)
}
if (property == FilmProperty::MARKERS || property == FilmProperty::CONTENT || property == FilmProperty::CONTENT_ORDER || property == FilmProperty::VIDEO_FRAME_RATE) {
- update_from_film(film);
+ layout();
}
}
void
-MarkersPanel::update_from_film(shared_ptr<Film> film)
+MarkersPanel::layout()
{
- _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
- );
-
+ auto film = _film.lock();
+ if (!film || !film->length().get()) {
+ _components.clear();
+ return;
}
- Refresh();
-}
+ wxClientDC dc(this);
+ auto const panel_width = GetSize().GetWidth();
-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;
+ /* 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;
+ 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();
+ _components = layout_markers(
+ film->markers(),
+ panel_width - end_gap,
+ film->length(),
+ 12,
+ 4,
+ [&dc](std::string text) { return dc.GetTextExtent(std_to_wx(text)).GetWidth(); }
+ );
+
+ _over = nullptr;
+ _menu_marker = nullptr;
+
+ Refresh();
}
void
MarkersPanel::mouse_moved(wxMouseEvent& ev)
{
- _over = boost::none;
+ _over = nullptr;
auto film = _film.lock();
if (!film) {
@@ -151,28 +159,27 @@ MarkersPanel::mouse_moved(wxMouseEvent& ev)
}
auto const panel_width = GetSize().GetWidth();
+
#if !defined(DCPOMATIC_LINUX)
auto const panel_height = GetSize().GetHeight();
auto const factor = GetContentScaleFactor();
#endif
+ auto const scale = static_cast<float>(panel_width) / film->length().get();
+
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;
+
+ for (auto const& marker: _components) {
+ auto const p = marker.t1.get() * scale;
+ if (marker.marker && std::abs(p - x) < 16) {
+ _over = &marker;
/* 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);
+ auto rect = wxRect(mouse.x, mouse.y, 8 * factor, panel_height * factor);
+ auto hmsf = marker.t1.split(film->video_frame_rate());
+ auto const tip_text = fmt::format("{} {:02d}:{:02d}:{:02d}:{:02d}", dcp::marker_to_string(*marker.marker), hmsf.h, hmsf.m, hmsf.s, hmsf.f);
_tip = new wxTipWindow(this, std_to_wx(tip_text), 100, &_tip, &rect);
}
#endif
@@ -192,42 +199,61 @@ MarkersPanel::paint()
}
gc->SetAntialiasMode(wxANTIALIAS_DEFAULT);
- gc->SetPen(wxPen(wxColour(200, 0, 0)));
- gc->SetFont(gc->CreateFont(*wxSMALL_FONT, wxColour(200, 0, 0)));
+ auto const colour = gui_is_dark() ? wxColour(199, 139, 167) : wxColour(200, 0, 0);
+ gc->SetPen(colour);
+ gc->SetFont(gc->CreateFont(*wxSMALL_FONT, colour));
+ gc->SetBrush(GetBackgroundColour());
- 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);
-
- auto label_x = 0;
-
- if (GetLayoutDirection() == wxLayout_RightToLeft) {
- auto matrix = dc.GetTransformMatrix();
- matrix.Translate(0, 0);
- matrix.Mirror(wxHORIZONTAL);
- dc.SetTransformMatrix(matrix);
- label_x = marker.second.line_before_label ? (pos + line_to_label_gap + marker.second.width) : (pos - line_to_label_gap);
- label_x = -label_x;
- } else {
- label_x = marker.second.line_before_label ? (pos + line_to_label_gap) : (pos - line_to_label_gap - marker.second.width);
+ int rows = 0;
+ for (auto const& component: _components) {
+ rows = std::max(rows, component.y + 1);
+ }
+
+ auto const row_height = std::min(static_cast<float>(panel_height) / rows, 16.0f);
+ auto const row_gap = 3;
+
+ auto const base = [row_height, panel_height](MarkerLayoutComponent const& component) {
+ return panel_height - (component.y + 1) * row_height;
+ };
+
+ for (auto const& component: _components) {
+ if (component.type == MarkerLayoutComponent::Type::LINE) {
+ gc->CreatePath();
+ auto line = gc->CreatePath();
+ line.MoveToPoint(component.x1, base(component) + (row_height - row_gap) / 2);
+ line.AddLineToPoint(component.x2, base(component) + (row_height - row_gap) / 2);
+ gc->StrokePath(line);
}
+ }
- gc->DrawText(label, label_x, 0);
+ for (auto const& component: _components) {
+ switch (component.type) {
+ case MarkerLayoutComponent::Type::LEFT:
+ case MarkerLayoutComponent::Type::RIGHT:
+ {
+ gc->CreatePath();
+ auto line = gc->CreatePath();
+ line.MoveToPoint(component.x1, base(component));
+ line.AddLineToPoint(component.x1, base(component) + row_height - row_gap);
+ gc->StrokePath(line);
+ break;
+ }
+ case MarkerLayoutComponent::Type::LABEL:
+ {
+ gc->CreatePath();
+ auto rectangle = gc->CreatePath();
+ rectangle.MoveToPoint(component.x1 - 2, base(component));
+ rectangle.AddRectangle(component.x1 - 2, base(component), component.x2 - component.x1, row_height);
+ gc->FillPath(rectangle);
+ gc->DrawText(std_to_wx(component.text), component.x1, base(component) - 4);
+ break;
+ }
- dc.ResetTransformMatrix();
+ case MarkerLayoutComponent::Type::LINE:
+ break;
+ }
}
}
@@ -236,7 +262,7 @@ void
MarkersPanel::mouse_left_down()
{
if (_over) {
- _viewer.seek(_markers[*_over].time, true);
+ _viewer.seek(_over->t1, true);
}
}
@@ -246,8 +272,9 @@ 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"), std_to_wx(dcp::marker_to_string(*_over))));
- menu.Append(ID_remove_marker, wxString::Format(_("Remove %s marker"), std_to_wx(dcp::marker_to_string(*_over))));
+ DCPOMATIC_ASSERT(_over->marker);
+ menu.Append(ID_move_marker_to_current_position, wxString::Format(_("Move %s marker to current position"), std_to_wx(dcp::marker_to_string(*_over->marker))));
+ menu.Append(ID_remove_marker, wxString::Format(_("Remove %s marker"), std_to_wx(dcp::marker_to_string(*_over->marker))));
}
auto add_marker = new wxMenu();
@@ -269,7 +296,8 @@ MarkersPanel::move_marker_to_current_position()
return;
}
- film->set_marker(*_menu_marker, _viewer.position());
+ DCPOMATIC_ASSERT(static_cast<bool>(_menu_marker->marker));
+ film->set_marker(*_menu_marker->marker, _viewer.position());
}
@@ -281,7 +309,8 @@ MarkersPanel::remove_marker()
return;
}
- film->unset_marker(*_menu_marker);
+ DCPOMATIC_ASSERT(static_cast<bool>(_menu_marker->marker));
+ film->unset_marker(*_menu_marker->marker);
}
diff --git a/src/wx/markers_panel.h b/src/wx/markers_panel.h
index 45caf9c4e..760df23ac 100644
--- a/src/wx/markers_panel.h
+++ b/src/wx/markers_panel.h
@@ -21,6 +21,7 @@
#include "lib/dcpomatic_time.h"
#include "lib/film_property.h"
+#include "lib/layout_markers.h"
#include <dcp/warnings.h>
LIBDCP_DISABLE_WARNINGS
#include <wx/wx.h>
@@ -40,36 +41,22 @@ public:
private:
void paint();
+ void size();
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, FilmProperty property);
- void update_from_film(std::shared_ptr<Film> film);
+ void layout();
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> _film;
- std::map<dcp::Marker, Marker> _markers;
- boost::optional<dcp::Marker> _over;
+ std::vector<MarkerLayoutComponent> _components;
+ MarkerLayoutComponent const* _over = nullptr;
FilmViewer& _viewer;
- boost::optional<dcp::Marker> _menu_marker;
+ MarkerLayoutComponent const* _menu_marker = nullptr;
};
diff --git a/test/layout_markers_test.cc b/test/layout_markers_test.cc
new file mode 100644
index 000000000..acb7fa60b
--- /dev/null
+++ b/test/layout_markers_test.cc
@@ -0,0 +1,154 @@
+/*
+ Copyright (C) 2025 Carl Hetherington <cth@carlh.net>
+
+ 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 <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "lib/dcpomatic_time.h"
+#include "lib/layout_markers.h"
+#include <dcp/types.h>
+#include <boost/test/unit_test.hpp>
+#include <iostream>
+
+
+using std::map;
+using std::string;
+using std::vector;
+
+
+BOOST_AUTO_TEST_CASE(allocation_row_test)
+{
+ AllocationRow row;
+ BOOST_CHECK(row.allocate(0, 5));
+ BOOST_CHECK(row.allocate(6, 10));
+ BOOST_CHECK(!row.allocate(1, 3));
+ BOOST_CHECK(!row.allocate(4, 7));
+ BOOST_CHECK(row.allocate(19, 20));
+ BOOST_CHECK(!row.allocate(19, 20));
+ BOOST_CHECK(!row.allocate(17, 20));
+ BOOST_CHECK(!row.allocate(10, 16));
+ BOOST_CHECK(!row.allocate(11, 19));
+ BOOST_CHECK(row.allocate(11, 18));
+}
+
+
+static
+vector<string>
+plot(vector<MarkerLayoutComponent> const& components)
+{
+ vector<string> out;
+
+ auto write = [&out](int x, int y, char c) {
+ while (y >= static_cast<int>(out.size())) {
+ out.push_back({});
+ }
+ while (x >= static_cast<int>(out[y].length())) {
+ out[y] += " ";
+ }
+ if (out[y][x] == ' ') {
+ out[y][x] = c;
+ }
+ };
+
+ for (auto component: components) {
+ switch (component.type) {
+ case MarkerLayoutComponent::Type::LEFT:
+ write(component.x1, component.y, '/');
+ break;
+ case MarkerLayoutComponent::Type::RIGHT:
+ write(component.x1, component.y, '|');
+ break;
+ case MarkerLayoutComponent::Type::LABEL:
+ for (auto i = 0U; i < component.text.length(); ++i) {
+ write(component.x1 + i, component.y, component.text[i]);
+ }
+ break;
+ case MarkerLayoutComponent::Type::LINE:
+ for (auto x = component.x1; x <= component.x2; ++x) {
+ write(x, component.y, '-');
+ }
+ break;
+ }
+ }
+
+ return out;
+}
+
+
+BOOST_AUTO_TEST_CASE(layout_test1)
+{
+ map<dcp::Marker, dcpomatic::DCPTime> markers = {
+ { dcp::Marker::FFOB, dcpomatic::DCPTime(1) },
+ { dcp::Marker::LFOB, dcpomatic::DCPTime(9) },
+ { dcp::Marker::FFTC, dcpomatic::DCPTime(13) },
+ { dcp::Marker::LFTC, dcpomatic::DCPTime(17) },
+ { dcp::Marker::FFOI, dcpomatic::DCPTime(12) },
+ { dcp::Marker::LFOI, dcpomatic::DCPTime(25) },
+ { dcp::Marker::FFEC, dcpomatic::DCPTime(20) },
+ { dcp::Marker::LFEC, dcpomatic::DCPTime(30) },
+ { dcp::Marker::FFMC, dcpomatic::DCPTime(0) },
+ { dcp::Marker::LFMC, dcpomatic::DCPTime(3) }
+ };
+
+ auto components = layout_markers(markers, 30, dcpomatic::DCPTime(30), 1, 1, [](std::string s) { return s.length(); });
+
+ auto check = plot(components);
+ BOOST_REQUIRE_EQUAL(check.size(), 2U);
+ BOOST_CHECK_EQUAL(check[0], " /--RB---| /TC-| /---EC----|");
+ BOOST_CHECK_EQUAL(check[1], "/C-| /----IN------|" );
+
+ for (auto const& line: check) {
+ std::cout << line << "\n";
+ }
+}
+
+
+BOOST_AUTO_TEST_CASE(layout_test2)
+{
+ map<dcp::Marker, dcpomatic::DCPTime> markers = {
+ { dcp::Marker::FFOB, dcpomatic::DCPTime(1) },
+ };
+
+ auto components = layout_markers(markers, 4, dcpomatic::DCPTime(4), 2, 2, [](std::string s) { return s.length(); });
+
+ auto check = plot(components);
+ BOOST_REQUIRE_EQUAL(check.size(), 1U);
+ BOOST_CHECK_EQUAL(check[0], " /-RB");
+
+ for (auto const& line: check) {
+ std::cout << line << "\n";
+ }
+}
+
+
+BOOST_AUTO_TEST_CASE(layout_test3)
+{
+ map<dcp::Marker, dcpomatic::DCPTime> markers = {
+ { dcp::Marker::LFOB, dcpomatic::DCPTime(4) },
+ };
+
+ auto components = layout_markers(markers, 4, dcpomatic::DCPTime(4), 2, 2, [](std::string s) { return s.length(); });
+
+ auto check = plot(components);
+ BOOST_REQUIRE_EQUAL(check.size(), 1U);
+ BOOST_CHECK_EQUAL(check[0], "RB--|");
+
+ for (auto const& line: check) {
+ std::cout << line << "\n";
+ }
+}
diff --git a/test/wscript b/test/wscript
index 022f3f7d7..50c86751f 100644
--- a/test/wscript
+++ b/test/wscript
@@ -130,6 +130,7 @@ def build(bld):
kdm_cli_test.cc
kdm_naming_test.cc
kdm_util_test.cc
+ layout_markers_test.cc
low_bitrate_test.cc
markers_test.cc
map_cli_test.cc