mirror of
https://github.com/wxWidgets/wxWidgets.git
synced 2026-09-25 00:43:39 +08:00
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:
committed by
Vadim Zeitlin
parent
e5f95a3bb0
commit
5f7d255be3
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user