Extract all of the column-specific logic from BaseGrid

This commit is contained in:
Thomas Goyne 2014-04-18 10:02:08 -07:00
parent 909be4494a
commit 5df871f599
8 changed files with 529 additions and 306 deletions

View File

@ -171,6 +171,7 @@
<ClInclude Include="$(SrcDir)frame_main.h" />
<ClInclude Include="$(SrcDir)gl_text.h" />
<ClInclude Include="$(SrcDir)gl_wrap.h" />
<ClInclude Include="$(SrcDir)grid_column.h" />
<ClInclude Include="$(SrcDir)help_button.h" />
<ClInclude Include="$(SrcDir)hotkey_data_view_model.h" />
<ClInclude Include="$(SrcDir)include\aegisub\audio_player.h" />
@ -373,6 +374,7 @@
<ClCompile Include="$(SrcDir)frame_main.cpp" />
<ClCompile Include="$(SrcDir)gl_text.cpp" />
<ClCompile Include="$(SrcDir)gl_wrap.cpp" />
<ClCompile Include="$(SrcDir)grid_column.cpp" />
<ClCompile Include="$(SrcDir)help_button.cpp" />
<ClCompile Include="$(SrcDir)hotkey.cpp" />
<ClCompile Include="$(SrcDir)hotkey_data_view_model.cpp" />

View File

@ -624,6 +624,9 @@
<ClInclude Include="$(SrcDir)resolution_resampler.h">
<Filter>Features\Resolution resampler</Filter>
</ClInclude>
<ClInclude Include="$(SrcDir)grid_column.h">
<Filter>Main UI\Grid</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="$(SrcDir)ass_dialogue.cpp">
@ -1181,6 +1184,9 @@
<ClCompile Include="$(SrcDir)text_selection_controller.cpp">
<Filter>Main UI\Edit box</Filter>
</ClCompile>
<ClCompile Include="$(SrcDir)grid_column.cpp">
<Filter>Main UI\Grid</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="$(SrcDir)res/res.rc">

View File

@ -19,6 +19,7 @@
#include <libaegisub/time.h>
#include <algorithm>
#include <boost/range/irange.hpp>
#include <cstdint>
#include <string>
#include <memory>
@ -74,5 +75,10 @@ namespace agi {
std::string ErrorString(int error);
template<typename Integer>
auto range(Integer end) -> decltype(boost::irange<Integer>(0, end)) {
return boost::irange<Integer>(0, end);
}
} // namespace util
} // namespace agi

View File

@ -194,6 +194,7 @@ SRC += \
frame_main.cpp \
gl_text.cpp \
gl_wrap.cpp \
grid_column.cpp \
help_button.cpp \
hotkey.cpp \
hotkey_data_view_model.cpp \

View File

