diff --git a/build/Aegisub/Aegisub.vcxproj b/build/Aegisub/Aegisub.vcxproj index 7c2e91466..b1bac423c 100644 --- a/build/Aegisub/Aegisub.vcxproj +++ b/build/Aegisub/Aegisub.vcxproj @@ -171,6 +171,7 @@ + @@ -373,6 +374,7 @@ + diff --git a/build/Aegisub/Aegisub.vcxproj.filters b/build/Aegisub/Aegisub.vcxproj.filters index b9a07eebd..2441580aa 100644 --- a/build/Aegisub/Aegisub.vcxproj.filters +++ b/build/Aegisub/Aegisub.vcxproj.filters @@ -624,6 +624,9 @@ Features\Resolution resampler + + Main UI\Grid + @@ -1181,6 +1184,9 @@ Main UI\Edit box + + Main UI\Grid + diff --git a/libaegisub/include/libaegisub/util.h b/libaegisub/include/libaegisub/util.h index ca92c3edd..f6589e83c 100644 --- a/libaegisub/include/libaegisub/util.h +++ b/libaegisub/include/libaegisub/util.h @@ -19,6 +19,7 @@ #include #include +#include #include #include #include @@ -74,5 +75,10 @@ namespace agi { std::string ErrorString(int error); + template + auto range(Integer end) -> decltype(boost::irange(0, end)) { + return boost::irange(0, end); + } + } // namespace util } // namespace agi diff --git a/src/Makefile b/src/Makefile index 25a1e001c..919c55abc 100644 --- a/src/Makefile +++ b/src/Makefile @@ -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 \ diff --git a/src/base_grid.cpp b/src/base_grid.cpp index 4002a5edb..970501c0b 100644 --- a/src/base_grid.cpp +++ b/src/base_grid.cpp @@ -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 #include #include -#include #include #include #include -#include #include #include @@ -65,23 +64,11 @@ enum { MENU_SHOW_COL = 1250 // Needs 15 IDs after this }; -namespace std { - template - struct hash> { - size_t operator()(boost::flyweight const& ss) const { - return hash()(&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(std::begin(showCol), std::end(showCol))); + OPT_SET("Subtitle/Grid/Column")->SetListBool(std::vector(std::begin(column_shown), std::end(column_shown))); SetColumnWidths(); Refresh(false); @@ -203,13 +190,12 @@ void BaseGrid::UpdateStyle() { // Set column widths std::vector 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 paint_columns; + std::vector 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(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, int> widths; - auto get_width = [&](boost::flyweight 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(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 { diff --git a/src/base_grid.h b/src/base_grid.h index 5c6d661e3..726ee726a 100644 --- a/src/base_grid.h +++ b/src/base_grid.h @@ -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 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> columns; + std::vector column_widths; + std::vector column_header_widths; + std::vector column_shown; + + std::vector 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 colWidth; ///< Width in pixels of each column - std::array headerWidth; ///< Width in pixels of each column's header - std::array 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 showCol; ///< Column visibility mask (Text can't be hidden) - int yPos = 0; void AdjustScrollbar(); diff --git a/src/grid_column.cpp b/src/grid_column.cpp new file mode 100644 index 000000000..e71b522f2 --- /dev/null +++ b/src/grid_column.cpp @@ -0,0 +1,332 @@ +// 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 "utils.h" +#include "video_context.h" + +#include + +int WidthHelper::operator()(boost::flyweight 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 +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, 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 +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, 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 +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 +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; +} diff --git a/src/grid_column.h b/src/grid_column.h new file mode 100644 index 000000000..10d8501d3 --- /dev/null +++ b/src/grid_column.h @@ -0,0 +1,60 @@ +// 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 +#include +#include +#include +#include + +class AssDialogue; +class wxClientDC; +class wxString; +namespace agi { struct Context; } + +namespace std { + template + struct hash> { + size_t operator()(boost::flyweight const& ss) const { + return hash()(&ss.get()); + } + }; +} + +struct WidthHelper { + wxDC &dc; + std::unordered_map, int> widths; + + int operator()(boost::flyweight 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> GetGridColumns(); \ No newline at end of file