Add wxTextCtrl::SearchText() for MSW

Add new API allowing for more efficient searches in wxTextCtrl and
implement it in wxMSW only for now.

See #24756.
This commit is contained in:
Blake-Madden
2024-10-18 19:32:58 +02:00
committed by Vadim Zeitlin
parent e5f95a3bb0
commit 5f7d255be3
5 changed files with 353 additions and 0 deletions
+1
View File
@@ -44,6 +44,7 @@ public:
virtual wxString GetRTFValue() const override;
virtual void SetRTFValue(const wxString& val) override;
virtual bool IsRTFSupported() override { return IsRich(); }
virtual wxTextSearchResult SearchText(const wxTextSearch& search) const override;
#endif // wxUSE_RICHEDIT
virtual bool IsEmpty() const;
+75
View File
@@ -608,6 +608,72 @@ private:
wxString m_listStyleName;
};
// ----------------------------------------------------------------------------
// Search features for wxTextCtrl
// ----------------------------------------------------------------------------
// search options
// --------------
struct wxTextSearch
{
explicit wxTextSearch(const wxString& text = wxString{}) : m_searchValue(text) {}
enum class Direction
{
Down,
Up
};
wxTextSearch& SearchValue(const wxString& value)
{
m_searchValue = value;
return *this;
}
wxTextSearch& MatchCase(bool matchCase = true)
{
m_matchCase = matchCase;
return *this;
}
wxTextSearch& MatchWholeWord(const bool matchWholeWord = true)
{
m_wholeWord = matchWholeWord;
return *this;
}
wxTextSearch& SearchDirection(const Direction direction)
{
m_direction = direction;
return *this;
}
wxTextSearch& Start(const long startPosition)
{
m_startingPosition = startPosition;
return *this;
}
wxString m_searchValue;
long m_startingPosition = -1;
bool m_matchCase = false;
bool m_wholeWord = false;
Direction m_direction = Direction::Down;
};
// results from a search operation
// -------------------------------
struct wxTextSearchResult
{
explicit operator bool() const { return m_start != wxNOT_FOUND; }
explicit wxTextSearchResult(long startPos, long endPos) :
m_start(startPos), m_end(endPos) {}
wxTextSearchResult() = default;
long m_start = wxNOT_FOUND;
long m_end = wxNOT_FOUND;
};
// ----------------------------------------------------------------------------
// wxTextAreaBase: multiline text control specific methods
// ----------------------------------------------------------------------------
@@ -698,6 +764,15 @@ public:
virtual wxString GetRTFValue() const;
virtual void SetRTFValue(const wxString& val);
// Searches for text.
// Base class implementations simply asserts,
// the port must override these functions to really implement them.
virtual wxTextSearchResult SearchText(const wxTextSearch& WXUNUSED(search)) const
{
wxFAIL_MSG("Text search not implemented for the current platform.");
return wxTextSearchResult();
}
protected:
// implementation of loading/saving
virtual bool DoLoadFile(const wxString& file, int fileType);
+120
View File
@@ -1128,6 +1128,112 @@ public:
void operator=(const wxTextAttr& attr);
};
/**
Search options for wxTextCtrl::SearchText().
This is a builder class, where property functions can be
called during construction. For example:
@code
wxTextSearchResult result =
textctrl->SearchText(wxTextSearch(L"Institutional Research").
SearchDirection(wxTextSearch::Direction::Down).
MatchCase().
MatchWholeWord());
@endcode
@since 3.3.0
*/
struct wxTextSearch
{
/**
The string to search for.
*/
wxTextSearch(const wxString& text) : m_searchValue(text) {}
/**
One of the following values can be passed to wxTextSearch::SearchDirection() to
control direction when searching a wxTextCtrl.
@since 3.3.0
*/
enum class Direction
{
Down,
Up
};
/**
The string to search for.
*/
wxTextSearch& SearchValue(const wxString& value)
{
m_searchValue = value;
return *this;
}
/**
Whether the search should match case (i.e., be case sensitive).
By default, this is @c false; search will be case insensitive.
*/
wxTextSearch& MatchCase(const bool matchCase = true)
{
m_matchCase = matchCase;
return *this;
}
/**
Whether the search should match the whole word.
By default, this is @c false; searching will not match by whole word.
*/
wxTextSearch& MatchWholeWord(const bool matchWholeWord = true)
{
m_wholeWord = matchWholeWord;
return *this;
}
/**
Whether the search should go up or down in the text control.
By default, search will go downward.
*/
wxTextSearch& SearchDirection(const wxTextSearch::Direction direction)
{
m_direction = direction;
return *this;
}
/**
Where the search should start from. By default, if searching down,
then the search will start at 0. If searching up, then will start
at the end of control.
*/
wxTextSearch& Start(const long startPosition)
{
m_startingPosition = startPosition;
return *this;
}
wxString m_searchValue;
long m_startingPosition = -1;
bool m_matchCase = true;
bool m_wholeWord = false;
Direction m_direction = Direction::Down;
};
/** Result from wxTextCtrl::SearchText(), specifying the range of the found text.
Range values will be @c wxNOT_FOUND if a match was not found.
@since 3.3.0
*/
struct wxTextSearchResult
{
long m_start = wxNOT_FOUND;
long m_end = wxNOT_FOUND;
};
/**
@class wxTextProofOptions
@@ -1701,6 +1807,20 @@ public:
*/
void SetRTFValue(const wxString& val);
/**
Searches for a string in the control, using the provided search options.
The range of the match will be returned as a wxTextSearchResult, which will
contain -1 values if no match was found.
This is currently only implemented under wxMSW.
@since 3.3.0
@onlyfor{wxmsw}
*/
wxTextSearchResult SearchText(const wxTextSearch& search) const;
/**
Finds the position of the character at the specified point.
+51
View File
@@ -1198,6 +1198,57 @@ void wxTextCtrl::SetRTFValue(const wxString& val)
SetInsertionPoint(0);
}
wxTextSearchResult wxTextCtrl::SearchText(const wxTextSearch& search) const
{
// set up the flags
WPARAM flags = 0;
switch ( search.m_direction )
{
case wxTextSearch::Direction::Down:
flags |= FR_DOWN;
break;
case wxTextSearch::Direction::Up:
// Nothing to do this is (surprisingly) the default.
break;
}
if (search.m_wholeWord)
{
flags |= FR_WHOLEWORD;
}
if (search.m_matchCase)
{
flags |= FR_MATCHCASE;
}
FINDTEXTEX findText;
findText.chrg.cpMin = (search.m_startingPosition != -1) ?
// user-provided start
search.m_startingPosition :
// if going down, then start from 0; otherwise, start from end
(search.m_direction == wxTextSearch::Direction::Down) ? 0 : GetLastPosition();
if (search.m_direction == wxTextSearch::Direction::Down)
{
// go to the end of the text
findText.chrg.cpMax = -1;
}
else
{
// will search from the start of the selection and upward
// to the start of the text
findText.chrg.cpMax = 0;
}
findText.lpstrText = search.m_searchValue.wc_str();
if (SendMessage(GetHwnd(), EM_FINDTEXTEXW, flags, (LPARAM)&findText) == -1)
{
return wxTextSearchResult();
}
return wxTextSearchResult{ findText.chrgText.cpMin, findText.chrgText.cpMax };
}
#endif // wxUSE_RICHEDIT
void wxTextCtrl::WriteText(const wxString& value)
+106
View File
@@ -1487,6 +1487,112 @@ TEST_CASE("wxTextCtrl::Get/SetRTFValue", "[wxTextCtrl][rtf]")
}
#endif
#ifdef __WXMSW__
TEST_CASE("wxTextCtrl::SearchText", "[wxTextCtrl][search]")
{
wxWindow* const parent = wxTheApp->GetTopWindow();
std::unique_ptr<wxTextCtrl> text(new wxTextCtrl(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_RICH2 | wxTE_MULTILINE));
text->SetValue(R"(Allows more than 30Kb of text
(on all Windows versions)
and a very very long line to test wxHSCROLL style
And here is a link in quotation marks to test wxTE_AUTO_URL: "http://www.wxwidgets.org"
First 10 characters should be in red
Next 10 characters should be in blue
Next 10 characters should be normal
And the next 10 characters should be green and italic
This text should be cyan on blue
And this should be in blue and the text you type should be in blue as well.
And there is a mispeled word)");
text->SetSelection(0, 0);
auto results = text->SearchText(wxTextSearch(L"IMnotHERE!").SearchDirection(wxTextSearch::Direction::Down));
CHECK_FALSE(results);
results = text->SearchText(wxTextSearch(L"window").SearchDirection(wxTextSearch::Direction::Down).MatchCase());
CHECK_FALSE(results); // case is different
// ignore case
results = text->SearchText(wxTextSearch(L"window").SearchDirection(wxTextSearch::Direction::Down).MatchCase(false));
CHECK(results);
CHECK(results.m_start == 38);
CHECK(results.m_end == 44);
results = text->SearchText(wxTextSearch(L"Window").SearchDirection(wxTextSearch::Direction::Down).MatchCase());
CHECK(results);
CHECK(results.m_start == 38);
CHECK(results.m_end == 44);
results = text->SearchText(wxTextSearch(L"Window").SearchDirection(wxTextSearch::Direction::Down).MatchCase().MatchWholeWord());
CHECK_FALSE(results); // whole word fails
results = text->SearchText(wxTextSearch(L"Windows").SearchDirection(wxTextSearch::Direction::Down).MatchCase().MatchWholeWord());
CHECK(results);
CHECK(results.m_start == 38);
CHECK(results.m_end == 45);
results = text->SearchText(wxTextSearch(L"very").SearchDirection(wxTextSearch::Direction::Down).MatchCase().MatchWholeWord());
CHECK(results);
CHECK(results.m_start == 62);
CHECK(results.m_end == 66);
// will find the same match
results = text->SearchText(wxTextSearch(L"very").SearchDirection(wxTextSearch::Direction::Down).MatchCase().MatchWholeWord().Start(results.m_start));
CHECK(results);
CHECK(results.m_start == 62);
CHECK(results.m_end == 66);
// goes to next match
results = text->SearchText(wxTextSearch(L"very").SearchDirection(wxTextSearch::Direction::Down).MatchCase().MatchWholeWord().Start(results.m_start + 1));
CHECK(results);
CHECK(results.m_start == 67);
CHECK(results.m_end == 71);
// no more matches going down
results = text->SearchText(wxTextSearch(L"very").SearchDirection(wxTextSearch::Direction::Down).MatchCase().MatchWholeWord().Start(results.m_start + 1));
CHECK_FALSE(results);
// go up from the end
results = text->SearchText(wxTextSearch(L"very").SearchDirection(wxTextSearch::Direction::Up).MatchCase().MatchWholeWord());
CHECK(results);
CHECK(results.m_start == 67);
CHECK(results.m_end == 71);
results = text->SearchText(wxTextSearch(L"very").SearchDirection(wxTextSearch::Direction::Up).MatchCase().MatchWholeWord().Start(results.m_start));
CHECK(results);
CHECK(results.m_start == 62);
CHECK(results.m_end == 66);
// no more going up
results = text->SearchText(wxTextSearch(L"very").SearchDirection(wxTextSearch::Direction::Up).MatchCase().MatchWholeWord().Start(results.m_start));
CHECK_FALSE(results);
// phrase
results = text->SearchText(wxTextSearch(L"Next 10 characters").SearchDirection(wxTextSearch::Direction::Down).MatchCase().MatchWholeWord());
CHECK(results);
CHECK(results.m_start == 233);
CHECK(results.m_end == 251);
// Edge cases
// last word
results = text->SearchText(wxTextSearch(L"word").SearchDirection(wxTextSearch::Direction::Up).MatchCase().MatchWholeWord());
CHECK(results);
CHECK(results.m_start == 494);
CHECK(results.m_end == 498);
// first word
results = text->SearchText(wxTextSearch(L"Allows").SearchDirection(wxTextSearch::Direction::Down).MatchCase().MatchWholeWord());
CHECK(results);
CHECK(results.m_start == 0);
CHECK(results.m_end == 6);
}
#endif
TEST_CASE("wxTextCtrl::InitialCanUndo", "[wxTextCtrl][undo]")
{
long style = 0;