Aegisub/src/grid_column.cpp

425 lines
13 KiB
C++

// Copyright (c) 2014, Thomas Goyne <plorkyeran@aegisub.org>
//
// 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 <libaegisub/character_count.h>
#include <wx/dc.h>
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<std::string> 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<wchar_t *>(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<typename T>
T max_value(T AssDialogueBase::*field, EntryList<AssDialogue> 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<typename T>
int max_width(T AssDialogueBase::*field, EntryList<AssDialogue> 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<size_t>(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<int>(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<typename T>
std::unique_ptr<GridColumn> make() {
return std::unique_ptr<GridColumn>(new T);
}
}
std::vector<std::unique_ptr<GridColumn>> GetGridColumns() {
std::vector<std::unique_ptr<GridColumn>> ret;
ret.push_back(make<GridColumnLineNumber>());
ret.push_back(make<GridColumnLayer>());
ret.push_back(make<GridColumnStartTime>());
ret.push_back(make<GridColumnEndTime>());
ret.push_back(make<GridColumnCPS>());
ret.push_back(make<GridColumnStyle>());
ret.push_back(make<GridColumnActor>());
ret.push_back(make<GridColumnEffect>());
ret.push_back(make<GridColumnMarginLeft>());
ret.push_back(make<GridColumnMarginRight>());
ret.push_back(make<GridColumnMarginVert>());
ret.push_back(make<GridColumnText>());
return ret;
}