mirror of https://github.com/odrling/Aegisub
Decouple SearchReplaceEngine from DialogSearchReplace
This commit is contained in:
parent
59db22e905
commit
b1dbb9a94b
|
@ -256,6 +256,7 @@
|
||||||
<ClInclude Include="$(SrcDir)visual_tool_vector_clip.h" />
|
<ClInclude Include="$(SrcDir)visual_tool_vector_clip.h" />
|
||||||
<ClInclude Include="$(SrcDir)ass_info.h" />
|
<ClInclude Include="$(SrcDir)ass_info.h" />
|
||||||
<ClInclude Include="$(SrcDir)options.h" />
|
<ClInclude Include="$(SrcDir)options.h" />
|
||||||
|
<ClInclude Include="$(SrcDir)search_replace_engine.h" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClCompile Include="$(SrcDir)aegisublocale.cpp" />
|
<ClCompile Include="$(SrcDir)aegisublocale.cpp" />
|
||||||
|
@ -448,6 +449,7 @@
|
||||||
<ClCompile Include="$(SrcDir)scintilla_text_selection_controller.cpp" />
|
<ClCompile Include="$(SrcDir)scintilla_text_selection_controller.cpp" />
|
||||||
<ClCompile Include="$(SrcDir)command\keyframe.cpp" />
|
<ClCompile Include="$(SrcDir)command\keyframe.cpp" />
|
||||||
<ClCompile Include="$(SrcDir)dialog_autosave.cpp" />
|
<ClCompile Include="$(SrcDir)dialog_autosave.cpp" />
|
||||||
|
<ClCompile Include="$(SrcDir)search_replace_engine.cpp" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ResourceCompile Include="$(SrcDir)res.rc" />
|
<ResourceCompile Include="$(SrcDir)res.rc" />
|
||||||
|
|
|
@ -686,6 +686,10 @@
|
||||||
<ClInclude Include="$(SrcDir)options.h">
|
<ClInclude Include="$(SrcDir)options.h">
|
||||||
<Filter>Preferences</Filter>
|
<Filter>Preferences</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
|
<ClInclude Include="$(SrcDir)include\aegisub\hotkey.h" />
|
||||||
|
<ClInclude Include="$(SrcDir)search_replace_engine.h">
|
||||||
|
<Filter>Features\Search-replace</Filter>
|
||||||
|
</ClInclude>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClCompile Include="$(SrcDir)ass_dialogue.cpp">
|
<ClCompile Include="$(SrcDir)ass_dialogue.cpp">
|
||||||
|
@ -1231,6 +1235,9 @@
|
||||||
<ClCompile Include="$(SrcDir)dialog_autosave.cpp">
|
<ClCompile Include="$(SrcDir)dialog_autosave.cpp">
|
||||||
<Filter>Features\Autosave</Filter>
|
<Filter>Features\Autosave</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include="$(SrcDir)search_replace_engine.cpp">
|
||||||
|
<Filter>Features\Search-replace</Filter>
|
||||||
|
</ClCompile>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ResourceCompile Include="$(SrcDir)res.rc">
|
<ResourceCompile Include="$(SrcDir)res.rc">
|
||||||
|
|
|
@ -213,6 +213,7 @@ SRC += \
|
||||||
plugin_manager.cpp \
|
plugin_manager.cpp \
|
||||||
preferences.cpp \
|
preferences.cpp \
|
||||||
preferences_base.cpp \
|
preferences_base.cpp \
|
||||||
|
search_replace_engine.cpp \
|
||||||
scintilla_text_ctrl.cpp \
|
scintilla_text_ctrl.cpp \
|
||||||
scintilla_text_selection_controller.cpp \
|
scintilla_text_selection_controller.cpp \
|
||||||
spellchecker.cpp \
|
spellchecker.cpp \
|
||||||
|
|
|
@ -54,6 +54,7 @@
|
||||||
#include "../dialog_search_replace.h"
|
#include "../dialog_search_replace.h"
|
||||||
#include "../include/aegisub/context.h"
|
#include "../include/aegisub/context.h"
|
||||||
#include "../options.h"
|
#include "../options.h"
|
||||||
|
#include "../search_replace_engine.h"
|
||||||
#include "../subs_edit_ctrl.h"
|
#include "../subs_edit_ctrl.h"
|
||||||
#include "../subs_grid.h"
|
#include "../subs_grid.h"
|
||||||
#include "../text_selection_controller.h"
|
#include "../text_selection_controller.h"
|
||||||
|
@ -462,7 +463,7 @@ struct edit_find_replace : public Command {
|
||||||
|
|
||||||
void operator()(agi::Context *c) {
|
void operator()(agi::Context *c) {
|
||||||
c->videoController->Stop();
|
c->videoController->Stop();
|
||||||
c->search->OpenDialog(true);
|
DialogSearchReplace::Show(c, true);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -58,6 +58,7 @@
|
||||||
#include "../include/aegisub/context.h"
|
#include "../include/aegisub/context.h"
|
||||||
#include "../main.h"
|
#include "../main.h"
|
||||||
#include "../options.h"
|
#include "../options.h"
|
||||||
|
#include "../search_replace_engine.h"
|
||||||
#include "../subs_grid.h"
|
#include "../subs_grid.h"
|
||||||
#include "../subtitle_format.h"
|
#include "../subtitle_format.h"
|
||||||
#include "../utils.h"
|
#include "../utils.h"
|
||||||
|
@ -105,7 +106,7 @@ struct subtitle_find : public Command {
|
||||||
|
|
||||||
void operator()(agi::Context *c) {
|
void operator()(agi::Context *c) {
|
||||||
c->videoController->Stop();
|
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) {
|
void operator()(agi::Context *c) {
|
||||||
c->videoController->Stop();
|
c->videoController->Stop();
|
||||||
c->search->FindNext();
|
if (!c->search->FindNext())
|
||||||
|
DialogSearchReplace::Show(c, false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,29 +1,16 @@
|
||||||
// Copyright (c) 2005, Rodrigo Braz Monteiro
|
// Copyright (c) 2013, Thomas Goyne <plorkyeran@aegisub.org>
|
||||||
// All rights reserved.
|
|
||||||
//
|
//
|
||||||
// Redistribution and use in source and binary forms, with or without
|
// Permission to use, copy, modify, and distribute this software for any
|
||||||
// modification, are permitted provided that the following conditions are met:
|
// 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,
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
// this list of conditions and the following disclaimer.
|
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
// * Redistributions in binary form must reproduce the above copyright notice,
|
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
// this list of conditions and the following disclaimer in the documentation
|
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
// and/or other materials provided with the distribution.
|
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
// * Neither the name of the Aegisub Group nor the names of its contributors
|
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
// may be used to endorse or promote products derived from this software
|
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF 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/
|
// Aegisub Project http://www.aegisub.org/
|
||||||
|
|
||||||
|
@ -36,156 +23,117 @@
|
||||||
|
|
||||||
#include "dialog_search_replace.h"
|
#include "dialog_search_replace.h"
|
||||||
|
|
||||||
#include "ass_dialogue.h"
|
|
||||||
#include "ass_file.h"
|
|
||||||
#include "compat.h"
|
#include "compat.h"
|
||||||
#include "include/aegisub/context.h"
|
#include "include/aegisub/context.h"
|
||||||
#include "options.h"
|
#include "options.h"
|
||||||
#include "selection_controller.h"
|
#include "search_replace_engine.h"
|
||||||
#include "text_selection_controller.h"
|
|
||||||
#include "subs_grid.h"
|
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
|
#include "validators.h"
|
||||||
#include <libaegisub/of_type_adaptor.h>
|
|
||||||
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
|
||||||
#include <wx/button.h>
|
#include <wx/button.h>
|
||||||
#include <wx/checkbox.h>
|
#include <wx/checkbox.h>
|
||||||
#include <wx/combobox.h>
|
#include <wx/combobox.h>
|
||||||
#include <wx/msgdlg.h>
|
|
||||||
#include <wx/radiobox.h>
|
#include <wx/radiobox.h>
|
||||||
#include <wx/regex.h>
|
|
||||||
#include <wx/sizer.h>
|
#include <wx/sizer.h>
|
||||||
#include <wx/stattext.h>
|
#include <wx/stattext.h>
|
||||||
#include <wx/textctrl.h>
|
#include <wx/textctrl.h>
|
||||||
|
#include <wx/valgen.h>
|
||||||
|
|
||||||
enum {
|
DialogSearchReplace::DialogSearchReplace(agi::Context* c, bool replace)
|
||||||
BUTTON_FIND_NEXT,
|
: wxDialog(c->parent, -1, replace ? _("Replace") : _("Find"))
|
||||||
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"))
|
|
||||||
, c(c)
|
, c(c)
|
||||||
, hasReplace(withReplace)
|
, settings(new SearchReplaceSettings)
|
||||||
|
, has_replace(replace)
|
||||||
{
|
{
|
||||||
wxSizer *FindSizer = new wxFlexGridSizer(2, 2, 5, 15);
|
auto recent_find(lagi_MRU_wxAS("Find"));
|
||||||
FindEdit = new wxComboBox(this, -1, "", wxDefaultPosition, wxSize(300, -1), lagi_MRU_wxAS("Find"), wxCB_DROPDOWN);
|
auto recent_replace(lagi_MRU_wxAS("Replace"));
|
||||||
if (!FindEdit->IsListEmpty())
|
|
||||||
FindEdit->SetSelection(0);
|
settings->field = static_cast<SearchReplaceSettings::Field>(OPT_GET("Tool/Search Replace/Field")->GetInt());
|
||||||
FindSizer->Add(new wxStaticText(this, -1, _("Find what:")), 0, wxALIGN_CENTER_VERTICAL);
|
settings->limit_to = static_cast<SearchReplaceSettings::Limit>(OPT_GET("Tool/Search Replace/Affect")->GetInt());
|
||||||
FindSizer->Add(FindEdit);
|
settings->find = recent_find.empty() ? wxString() : recent_find.front();
|
||||||
if (hasReplace) {
|
settings->replace_with = recent_replace.empty() ? wxString() : recent_replace.front();
|
||||||
ReplaceEdit = new wxComboBox(this, -1, "", wxDefaultPosition, wxSize(300, -1), lagi_MRU_wxAS("Replace"), wxCB_DROPDOWN);
|
settings->match_case = OPT_GET("Tool/Search Replace/Match Case")->GetBool();
|
||||||
FindSizer->Add(new wxStaticText(this, -1, _("Replace with:")), 0, wxALIGN_CENTER_VERTICAL);
|
settings->use_regex = OPT_GET("Tool/Search Replace/RegExp")->GetBool();
|
||||||
FindSizer->Add(ReplaceEdit);
|
|
||||||
if (!ReplaceEdit->IsListEmpty())
|
auto find_sizer = new wxFlexGridSizer(2, 2, 5, 15);
|
||||||
ReplaceEdit->SetSelection(0);
|
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);
|
auto options_sizer = new wxBoxSizer(wxVERTICAL);
|
||||||
CheckMatchCase = new wxCheckBox(this, -1, _("&Match case"));
|
options_sizer->Add(new wxCheckBox(this, -1, _("&Match case"), wxDefaultPosition, wxDefaultSize, 0, wxGenericValidator(&settings->match_case)), wxSizerFlags().Border(wxBOTTOM));
|
||||||
CheckRegExp = new wxCheckBox(this, -1, _("&Use regular expressions"));
|
options_sizer->Add(new wxCheckBox(this, -1, _("&Use regular expressions"), wxDefaultPosition, wxDefaultSize, 0, wxGenericValidator(&settings->use_regex)));
|
||||||
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);
|
|
||||||
|
|
||||||
// Left sizer
|
auto left_sizer = new wxBoxSizer(wxVERTICAL);
|
||||||
wxSizer *LeftSizer = new wxBoxSizer(wxVERTICAL);
|
left_sizer->Add(find_sizer, wxSizerFlags().DoubleBorder(wxBOTTOM));
|
||||||
LeftSizer->Add(FindSizer, wxSizerFlags().DoubleBorder(wxBOTTOM));
|
left_sizer->Add(options_sizer);
|
||||||
LeftSizer->Add(OptionsSizer);
|
|
||||||
|
|
||||||
// Limits sizer
|
|
||||||
wxString field[] = { _("Text"), _("Style"), _("Actor"), _("Effect") };
|
wxString field[] = { _("Text"), _("Style"), _("Actor"), _("Effect") };
|
||||||
wxString affect[] = { _("All rows"), _("Selected rows") };
|
wxString affect[] = { _("All rows"), _("Selected rows") };
|
||||||
Field = new wxRadioBox(this, -1, _("In Field"), wxDefaultPosition, wxDefaultSize, countof(field), field);
|
auto limit_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||||
Affect = new wxRadioBox(this, -1, _("Limit to"), wxDefaultPosition, wxDefaultSize, countof(affect), affect);
|
limit_sizer->Add(new wxRadioBox(this, -1, _("In Field"), wxDefaultPosition, wxDefaultSize, countof(field), field, 0, wxRA_SPECIFY_COLS, MakeEnumBinder(&settings->field)), wxSizerFlags().Border(wxRIGHT));
|
||||||
wxSizer *LimitSizer = new wxBoxSizer(wxHORIZONTAL);
|
limit_sizer->Add(new wxRadioBox(this, -1, _("Limit to"), wxDefaultPosition, wxDefaultSize, countof(affect), affect, 0, wxRA_SPECIFY_COLS, MakeEnumBinder(&settings->limit_to)));
|
||||||
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());
|
|
||||||
|
|
||||||
// Buttons
|
auto find_next = new wxButton(this, -1, _("&Find next"));
|
||||||
wxSizer *ButtonSizer = new wxBoxSizer(wxVERTICAL);
|
auto replace_next = new wxButton(this, -1, _("Replace &next"));
|
||||||
wxButton *FindNext = new wxButton(this, BUTTON_FIND_NEXT, _("&Find next"));
|
auto replace_all = new wxButton(this, -1, _("Replace &all"));
|
||||||
FindNext->SetDefault();
|
find_next->SetDefault();
|
||||||
ButtonSizer->Add(FindNext, wxSizerFlags().Border(wxBOTTOM));
|
|
||||||
if (hasReplace) {
|
auto button_sizer = new wxBoxSizer(wxVERTICAL);
|
||||||
ButtonSizer->Add(new wxButton(this, BUTTON_REPLACE_NEXT, _("Replace &next")), wxSizerFlags().Border(wxBOTTOM));
|
button_sizer->Add(find_next, wxSizerFlags().Border(wxBOTTOM));
|
||||||
ButtonSizer->Add(new wxButton(this, BUTTON_REPLACE_ALL, _("Replace &all")), 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);
|
auto top_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||||
TopSizer->Add(LeftSizer, wxSizerFlags().Border());
|
top_sizer->Add(left_sizer, wxSizerFlags().Border());
|
||||||
TopSizer->Add(ButtonSizer, wxSizerFlags().Border());
|
top_sizer->Add(button_sizer, wxSizerFlags().Border());
|
||||||
|
|
||||||
// Main sizer
|
auto main_sizer = new wxBoxSizer(wxVERTICAL);
|
||||||
wxSizer *MainSizer = new wxBoxSizer(wxVERTICAL);
|
main_sizer->Add(top_sizer);
|
||||||
MainSizer->Add(TopSizer);
|
main_sizer->Add(limit_sizer, wxSizerFlags().Border());
|
||||||
MainSizer->Add(LimitSizer, wxSizerFlags().Border());
|
SetSizerAndFit(main_sizer);
|
||||||
SetSizerAndFit(MainSizer);
|
|
||||||
CenterOnParent();
|
CenterOnParent();
|
||||||
|
|
||||||
c->search->OnDialogOpen();
|
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));
|
||||||
Bind(wxEVT_COMMAND_BUTTON_CLICKED, std::bind(&DialogSearchReplace::FindReplace, this, 0), BUTTON_FIND_NEXT);
|
replace_all->Bind(wxEVT_COMMAND_BUTTON_CLICKED, std::bind(&DialogSearchReplace::FindReplace, this, &SearchReplaceEngine::ReplaceAll));
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DialogSearchReplace::~DialogSearchReplace() {
|
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) {
|
void DialogSearchReplace::FindReplace(bool (SearchReplaceEngine::*func)()) {
|
||||||
if (mode < 0 || mode > 2) return;
|
TransferDataFromWindow();
|
||||||
|
|
||||||
wxString LookFor = FindEdit->GetValue();
|
if (settings->find.empty())
|
||||||
if (!LookFor) return;
|
return;
|
||||||
|
|
||||||
c->search->isReg = CheckRegExp->IsChecked() && CheckRegExp->IsEnabled();
|
c->search->Configure(*settings);
|
||||||
c->search->matchCase = CheckMatchCase->IsChecked();
|
(c->search->*func)();
|
||||||
c->search->LookFor = LookFor;
|
|
||||||
c->search->initialized = true;
|
|
||||||
c->search->affect = Affect->GetSelection();
|
|
||||||
c->search->field = Field->GetSelection();
|
|
||||||
|
|
||||||
if (hasReplace) {
|
config::mru->Add("Find", from_wx(settings->find));
|
||||||
wxString ReplaceWith = ReplaceEdit->GetValue();
|
if (has_replace)
|
||||||
c->search->ReplaceWith = ReplaceWith;
|
config::mru->Add("Replace", from_wx(settings->replace_with));
|
||||||
config::mru->Add("Replace", from_wx(ReplaceWith));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mode == 0)
|
OPT_SET("Tool/Search Replace/Match Case")->SetBool(settings->match_case);
|
||||||
c->search->FindNext();
|
OPT_SET("Tool/Search Replace/RegExp")->SetBool(settings->use_regex);
|
||||||
else if (mode == 1)
|
OPT_SET("Tool/Search Replace/Field")->SetInt(static_cast<int>(settings->field));
|
||||||
c->search->ReplaceNext();
|
OPT_SET("Tool/Search Replace/Affect")->SetInt(static_cast<int>(settings->limit_to));
|
||||||
else
|
|
||||||
c->search->ReplaceAll();
|
|
||||||
|
|
||||||
config::mru->Add("Find", from_wx(LookFor));
|
|
||||||
UpdateDropDowns();
|
UpdateDropDowns();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -199,235 +147,16 @@ static void update_mru(wxComboBox *cb, const char *mru_name) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void DialogSearchReplace::UpdateDropDowns() {
|
void DialogSearchReplace::UpdateDropDowns() {
|
||||||
update_mru(FindEdit, "Find");
|
update_mru(find_edit, "Find");
|
||||||
|
|
||||||
if (hasReplace)
|
if (has_replace)
|
||||||
update_mru(ReplaceEdit, "Replace");
|
update_mru(replace_edit, "Replace");
|
||||||
}
|
}
|
||||||
|
|
||||||
SearchReplaceEngine::SearchReplaceEngine(agi::Context *c)
|
void DialogSearchReplace::Show(agi::Context *context, bool replace) {
|
||||||
: 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<wxString> *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<wxString> *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<AssDialogue>()) {
|
|
||||||
if (inSel && hasSelection && !sel.count(diag))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
boost::flyweight<wxString> *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) {
|
|
||||||
static DialogSearchReplace *diag = nullptr;
|
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
|
// Already opened, but wrong type - destroy and create the right one
|
||||||
diag->Destroy();
|
diag->Destroy();
|
||||||
diag = nullptr;
|
diag = nullptr;
|
||||||
|
@ -436,7 +165,6 @@ void SearchReplaceEngine::OpenDialog(bool replace) {
|
||||||
if (!diag)
|
if (!diag)
|
||||||
diag = new DialogSearchReplace(context, replace);
|
diag = new DialogSearchReplace(context, replace);
|
||||||
|
|
||||||
diag->FindEdit->SetFocus();
|
diag->find_edit->SetFocus();
|
||||||
diag->Show();
|
diag->wxDialog::Show();
|
||||||
hasReplace = replace;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,29 +1,16 @@
|
||||||
// Copyright (c) 2005, Rodrigo Braz Monteiro
|
// Copyright (c) 2013, Thomas Goyne <plorkyeran@aegisub.org>
|
||||||
// All rights reserved.
|
|
||||||
//
|
//
|
||||||
// Redistribution and use in source and binary forms, with or without
|
// Permission to use, copy, modify, and distribute this software for any
|
||||||
// modification, are permitted provided that the following conditions are met:
|
// 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,
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
// this list of conditions and the following disclaimer.
|
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
// * Redistributions in binary form must reproduce the above copyright notice,
|
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
// this list of conditions and the following disclaimer in the documentation
|
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
// and/or other materials provided with the distribution.
|
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
// * Neither the name of the Aegisub Group nor the names of its contributors
|
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
// may be used to endorse or promote products derived from this software
|
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF 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/
|
// Aegisub Project http://www.aegisub.org/
|
||||||
|
|
||||||
|
@ -32,61 +19,28 @@
|
||||||
/// @ingroup secondary_ui
|
/// @ingroup secondary_ui
|
||||||
///
|
///
|
||||||
|
|
||||||
|
#include <libaegisub/scoped_ptr.h>
|
||||||
|
|
||||||
#include <wx/dialog.h>
|
#include <wx/dialog.h>
|
||||||
#include <wx/string.h>
|
|
||||||
|
|
||||||
namespace agi { struct Context; }
|
namespace agi { struct Context; }
|
||||||
class wxCheckBox;
|
class SearchReplaceEngine;
|
||||||
|
struct SearchReplaceSettings;
|
||||||
class wxComboBox;
|
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 {
|
class DialogSearchReplace : public wxDialog {
|
||||||
friend class SearchReplaceEngine;
|
|
||||||
|
|
||||||
agi::Context *c;
|
agi::Context *c;
|
||||||
|
agi::scoped_ptr<SearchReplaceSettings> settings;
|
||||||
bool hasReplace;
|
bool has_replace;
|
||||||
|
wxComboBox *find_edit;
|
||||||
wxComboBox *FindEdit;
|
wxComboBox *replace_edit;
|
||||||
wxComboBox *ReplaceEdit;
|
|
||||||
wxCheckBox *CheckMatchCase;
|
|
||||||
wxCheckBox *CheckRegExp;
|
|
||||||
wxCheckBox *CheckUpdateVideo;
|
|
||||||
wxRadioBox *Affect;
|
|
||||||
wxRadioBox *Field;
|
|
||||||
|
|
||||||
void UpdateDropDowns();
|
void UpdateDropDowns();
|
||||||
void FindReplace(int mode); // 0 = find, 1 = replace next, 2 = replace all
|
void FindReplace(bool (SearchReplaceEngine::*func)());
|
||||||
|
|
||||||
public:
|
public:
|
||||||
DialogSearchReplace(agi::Context* c, bool withReplace);
|
static void Show(agi::Context *context, bool with_replace);
|
||||||
|
|
||||||
|
DialogSearchReplace(agi::Context* c, bool with_replace);
|
||||||
~DialogSearchReplace();
|
~DialogSearchReplace();
|
||||||
};
|
};
|
||||||
|
|
|
@ -59,12 +59,12 @@
|
||||||
#include "command/command.h"
|
#include "command/command.h"
|
||||||
#include "dialog_detached_video.h"
|
#include "dialog_detached_video.h"
|
||||||
#include "dialog_manager.h"
|
#include "dialog_manager.h"
|
||||||
#include "dialog_search_replace.h"
|
|
||||||
#include "dialog_version_check.h"
|
#include "dialog_version_check.h"
|
||||||
#include "help_button.h"
|
#include "help_button.h"
|
||||||
#include "libresrc/libresrc.h"
|
#include "libresrc/libresrc.h"
|
||||||
#include "main.h"
|
#include "main.h"
|
||||||
#include "options.h"
|
#include "options.h"
|
||||||
|
#include "search_replace_engine.h"
|
||||||
#include "standard_paths.h"
|
#include "standard_paths.h"
|
||||||
#include "subs_edit_box.h"
|
#include "subs_edit_box.h"
|
||||||
#include "subs_edit_ctrl.h"
|
#include "subs_edit_ctrl.h"
|
||||||
|
|
|
@ -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 <libaegisub/of_type_adaptor.h>
|
||||||
|
|
||||||
|
#include <wx/msgdlg.h>
|
||||||
|
#include <wx/regex.h>
|
||||||
|
|
||||||
|
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<wxString> *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<wxString> *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<AssDialogue>()) {
|
||||||
|
if (inSel && hasSelection && !sel.count(diag))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
boost::flyweight<wxString> *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;
|
||||||
|
}
|
|
@ -0,0 +1,66 @@
|
||||||
|
// Copyright (c) 2013, Thomas Goyne <plorkyeran@aegisub.org>
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
//
|
||||||
|
// Aegisub Project http://www.aegisub.org/
|
||||||
|
|
||||||
|
#include <wx/string.h>
|
||||||
|
|
||||||
|
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);
|
||||||
|
};
|
|
@ -32,6 +32,9 @@
|
||||||
/// @ingroup custom_control utility
|
/// @ingroup custom_control utility
|
||||||
///
|
///
|
||||||
|
|
||||||
|
#include <libaegisub/exception.h>
|
||||||
|
|
||||||
|
#include <wx/radiobox.h>
|
||||||
#include <wx/validate.h>
|
#include <wx/validate.h>
|
||||||
|
|
||||||
/// A wx validator that only allows valid numbers
|
/// A wx validator that only allows valid numbers
|
||||||
|
@ -88,3 +91,35 @@ public:
|
||||||
|
|
||||||
DECLARE_EVENT_TABLE()
|
DECLARE_EVENT_TABLE()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
class EnumBinder : public wxValidator {
|
||||||
|
T *value;
|
||||||
|
|
||||||
|
wxObject *Clone() const override { return new EnumBinder<T>(value); }
|
||||||
|
|
||||||
|
bool TransferFromWindow() override {
|
||||||
|
if (wxRadioBox *rb = dynamic_cast<wxRadioBox*>(GetWindow()))
|
||||||
|
*value = static_cast<T>(rb->GetSelection());
|
||||||
|
else
|
||||||
|
throw agi::InternalError("Control type not supported by EnumBinder", 0);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TransferToWindow() override {
|
||||||
|
if (wxRadioBox *rb = dynamic_cast<wxRadioBox*>(GetWindow()))
|
||||||
|
rb->SetSelection(static_cast<int>(*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<typename T>
|
||||||
|
EnumBinder<T> MakeEnumBinder(T *value) {
|
||||||
|
return EnumBinder<T>(value);
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue