From d5aae26d838db103fe84f6b4c3ae72ca65403019 Mon Sep 17 00:00:00 2001 From: Thomas Goyne Date: Tue, 4 Dec 2012 14:35:59 -0800 Subject: [PATCH] Use boost::flyweight to intern the wxString members of AssDialogue 100 no-op non-amend commits on a subtitle file with 6689 dialogue lines, with the undo limit set to 100: Without flyweight: No video open: Initial memory usage: 30.6 MB Final memory usage: 498.0 MB Elapsed time: 6.3 seconds Video open, using libass: Initial memory usage: 54.3 MB Final memory usage: 653.3 MB Elapsed time: 23.7 seconds With flyweight: No video open: Initial memory usage: 26.0 MB Final memory usage: 104.5 MB Elapsed time: 3.0 seconds Video open, using libass: Initial memory usage: 46.7 MB Final memory usage: 251.8 MB Elapsed time: 13.0 seconds No video open: Memory usage: -79% Time: -52% Video open: Memory usage: -61.5% Time: -45% 100 no-op amend commits on a line in the middle of a subtitle file with 6689 dialogue lines, with video open: Without flyweight: Initial memory usage: 48.2 MB Final memory usage: 182.3 MB Elapsed time: 22.3 seconds With flyweight: Initial memory usage: 39.8 MB Final memory usage: 165.8 MB Elapsed time: 13.8 seconds Note: The large jump in memory usage here is due to that the benchmark is blocking the main thread, so at the end there are ~100 video frames waiting to be displayed. --- aegisub/src/agi_pre.h | 1 + aegisub/src/ass_dialogue.cpp | 48 ++++++++-------- aegisub/src/ass_dialogue.h | 11 ++-- aegisub/src/base_grid.cpp | 10 ++-- aegisub/src/command/edit.cpp | 8 +-- aegisub/src/dialog_resample.cpp | 2 +- aegisub/src/dialog_search_replace.cpp | 41 +++++++------- aegisub/src/dialog_search_replace.h | 2 - aegisub/src/dialog_selection.cpp | 4 +- aegisub/src/dialog_spellchecker.cpp | 6 +- aegisub/src/dialog_styling_assistant.cpp | 2 +- aegisub/src/export_fixstyle.cpp | 2 +- aegisub/src/font_file_lister.cpp | 2 +- aegisub/src/subs_edit_box.cpp | 10 ++-- aegisub/src/subs_edit_box.h | 3 +- aegisub/src/subs_edit_ctrl.cpp | 2 +- aegisub/src/subs_grid.cpp | 63 ++++++++++++--------- aegisub/src/subtitle_format.cpp | 12 ++-- aegisub/src/subtitle_format_ebu3264.cpp | 2 +- aegisub/src/subtitle_format_encore.cpp | 7 +-- aegisub/src/subtitle_format_microdvd.cpp | 2 +- aegisub/src/subtitle_format_srt.cpp | 18 +++--- aegisub/src/subtitle_format_transtation.cpp | 13 +---- aegisub/src/subtitle_format_txt.cpp | 2 +- aegisub/src/visual_tool_clip.cpp | 2 +- aegisub/src/visual_tool_vector_clip.cpp | 2 +- 26 files changed, 144 insertions(+), 133 deletions(-) diff --git a/aegisub/src/agi_pre.h b/aegisub/src/agi_pre.h index 6b2c7f6c5..125b081bf 100644 --- a/aegisub/src/agi_pre.h +++ b/aegisub/src/agi_pre.h @@ -69,6 +69,7 @@ #include #endif +#include #include #include #include diff --git a/aegisub/src/ass_dialogue.cpp b/aegisub/src/ass_dialogue.cpp index 0d4b0dc84..f1491bd05 100644 --- a/aegisub/src/ass_dialogue.cpp +++ b/aegisub/src/ass_dialogue.cpp @@ -33,6 +33,7 @@ #include "config.h" +#include #include #include @@ -47,6 +48,10 @@ #include +std::size_t hash_value(wxString const& s) { + return wxStringHash()(s); +} + AssDialogue::AssDialogue() : AssEntry(wxString()) , Comment(false) @@ -128,15 +133,11 @@ bool AssDialogue::Parse(wxString const& rawData) { // Get style if (!tkn.HasMoreTokens()) return false; - Style = tkn.GetNextToken(); - Style.Trim(true); - Style.Trim(false); + Style = tkn.GetNextToken().Trim(true).Trim(false); // Get actor if (!tkn.HasMoreTokens()) return false; - Actor = tkn.GetNextToken(); - Actor.Trim(true); - Actor.Trim(false); + Actor = tkn.GetNextToken().Trim(true).Trim(false); // Get margins for (int i = 0; i < 3; ++i) { @@ -145,9 +146,7 @@ bool AssDialogue::Parse(wxString const& rawData) { } if (!tkn.HasMoreTokens()) return false; - Effect = tkn.GetNextToken(); - Effect.Trim(true); - Effect.Trim(false); + Effect = tkn.GetNextToken().Trim(true).Trim(false); // Get text Text = rawData.Mid(pos + tkn.GetPosition()); @@ -172,7 +171,7 @@ wxString AssDialogue::GetData(bool ssa) const { s, a, Margin[0], Margin[1], Margin[2], e, - Text); + Text.get()); // Make sure that final has no line breaks str.Replace("\n", ""); @@ -193,17 +192,17 @@ std::auto_ptr> AssDialogue::ParseTags() cons boost::ptr_vector Blocks; // Empty line, make an empty block - if (Text.empty()) { + if (Text.get().empty()) { Blocks.push_back(new AssDialogueBlockPlain); return Blocks.release(); } int drawingLevel = 0; - for (size_t len = Text.size(), cur = 0; cur < len; ) { + for (size_t len = Text.get().size(), cur = 0; cur < len; ) { // Overrides block - if (Text[cur] == '{') { - size_t end = Text.find('}', cur); + if (Text.get()[cur] == '{') { + size_t end = Text.get().find('}', cur); // VSFilter requires that override blocks be closed, while libass // does not. We match VSFilter here. @@ -212,7 +211,7 @@ std::auto_ptr> AssDialogue::ParseTags() cons ++cur; // Get contents of block - wxString work = Text.substr(cur, end - cur); + wxString work = Text.get().substr(cur, end - cur); cur = end + 1; if (work.size() && work.find('\\') == wxString::npos) { @@ -240,13 +239,13 @@ std::auto_ptr> AssDialogue::ParseTags() cons // Plain-text/drawing block plain: wxString work; - size_t end = Text.find('{', cur + 1); + size_t end = Text.get().find('{', cur + 1); if (end == wxString::npos) { - work = Text.substr(cur); + work = Text.get().substr(cur); cur = len; } else { - work = Text.substr(cur, end - cur); + work = Text.get().substr(cur, end - cur); cur = end; } @@ -265,12 +264,12 @@ void AssDialogue::StripTags() { void AssDialogue::StripTag(wxString const& tag_name) { boost::ptr_vector blocks(ParseTags()); - Text.clear(); + wxString new_text; // Look for blocks for (auto& block : blocks) { if (block.GetType() != BLOCK_OVERRIDE) { - Text += block.GetText(); + new_text += block.GetText(); continue; } @@ -282,15 +281,16 @@ void AssDialogue::StripTag(wxString const& tag_name) { } if (!temp.empty()) - Text += "{" + temp + "}"; + new_text += "{" + temp + "}"; } + + Text = new_text; } +static wxString get_text(AssDialogueBlock &d) { return d.GetText(); } void AssDialogue::UpdateText(boost::ptr_vector& blocks) { if (blocks.empty()) return; - Text.clear(); - for (auto& block : blocks) - Text += block.GetText(); + Text = join(blocks | boost::adaptors::transformed(get_text), wxS("")); } void AssDialogue::SetMarginString(wxString const& origvalue, int which) { diff --git a/aegisub/src/ass_dialogue.h b/aegisub/src/ass_dialogue.h index 7c8d51f0f..1d2099887 100644 --- a/aegisub/src/ass_dialogue.h +++ b/aegisub/src/ass_dialogue.h @@ -37,6 +37,7 @@ #include +#include #include #include @@ -50,6 +51,8 @@ enum AssBlockType { class AssOverrideParameter; class AssOverrideTag; +std::size_t hash_value(wxString const& s); + /// @class AssDialogueBlock /// @brief AssDialogue Blocks /// @@ -131,13 +134,13 @@ public: /// Ending time AssTime End; /// Style name - wxString Style; + boost::flyweight Style; /// Actor name - wxString Actor; + boost::flyweight Actor; /// Effect name - wxString Effect; + boost::flyweight Effect; /// Raw text data - wxString Text; + boost::flyweight Text; AssEntryGroup Group() const override { return ENTRY_DIALOGUE; } diff --git a/aegisub/src/base_grid.cpp b/aegisub/src/base_grid.cpp index e6607a1f2..6366ae4cc 100644 --- a/aegisub/src/base_grid.cpp +++ b/aegisub/src/base_grid.cpp @@ -607,15 +607,15 @@ void BaseGrid::GetRowStrings(int row, AssDialogue *line, bool *paint_columns, wx // Hidden overrides if (replace) { - strings[10].reserve(line->Text.size()); + strings[10].reserve(line->Text.get().size()); size_t start = 0, pos; - while ((pos = line->Text.find('{', start)) != wxString::npos) { - strings[10] += line->Text.Mid(start, pos - start); + while ((pos = line->Text.get().find('{', start)) != wxString::npos) { + strings[10] += line->Text.get().Mid(start, pos - start); strings[10] += rep_char; - start = line->Text.find('}', pos); + start = line->Text.get().find('}', pos); if (start != wxString::npos) ++start; } - strings[10] += line->Text.Mid(start); + strings[10] += line->Text.get().Mid(start); } // Show overrides diff --git a/aegisub/src/command/edit.cpp b/aegisub/src/command/edit.cpp index f9ab14512..6afe4cccf 100644 --- a/aegisub/src/command/edit.cpp +++ b/aegisub/src/command/edit.cpp @@ -207,7 +207,7 @@ void set_tag(AssDialogue *line, boost::ptr_vector &blocks, wxS // Cursor is in a comment block, so try the previous block instead if (plain->GetText().StartsWith("{")) { --blockn; - start = line->Text.rfind('{', start); + start = line->Text.get().rfind('{', start); } else break; @@ -227,7 +227,7 @@ void set_tag(AssDialogue *line, boost::ptr_vector &blocks, wxS wxString insert = tag + value; int shift = insert.size(); if (plain || blockn < 0) { - line->Text = line->Text.Left(start) + "{" + insert + "}" + line->Text.Mid(start); + line->Text = line->Text.get().Left(start) + "{" + insert + "}" + line->Text.get().Mid(start); shift += 2; blocks = line->ParseTags(); } @@ -681,11 +681,11 @@ static void combine_lines(agi::Context *c, void (*combiner)(AssDialogue *, AssDi } static void combine_karaoke(AssDialogue *first, AssDialogue *second) { - first->Text += wxString::Format("{\\k%d}%s", (second->Start - first->End) / 10, second->Text); + first->Text = wxString::Format("%s{\\k%d}%s", first->Text.get(), (second->Start - first->End) / 10, second->Text.get()); } static void combine_concat(AssDialogue *first, AssDialogue *second) { - first->Text += " " + second->Text; + first->Text = first->Text + " " + second->Text; } static void combine_drop(AssDialogue *, AssDialogue *) { } diff --git a/aegisub/src/dialog_resample.cpp b/aegisub/src/dialog_resample.cpp index a04fa301d..681fec3e4 100644 --- a/aegisub/src/dialog_resample.cpp +++ b/aegisub/src/dialog_resample.cpp @@ -202,7 +202,7 @@ namespace { void resample_line(resample_state *state, AssEntry &line) { AssDialogue *diag = dynamic_cast(&line); - if (diag && !(diag->Comment && (diag->Effect.StartsWith("template") || diag->Effect.StartsWith("code")))) { + if (diag && !(diag->Comment && (diag->Effect.get().StartsWith("template") || diag->Effect.get().StartsWith("code")))) { boost::ptr_vector blocks(diag->ParseTags()); for (auto block : blocks | agi::of_type()) diff --git a/aegisub/src/dialog_search_replace.cpp b/aegisub/src/dialog_search_replace.cpp index 5751398ef..e7d21e898 100644 --- a/aegisub/src/dialog_search_replace.cpp +++ b/aegisub/src/dialog_search_replace.cpp @@ -243,6 +243,13 @@ void SearchReplaceEngine::FindNext() { ReplaceNext(false); } +static boost::flyweight *get_text(AssDialogue *cur, int field) { + if (field == 0) return &cur->Text; + else if (field == 1) return &cur->Style; + else if (field == 2) return &cur->Actor; + else if (field == 3) return &cur->Effect; + else throw wxString("Invalid field"); +} void SearchReplaceEngine::ReplaceNext(bool DoReplace) { if (!CanContinue) { @@ -267,7 +274,6 @@ void SearchReplaceEngine::ReplaceNext(bool DoReplace) { int start = curLine; int nrows = context->subsGrid->GetRows(); bool found = false; - wxString *Text = nullptr; size_t tempPos; int regFlags = wxRE_ADVANCED; if (!matchCase) { @@ -276,8 +282,9 @@ void SearchReplaceEngine::ReplaceNext(bool DoReplace) { } // Search for it + boost::flyweight *Text = nullptr; while (!found) { - Text = GetText(context->subsGrid->GetDialogue(curLine), field); + Text = get_text(context->subsGrid->GetDialogue(curLine), field); if (DoReplace && LastWasFind) tempPos = pos; else @@ -287,7 +294,7 @@ void SearchReplaceEngine::ReplaceNext(bool DoReplace) { if (isReg) { wxRegEx regex (LookFor,regFlags); if (regex.IsValid()) { - if (regex.Matches(Text->Mid(tempPos))) { + if (regex.Matches(Text->get().Mid(tempPos))) { size_t match_start; regex.GetMatch(&match_start,&matchLen,0); pos = match_start + tempPos; @@ -298,7 +305,7 @@ void SearchReplaceEngine::ReplaceNext(bool DoReplace) { // Normal else { - wxString src = Text->Mid(tempPos); + wxString src = Text->get().Mid(tempPos); if (!matchCase) src.MakeLower(); int textPos = src.Find(LookFor); if (textPos != -1) { @@ -325,16 +332,16 @@ void SearchReplaceEngine::ReplaceNext(bool DoReplace) { if (DoReplace) { // Replace with regular expressions if (isReg) { - wxString toReplace = Text->Mid(pos,matchLen); + wxString toReplace = Text->get().Mid(pos,matchLen); wxRegEx regex(LookFor,regFlags); regex.ReplaceFirst(&toReplace,ReplaceWith); - *Text = Text->Left(pos) + toReplace + Text->Mid(pos+matchLen); + *Text = Text->get().Left(pos) + toReplace + Text->get().Mid(pos+matchLen); replaceLen = toReplace.Length(); } // Normal replace else { - *Text = Text->Left(pos) + ReplaceWith + Text->Mid(pos+matchLen); + *Text = Text->get().Left(pos) + ReplaceWith + Text->get().Mid(pos+matchLen); replaceLen = ReplaceWith.Length(); } @@ -387,7 +394,7 @@ void SearchReplaceEngine::ReplaceAll() { if (inSel && hasSelection && !sel.count(diag)) continue; - wxString *Text = GetText(diag, field); + boost::flyweight *Text = get_text(diag, field); // Regular expressions if (isReg) { @@ -398,7 +405,9 @@ void SearchReplaceEngine::ReplaceAll() { // A zero length match (such as '$') will always be replaced // maxMatches times, which is almost certainly not what the user // wanted, so limit it to one replacement in that situation - count += reg.Replace(Text, ReplaceWith, len > 0 ? 1000 : 1); + wxString repl(*Text); + count += reg.Replace(&repl, ReplaceWith, len > 0 ? 1000 : 1); + *Text = repl; } } // Normal replace @@ -424,8 +433,10 @@ void SearchReplaceEngine::ReplaceAll() { *Text = Left + Right; } } - else if(Text->Contains(LookFor)) { - count += Text->Replace(LookFor, ReplaceWith); + else if(Text->get().Contains(LookFor)) { + wxString repl(*Text); + count += repl.Replace(LookFor, ReplaceWith); + *Text = repl; } } } @@ -475,12 +486,4 @@ void SearchReplaceEngine::OpenDialog (bool replace) { hasReplace = replace; } -wxString *SearchReplaceEngine::GetText(AssDialogue *cur, int field) { - if (field == 0) return &cur->Text; - else if (field == 1) return &cur->Style; - else if (field == 2) return &cur->Actor; - else if (field == 3) return &cur->Effect; - else throw wxString("Invalid field"); -} - SearchReplaceEngine Search; diff --git a/aegisub/src/dialog_search_replace.h b/aegisub/src/dialog_search_replace.h index 76a1cc3e5..b14399b0b 100644 --- a/aegisub/src/dialog_search_replace.h +++ b/aegisub/src/dialog_search_replace.h @@ -59,8 +59,6 @@ class SearchReplaceEngine { wxString LookFor; wxString ReplaceWith; - wxString *GetText(AssDialogue *cur, int field); - public: agi::Context *context; diff --git a/aegisub/src/dialog_selection.cpp b/aegisub/src/dialog_selection.cpp index 1751178d8..46e30441f 100644 --- a/aegisub/src/dialog_selection.cpp +++ b/aegisub/src/dialog_selection.cpp @@ -67,7 +67,7 @@ enum { DEFINE_SIMPLE_EXCEPTION(BadRegex, agi::InvalidInputException, "bad_regex") -static wxString AssDialogue::* get_field(int field_n) { +static boost::flyweight AssDialogue::* get_field(int field_n) { switch(field_n) { case FIELD_TEXT: return &AssDialogue::Text; break; case FIELD_STYLE: return &AssDialogue::Style; break; @@ -109,7 +109,7 @@ static std::set process(wxString match_text, bool match_case, int match_case = false; } - wxString AssDialogue::*field = get_field(field_n); + boost::flyweight AssDialogue::*field = get_field(field_n); std::function pred = get_predicate(mode, &re, match_case, match_text); std::set matches; diff --git a/aegisub/src/dialog_spellchecker.cpp b/aegisub/src/dialog_spellchecker.cpp index 20145ea3e..edb4cd8c4 100644 --- a/aegisub/src/dialog_spellchecker.cpp +++ b/aegisub/src/dialog_spellchecker.cpp @@ -281,8 +281,10 @@ void DialogSpellChecker::Replace() { AssDialogue *active_line = context->selectionController->GetActiveLine(); // Only replace if the user hasn't changed the selection to something else - if (active_line->Text.Mid(word_start, word_len) == orig_word->GetValue()) { - active_line->Text.replace(word_start, word_len, replace_word->GetValue()); + if (active_line->Text.get().Mid(word_start, word_len) == orig_word->GetValue()) { + wxString text = active_line->Text; + text.replace(word_start, word_len, replace_word->GetValue()); + active_line->Text = text; context->ass->Commit(_("spell check replace"), AssFile::COMMIT_DIAG_TEXT); context->textSelectionController->SetInsertionPoint(word_start + replace_word->GetValue().size()); } diff --git a/aegisub/src/dialog_styling_assistant.cpp b/aegisub/src/dialog_styling_assistant.cpp index 661c4d5b5..7442840a8 100644 --- a/aegisub/src/dialog_styling_assistant.cpp +++ b/aegisub/src/dialog_styling_assistant.cpp @@ -159,7 +159,7 @@ void DialogStyling::OnActiveLineChanged(AssDialogue *new_line) { current_line_text->SetValue(active_line->Text); style_name->SetValue(active_line->Style); - style_name->SetSelection(0, active_line->Style.size()); + style_name->SetSelection(0, active_line->Style.get().size()); style_name->SetFocus(); style_list->SetStringSelection(active_line->Style); diff --git a/aegisub/src/export_fixstyle.cpp b/aegisub/src/export_fixstyle.cpp index a8967406a..bb129947d 100644 --- a/aegisub/src/export_fixstyle.cpp +++ b/aegisub/src/export_fixstyle.cpp @@ -55,7 +55,7 @@ void AssFixStylesFilter::ProcessSubs(AssFile *subs, wxWindow *) { styles.Sort(); for (auto diag : subs->Line | agi::of_type()) { - if (!std::binary_search(styles.begin(), styles.end(), diag->Style.Lower())) + if (!std::binary_search(styles.begin(), styles.end(), diag->Style.get().Lower())) diag->Style = "Default"; } } diff --git a/aegisub/src/font_file_lister.cpp b/aegisub/src/font_file_lister.cpp index 5184d74dd..9c4e276a1 100644 --- a/aegisub/src/font_file_lister.cpp +++ b/aegisub/src/font_file_lister.cpp @@ -60,7 +60,7 @@ void FontCollector::ProcessDialogueLine(const AssDialogue *line, int index) { wxString name = tag->Name; if (name == "\\r") { - style = styles[tag->Params[0]->Get(line->Style)]; + style = styles[tag->Params[0]->Get(line->Style)]; overriden = false; } else if (name == "\\b") { diff --git a/aegisub/src/subs_edit_box.cpp b/aegisub/src/subs_edit_box.cpp index aff468e0d..27efa2c67 100644 --- a/aegisub/src/subs_edit_box.cpp +++ b/aegisub/src/subs_edit_box.cpp @@ -306,7 +306,7 @@ void SubsEditBox::OnCommit(int type) { } } -void SubsEditBox::PopulateList(wxComboBox *combo, wxString AssDialogue::*field) { +void SubsEditBox::PopulateList(wxComboBox *combo, boost::flyweight AssDialogue::*field) { wxEventBlocker blocker(this); std::set values; @@ -397,7 +397,7 @@ void SubsEditBox::SetSelectedRows(T AssDialogue::*field, T value, wxString const } void SubsEditBox::CommitText(wxString const& desc) { - SetSelectedRows(&AssDialogue::Text, TextEdit->GetText(), desc, AssFile::COMMIT_DIAG_TEXT, true); + SetSelectedRows(&AssDialogue::Text, boost::flyweight(TextEdit->GetText()), desc, AssFile::COMMIT_DIAG_TEXT, true); } void SubsEditBox::CommitTimes(TimeField field) { @@ -487,12 +487,12 @@ void SubsEditBox::SetControlsState(bool state) { } void SubsEditBox::OnStyleChange(wxCommandEvent &) { - SetSelectedRows(&AssDialogue::Style, StyleBox->GetValue(), _("style change"), AssFile::COMMIT_DIAG_META); + SetSelectedRows(&AssDialogue::Style, boost::flyweight(StyleBox->GetValue()), _("style change"), AssFile::COMMIT_DIAG_META); } void SubsEditBox::OnActorChange(wxCommandEvent &evt) { bool amend = evt.GetEventType() == wxEVT_COMMAND_TEXT_UPDATED; - SetSelectedRows(&AssDialogue::Actor, ActorBox->GetValue(), _("actor change"), AssFile::COMMIT_DIAG_META, amend); + SetSelectedRows(&AssDialogue::Actor, boost::flyweight(ActorBox->GetValue()), _("actor change"), AssFile::COMMIT_DIAG_META, amend); PopulateList(ActorBox, &AssDialogue::Actor); } @@ -502,7 +502,7 @@ void SubsEditBox::OnLayerEnter(wxCommandEvent &) { void SubsEditBox::OnEffectChange(wxCommandEvent &evt) { bool amend = evt.GetEventType() == wxEVT_COMMAND_TEXT_UPDATED; - SetSelectedRows(&AssDialogue::Effect, Effect->GetValue(), _("effect change"), AssFile::COMMIT_DIAG_META, amend); + SetSelectedRows(&AssDialogue::Effect, boost::flyweight(Effect->GetValue()), _("effect change"), AssFile::COMMIT_DIAG_META, amend); PopulateList(Effect, &AssDialogue::Effect); } diff --git a/aegisub/src/subs_edit_box.h b/aegisub/src/subs_edit_box.h index f66f404d9..9cc03ec36 100644 --- a/aegisub/src/subs_edit_box.h +++ b/aegisub/src/subs_edit_box.h @@ -35,6 +35,7 @@ #include #include #include +#include #include #include @@ -178,7 +179,7 @@ class SubsEditBox : public wxPanel { void OnCommit(int type); /// Regenerate a dropdown list with the unique values of a dialogue field - void PopulateList(wxComboBox *combo, wxString AssDialogue::*field); + void PopulateList(wxComboBox *combo, boost::flyweight AssDialogue::*field); /// @brief Enable or disable frame timing mode void UpdateFrameTiming(agi::vfr::Framerate const& fps); diff --git a/aegisub/src/subs_edit_ctrl.cpp b/aegisub/src/subs_edit_ctrl.cpp index c1951027a..3cdf73427 100644 --- a/aegisub/src/subs_edit_ctrl.cpp +++ b/aegisub/src/subs_edit_ctrl.cpp @@ -212,7 +212,7 @@ void SubsTextEditCtrl::UpdateStyle() { } AssDialogue *diag = context ? context->selectionController->GetActiveLine() : 0; - bool template_line = diag && diag->Comment && diag->Effect.Lower().StartsWith("template"); + bool template_line = diag && diag->Comment && diag->Effect.get().Lower().StartsWith("template"); tokenized_line = agi::ass::TokenizeDialogueBody(line_text, template_line); agi::ass::SplitWords(line_text, tokenized_line); diff --git a/aegisub/src/subs_grid.cpp b/aegisub/src/subs_grid.cpp index 936cbf267..c68f4bedc 100644 --- a/aegisub/src/subs_grid.cpp +++ b/aegisub/src/subs_grid.cpp @@ -57,11 +57,11 @@ SubtitlesGrid::SubtitlesGrid(wxWindow *parent, agi::Context *context) { } -static void trim_text(AssDialogue *diag) { +static void trim_text(wxString *text) { static wxRegEx start("^( |\t|\\\\[nNh])+"); static wxRegEx end("( |\t|\\\\[nNh])+$"); - start.ReplaceFirst(&diag->Text, ""); - end.ReplaceFirst(&diag->Text, ""); + start.ReplaceFirst(text, ""); + end.ReplaceFirst(text, ""); } static void expand_times(AssDialogue *src, AssDialogue *dst) { @@ -69,6 +69,25 @@ static void expand_times(AssDialogue *src, AssDialogue *dst) { dst->End = std::max(dst->End, src->End); } +static bool check_lines(AssDialogue *d1, AssDialogue *d2, bool (wxString::*pred)(wxString const&, wxString *) const) { + wxString rest; + if ((d1->Text.get().*pred)(d2->Text.get(), &rest)) { + trim_text(&rest); + d1->Text = rest; + expand_times(d1, d2); + return true; + } + return false; +} + +static bool check_start(AssDialogue *d1, AssDialogue *d2) { + return check_lines(d1, d2, &wxString::StartsWith); +} + +static bool check_end(AssDialogue *d1, AssDialogue *d2) { + return check_lines(d1, d2, &wxString::EndsWith); +} + /// @brief Recombine void SubtitlesGrid::RecombineLines() { Selection selectedSet = GetSelectedSet(); @@ -77,14 +96,17 @@ void SubtitlesGrid::RecombineLines() { AssDialogue *activeLine = GetActiveLine(); std::vector sel(selectedSet.begin(), selectedSet.end()); - for_each(sel.begin(), sel.end(), trim_text); sort(sel.begin(), sel.end(), &AssFile::CompStart); + for (auto &diag : sel) { + wxString text = diag->Text; + trim_text(&text); + diag->Text = text; + } - typedef std::vector::iterator diag_iter; - diag_iter end = sel.end() - 1; - for (diag_iter cur = sel.begin(); cur != end; ++cur) { + auto end = sel.end() - 1; + for (auto cur = sel.begin(); cur != end; ++cur) { AssDialogue *d1 = *cur; - diag_iter d2 = cur + 1; + auto d2 = cur + 1; // 1, 1+2 (or 2+1), 2 gets turned into 1, 2, 2 so kill the duplicate if (d1->Text == (*d2)->Text) { @@ -94,43 +116,32 @@ void SubtitlesGrid::RecombineLines() { } // 1, 1+2, 1 turns into 1, 2, [empty] - if (d1->Text.empty()) { + if (d1->Text.get().empty()) { delete d1; continue; } + // If d2 is the last line in the selection it'll never hit the above test - if (d2 == end && (*d2)->Text.empty()) { + if (d2 == end && (*d2)->Text.get().empty()) { delete *d2; continue; } // 1, 1+2 - while (d2 <= end && (*d2)->Text.StartsWith(d1->Text, &(*d2)->Text)) { - expand_times(*d2, d1); - trim_text(*d2); + while (d2 <= end && check_start(*d2, d1)) ++d2; - } // 1, 2+1 - while (d2 <= end && (*d2)->Text.EndsWith(d1->Text, &(*d2)->Text)) { - expand_times(*d2, d1); - trim_text(*d2); + while (d2 <= end && check_end(*d2, d1)) ++d2; - } // 1+2, 2 - while (d2 <= end && d1->Text.EndsWith((*d2)->Text, &d1->Text)) { - expand_times(d1, *d2); - trim_text(d1); + while (d2 <= end && check_end(d1, *d2)) ++d2; - } // 2+1, 2 - while (d2 <= end && d1->Text.StartsWith((*d2)->Text, &d1->Text)) { - expand_times(d1, *d2); - trim_text(d1); + while (d2 <= end && check_start(d1, *d2)) ++d2; - } } // Remove now non-existent lines from the selection diff --git a/aegisub/src/subtitle_format.cpp b/aegisub/src/subtitle_format.cpp index 924f2b27d..34f8794c1 100644 --- a/aegisub/src/subtitle_format.cpp +++ b/aegisub/src/subtitle_format.cpp @@ -170,19 +170,21 @@ void SubtitleFormat::StripTags(AssFile &file) { void SubtitleFormat::ConvertNewlines(AssFile &file, wxString const& newline, bool mergeLineBreaks) { for (auto current : file.Line | agi::of_type()) { - current->Text.Replace("\\h", " "); - current->Text.Replace("\\n", newline); - current->Text.Replace("\\N", newline); + wxString repl(current->Text); + repl.Replace("\\h", " "); + repl.Replace("\\n", newline); + repl.Replace("\\N", newline); if (mergeLineBreaks) { - while (current->Text.Replace(newline+newline, newline)); + while (repl.Replace(newline+newline, newline)); } + current->Text = repl; } } void SubtitleFormat::StripComments(AssFile &file) { file.Line.remove_and_dispose_if([](AssEntry const& e) { const AssDialogue *diag = dynamic_cast(&e); - return diag && (diag->Comment || !diag->Text); + return diag && (diag->Comment || !diag->Text.get()); }, [](AssEntry *e) { delete e; }); } diff --git a/aegisub/src/subtitle_format_ebu3264.cpp b/aegisub/src/subtitle_format_ebu3264.cpp index 537c3d433..f8da0bab4 100644 --- a/aegisub/src/subtitle_format_ebu3264.cpp +++ b/aegisub/src/subtitle_format_ebu3264.cpp @@ -416,7 +416,7 @@ namespace else if (!imline.CheckLineLengths(export_settings.max_line_length)) { if (export_settings.line_wrapping_mode == EbuExportSettings::AbortOverLength) - throw Ebu3264SubtitleFormat::ConversionFailed(STD_STR(wxString::Format(_("Line over maximum length: %s"), line->Text.c_str())), 0); + throw Ebu3264SubtitleFormat::ConversionFailed(STD_STR(wxString::Format(_("Line over maximum length: %s"), line->Text.get())), 0); else // skip over-long lines subs_list.pop_back(); } diff --git a/aegisub/src/subtitle_format_encore.cpp b/aegisub/src/subtitle_format_encore.cpp index 634a213af..2312d765f 100644 --- a/aegisub/src/subtitle_format_encore.cpp +++ b/aegisub/src/subtitle_format_encore.cpp @@ -70,7 +70,6 @@ void EncoreSubtitleFormat::WriteFile(const AssFile *src, wxString const& filenam StripTags(copy); ConvertNewlines(copy, "\r\n"); - // Encode wants ; for NTSC and : for PAL // The manual suggests no other frame rates are supported char sep = fps.NeedsDropFrames() ? ';' : ':'; @@ -79,8 +78,6 @@ void EncoreSubtitleFormat::WriteFile(const AssFile *src, wxString const& filenam // Write lines int i = 0; TextFileWriter file(filename, "UTF-8"); - for (auto current : copy.Line | agi::of_type()) { - ++i; - file.WriteLineToFile(wxString::Format("%i %s %s %s", i, ft.ToSMPTE(current->Start), ft.ToSMPTE(current->End), current->Text)); - } + for (auto current : copy.Line | agi::of_type()) + file.WriteLineToFile(wxString::Format("%i %s %s %s", ++i, ft.ToSMPTE(current->Start), ft.ToSMPTE(current->End), current->Text.get())); } diff --git a/aegisub/src/subtitle_format_microdvd.cpp b/aegisub/src/subtitle_format_microdvd.cpp index d7f27b298..0468de18a 100644 --- a/aegisub/src/subtitle_format_microdvd.cpp +++ b/aegisub/src/subtitle_format_microdvd.cpp @@ -146,6 +146,6 @@ void MicroDVDSubtitleFormat::WriteFile(const AssFile *src, wxString const& filen int start = fps.FrameAtTime(current->Start, agi::vfr::START); int end = fps.FrameAtTime(current->End, agi::vfr::END); - file.WriteLineToFile(wxString::Format("{%i}{%i}%s", start, end, current->Text)); + file.WriteLineToFile(wxString::Format("{%i}{%i}%s", start, end, current->Text.get())); } } diff --git a/aegisub/src/subtitle_format_srt.cpp b/aegisub/src/subtitle_format_srt.cpp index c3d29b93f..2e281bbc1 100644 --- a/aegisub/src/subtitle_format_srt.cpp +++ b/aegisub/src/subtitle_format_srt.cpp @@ -402,6 +402,7 @@ void SRTSubtitleFormat::ReadFile(AssFile *target, wxString const& filename, wxSt int line_num = 0; int linebreak_debt = 0; AssDialogue *line = 0; + wxString text; while (file.HasMoreLines()) { wxString text_line = file.ReadLineFromFile(); line_num++; @@ -426,8 +427,8 @@ void SRTSubtitleFormat::ReadFile(AssFile *target, wxString const& filename, wxSt found_timestamps: if (line) { // finalize active line - line->Text = tag_parser.ToAss(line->Text); - line = 0; + line->Text = tag_parser.ToAss(text); + text.clear(); } // create new subtitle line = new AssDialogue; @@ -446,7 +447,7 @@ found_timestamps: linebreak_debt = 0; break; } - line->Text.Append(text_line); + text.Append(text_line); state = STATE_REST_OF_BODY; break; case STATE_REST_OF_BODY: @@ -458,7 +459,7 @@ found_timestamps: linebreak_debt = 1; break; } - line->Text.Append("\\N").Append(text_line); + text.Append("\\N").Append(text_line); break; case STATE_LAST_WAS_BLANK: ++linebreak_debt; @@ -475,20 +476,19 @@ found_timestamps: // assume it's a continuation of the subtitle text // resolve our line break debt and append the line text while (linebreak_debt-- > 0) - line->Text.Append("\\N"); - line->Text.Append(text_line); + text.Append("\\N"); + text.Append(text_line); state = STATE_REST_OF_BODY; break; } } - if (state == 1 || state == 2) { + if (state == 1 || state == 2) throw SRTParseError("Parsing SRT: Incomplete file", 0); - } if (line) // an unfinalized line - line->Text = tag_parser.ToAss(line->Text); + line->Text = tag_parser.ToAss(text); } void SRTSubtitleFormat::WriteFile(const AssFile *src, wxString const& filename, wxString const& encoding) const { diff --git a/aegisub/src/subtitle_format_transtation.cpp b/aegisub/src/subtitle_format_transtation.cpp index 5513546bb..9bec2e137 100644 --- a/aegisub/src/subtitle_format_transtation.cpp +++ b/aegisub/src/subtitle_format_transtation.cpp @@ -71,6 +71,8 @@ void TranStationSubtitleFormat::WriteFile(const AssFile *src, wxString const& fi StripComments(copy); RecombineOverlaps(copy); MergeIdentical(copy); + StripTags(copy); + ConvertNewlines(copy, "\r\n"); SmpteFormatter ft(fps); TextFileWriter file(filename, encoding); @@ -106,7 +108,7 @@ wxString TranStationSubtitleFormat::ConvertLine(AssFile *file, AssDialogue *curr // Hack: If an italics-tag (\i1) appears anywhere in the line, // make it all italics - if (current->Text.Find("\\i1") != wxNOT_FOUND) type = "I"; + if (current->Text.get().Find("\\i1") != wxNOT_FOUND) type = "I"; // Write header AssTime end = current->End; @@ -118,14 +120,5 @@ wxString TranStationSubtitleFormat::ConvertLine(AssFile *file, AssDialogue *curr end = fps.TimeAtFrame(fps.FrameAtTime(end, agi::vfr::END) - 1, agi::vfr::END); wxString header = wxString::Format("SUB[%i%s%s ", valign, halign, type) + ft.ToSMPTE(current->Start) + ">" + ft.ToSMPTE(end) + "]\r\n"; - - // Process text - wxString lineEnd = "\r\n"; - current->StripTags(); - current->Text.Replace("\\h", " ", true); - current->Text.Replace("\\n", lineEnd, true); - current->Text.Replace("\\N", lineEnd, true); - while (current->Text.Replace(lineEnd + lineEnd, lineEnd, true)); - return header + current->Text; } diff --git a/aegisub/src/subtitle_format_txt.cpp b/aegisub/src/subtitle_format_txt.cpp index 58fcd4b12..f7bc41b9e 100644 --- a/aegisub/src/subtitle_format_txt.cpp +++ b/aegisub/src/subtitle_format_txt.cpp @@ -134,7 +134,7 @@ void TXTSubtitleFormat::WriteFile(const AssFile *src, wxString const& filename, for (auto dia : src->Line | agi::of_type()) { if (!dia->Comment) { num_dialogue_lines++; - if (!dia->Actor.empty()) + if (!dia->Actor.get().empty()) num_actor_names++; } } diff --git a/aegisub/src/visual_tool_clip.cpp b/aegisub/src/visual_tool_clip.cpp index ec3dd7003..aebab905c 100644 --- a/aegisub/src/visual_tool_clip.cpp +++ b/aegisub/src/visual_tool_clip.cpp @@ -117,7 +117,7 @@ void VisualToolClip::CommitHold() { for (auto line : c->selectionController->GetSelectedSet()) { // This check is technically not correct as it could be outside of an // override block... but that's rather unlikely - bool has_iclip = line->Text.find("\\iclip") != wxString::npos; + bool has_iclip = line->Text.get().find("\\iclip") != wxString::npos; SetOverride(line, has_iclip ? "\\iclip" : "\\clip", value); } } diff --git a/aegisub/src/visual_tool_vector_clip.cpp b/aegisub/src/visual_tool_vector_clip.cpp index 29cb2d800..f0379cb64 100644 --- a/aegisub/src/visual_tool_vector_clip.cpp +++ b/aegisub/src/visual_tool_vector_clip.cpp @@ -198,7 +198,7 @@ void VisualToolVectorClip::Save() { for (auto line : c->selectionController->GetSelectedSet()) { // This check is technically not correct as it could be outside of an // override block... but that's rather unlikely - bool has_iclip = line->Text.find("\\iclip") != wxString::npos; + bool has_iclip = line->Text.get().find("\\iclip") != wxString::npos; SetOverride(line, has_iclip ? "\\iclip" : "\\clip", value); } }