diff options
| -rw-r--r-- | src/lib/layout_markers.cc | 158 | ||||
| -rw-r--r-- | src/lib/layout_markers.h | 93 | ||||
| -rw-r--r-- | src/lib/wscript | 1 | ||||
| -rw-r--r-- | src/wx/markers.cc | 20 | ||||
| -rw-r--r-- | src/wx/markers_panel.cc | 189 | ||||
| -rw-r--r-- | src/wx/markers_panel.h | 25 | ||||
| -rw-r--r-- | test/layout_markers_test.cc | 154 | ||||
| -rw-r--r-- | test/wscript | 1 |
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 = ▮ /* 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 |
