diff --git a/aegisub/build/Aegisub/Aegisub.vcxproj b/aegisub/build/Aegisub/Aegisub.vcxproj
index 524ef41e6..be0a770e1 100644
--- a/aegisub/build/Aegisub/Aegisub.vcxproj
+++ b/aegisub/build/Aegisub/Aegisub.vcxproj
@@ -256,6 +256,7 @@
+
@@ -448,6 +449,7 @@
+
diff --git a/aegisub/build/Aegisub/Aegisub.vcxproj.filters b/aegisub/build/Aegisub/Aegisub.vcxproj.filters
index 9dc72df3f..7fd87b4f9 100644
--- a/aegisub/build/Aegisub/Aegisub.vcxproj.filters
+++ b/aegisub/build/Aegisub/Aegisub.vcxproj.filters
@@ -686,6 +686,10 @@
Preferences
+
+
+ Features\Search-replace
+
@@ -1231,6 +1235,9 @@
Features\Autosave
+
+ Features\Search-replace
+
diff --git a/aegisub/src/Makefile b/aegisub/src/Makefile
index a3290337e..2567021ad 100644
--- a/aegisub/src/Makefile
+++ b/aegisub/src/Makefile
@@ -213,6 +213,7 @@ SRC += \
plugin_manager.cpp \
preferences.cpp \
preferences_base.cpp \
+ search_replace_engine.cpp \
scintilla_text_ctrl.cpp \
scintilla_text_selection_controller.cpp \
spellchecker.cpp \
diff --git a/aegisub/src/command/edit.cpp b/aegisub/src/command/edit.cpp
index bcb173d63..bd06ad180 100644
--- a/aegisub/src/command/edit.cpp
+++ b/aegisub/src/command/edit.cpp
@@ -54,6 +54,7 @@
#include "../dialog_search_replace.h"
#include "../include/aegisub/context.h"
#include "../options.h"
+#include "../search_replace_engine.h"
#include "../subs_edit_ctrl.h"
#include "../subs_grid.h"
#include "../text_selection_controller.h"
@@ -462,7 +463,7 @@ struct edit_find_replace : public Command {
void operator()(agi::Context *c) {
c->videoController->Stop();
- c->search->OpenDialog(true);
+ DialogSearchReplace::Show(c, true);
}
};
diff --git a/aegisub/src/command/subtitle.cpp b/aegisub/src/command/subtitle.cpp
index a82146339..fe0bb734b 100644
--- a/aegisub/src/command/subtitle.cpp
+++ b/aegisub/src/command/subtitle.cpp
@@ -58,6 +58,7 @@
#include "../include/aegisub/context.h"
#include "../main.h"
#include "../options.h"
+#include "../search_replace_engine.h"
#include "../subs_grid.h"
#include "../subtitle_format.h"
#include "../utils.h"
@@ -105,7 +106,7 @@ struct subtitle_find : public Command {
void operator()(agi::Context *c) {
c->videoController->Stop();
- c->search->OpenDialog(false);
+ DialogSearchReplace::Show(c, false);
}
};
@@ -119,7 +120,8 @@ struct subtitle_find_next : public Command {
void operator()(agi::Context *c) {
c->videoController->Stop();
- c->search->FindNext();
+ if (!c->search->FindNext())
+ DialogSearchReplace::Show(c, false);
}
};
diff --git a/aegisub/src/dialog_search_replace.cpp b/aegisub/src/dialog_search_replace.cpp
index 901cafbe1..d427fdc0f 100644
--- a/aegisub/src/dialog_search_replace.cpp
+++ b/aegisub/src/dialog_search_replace.cpp
@@ -1,29 +1,16 @@
-// Copyright (c) 2005, Rodrigo Braz Monteiro
-// All rights reserved.
+// Copyright (c) 2013, Thomas Goyne
//
-// Redistribution and use in source and binary forms, with or without
-// modification, are permitted provided that the following conditions are met:
+// 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.
//
-// * Redistributions of source code must retain the above copyright notice,
-// this list of conditions and the following disclaimer.
-// * Redistributions in binary form must reproduce the above copyright notice,
-// this list of conditions and the following disclaimer in the documentation
-// and/or other materials provided with the distribution.
-// * Neither the name of the Aegisub Group nor the names of its contributors
-// may be used to endorse or promote products derived from this software
-// without specific prior written permission.
-//
-// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
-// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
-// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-// POSSIBILITY OF SUCH DAMAGE.
+// 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/
@@ -36,156 +23,117 @@
#include "dialog_search_replace.h"
-#include "ass_dialogue.h"
-#include "ass_file.h"
#include "compat.h"
#include "include/aegisub/context.h"
#include "options.h"
-#include "selection_controller.h"
-#include "text_selection_controller.h"
-#include "subs_grid.h"
+#include "search_replace_engine.h"
#include "utils.h"
-
-#include
+#include "validators.h"
#include
#include
#include
#include
-#include
#include
-#include
#include
#include
#include
+#include
-enum {
- BUTTON_FIND_NEXT,
- BUTTON_REPLACE_NEXT,
- BUTTON_REPLACE_ALL
-};
-
-enum {
- FIELD_TEXT = 0,
- FIELD_STYLE,
- FIELD_ACTOR,
- FIELD_EFFECT
-};
-
-enum {
- LIMIT_ALL = 0,
- LIMIT_SELECTED
-};
-
-DialogSearchReplace::DialogSearchReplace(agi::Context* c, bool withReplace)
-: wxDialog(c->parent, -1, withReplace ? _("Replace") : _("Find"))
+DialogSearchReplace::DialogSearchReplace(agi::Context* c, bool replace)
+: wxDialog(c->parent, -1, replace ? _("Replace") : _("Find"))
, c(c)
-, hasReplace(withReplace)
+, settings(new SearchReplaceSettings)
+, has_replace(replace)
{
- wxSizer *FindSizer = new wxFlexGridSizer(2, 2, 5, 15);
- FindEdit = new wxComboBox(this, -1, "", wxDefaultPosition, wxSize(300, -1), lagi_MRU_wxAS("Find"), wxCB_DROPDOWN);
- if (!FindEdit->IsListEmpty())
- FindEdit->SetSelection(0);
- FindSizer->Add(new wxStaticText(this, -1, _("Find what:")), 0, wxALIGN_CENTER_VERTICAL);
- FindSizer->Add(FindEdit);
- if (hasReplace) {
- ReplaceEdit = new wxComboBox(this, -1, "", wxDefaultPosition, wxSize(300, -1), lagi_MRU_wxAS("Replace"), wxCB_DROPDOWN);
- FindSizer->Add(new wxStaticText(this, -1, _("Replace with:")), 0, wxALIGN_CENTER_VERTICAL);
- FindSizer->Add(ReplaceEdit);
- if (!ReplaceEdit->IsListEmpty())
- ReplaceEdit->SetSelection(0);
+ auto recent_find(lagi_MRU_wxAS("Find"));
+ auto recent_replace(lagi_MRU_wxAS("Replace"));
+
+ settings->field = static_cast(OPT_GET("Tool/Search Replace/Field")->GetInt());
+ settings->limit_to = static_cast(OPT_GET("Tool/Search Replace/Affect")->GetInt());
+ settings->find = recent_find.empty() ? wxString() : recent_find.front();
+ settings->replace_with = recent_replace.empty() ? wxString() : recent_replace.front();
+ settings->match_case = OPT_GET("Tool/Search Replace/Match Case")->GetBool();
+ settings->use_regex = OPT_GET("Tool/Search Replace/RegExp")->GetBool();
+
+ auto find_sizer = new wxFlexGridSizer(2, 2, 5, 15);
+ find_edit = new wxComboBox(this, -1, "", wxDefaultPosition, wxSize(300, -1), recent_find, wxCB_DROPDOWN, wxGenericValidator(&settings->find));
+ find_sizer->Add(new wxStaticText(this, -1, _("Find what:")), wxSizerFlags().Center().Left());
+ find_sizer->Add(find_edit);
+
+ if (has_replace) {
+ replace_edit = new wxComboBox(this, -1, "", wxDefaultPosition, wxSize(300, -1), lagi_MRU_wxAS("Replace"), wxCB_DROPDOWN, wxGenericValidator(&settings->replace_with));
+ find_sizer->Add(new wxStaticText(this, -1, _("Replace with:")), wxSizerFlags().Center().Left());
+ find_sizer->Add(replace_edit);
}
- wxSizer *OptionsSizer = new wxBoxSizer(wxVERTICAL);
- CheckMatchCase = new wxCheckBox(this, -1, _("&Match case"));
- CheckRegExp = new wxCheckBox(this, -1, _("&Use regular expressions"));
- CheckMatchCase->SetValue(OPT_GET("Tool/Search Replace/Match Case")->GetBool());
- CheckRegExp->SetValue(OPT_GET("Tool/Search Replace/RegExp")->GetBool());
- OptionsSizer->Add(CheckMatchCase, wxSizerFlags().Border(wxBOTTOM));
- OptionsSizer->Add(CheckRegExp);
+ auto options_sizer = new wxBoxSizer(wxVERTICAL);
+ options_sizer->Add(new wxCheckBox(this, -1, _("&Match case"), wxDefaultPosition, wxDefaultSize, 0, wxGenericValidator(&settings->match_case)), wxSizerFlags().Border(wxBOTTOM));
+ options_sizer->Add(new wxCheckBox(this, -1, _("&Use regular expressions"), wxDefaultPosition, wxDefaultSize, 0, wxGenericValidator(&settings->use_regex)));
- // Left sizer
- wxSizer *LeftSizer = new wxBoxSizer(wxVERTICAL);
- LeftSizer->Add(FindSizer, wxSizerFlags().DoubleBorder(wxBOTTOM));
- LeftSizer->Add(OptionsSizer);
+ auto left_sizer = new wxBoxSizer(wxVERTICAL);
+ left_sizer->Add(find_sizer, wxSizerFlags().DoubleBorder(wxBOTTOM));
+ left_sizer->Add(options_sizer);
- // Limits sizer
wxString field[] = { _("Text"), _("Style"), _("Actor"), _("Effect") };
wxString affect[] = { _("All rows"), _("Selected rows") };
- Field = new wxRadioBox(this, -1, _("In Field"), wxDefaultPosition, wxDefaultSize, countof(field), field);
- Affect = new wxRadioBox(this, -1, _("Limit to"), wxDefaultPosition, wxDefaultSize, countof(affect), affect);
- wxSizer *LimitSizer = new wxBoxSizer(wxHORIZONTAL);
- LimitSizer->Add(Field, wxSizerFlags().Border(wxRIGHT));
- LimitSizer->Add(Affect);
- Field->SetSelection(OPT_GET("Tool/Search Replace/Field")->GetInt());
- Affect->SetSelection(OPT_GET("Tool/Search Replace/Affect")->GetInt());
+ auto limit_sizer = new wxBoxSizer(wxHORIZONTAL);
+ limit_sizer->Add(new wxRadioBox(this, -1, _("In Field"), wxDefaultPosition, wxDefaultSize, countof(field), field, 0, wxRA_SPECIFY_COLS, MakeEnumBinder(&settings->field)), wxSizerFlags().Border(wxRIGHT));
+ limit_sizer->Add(new wxRadioBox(this, -1, _("Limit to"), wxDefaultPosition, wxDefaultSize, countof(affect), affect, 0, wxRA_SPECIFY_COLS, MakeEnumBinder(&settings->limit_to)));
- // Buttons
- wxSizer *ButtonSizer = new wxBoxSizer(wxVERTICAL);
- wxButton *FindNext = new wxButton(this, BUTTON_FIND_NEXT, _("&Find next"));
- FindNext->SetDefault();
- ButtonSizer->Add(FindNext, wxSizerFlags().Border(wxBOTTOM));
- if (hasReplace) {
- ButtonSizer->Add(new wxButton(this, BUTTON_REPLACE_NEXT, _("Replace &next")), wxSizerFlags().Border(wxBOTTOM));
- ButtonSizer->Add(new wxButton(this, BUTTON_REPLACE_ALL, _("Replace &all")), wxSizerFlags().Border(wxBOTTOM));
+ auto find_next = new wxButton(this, -1, _("&Find next"));
+ auto replace_next = new wxButton(this, -1, _("Replace &next"));
+ auto replace_all = new wxButton(this, -1, _("Replace &all"));
+ find_next->SetDefault();
+
+ auto button_sizer = new wxBoxSizer(wxVERTICAL);
+ button_sizer->Add(find_next, wxSizerFlags().Border(wxBOTTOM));
+ button_sizer->Add(replace_next, wxSizerFlags().Border(wxBOTTOM));
+ button_sizer->Add(replace_all, wxSizerFlags().Border(wxBOTTOM));
+ button_sizer->Add(new wxButton(this, wxID_CANCEL));
+
+ if (!has_replace) {
+ button_sizer->Hide(replace_next);
+ button_sizer->Hide(replace_all);
}
- ButtonSizer->Add(new wxButton(this, wxID_CANCEL));
- wxSizer *TopSizer = new wxBoxSizer(wxHORIZONTAL);
- TopSizer->Add(LeftSizer, wxSizerFlags().Border());
- TopSizer->Add(ButtonSizer, wxSizerFlags().Border());
+ auto top_sizer = new wxBoxSizer(wxHORIZONTAL);
+ top_sizer->Add(left_sizer, wxSizerFlags().Border());
+ top_sizer->Add(button_sizer, wxSizerFlags().Border());
- // Main sizer
- wxSizer *MainSizer = new wxBoxSizer(wxVERTICAL);
- MainSizer->Add(TopSizer);
- MainSizer->Add(LimitSizer, wxSizerFlags().Border());
- SetSizerAndFit(MainSizer);
+ auto main_sizer = new wxBoxSizer(wxVERTICAL);
+ main_sizer->Add(top_sizer);
+ main_sizer->Add(limit_sizer, wxSizerFlags().Border());
+ SetSizerAndFit(main_sizer);
CenterOnParent();
- c->search->OnDialogOpen();
-
- Bind(wxEVT_COMMAND_BUTTON_CLICKED, std::bind(&DialogSearchReplace::FindReplace, this, 0), BUTTON_FIND_NEXT);
- Bind(wxEVT_COMMAND_BUTTON_CLICKED, std::bind(&DialogSearchReplace::FindReplace, this, 1), BUTTON_REPLACE_NEXT);
- Bind(wxEVT_COMMAND_BUTTON_CLICKED, std::bind(&DialogSearchReplace::FindReplace, this, 2), BUTTON_REPLACE_ALL);
+ find_next->Bind(wxEVT_COMMAND_BUTTON_CLICKED, std::bind(&DialogSearchReplace::FindReplace, this, &SearchReplaceEngine::FindNext));
+ replace_next->Bind(wxEVT_COMMAND_BUTTON_CLICKED, std::bind(&DialogSearchReplace::FindReplace, this, &SearchReplaceEngine::ReplaceNext));
+ replace_all->Bind(wxEVT_COMMAND_BUTTON_CLICKED, std::bind(&DialogSearchReplace::FindReplace, this, &SearchReplaceEngine::ReplaceAll));
}
DialogSearchReplace::~DialogSearchReplace() {
- c->search->isReg = CheckRegExp->IsChecked() && CheckRegExp->IsEnabled();
- c->search->matchCase = CheckMatchCase->IsChecked();
- OPT_SET("Tool/Search Replace/Match Case")->SetBool(CheckMatchCase->IsChecked());
- OPT_SET("Tool/Search Replace/RegExp")->SetBool(CheckRegExp->IsChecked());
- OPT_SET("Tool/Search Replace/Field")->SetInt(Field->GetSelection());
- OPT_SET("Tool/Search Replace/Affect")->SetInt(Affect->GetSelection());
}
-void DialogSearchReplace::FindReplace(int mode) {
- if (mode < 0 || mode > 2) return;
+void DialogSearchReplace::FindReplace(bool (SearchReplaceEngine::*func)()) {
+ TransferDataFromWindow();
- wxString LookFor = FindEdit->GetValue();
- if (!LookFor) return;
+ if (settings->find.empty())
+ return;
- c->search->isReg = CheckRegExp->IsChecked() && CheckRegExp->IsEnabled();
- c->search->matchCase = CheckMatchCase->IsChecked();
- c->search->LookFor = LookFor;
- c->search->initialized = true;
- c->search->affect = Affect->GetSelection();
- c->search->field = Field->GetSelection();
+ c->search->Configure(*settings);
+ (c->search->*func)();
- if (hasReplace) {
- wxString ReplaceWith = ReplaceEdit->GetValue();
- c->search->ReplaceWith = ReplaceWith;
- config::mru->Add("Replace", from_wx(ReplaceWith));
- }
+ config::mru->Add("Find", from_wx(settings->find));
+ if (has_replace)
+ config::mru->Add("Replace", from_wx(settings->replace_with));
- if (mode == 0)
- c->search->FindNext();
- else if (mode == 1)
- c->search->ReplaceNext();
- else
- c->search->ReplaceAll();
+ OPT_SET("Tool/Search Replace/Match Case")->SetBool(settings->match_case);
+ OPT_SET("Tool/Search Replace/RegExp")->SetBool(settings->use_regex);
+ OPT_SET("Tool/Search Replace/Field")->SetInt(static_cast(settings->field));
+ OPT_SET("Tool/Search Replace/Affect")->SetInt(static_cast(settings->limit_to));
- config::mru->Add("Find", from_wx(LookFor));
UpdateDropDowns();
}
@@ -199,235 +147,16 @@ static void update_mru(wxComboBox *cb, const char *mru_name) {
}
void DialogSearchReplace::UpdateDropDowns() {
- update_mru(FindEdit, "Find");
+ update_mru(find_edit, "Find");
- if (hasReplace)
- update_mru(ReplaceEdit, "Replace");
+ if (has_replace)
+ update_mru(replace_edit, "Replace");
}
-SearchReplaceEngine::SearchReplaceEngine(agi::Context *c)
-: context(c)
-, curLine(0)
-, pos(0)
-, matchLen(0)
-, replaceLen(0)
-, LastWasFind(true)
-, hasReplace(false)
-, isReg(false)
-, matchCase(false)
-, initialized(false)
-, field(FIELD_TEXT)
-, affect(LIMIT_ALL)
-{
-}
-
-static boost::flyweight *get_text(AssDialogue *cur, int field) {
- switch (field) {
- case FIELD_TEXT: return &cur->Text;
- case FIELD_STYLE: return &cur->Style;
- case FIELD_ACTOR: return &cur->Actor;
- case FIELD_EFFECT: return &cur->Effect;
- default: throw agi::InternalError("Bad find/replace field", 0);
- }
-}
-
-void SearchReplaceEngine::ReplaceNext(bool DoReplace) {
- if (!initialized) {
- OpenDialog(DoReplace);
- return;
- }
-
- wxArrayInt sels = context->subsGrid->GetSelection();
- int firstLine = 0;
- if (sels.Count() > 0) firstLine = sels[0];
- // if selection has changed reset values
- if (firstLine != curLine) {
- curLine = firstLine;
- LastWasFind = true;
- pos = 0;
- matchLen = 0;
- replaceLen = 0;
- }
-
- // Setup
- int start = curLine;
- int nrows = context->subsGrid->GetRows();
- bool found = false;
- int regFlags = wxRE_ADVANCED;
- if (!matchCase) {
- if (isReg)
- regFlags |= wxRE_ICASE;
- else
- LookFor.MakeLower();
- }
- wxRegEx regex;
- if (isReg) {
- regex.Compile(LookFor, regFlags);
-
- if (!regex.IsValid()) {
- LastWasFind = !DoReplace;
- return;
- }
- }
-
- // Search for it
- boost::flyweight *Text = nullptr;
- while (!found) {
- Text = get_text(context->subsGrid->GetDialogue(curLine), field);
- size_t tempPos;
- if (DoReplace && LastWasFind)
- tempPos = pos;
- else
- tempPos = pos + replaceLen;
-
- if (isReg) {
- if (regex.Matches(Text->get().substr(tempPos))) {
- size_t match_start;
- regex.GetMatch(&match_start, &matchLen, 0);
- pos = match_start + tempPos;
- found = true;
- }
- }
- else {
- wxString src = Text->get().substr(tempPos);
- if (!matchCase) src.MakeLower();
- size_t textPos = src.find(LookFor);
- if (textPos != src.npos) {
- pos = tempPos+textPos;
- found = true;
- matchLen = LookFor.size();
- }
- }
-
- // Didn't find, go to next line
- if (!found) {
- curLine = (curLine + 1) % nrows;
- pos = 0;
- matchLen = 0;
- replaceLen = 0;
- if (curLine == start) break;
- }
- }
-
- if (found) {
- if (!DoReplace)
- replaceLen = matchLen;
- else {
- if (isReg) {
- wxString toReplace = Text->get().substr(pos,matchLen);
- regex.ReplaceFirst(&toReplace,ReplaceWith);
- *Text = Text->get().Left(pos) + toReplace + Text->get().substr(pos+matchLen);
- replaceLen = toReplace.size();
- }
- else {
- *Text = Text->get().Left(pos) + ReplaceWith + Text->get().substr(pos+matchLen);
- replaceLen = ReplaceWith.size();
- }
-
- context->ass->Commit(_("replace"), AssFile::COMMIT_DIAG_TEXT);
- }
-
- context->subsGrid->SelectRow(curLine,false);
- context->subsGrid->MakeCellVisible(curLine,0);
- if (field == FIELD_TEXT) {
- context->selectionController->SetActiveLine(context->subsGrid->GetDialogue(curLine));
- context->textSelectionController->SetSelection(pos, pos + replaceLen);
- }
- // hAx to prevent double match on style/actor
- else
- replaceLen = 99999;
- }
- LastWasFind = !DoReplace;
-}
-
-void SearchReplaceEngine::ReplaceAll() {
- size_t count = 0;
-
- int regFlags = wxRE_ADVANCED;
- if (!matchCase)
- regFlags |= wxRE_ICASE;
- wxRegEx reg;
- if (isReg)
- reg.Compile(LookFor, regFlags);
-
- SubtitleSelection const& sel = context->selectionController->GetSelectedSet();
- bool hasSelection = !sel.empty();
- bool inSel = affect == LIMIT_SELECTED;
-
- for (auto diag : context->ass->Line | agi::of_type()) {
- if (inSel && hasSelection && !sel.count(diag))
- continue;
-
- boost::flyweight *Text = get_text(diag, field);
-
- if (isReg) {
- if (reg.Matches(*Text)) {
- size_t start, len;
- reg.GetMatch(&start, &len);
-
- // 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
- wxString repl(*Text);
- count += reg.Replace(&repl, ReplaceWith, len > 0 ? 1000 : 1);
- *Text = repl;
- }
- }
- else {
- if (!matchCase) {
- bool replaced = false;
- wxString Left, Right = *Text;
- size_t pos = 0;
- Left.reserve(Right.size());
- while (pos + LookFor.size() <= Right.size()) {
- if (Right.substr(pos, LookFor.size()).CmpNoCase(LookFor) == 0) {
- Left.Append(Right.Left(pos)).Append(ReplaceWith);
- Right = Right.substr(pos + LookFor.size());
- ++count;
- replaced = true;
- pos = 0;
- }
- else {
- pos++;
- }
- }
- if (replaced) {
- *Text = Left + Right;
- }
- }
- else if(Text->get().Contains(LookFor)) {
- wxString repl(*Text);
- count += repl.Replace(LookFor, ReplaceWith);
- *Text = repl;
- }
- }
- }
-
- if (count > 0) {
- context->ass->Commit(_("replace"), AssFile::COMMIT_DIAG_TEXT);
- wxMessageBox(wxString::Format(_("%i matches were replaced."), (int)count));
- }
- else {
- wxMessageBox(_("No matches found."));
- }
- LastWasFind = false;
-}
-
-void SearchReplaceEngine::OnDialogOpen() {
- wxArrayInt sels = context->subsGrid->GetSelection();
- curLine = 0;
- if (sels.Count() > 0) curLine = sels[0];
-
- LastWasFind = true;
- pos = 0;
- matchLen = 0;
- replaceLen = 0;
-}
-
-void SearchReplaceEngine::OpenDialog(bool replace) {
+void DialogSearchReplace::Show(agi::Context *context, bool replace) {
static DialogSearchReplace *diag = nullptr;
- if (diag && replace != hasReplace) {
+ if (diag && replace != diag->has_replace) {
// Already opened, but wrong type - destroy and create the right one
diag->Destroy();
diag = nullptr;
@@ -436,7 +165,6 @@ void SearchReplaceEngine::OpenDialog(bool replace) {
if (!diag)
diag = new DialogSearchReplace(context, replace);
- diag->FindEdit->SetFocus();
- diag->Show();
- hasReplace = replace;
+ diag->find_edit->SetFocus();
+ diag->wxDialog::Show();
}
diff --git a/aegisub/src/dialog_search_replace.h b/aegisub/src/dialog_search_replace.h
index 639f3c1bd..5fe13c682 100644
--- a/aegisub/src/dialog_search_replace.h
+++ b/aegisub/src/dialog_search_replace.h
@@ -1,29 +1,16 @@
-// Copyright (c) 2005, Rodrigo Braz Monteiro
-// All rights reserved.
+// Copyright (c) 2013, Thomas Goyne
//
-// Redistribution and use in source and binary forms, with or without
-// modification, are permitted provided that the following conditions are met:
+// 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.
//
-// * Redistributions of source code must retain the above copyright notice,
-// this list of conditions and the following disclaimer.
-// * Redistributions in binary form must reproduce the above copyright notice,
-// this list of conditions and the following disclaimer in the documentation
-// and/or other materials provided with the distribution.
-// * Neither the name of the Aegisub Group nor the names of its contributors
-// may be used to endorse or promote products derived from this software
-// without specific prior written permission.
-//
-// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
-// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
-// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-// POSSIBILITY OF SUCH DAMAGE.
+// 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/
@@ -32,61 +19,28 @@
/// @ingroup secondary_ui
///
+#include
+
#include
-#include
namespace agi { struct Context; }
-class wxCheckBox;
+class SearchReplaceEngine;
+struct SearchReplaceSettings;
class wxComboBox;
-class wxRadioBox;
-
-class SearchReplaceEngine {
- agi::Context *context;
-
- int curLine;
- size_t pos;
- size_t matchLen;
- size_t replaceLen;
- bool LastWasFind;
- bool hasReplace;
- bool isReg;
- bool matchCase;
- bool initialized;
- int field;
- int affect;
- wxString LookFor;
- wxString ReplaceWith;
-
-public:
- void FindNext() { ReplaceNext(false); }
- void ReplaceNext(bool DoReplace=true);
- void ReplaceAll();
- void OpenDialog(bool HasReplace);
- void OnDialogOpen();
-
- SearchReplaceEngine(agi::Context *c);
- friend class DialogSearchReplace;
-};
class DialogSearchReplace : public wxDialog {
- friend class SearchReplaceEngine;
-
agi::Context *c;
-
- bool hasReplace;
-
- wxComboBox *FindEdit;
- wxComboBox *ReplaceEdit;
- wxCheckBox *CheckMatchCase;
- wxCheckBox *CheckRegExp;
- wxCheckBox *CheckUpdateVideo;
- wxRadioBox *Affect;
- wxRadioBox *Field;
+ agi::scoped_ptr settings;
+ bool has_replace;
+ wxComboBox *find_edit;
+ wxComboBox *replace_edit;
void UpdateDropDowns();
- void FindReplace(int mode); // 0 = find, 1 = replace next, 2 = replace all
+ void FindReplace(bool (SearchReplaceEngine::*func)());
public:
- DialogSearchReplace(agi::Context* c, bool withReplace);
+ static void Show(agi::Context *context, bool with_replace);
+
+ DialogSearchReplace(agi::Context* c, bool with_replace);
~DialogSearchReplace();
};
diff --git a/aegisub/src/frame_main.cpp b/aegisub/src/frame_main.cpp
index fb63df657..e16f1a968 100644
--- a/aegisub/src/frame_main.cpp
+++ b/aegisub/src/frame_main.cpp
@@ -59,12 +59,12 @@
#include "command/command.h"
#include "dialog_detached_video.h"
#include "dialog_manager.h"
-#include "dialog_search_replace.h"
#include "dialog_version_check.h"
#include "help_button.h"
#include "libresrc/libresrc.h"
#include "main.h"
#include "options.h"
+#include "search_replace_engine.h"
#include "standard_paths.h"
#include "subs_edit_box.h"
#include "subs_edit_ctrl.h"
diff --git a/aegisub/src/search_replace_engine.cpp b/aegisub/src/search_replace_engine.cpp
new file mode 100644
index 000000000..db917ed42
--- /dev/null
+++ b/aegisub/src/search_replace_engine.cpp
@@ -0,0 +1,264 @@
+// Copyright (c) 2005, Rodrigo Braz Monteiro
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+// * Neither the name of the Aegisub Group nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+//
+// Aegisub Project http://www.aegisub.org/
+
+#include "config.h"
+
+#include "search_replace_engine.h"
+
+#include "ass_dialogue.h"
+#include "ass_file.h"
+#include "include/aegisub/context.h"
+#include "subs_grid.h"
+#include "text_selection_controller.h"
+
+#include
+
+#include
+#include
+
+SearchReplaceEngine::SearchReplaceEngine(agi::Context *c)
+: context(c)
+, cur_line(0)
+, pos(0)
+, match_len(0)
+, replace_len(0)
+, last_was_find(true)
+, initialized(false)
+{
+}
+
+static boost::flyweight *get_text(AssDialogue *cur, SearchReplaceSettings::Field field) {
+ switch (field) {
+ case SearchReplaceSettings::Field::TEXT: return &cur->Text;
+ case SearchReplaceSettings::Field::STYLE: return &cur->Style;
+ case SearchReplaceSettings::Field::ACTOR: return &cur->Actor;
+ case SearchReplaceSettings::Field::EFFECT: return &cur->Effect;
+ }
+ throw agi::InternalError("Bad field for search", 0);
+}
+
+bool SearchReplaceEngine::FindReplace(bool replace) {
+ if (!initialized)
+ return false;
+
+ wxArrayInt sels = context->subsGrid->GetSelection();
+ int firstLine = sels.empty() ? 0 : sels.front();
+
+ // if selection has changed reset values
+ if (firstLine != cur_line) {
+ cur_line = firstLine;
+ last_was_find = true;
+ pos = 0;
+ match_len = 0;
+ replace_len = 0;
+ }
+
+ // Setup
+ int start = cur_line;
+ int nrows = context->subsGrid->GetRows();
+ bool found = false;
+ int regFlags = wxRE_ADVANCED;
+ if (!settings.match_case) {
+ if (settings.use_regex)
+ regFlags |= wxRE_ICASE;
+ else
+ settings.find.MakeLower();
+ }
+ wxRegEx regex;
+ if (settings.use_regex) {
+ regex.Compile(settings.find, regFlags);
+
+ if (!regex.IsValid()) {
+ last_was_find = !replace;
+ return true;
+ }
+ }
+
+ // Search for it
+ boost::flyweight *Text = nullptr;
+ while (!found) {
+ Text = get_text(context->subsGrid->GetDialogue(cur_line), settings.field);
+ size_t tempPos;
+ if (replace && last_was_find)
+ tempPos = pos;
+ else
+ tempPos = pos + replace_len;
+
+ if (settings.use_regex) {
+ if (regex.Matches(Text->get().substr(tempPos))) {
+ size_t match_start;
+ regex.GetMatch(&match_start, &match_len, 0);
+ pos = match_start + tempPos;
+ found = true;
+ }
+ }
+ else {
+ wxString src = Text->get().substr(tempPos);
+ if (!settings.match_case) src.MakeLower();
+ size_t textPos = src.find(settings.find);
+ if (textPos != src.npos) {
+ pos = tempPos+textPos;
+ found = true;
+ match_len = settings.find.size();
+ }
+ }
+
+ // Didn't find, go to next line
+ if (!found) {
+ cur_line = (cur_line + 1) % nrows;
+ pos = 0;
+ match_len = 0;
+ replace_len = 0;
+ if (cur_line == start) break;
+ }
+ }
+
+ if (found) {
+ if (!replace)
+ replace_len = match_len;
+ else {
+ if (settings.use_regex) {
+ wxString toReplace = Text->get().substr(pos,match_len);
+ regex.ReplaceFirst(&toReplace,settings.replace_with);
+ *Text = Text->get().Left(pos) + toReplace + Text->get().substr(pos+match_len);
+ replace_len = toReplace.size();
+ }
+ else {
+ *Text = Text->get().Left(pos) + settings.replace_with + Text->get().substr(pos+match_len);
+ replace_len = settings.replace_with.size();
+ }
+
+ context->ass->Commit(_("replace"), AssFile::COMMIT_DIAG_TEXT);
+ }
+
+ context->subsGrid->SelectRow(cur_line,false);
+ context->subsGrid->MakeCellVisible(cur_line,0);
+ if (settings.field == SearchReplaceSettings::Field::TEXT) {
+ context->selectionController->SetActiveLine(context->subsGrid->GetDialogue(cur_line));
+ context->textSelectionController->SetSelection(pos, pos + replace_len);
+ }
+ // hAx to prevent double match on style/actor
+ else
+ replace_len = 99999;
+ }
+ last_was_find = !replace;
+
+ return true;
+}
+
+bool SearchReplaceEngine::ReplaceAll() {
+ if (!initialized)
+ return false;
+
+ size_t count = 0;
+
+ int regFlags = wxRE_ADVANCED;
+ if (!settings.match_case)
+ regFlags |= wxRE_ICASE;
+ wxRegEx reg;
+ if (settings.use_regex)
+ reg.Compile(settings.find, regFlags);
+
+ SubtitleSelection const& sel = context->selectionController->GetSelectedSet();
+ bool hasSelection = !sel.empty();
+ bool inSel = settings.limit_to == SearchReplaceSettings::Limit::SELECTED;
+
+ for (auto diag : context->ass->Line | agi::of_type()) {
+ if (inSel && hasSelection && !sel.count(diag))
+ continue;
+
+ boost::flyweight *Text = get_text(diag, settings.field);
+
+ if (settings.use_regex) {
+ if (reg.Matches(*Text)) {
+ size_t start, len;
+ reg.GetMatch(&start, &len);
+
+ // 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
+ wxString repl(*Text);
+ count += reg.Replace(&repl, settings.replace_with, len > 0 ? 1000 : 1);
+ *Text = repl;
+ }
+ }
+ else {
+ if (!settings.match_case) {
+ bool replaced = false;
+ wxString Left, Right = *Text;
+ size_t pos = 0;
+ Left.reserve(Right.size());
+ while (pos + settings.find.size() <= Right.size()) {
+ if (Right.substr(pos, settings.find.size()).CmpNoCase(settings.find) == 0) {
+ Left.Append(Right.Left(pos)).Append(settings.replace_with);
+ Right = Right.substr(pos + settings.find.size());
+ ++count;
+ replaced = true;
+ pos = 0;
+ }
+ else {
+ pos++;
+ }
+ }
+ if (replaced) {
+ *Text = Left + Right;
+ }
+ }
+ else if(Text->get().Contains(settings.find)) {
+ wxString repl(*Text);
+ count += repl.Replace(settings.find, settings.replace_with);
+ *Text = repl;
+ }
+ }
+ }
+
+ if (count > 0) {
+ context->ass->Commit(_("replace"), AssFile::COMMIT_DIAG_TEXT);
+ wxMessageBox(wxString::Format(_("%i matches were replaced."), (int)count));
+ }
+ else {
+ wxMessageBox(_("No matches found."));
+ }
+ last_was_find = false;
+
+ return true;
+}
+
+void SearchReplaceEngine::Configure(SearchReplaceSettings const& new_settings) {
+ wxArrayInt sels = context->subsGrid->GetSelection();
+ cur_line = 0;
+ if (sels.size() > 0) cur_line = sels[0];
+
+ last_was_find = true;
+ pos = 0;
+ match_len = 0;
+ replace_len = 0;
+
+ settings = new_settings;
+}
diff --git a/aegisub/src/search_replace_engine.h b/aegisub/src/search_replace_engine.h
new file mode 100644
index 000000000..5a9361bd7
--- /dev/null
+++ b/aegisub/src/search_replace_engine.h
@@ -0,0 +1,66 @@
+// Copyright (c) 2013, 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
+
+namespace agi { struct Context; }
+
+struct SearchReplaceSettings {
+ enum class Field {
+ TEXT = 0,
+ STYLE,
+ ACTOR,
+ EFFECT
+ };
+
+ enum class Limit {
+ ALL = 0,
+ SELECTED
+ };
+
+ wxString find;
+ wxString replace_with;
+
+ Field field;
+ Limit limit_to;
+
+ bool match_case;
+ bool use_regex;
+};
+
+class SearchReplaceEngine {
+ agi::Context *context;
+
+ int cur_line;
+ size_t pos;
+ size_t match_len;
+ size_t replace_len;
+ bool last_was_find;
+ bool initialized;
+
+ SearchReplaceSettings settings;
+
+ bool FindReplace(bool replace);
+
+public:
+ bool FindNext() { return FindReplace(false); }
+ bool ReplaceNext() { return FindReplace(true); }
+ bool ReplaceAll();
+
+ void Configure(SearchReplaceSettings const& new_settings);
+
+ SearchReplaceEngine(agi::Context *c);
+};
diff --git a/aegisub/src/validators.h b/aegisub/src/validators.h
index 18a0ba903..fc07ec24b 100644
--- a/aegisub/src/validators.h
+++ b/aegisub/src/validators.h
@@ -32,6 +32,9 @@
/// @ingroup custom_control utility
///
+#include
+
+#include
#include
/// A wx validator that only allows valid numbers
@@ -88,3 +91,35 @@ public:
DECLARE_EVENT_TABLE()
};
+
+template
+class EnumBinder : public wxValidator {
+ T *value;
+
+ wxObject *Clone() const override { return new EnumBinder(value); }
+
+ bool TransferFromWindow() override {
+ if (wxRadioBox *rb = dynamic_cast(GetWindow()))
+ *value = static_cast(rb->GetSelection());
+ else
+ throw agi::InternalError("Control type not supported by EnumBinder", 0);
+ return true;
+ }
+
+ bool TransferToWindow() override {
+ if (wxRadioBox *rb = dynamic_cast(GetWindow()))
+ rb->SetSelection(static_cast(*value));
+ else
+ throw agi::InternalError("Control type not supported by EnumBinder", 0);
+ return true;
+ }
+
+public:
+ explicit EnumBinder(T *value) : value(value) { }
+ EnumBinder(EnumBinder const& rhs) : value(rhs.value) { }
+};
+
+template
+EnumBinder MakeEnumBinder(T *value) {
+ return EnumBinder(value);
+}