@ -35,24 +35,23 @@
#include "ass_dialogue.h"
#include "ass_file.h"
#include "ass_style.h"
#include "audio_box.h"
#include "compat.h"
#include "frame_main.h"
#include "grid_column.h"
#include "options.h"
#include "utils.h"
#include "selection_controller.h"
#include "subs_controller.h"
#include "video_context.h"
#include "video_slider.h"
#include <libaegisub/util.h>
#include <algorithm>
#include <boost/range/algorithm.hpp>
#include <boost/range/irange.hpp>
#include <cmath>
#include <iterator>
#include <numeric>
#include <unordered_map>
#include <wx/dcbuffer.h>
#include <wx/kbdstate.h>
@ -65,23 +64,11 @@ enum {
MENU_SHOW_COL = 1250 // Needs 15 IDs after this
};
namespace std {
template <typename T>
struct hash<boost::flyweight<T>> {
size_t operator()(boost::flyweight<T> const& ss) const {
return hash<const void*>()(&ss.get());
}
};
}
BaseGrid::BaseGrid(wxWindow* parent, agi::Context *context)
: wxWindow(parent, -1, wxDefaultPosition, wxDefaultSize, wxWANTS_CHARS | wxSUNKEN_BORDER)
, scrollBar(new wxScrollBar(this, GRID_SCROLLBAR, wxDefaultPosition, wxDefaultSize, wxSB_VERTICAL))
, seek_listener(context->videoController->AddSeekListener(std::bind(&BaseGrid::Refresh, this, false, nullptr)))
, headerNames({{
_("#"), _("L"), _("Start"), _("End"), _("CPS"), _("Style"), _("Actor"),
_("Effect"), _("Left"), _("Right"), _("Vert"), _("Text")
}})
, columns(GetGridColumns())
, seek_listener(context->videoController->AddSeekListener([&] { Refresh(false); }))
, context(context)
{
scrollBar->SetScrollbar(0,10,100,10);
@ -149,8 +136,8 @@ void BaseGrid::OnSubtitlesCommit(int type) {
if (type & AssFile::COMMIT_DIAG_TIME)
Refresh(false);
else if (type & AssFile::COMMIT_DIAG_TEXT) {
RefreshRect(wxRect(cps_col_x, 0, cps_col_w, GetClientSize().GetHeight()), false);
RefreshRect(wxRect(text_col_x, 0, text_col_w, GetClientSize().GetHeight()), false);
for (auto const& rect : text_refresh_rects)
RefreshRect(rect, false);
}
}
@ -164,9 +151,9 @@ void BaseGrid::OnSubtitlesSave() {
void BaseGrid::OnShowColMenu(wxCommandEvent &event) {
int item = event.GetId() - MENU_SHOW_COL;
showCol[item] = !showCol[item];
column_shown[item] = !column_shown[item];
OPT_SET("Subtitle/Grid/Column")->SetListBool(std::vector<bool>(std::begin(showCol), std::end(showCol)));
OPT_SET("Subtitle/Grid/Column")->SetListBool(std::vector<bool>(std::begin(column_shown), std::end(column_shown)));
SetColumnWidths();
Refresh(false);
@ -203,13 +190,12 @@ void BaseGrid::UpdateStyle() {
// Set column widths
std::vector<bool> column_array(OPT_GET("Subtitle/Grid/Column")->GetListBool());
assert(column_array.size() <= showCol.size());
boost::copy(column_array, std::begin(showCol));
for (size_t i : boost::irange(column_array.size(), showCol.size()))
showCol[i] = true;
column_shown.assign(column_array.begin(), column_array.end());
column_shown.resize(columns.size(), true);
for (int i : boost::irange(0, column_count))
headerWidth[i] = dc.GetTextExtent(headerNames[i]).GetWidth();
column_header_widths.resize(columns.size());
for (size_t i : agi::util::range(columns.size()))
column_header_widths[i] = dc.GetTextExtent(columns[i]->Header()).GetWidth();
SetColumnWidths();
@ -268,32 +254,29 @@ void BaseGrid::SelectRow(int row, bool addToSelected, bool select) {
}
void BaseGrid::OnPaint(wxPaintEvent &) {
// Get size and pos
wxSize cs = GetClientSize();
cs.SetWidth(cs.GetWidth() - scrollBar->GetSize().GetWidth());
// Find which columns need to be repainted
bool paint_columns[column_count] = {0};
std::vector<GridColumn *> paint_columns;
std::vector<int> paint_column_widths;
for (wxRegionIterator region(GetUpdateRegion()); region; ++region) {
wxRect updrect = region.GetRect();
int x = 0;
for (int i : boost::irange(0, column_count)) {
if (updrect.x < x + colWidth[i] && updrect.x + updrect.width > x && colWidth[i])
paint_columns[i] = true;
x += colWidth[i];
for (size_t i : agi::util::range(columns.size())) {
if (updrect.x < x + column_widths[i] && updrect.x + updrect.width > x && column_widths[i])
paint_columns.push_back(columns[i].get());
else
paint_columns.push_back(nullptr);
x += column_widths[i];
}
}
wxAutoBufferedPaintDC dc(this);
DrawImage(dc, paint_columns);
}
if (paint_columns.empty()) return;
void BaseGrid::DrawImage(wxDC &dc, bool paint_columns[]) {
int w = 0;
int h = 0;
GetClientSize(&w,&h);
w -= scrollBar->GetSize().GetWidth();
wxAutoBufferedPaintDC dc(this);
dc.SetFont(font);
dc.SetBackground(row_colors.Default);
@ -302,12 +285,7 @@ void BaseGrid::DrawImage(wxDC &dc, bool paint_columns[]) {
// Draw labels
dc.SetPen(*wxTRANSPARENT_PEN);
dc.SetBrush(row_colors.LeftCol);
dc.DrawRectangle(0,lineHeight,colWidth[0],h-lineHeight);
// Visible lines
int drawPerScreen = h/lineHeight + 1;
int nDraw = mid(0,drawPerScreen,GetRows()-yPos);
int maxH = (nDraw+1) * lineHeight;
dc.DrawRectangle(0, lineHeight, column_widths[0], h-lineHeight);
// Row colors
wxColour text_standard(to_wx(OPT_GET("Colour/Subtitle Grid/Standard")->GetColor()));
@ -320,181 +298,109 @@ void BaseGrid::DrawImage(wxDC &dc, bool paint_columns[]) {
dc.DrawLine(0, 0, w, 0);
dc.SetPen(*wxTRANSPARENT_PEN);
auto strings = headerNames;
auto paint_text = [&](wxString const& str, int x, int y, int col) {
wxSize ext = dc.GetTextExtent(str);
int override_mode = OPT_GET("Subtitle/Grid/Hide Overrides")->GetInt();
wxString replace_char;
if (override_mode == 1)
replace_char = to_wx(OPT_GET("Subtitle/Grid/Hide Overrides Char")->GetString());
int top = y + (lineHeight - ext.GetHeight()) / 2;
int left = x + 4;
if (columns[col]->Centered())
left += (column_widths[col] - 6 - ext.GetWidth()) / 2;
dc.DrawText(str, left, top);
};
// Paint header
{
dc.SetTextForeground(text_standard);
dc.SetBrush(row_colors.Header);
dc.DrawRectangle(0, 0, w, lineHeight);
int x = 0;
for (size_t i : agi::util::range(columns.size())) {
if (paint_columns[i])
paint_text(columns[i]->Header(), x, 0, i);
x += column_widths[i];
}
dc.SetPen(grid_pen);
dc.DrawLine(0, lineHeight, w, lineHeight);
}
// Paint the rows
int drawPerScreen = h/lineHeight + 1;
int nDraw = mid(0, drawPerScreen, GetRows() - yPos);
auto active_line = context->selectionController->GetActiveLine();
auto const& selection = context->selectionController->GetSelectedSet();
for (int i = 0; i < nDraw + 1; i++) {
int curRow = i + yPos - 1;
wxBrush curColor = row_colors.Default;
AssDialogue *curDiag = nullptr;
for (int i : agi::util::range(nDraw)) {
wxBrush color = row_colors.Default;
AssDialogue *curDiag = index_line_map[i + yPos];
// Header
if (i == 0) {
curColor = row_colors.Header;
bool inSel = !!selection.count(curDiag);
if (inSel && curDiag->Comment)
color = row_colors.SelectedComment;
else if (inSel)
color = row_colors.Selection;
else if (curDiag->Comment)
color = row_colors.Comment;
else if (OPT_GET("Subtitle/Grid/Highlight Subtitles in Frame")->GetBool() && IsDisplayed(curDiag))
color = row_colors.Visible;
if (active_line != curDiag && curDiag->CollidesWith(active_line))
dc.SetTextForeground(text_collision);
else if (inSel)
dc.SetTextForeground(text_selection);
else
dc.SetTextForeground(text_standard);
}
// Lines
else if ((curDiag = GetDialogue(curRow))) {
GetRowStrings(curRow, curDiag, paint_columns, &strings[0], !!override_mode, replace_char);
bool inSel = !!selection.count(curDiag);
if (inSel && curDiag->Comment)
curColor = row_colors.SelectedComment;
else if (inSel)
curColor = row_colors.Selection;
else if (curDiag->Comment)
curColor = row_colors.Comment;
else if (OPT_GET("Subtitle/Grid/Highlight Subtitles in Frame")->GetBool() && IsDisplayed(curDiag))
curColor = row_colors.Visible;
if (active_line != curDiag && curDiag->CollidesWith(active_line))
dc.SetTextForeground(text_collision);
else if (inSel)
dc.SetTextForeground(text_selection);
else
dc.SetTextForeground(text_standard);
}
else {
assert(false);
}
// Draw row background color
if (curColor != row_colors.Default) {
dc.SetBrush(curColor);
dc.DrawRectangle(
(i == 0) ? 0 : colWidth[0], i * lineHeight + 1,
w, lineHeight);
if (color != row_colors.Default) {
dc.SetBrush(color);
dc.DrawRectangle(column_widths[0], (i + 1) * lineHeight + 1, w, lineHeight);
}
// Draw text
int dx = 0;
int dy = i*lineHeight;
for (int j : boost::irange(0, column_count)) {
if (colWidth[j] == 0) continue;
int x = 0;
int y = (i + 1) * lineHeight;
for (size_t j : agi::util::range(columns.size())) {
if (column_widths[j] == 0) continue;
if (paint_columns[j]) {
wxSize ext = dc.GetTextExtent(strings[j]);
int left = dx + 4;
int top = dy + (lineHeight - ext.GetHeight()) / 2;
// Centered columns
if (!(j == 5 || j == 6 || j == 7 || j == 11)) {
left += (colWidth[j] - 6 - ext.GetWidth()) / 2;
}
dc.DrawText(strings[j], left, top);
}
dx += colWidth[j];
if (paint_columns[j])
paint_text(columns[j]->Value(curDiag, byFrame ? context : nullptr), x, y, j);
x += column_widths[j];
}
// Draw grid
dc.DestroyClippingRegion();
if (curDiag == active_line) {
dc.SetPen(wxPen(to_wx(OPT_GET("Colour/Subtitle Grid/Active Border")->GetColor())));
dc.SetBrush(*wxTRANSPARENT_BRUSH);
dc.DrawRectangle(0, dy, w, lineHeight + 1);
dc.DrawRectangle(0, y, w, lineHeight + 1);
}
else {
dc.SetPen(grid_pen);
dc.DrawLine(0, dy + lineHeight, w , dy + lineHeight);
dc.DrawLine(0, y + lineHeight, w , y + lineHeight);
}
dc.SetPen(*wxTRANSPARENT_PEN);
}
// Draw grid columns
int dx = 0;
dc.SetPen(grid_pen);
for (int i : boost::irange(0, column_count - 1)) {
dx += colWidth[i];
dc.DrawLine(dx, 0, dx, maxH);
}
dc.DrawLine(0, 0, 0, maxH);
dc.DrawLine(w-1, 0, w-1, maxH);
}
void BaseGrid::GetRowStrings(int row, AssDialogue *line, bool *paint_columns, wxString *strings, bool replace, wxString const& rep_char) const {
if (paint_columns[0]) strings[0] = std::to_wstring(row + 1);
if (paint_columns[1]) strings[1] = std::to_wstring(line->Layer);
if (byFrame) {
if (paint_columns[2]) strings[2] = std::to_wstring(context->videoController->FrameAtTime(line->Start, agi::vfr::START));
if (paint_columns[3]) strings[3] = std::to_wstring(context->videoController->FrameAtTime(line->End, agi::vfr::END));
}
else {
if (paint_columns[2]) strings[2] = to_wx(line->Start.GetAssFormated());
if (paint_columns[3]) strings[3] = to_wx(line->End.GetAssFormated());
}
if (paint_columns[5]) strings[5] = to_wx(line->Style);
if (paint_columns[6]) strings[6] = to_wx(line->Actor);
if (paint_columns[7]) strings[7] = to_wx(line->Effect);
if (paint_columns[8]) strings[8] = line->Margin[0] ? wxString(std::to_wstring(line->Margin[0])) : wxString();
if (paint_columns[9]) strings[9] = line->Margin[1] ? wxString(std::to_wstring(line->Margin[1])) : wxString();
if (paint_columns[10]) strings[10] = line->Margin[2] ? wxString(std::to_wstring(line->Margin[2])) : wxString();
if (paint_columns[4]) {
int characters = 0;
auto const& text = line->Text.get();
auto pos = begin(text);
do {
auto it = std::find(pos, end(text), '{');
characters += CharacterCount(pos, it, true);
if (it == end(text)) break;
pos = std::find(pos, end(text), '}');
if (pos == end(text)) {
characters += CharacterCount(it, pos, true);
break;
}
} while (++pos != end(text));
int duration = line->End - line->Start;
if (duration <= 0 || characters * 1000 / duration >= 1000)
strings[4] = "";
else
strings[4] = std::to_wstring(characters * 1000 / duration);
}
if (paint_columns[11]) {
strings[11].clear();
// Show overrides
if (!replace)
strings[11] = to_wx(line->Text);
// Hidden overrides
else {
strings[11].reserve(line->Text.get().size());
size_t start = 0, pos;
while ((pos = line->Text.get().find('{', start)) != std::string::npos) {
strings[11] += to_wx(line->Text.get().substr(start, pos - start));
strings[11] += rep_char;
start = line->Text.get().find('}', pos);
if (start != std::string::npos) ++start;
}
if (start != std::string::npos)
strings[11] += to_wx(line->Text.get().substr(start));
{
int maxH = (nDraw + 1) * lineHeight;
int x = 0;
dc.SetPen(grid_pen);
for (int width : column_widths) {
x += width;
if (x < w)
dc.DrawLine(x, 0, x, maxH);
}
// Cap length and set text
if (strings[11].size() > 512)
strings[11] = strings[11].Left(512) + "...";
dc.DrawLine(0, 0, 0, maxH);
dc.DrawLine(w - 1, 0, w - 1, maxH);
}
}
void BaseGrid::OnSize(wxSizeEvent &) {
AdjustScrollbar();
int w, h;
GetClientSize(&w, &h);
colWidth[11] = text_col_w = w - text_col_x;
Refresh(false);
}
@ -625,23 +531,11 @@ void BaseGrid::OnContextMenu(wxContextMenuEvent &evt) {
menu::OpenPopupMenu(context_menu.get(), this);
}
else {
const wxString strings[] = {
_("Line Number"),
_("Layer"),
_("Start"),
_("End"),
_("Characters per Second"),
_("Style"),
_("Actor"),
_("Effect"),
_("Left"),
_("Right"),
_("Vert"),
};
wxMenu menu;
for (size_t i : boost::irange<size_t>(0, boost::size(strings)))
menu.Append(MENU_SHOW_COL + i, strings[i], "", wxITEM_CHECK)->Check(showCol[i]);
for (size_t i : agi::util::range(columns.size())) {
if (columns[i]->CanHide())
menu.Append(MENU_SHOW_COL + i, columns[i]->Description(), "", wxITEM_CHECK)->Check(!!column_shown[i]);
}
PopupMenu(&menu);
}
}
@ -688,94 +582,26 @@ void BaseGrid::SetColumnWidths() {
wxClientDC dc(this);
dc.SetFont(font);
// O(1) widths
int marginLen = dc.GetTextExtent("0000").GetWidth();
int cpsLen = dc.GetTextExtent("999").GetWidth();
WidthHelper helper{dc, {}};
int labelLen = dc.GetTextExtent(std::to_wstring(GetRows())).GetWidth();
int startLen = 0;
int endLen = 0;
if (!byFrame)
startLen = endLen = dc.GetTextExtent(to_wx(AssTime().GetAssFormated())).GetWidth();
std::unordered_map<boost::flyweight<std::string>, int> widths;
auto get_width = [&](boost::flyweight<std::string> const& str) -> int {
if (str.get().empty()) return 0;
auto it = widths.find(str);
if (it != end(widths)) return it->second;
int width = dc.GetTextExtent(to_wx(str)).GetWidth();
widths[str] = width;
return width;
};
// O(n) widths
bool showMargin[3] = { false, false, false };
int styleLen = 0;
int actorLen = 0;
int effectLen = 0;
int maxLayer = 0;
int maxStart = 0;
int maxEnd = 0;
for (auto const& diag : context->ass->Events) {
maxLayer = std::max(maxLayer, diag.Layer);
actorLen = std::max(actorLen, get_width(diag.Actor));
styleLen = std::max(styleLen, get_width(diag.Style));
effectLen = std::max(effectLen, get_width(diag.Effect));
// Margins
for (int j = 0; j < 3; j++) {
if (diag.Margin[j])
showMargin[j] = true;
}
// Times
if (byFrame) {
maxStart = std::max(maxStart, context->videoController->FrameAtTime(diag.Start, agi::vfr::START));
maxEnd = std::max(maxEnd, context->videoController->FrameAtTime(diag.End, agi::vfr::END));
column_widths.clear();
for (auto i : agi::util::range(columns.size())) {
if (!column_shown[i])
column_widths.push_back(0);
else {
int width = columns[i]->Width(context, helper, byFrame);
if (width) // 10 is an arbitrary amount of padding
width = 10 + std::max(width, column_header_widths[i]);
column_widths.push_back(width);
}
}
// Finish layer
int layerLen = maxLayer ? dc.GetTextExtent(std::to_wstring(maxLayer)).GetWidth() : 0;
// Finish times
if (byFrame) {
startLen = dc.GetTextExtent(std::to_wstring(maxStart)).GetWidth();
endLen = dc.GetTextExtent(std::to_wstring(maxEnd)).GetWidth();
text_refresh_rects.clear();
int x = 0;
for (auto i : agi::util::range(columns.size())) {
if (columns[i]->RefreshOnTextChange() && column_widths[i])
text_refresh_rects.emplace_back(x, 0, column_widths[i], h);
}
// Set column widths
colWidth[0] = labelLen;
colWidth[1] = layerLen;
colWidth[2] = startLen;
colWidth[3] = endLen;
colWidth[4] = cpsLen;
colWidth[5] = styleLen;
colWidth[6] = actorLen;
colWidth[7] = effectLen;
for (int i = 0; i < 3; i++)
colWidth[i + 8] = showMargin[i] ? marginLen : 0;
colWidth[11] = 1;
// Hide columns and ensure every visible column is at least as big as its
// header plus padding
for (size_t i : boost::irange<size_t>(0u, showCol.size())) {
if (!showCol[i])
colWidth[i] = 0;
else if (colWidth[i])
colWidth[i] = 10 + std::max(colWidth[i], headerWidth[i]);
}
// Set size of last
int total = std::accumulate(colWidth.begin(), colWidth.end(), 0);
colWidth[11] = std::max(w - total, 0);
time_cols_x = colWidth[0] + colWidth[1];
time_cols_w = colWidth[2] + colWidth[3] + colWidth[4];
cps_col_x = time_cols_x + colWidth[2] + colWidth[3];
cps_col_w = colWidth[4];
text_col_x = total;
text_col_w = colWidth[11];
}
AssDialogue *BaseGrid::GetDialogue(int n) const {

View File

@ -39,10 +39,9 @@ namespace agi {
class OptionValue;
}
class AssDialogue;
struct GridColumn;
class BaseGrid final : public wxWindow {
static const int column_count = 12;
std::vector<agi::signal::Connection> connections;
int lineHeight = 1; ///< Height of a line in pixels in the current font
bool holding = false; ///< Is a drag selection in process?
@ -50,6 +49,13 @@ class BaseGrid final : public wxWindow {
wxScrollBar *scrollBar; ///< The grid's scrollbar
bool byFrame = false; ///< Should times be displayed as frame numbers
std::vector<std::unique_ptr<GridColumn>> columns;
std::vector<int> column_widths;
std::vector<int> column_header_widths;
std::vector<char> column_shown;
std::vector<wxRect> text_refresh_rects;
/// Cached brushes used for row backgrounds
struct {
wxBrush Default;
@ -88,24 +94,8 @@ class BaseGrid final : public wxWindow {
void OnSubtitlesSave();
void OnActiveLineChanged(AssDialogue *);
void DrawImage(wxDC &dc, bool paint_columns[]);
void GetRowStrings(int row, AssDialogue *line, bool *paint_columns, wxString *strings, bool replace, wxString const& rep_char) const;
void ScrollTo(int y);
std::array<int, column_count> colWidth; ///< Width in pixels of each column
std::array<int, column_count> headerWidth; ///< Width in pixels of each column's header
std::array<wxString, column_count> headerNames;
int time_cols_x; ///< Left edge of the times columns
int time_cols_w; ///< Width of the two times columns
int text_col_x; ///< Left edge of the text column
int text_col_w; ///< Width of the text column
int cps_col_x; ///< Left edge of the cps column
int cps_col_w; ///< Width of the cps column
std::array<bool, column_count - 1> showCol; ///< Column visibility mask (Text can't be hidden)
int yPos = 0;
void AdjustScrollbar();

332
src/grid_column.cpp Normal file
View File

@ -0,0 +1,332 @@
// 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 "utils.h"
#include "video_context.h"
#include <wx/dc.h>
int WidthHelper::operator()(boost::flyweight<std::string> const& str) {
if (str.get().empty()) return 0;
auto it = widths.find(str);
if (it != end(widths)) return it->second;
int width = dc.GetTextExtent(to_wx(str)).GetWidth();
widths[str] = width;
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();
}
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, bool) 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, bool) const override {
int max_layer = max_value(&AssDialogue::Layer, c->ass->Events);
return max_layer == 0 ? 0 : helper(std::to_wstring(max_layer));
}
};
struct GridColumnStartTime final : GridColumn {
COLUMN_HEADER(_("Start"))
COLUMN_DESCRIPTION(_("Start Time"))
bool Centered() const override { return true; }
wxString Value(const AssDialogue *d, const agi::Context *c) const override {
if (c)
return std::to_wstring(c->videoController->FrameAtTime(d->Start, agi::vfr::START));
return to_wx(d->Start.GetAssFormated());
}
int Width(const agi::Context *c, WidthHelper &helper, bool by_frame) 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 : GridColumn {
COLUMN_HEADER(_("End"))
COLUMN_DESCRIPTION(_("End Time"))
bool Centered() const override { return true; }
wxString Value(const AssDialogue *d, const agi::Context *c) const override {
if (c)
return std::to_wstring(c->videoController->FrameAtTime(d->End, agi::vfr::END));
return to_wx(d->End.GetAssFormated());
}
int Width(const agi::Context *c, WidthHelper &helper, bool by_frame) 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, bool) 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, bool) 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, bool) const override {
return max_width(&AssDialogue::Actor, c->ass->Events, helper);
}
};
template<int Index>
struct GridColumnMargin : GridColumn {
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, bool) 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<0> {
COLUMN_HEADER(_("Left"))
COLUMN_DESCRIPTION(_("Left Margin"))
};
struct GridColumnMarginRight final : GridColumnMargin<0> {
COLUMN_HEADER(_("Right"))
COLUMN_DESCRIPTION(_("Right Margin"))
};
struct GridColumnMarginVert final : GridColumnMargin<0> {
COLUMN_HEADER(_("Vert"))
COLUMN_DESCRIPTION(_("Vertical Margin"))
};
struct GridColumnCPS final : GridColumn {
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 {
int characters = 0;
auto const& text = d->Text.get();
auto pos = begin(text);
do {
auto it = std::find(pos, end(text), '{');
characters += CharacterCount(pos, it, true);
if (it == end(text)) break;
pos = std::find(pos, end(text), '}');
if (pos == end(text)) {
characters += CharacterCount(it, pos, true);
break;
}
} while (++pos != end(text));
int duration = d->End - d->Start;
if (duration <= 0 || characters * 1000 / duration >= 1000)
return wxS("");
else
return std::to_wstring(characters * 1000 / duration);
}
int Width(const agi::Context *c, WidthHelper &helper, bool) const override {
return helper(wxS("999"));
}
};
class GridColumnText final : public GridColumn {
int override_mode;
wxString replace_char;
agi::signal::Connection override_mode_connection;
agi::signal::Connection replace_char_connection;
public:
GridColumnText()
: override_mode(OPT_GET("Subtitle/Grid/Hide Overrides")->GetInt())
, replace_char(to_wx(OPT_GET("Subtitle/Grid/Hide Overrides Char")->GetString()))
, override_mode_connection(OPT_SUB("Subtitle/Grid/Hide Overrides",
[&](agi::OptionValue const& v) { override_mode = v.GetInt(); }))
, 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;
// Show overrides
if (override_mode == 0)
str = to_wx(d->Text);
// Hidden overrides
else {
str.reserve(d->Text.get().size());
size_t start = 0, pos;
while ((pos = d->Text.get().find('{', start)) != std::string::npos) {
str += to_wx(d->Text.get().substr(start, pos - start));
if (override_mode == 1)
str += replace_char;
start = d->Text.get().find('}', pos);
if (start != std::string::npos) ++start;
}
if (start != std::string::npos)
str += to_wx(d->Text.get().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, bool) 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<GridColumnEffect>());
ret.push_back(make<GridColumnActor>());
ret.push_back(make<GridColumnMarginLeft>());
ret.push_back(make<GridColumnMarginRight>());
ret.push_back(make<GridColumnMarginVert>());
ret.push_back(make<GridColumnText>());
return ret;
}

60
src/grid_column.h Normal file
View File

@ -0,0 +1,60 @@
// 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 <boost/flyweight.hpp>
#include <memory>
#include <string>
#include <vector>
#include <unordered_map>
class AssDialogue;
class wxClientDC;
class wxString;
namespace agi { struct Context; }
namespace std {
template <typename T>
struct hash<boost::flyweight<T>> {
size_t operator()(boost::flyweight<T> const& ss) const {
return hash<const void*>()(&ss.get());
}
};
}
struct WidthHelper {
wxDC &dc;
std::unordered_map<boost::flyweight<std::string>, int> widths;
int operator()(boost::flyweight<std::string> const& str);
int operator()(std::string const& str);
int operator()(wxString const& str);
};
struct GridColumn {
virtual ~GridColumn() = default;
virtual bool Centered() const = 0;
virtual bool CanHide() const { return true; }
virtual bool RefreshOnTextChange() const { return false; }
virtual wxString const& Header() const = 0;
virtual wxString const& Description() const = 0;
virtual wxString Value(const AssDialogue *d, const agi::Context * = nullptr) const = 0;
virtual int Width(const agi::Context *c, WidthHelper &helper, bool by_frame) const = 0;
};
std::vector<std::unique_ptr<GridColumn>> GetGridColumns();