// Copyright (c) 2014, Thomas Goyne // // Permission to use, copy, modify, and distribute this software for any // purpose with or without fee is hereby granted, provided that the above // copyright notice and this permission notice appear in all copies. // // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. // // Aegisub Project http://www.aegisub.org/ #include "grid_column.h" #include "ass_dialogue.h" #include "ass_file.h" #include "compat.h" #include "include/aegisub/context.h" #include "options.h" #include "video_controller.h" #include #include void WidthHelper::Age() { for (auto it = begin(widths), e = end(widths); it != e; ) { if (it->second.age == age) ++it; else it = widths.erase(it); } ++age; } int WidthHelper::operator()(boost::flyweight const& str) { if (str.get().empty()) return 0; auto it = widths.find(str); if (it != end(widths)) { it->second.age = age; return it->second.width; } #ifdef _WIN32 wxMBConvUTF8 conv; size_t len = conv.ToWChar(nullptr, 0, str.get().c_str(), str.get().size()); scratch.resize(len); conv.ToWChar(const_cast(scratch.wx_str()), len, str.get().c_str(), str.get().size()); int width = dc->GetTextExtent(scratch).GetWidth(); #else int width = dc->GetTextExtent(to_wx(str)).GetWidth(); #endif widths[str] = {width, age}; return width; } int WidthHelper::operator()(std::string const& str) { return dc->GetTextExtent(to_wx(str)).GetWidth(); } int WidthHelper::operator()(wxString const& str) { return dc->GetTextExtent(str).GetWidth(); } int WidthHelper::operator()(const char *str) { return dc->GetTextExtent(wxString::FromUTF8(str)).GetWidth(); } int WidthHelper::operator()(const wchar_t *str) { return dc->GetTextExtent(str).GetWidth(); } void GridColumn::UpdateWidth(const agi::Context *c, WidthHelper &helper) { if (!visible) { width = 0; return; } width = Width(c, helper); if (width) // 10 is an arbitrary amount of padding width = 10 + std::max(width, helper(Header())); } void GridColumn::Paint(wxDC &dc, int x, int y, const AssDialogue *d, const agi::Context *c) const { wxString str = Value(d, c); if (Centered()) x += (width - 6 - dc.GetTextExtent(str).GetWidth()) / 2; dc.DrawText(str, x + 4, y + 2); } namespace { #define COLUMN_HEADER(value) \ private: const wxString header = value; \ public: wxString const& Header() const override { return header; } #define COLUMN_DESCRIPTION(value) \ private: const wxString description = value; \ public: wxString const& Description() const override { return description; } struct GridColumnLineNumber final : GridColumn { COLUMN_HEADER(_("#")) COLUMN_DESCRIPTION(_("Line Number")) bool Centered() const override { return true; } wxString Value(const AssDialogue *d, const agi::Context * = nullptr) const override { return std::to_wstring(d->Row + 1); } int Width(const agi::Context *c, WidthHelper &helper) const override { return helper(Value(&c->ass->Events.back())); } }; template T max_value(T AssDialogueBase::*field, EntryList const& lines) { T value = 0; for (AssDialogue const& line : lines) { if (line.*field > value) value = line.*field; } return value; } struct GridColumnLayer final : GridColumn { COLUMN_HEADER(_("L")) COLUMN_DESCRIPTION(_("Layer")) bool Centered() const override { return true; } wxString Value(const AssDialogue *d, const agi::Context *) const override { return d->Layer ? wxString(std::to_wstring(d->Layer)) : wxString(); } int Width(const agi::Context *c, WidthHelper &helper) const override { int max_layer = max_value(&AssDialogue::Layer, c->ass->Events); return max_layer == 0 ? 0 : helper(std::to_wstring(max_layer)); } }; struct GridColumnTime : GridColumn { bool by_frame = false; bool Centered() const override { return true; } void SetByFrame(bool by_frame) override { this->by_frame = by_frame; } }; struct GridColumnStartTime final : GridColumnTime { COLUMN_HEADER(_("Start")) COLUMN_DESCRIPTION(_("Start Time")) wxString Value(const AssDialogue *d, const agi::Context *c) const override { if (by_frame) return std::to_wstring(c->videoController->FrameAtTime(d->Start, agi::vfr::START)); return to_wx(d->Start.GetAssFormatted()); } int Width(const agi::Context *c, WidthHelper &helper) const override { if (!by_frame) return helper(wxS("0:00:00.00")); int frame = c->videoController->FrameAtTime(max_value(&AssDialogue::Start, c->ass->Events), agi::vfr::START); return helper(std::to_wstring(frame)); } }; struct GridColumnEndTime final : GridColumnTime { COLUMN_HEADER(_("End")) COLUMN_DESCRIPTION(_("End Time")) wxString Value(const AssDialogue *d, const agi::Context *c) const override { if (by_frame) return std::to_wstring(c->videoController->FrameAtTime(d->End, agi::vfr::END)); return to_wx(d->End.GetAssFormatted()); } int Width(const agi::Context *c, WidthHelper &helper) const override { if (!by_frame) return helper(wxS("0:00:00.00")); int frame = c->videoController->FrameAtTime(max_value(&AssDialogue::End, c->ass->Events), agi::vfr::END); return helper(std::to_wstring(frame)); } }; template int max_width(T AssDialogueBase::*field, EntryList const& lines, WidthHelper &helper) { int w = 0; for (AssDialogue const& line : lines) { auto const& v = line.*field; if (v.get().empty()) continue; int width = helper(v); if (width > w) w = width; } return w; } struct GridColumnStyle final : GridColumn { COLUMN_HEADER(_("Style")) COLUMN_DESCRIPTION(_("Style")) bool Centered() const override { return false; } wxString Value(const AssDialogue *d, const agi::Context *c) const override { return to_wx(d->Style); } int Width(const agi::Context *c, WidthHelper &helper) const override { return max_width(&AssDialogue::Style, c->ass->Events, helper); } }; struct GridColumnEffect final : GridColumn { COLUMN_HEADER(_("Effect")) COLUMN_DESCRIPTION(_("Effect")) bool Centered() const override { return false; } wxString Value(const AssDialogue *d, const agi::Context *) const override { return to_wx(d->Effect); } int Width(const agi::Context *c, WidthHelper &helper) const override { return max_width(&AssDialogue::Effect, c->ass->Events, helper); } }; struct GridColumnActor final : GridColumn { COLUMN_HEADER(_("Actor")) COLUMN_DESCRIPTION(_("Actor")) bool Centered() const override { return false; } wxString Value(const AssDialogue *d, const agi::Context *) const override { return to_wx(d->Actor); } int Width(const agi::Context *c, WidthHelper &helper) const override { return max_width(&AssDialogue::Actor, c->ass->Events, helper); } }; struct GridColumnMargin : GridColumn { int index; GridColumnMargin(int index) : index(index) { } bool Centered() const override { return true; } wxString Value(const AssDialogue *d, const agi::Context *) const override { return d->Margin[index] ? wxString(std::to_wstring(d->Margin[index])) : wxString(); } int Width(const agi::Context *c, WidthHelper &helper) const override { int max = 0; for (AssDialogue const& line : c->ass->Events) { if (line.Margin[index] > max) max = line.Margin[index]; } return max == 0 ? 0 : helper(std::to_wstring(max)); } }; struct GridColumnMarginLeft final : GridColumnMargin { GridColumnMarginLeft() : GridColumnMargin(0) { } COLUMN_HEADER(_("Left")) COLUMN_DESCRIPTION(_("Left Margin")) }; struct GridColumnMarginRight final : GridColumnMargin { GridColumnMarginRight() : GridColumnMargin(1) { } COLUMN_HEADER(_("Right")) COLUMN_DESCRIPTION(_("Right Margin")) }; struct GridColumnMarginVert final : GridColumnMargin { GridColumnMarginVert() : GridColumnMargin(2) { } COLUMN_HEADER(_("Vert")) COLUMN_DESCRIPTION(_("Vertical Margin")) }; wxColor blend(wxColor fg, wxColor bg, double alpha) { return wxColor( wxColor::AlphaBlend(fg.Red(), bg.Red(), alpha), wxColor::AlphaBlend(fg.Green(), bg.Green(), alpha), wxColor::AlphaBlend(fg.Blue(), bg.Blue(), alpha)); } class GridColumnCPS final : public GridColumn { const agi::OptionValue *ignore_whitespace = OPT_GET("Subtitle/Character Counter/Ignore Whitespace"); const agi::OptionValue *ignore_punctuation = OPT_GET("Subtitle/Character Counter/Ignore Punctuation"); const agi::OptionValue *cps_warn = OPT_GET("Subtitle/Character Counter/CPS Warning Threshold"); const agi::OptionValue *cps_error = OPT_GET("Subtitle/Character Counter/CPS Error Threshold"); const agi::OptionValue *bg_color = OPT_GET("Colour/Subtitle Grid/CPS Error"); public: COLUMN_HEADER(_("CPS")) COLUMN_DESCRIPTION(_("Characters Per Second")) bool Centered() const override { return true; } bool RefreshOnTextChange() const override { return true; } wxString Value(const AssDialogue *d, const agi::Context *) const override { return wxS(""); } int CPS(const AssDialogue *d) const { int duration = d->End - d->Start; auto const& text = d->Text.get(); if (duration <= 100 || text.size() > static_cast(duration)) return -1; int ignore = agi::IGNORE_BLOCKS; if (ignore_whitespace->GetBool()) ignore |= agi::IGNORE_WHITESPACE; if (ignore_punctuation->GetBool()) ignore |= agi::IGNORE_PUNCTUATION; return agi::CharacterCount(text, ignore) * 1000 / duration; } int Width(const agi::Context *c, WidthHelper &helper) const override { return helper(wxS("999")); } void Paint(wxDC &dc, int x, int y, const AssDialogue *d, const agi::Context *) const override { int cps = CPS(d); if (cps < 0 || cps > 100) return; wxString str = std::to_wstring(cps); wxSize ext = dc.GetTextExtent(str); auto tc = dc.GetTextForeground(); int cps_min = cps_warn->GetInt(); int cps_max = std::max(cps_min, cps_error->GetInt()); if (cps > cps_min) { double alpha = std::min((double)(cps - cps_min + 1) / (cps_max - cps_min + 1), 1.0); dc.SetBrush(wxBrush(blend(to_wx(bg_color->GetColor()), dc.GetBrush().GetColour(), alpha))); dc.SetPen(*wxTRANSPARENT_PEN); dc.DrawRectangle(x, y + 1, width, ext.GetHeight() + 3); dc.SetTextForeground(blend(*wxBLACK, tc, alpha)); } x += (width + 2 - ext.GetWidth()) / 2; dc.DrawText(str, x, y + 2); dc.SetTextForeground(tc); } }; class GridColumnText final : public GridColumn { const agi::OptionValue *override_mode; wxString replace_char; agi::signal::Connection replace_char_connection; public: GridColumnText() : override_mode(OPT_GET("Subtitle/Grid/Hide Overrides")) , replace_char(to_wx(OPT_GET("Subtitle/Grid/Hide Overrides Char")->GetString())) , replace_char_connection(OPT_SUB("Subtitle/Grid/Hide Overrides Char", [&](agi::OptionValue const& v) { replace_char = to_wx(v.GetString()); })) { } COLUMN_HEADER(_("Text")) COLUMN_DESCRIPTION(_("Text")) bool Centered() const override { return false; } bool CanHide() const override { return false; } bool RefreshOnTextChange() const override { return true; } wxString Value(const AssDialogue *d, const agi::Context *) const override { wxString str; int mode = override_mode->GetInt(); // Show overrides if (mode == 0) str = to_wx(d->Text); // Hidden overrides else { auto const& text = d->Text.get(); str.reserve(text.size()); size_t start = 0, pos; while ((pos = text.find('{', start)) != std::string::npos) { str += to_wx(text.substr(start, pos - start)); if (mode == 1) str += replace_char; start = text.find('}', pos); if (start != std::string::npos) ++start; } if (start != std::string::npos) str += to_wx(text.substr(start)); } // Cap length and set text if (str.size() > 512) str = str.Left(512) + "..."; return str; } int Width(const agi::Context *c, WidthHelper &helper) const override { return 5000; } }; template std::unique_ptr make() { return std::unique_ptr(new T); } } std::vector> GetGridColumns() { std::vector> ret; ret.push_back(make()); ret.push_back(make()); ret.push_back(make()); ret.push_back(make()); ret.push_back(make()); ret.push_back(make()); ret.push_back(make()); ret.push_back(make()); ret.push_back(make()); ret.push_back(make()); ret.push_back(make()); ret.push_back(make()); return ret; }