tdf#161543 Enhance the searching functionality in FD & FW

Change-Id: I1a21595228f886c942ae46d90e41705443d31550
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/170073
Reviewed-by: Heiko Tietze <heiko.tietze@documentfoundation.org>
Reviewed-by: Andreas Heinisch <andreas.heinisch@yahoo.de>
Tested-by: Jenkins
This commit is contained in:
AhmedHamed 2024-07-05 21:41:26 +03:00 committed by Andreas Heinisch
parent 5b0256f30e
commit d05e0be5f4
8 changed files with 276 additions and 46 deletions

View file

@ -25,6 +25,7 @@
#include "funcpage.hxx"
#include <unotools/syslocale.hxx>
#include <unotools/charclass.hxx>
#include <unotools/textsearch.hxx>
namespace formula
{
@ -48,10 +49,10 @@ FuncPage::FuncPage(weld::Container* pParent, const IFunctionManager* _pFunctionM
, m_xLbFunction(m_xBuilder->weld_tree_view(u"function"_ustr))
, m_xScratchIter(m_xLbFunction->make_iterator())
, m_xLbFunctionSearchString(m_xBuilder->weld_entry(u"search"_ustr))
, m_xSimilaritySearch(m_xBuilder->weld_check_button(u"similaritysearch"_ustr))
, m_xHelpButton(m_xBuilder->weld_button(u"help"_ustr))
, m_pFunctionManager(_pFunctionManager)
{
m_xLbFunction->make_sorted();
m_aHelpId = m_xLbFunction->get_help_id();
m_pFunctionManager->fillLastRecentlyUsedFunctions(aLRUList);
@ -76,6 +77,7 @@ FuncPage::FuncPage(weld::Container* pParent, const IFunctionManager* _pFunctionM
m_xLbFunction->connect_row_activated(LINK(this, FuncPage, DblClkHdl));
m_xLbFunction->connect_key_press(LINK(this, FuncPage, KeyInputHdl));
m_xLbFunctionSearchString->connect_changed(LINK(this, FuncPage, ModifyHdl));
m_xSimilaritySearch->connect_toggled(LINK(this, FuncPage, SimilarityToggleHdl));
m_xHelpButton->connect_clicked(LINK(this, FuncPage, SelHelpClickHdl));
m_xHelpButton->set_sensitive(false);
@ -117,12 +119,26 @@ void FuncPage::impl_addFunctions(const IFunctionCategory* _pCategory, bool bFill
}
}
void FuncPage::SearchFunction(const OUString& rFuncName, const OUString& rSearchString,
TFunctionDesc pDesc, const bool bSimilaritySearch)
{
std::pair<sal_Int32, sal_Int32> score = std::make_pair(0, 0);
if (bSimilaritySearch && !utl::TextSearch::SimilaritySearch(rFuncName, rSearchString, score))
return;
if (!bSimilaritySearch && rFuncName.indexOf(rSearchString) < 0
&& rSearchString.indexOf(rFuncName) < 0)
return;
sFuncScores.insert(std::make_pair(score, std::make_pair(rFuncName, pDesc)));
}
//aStr is non-empty when user types in the search box to search some function
void FuncPage::UpdateFunctionList(const OUString& aStr)
{
m_xLbFunction->clear();
m_xLbFunction->freeze();
mCategories.clear();
sFuncScores.clear();
const sal_Int32 nSelPos = m_xLbCategory->get_active();
bool bCollapse = nSelPos == 1;
@ -195,21 +211,23 @@ void FuncPage::UpdateFunctionList(const OUString& aStr)
for (sal_uInt32 j = 0; j < nFunctionCount; ++j)
{
TFunctionDesc pDesc(pCategory->getFunction(j));
// tdf#146781 - search for the desired function also in the description
if (rCharClass.uppercase(pDesc->getFunctionName()).indexOf(aSearchStr) >= 0
|| rCharClass.uppercase(pDesc->getDescription()).indexOf(aSearchStr) >= 0)
{
if (!pDesc->isHidden())
{
OUString aFunction(pDesc->getFunctionName());
OUString sId(weld::toId(pDesc));
const OUString aFunction(rCharClass.uppercase(pDesc->getFunctionName()));
SearchFunction(aFunction, aSearchStr, pDesc, m_xSimilaritySearch->get_active());
}
}
weld::TreeIter* pCategoryIter
= FillCategoriesMap(pCategory->getName(), bCollapse);
m_xLbFunction->insert(pCategoryIter, -1, &aFunction, &sId, nullptr, nullptr,
false, m_xScratchIter.get());
}
}
for (const auto& func : sFuncScores)
{
TFunctionDesc pDesc(func.second.second);
if (!pDesc->isHidden())
{
const OUString aCategory(pDesc->getCategory()->getName());
const OUString aFunction(func.second.first);
const OUString aFuncDescId(weld::toId(pDesc));
weld::TreeIter* pCategory = FillCategoriesMap(aCategory, bCollapse);
m_xLbFunction->insert(pCategory, -1, &aFunction, &aFuncDescId, nullptr, nullptr,
false, m_xScratchIter.get());
}
}
}
@ -231,11 +249,12 @@ void FuncPage::UpdateFunctionList(const OUString& aStr)
IMPL_LINK_NOARG(FuncPage, SelComboBoxHdl, weld::ComboBox&, void)
{
m_xLbFunctionSearchString->set_sensitive(m_xLbCategory->get_active() > 0);
if (m_xLbCategory->get_active() == 0)
m_xLbFunctionSearchString->set_text(u""_ustr);
m_xHelpButton->set_sensitive(false);
OUString searchStr = m_xLbFunctionSearchString->get_text();
m_xLbFunction->set_help_id(m_aHelpId);
UpdateFunctionList(searchStr);
m_xHelpButton->set_sensitive(false);
}
IMPL_LINK_NOARG(FuncPage, SelTreeViewHdl, weld::TreeView&, void)
@ -270,6 +289,17 @@ IMPL_LINK_NOARG(FuncPage, DblClkHdl, weld::TreeView&, bool)
}
IMPL_LINK_NOARG(FuncPage, ModifyHdl, weld::Entry&, void)
{
if (m_xLbCategory->get_active() == 0)
{
m_xLbCategory->set_active(1);
m_xHelpButton->set_sensitive(false);
}
OUString searchStr = m_xLbFunctionSearchString->get_text();
UpdateFunctionList(searchStr);
}
IMPL_LINK_NOARG(FuncPage, SimilarityToggleHdl, weld::Toggleable&, void)
{
OUString searchStr = m_xLbFunctionSearchString->get_text();
UpdateFunctionList(searchStr);

View file

@ -21,6 +21,7 @@
#include <vcl/weld.hxx>
#include <vector>
#include <set>
#include <unordered_map>
namespace formula
@ -42,6 +43,7 @@ private:
std::unique_ptr<weld::TreeView> m_xLbFunction;
std::unique_ptr<weld::TreeIter> m_xScratchIter;
std::unique_ptr<weld::Entry> m_xLbFunctionSearchString;
std::unique_ptr<weld::CheckButton> m_xSimilaritySearch;
std::unique_ptr<weld::Button> m_xHelpButton;
Link<FuncPage&,void> aDoubleClickLink;
@ -50,6 +52,8 @@ private:
::std::vector< TFunctionDesc > aLRUList;
::std::unordered_map<OUString, std::unique_ptr<weld::TreeIter>> mCategories;
::std::set<std::pair<std::pair<sal_Int32, sal_Int32>, std::pair<OUString, TFunctionDesc>>>
sFuncScores;
OUString m_aHelpId;
// tdf#104487 - remember last used function category
@ -64,6 +68,7 @@ private:
DECL_LINK(KeyInputHdl, const KeyEvent&, bool);
DECL_LINK(ModifyHdl, weld::Entry&, void);
DECL_LINK(SelHelpClickHdl, weld::Button&, void);
DECL_LINK(SimilarityToggleHdl, weld::Toggleable&, void);
void UpdateFunctionList(const OUString&);
@ -92,6 +97,8 @@ public:
void SetSelectHdl( const Link<FuncPage&,void>& rLink ) { aSelectionLink = rLink; }
bool IsVisible() const { return m_xContainer->get_visible(); }
void SearchFunction(const OUString&, const OUString&, TFunctionDesc, const bool);
};
} // formula

View file

@ -52,6 +52,26 @@
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="similaritysearch">
<property name="label" translatable="yes" context="functionpage|similaritysearch">Similar</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
<property name="use-underline">True</property>
<property name="draw-indicator">True</property>
<child internal-child="accessible">
<object class="AtkObject" id="similaritysearch-atkobject">
<property name="AtkObject::accessible-description" translatable="yes" context="functionpage|extended_tip|similaritysearch">Search and Sort functions by similarity</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label1">
<property name="visible">True</property>
@ -64,7 +84,7 @@
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
<property name="position">3</property>
</packing>
</child>
<child>
@ -84,7 +104,7 @@
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">3</property>
<property name="position">4</property>
</packing>
</child>
<child>
@ -99,7 +119,7 @@
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">4</property>
<property name="position">5</property>
</packing>
</child>
<child>
@ -147,7 +167,7 @@
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">5</property>
<property name="position">6</property>
</packing>
</child>
<child>
@ -163,7 +183,7 @@
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">6</property>
<property name="position">7</property>
</packing>
</child>
<child internal-child="accessible">

View file

@ -27,6 +27,9 @@
#include <ostream>
#define WLD_THRESHOLD 3
#define SMALL_STRING_THRESHOLD 4
class CharClass;
namespace com::sun::star::lang { struct Locale; }
@ -209,6 +212,30 @@ public:
/* replace back references in the replace string by the sub expressions from the search result */
static void ReplaceBackReferences( OUString& rReplaceStr, std::u16string_view rStr, const css::util::SearchResult& rResult );
/**
* @brief Search for a string in a another one based on similarity
* @param rString The string we compare with
* @param rSearchString The search term
* @param rSimilarityScore The similarity score (sent by reference to be filled)
* @return True if the search term is found, false otherwise
*/
static bool SimilaritySearch(const OUString& rString, const OUString& rSearchString,
::std::pair<sal_Int32, sal_Int32>& rSimilarityScore);
/**
* @brief Get similarity score between two strings
* according to the length of the common substring and its position
* @param rString The string we compare with
* @param rSearchString The search term
* @param nInitialScore The initial score
* @param bFromStart True if the search is from the start
* @return Score if the search term is found in the text, -1 otherwise
*/
static sal_Int32 GetSubstringSimilarity(std::u16string_view rString,
std::u16string_view rSearchString,
sal_Int32& nInitialScore, const bool bFromStart);
static sal_Int32 GetWeightedLevenshteinDistance(const OUString& rString,
const OUString& rSearchString);
};
} // namespace utl

View file

@ -22,6 +22,7 @@
#include <sfx2/viewsh.hxx>
#include <formula/funcvarargs.h>
#include <unotools/charclass.hxx>
#include <unotools/textsearch.hxx>
#include <vcl/svapp.hxx>
#include <vcl/help.hxx>
@ -55,6 +56,7 @@ ScFunctionWin::ScFunctionWin(weld::Widget* pParent)
, xScratchIter(xFuncList->make_iterator())
, xInsertButton(m_xBuilder->weld_button(u"insert"_ustr))
, xHelpButton(m_xBuilder->weld_button(u"help"_ustr))
, xSimilaritySearch(m_xBuilder->weld_check_button(u"similaritysearch"_ustr))
, xFiFuncDesc(m_xBuilder->weld_text_view(u"funcdesc"_ustr))
, m_xSearchString(m_xBuilder->weld_entry(u"search"_ustr))
, xConfigListener(new comphelper::ConfigurationListener(u"/org.openoffice.Office.Calc/Formula/Syntax"_ustr))
@ -79,6 +81,7 @@ ScFunctionWin::ScFunctionWin(weld::Widget* pParent)
xFuncList->connect_row_activated(LINK( this, ScFunctionWin, SetRowActivatedHdl));
xInsertButton->connect_clicked(LINK( this, ScFunctionWin, SetSelectionClickHdl));
xHelpButton->connect_clicked(LINK( this, ScFunctionWin, SetHelpClickHdl));
xSimilaritySearch->connect_toggled(LINK(this, ScFunctionWin, SetSimilarityToggleHdl));
xCatBox->set_active(0);
@ -109,6 +112,7 @@ ScFunctionWin::~ScFunctionWin()
xFuncList.reset();
xInsertButton.reset();
xHelpButton.reset();
xSimilaritySearch.reset();
xFiFuncDesc.reset();
}
@ -189,6 +193,19 @@ void ScFunctionWin::UpdateLRUList()
}
}
void ScFunctionWin::SearchFunction(const OUString& rFuncName, const OUString& rSearchString,
const ScFuncDesc* pDesc, const bool bSimilaritySearch)
{
std::pair<sal_Int32, sal_Int32> score = std::make_pair(0, 0);
if (bSimilaritySearch && !utl::TextSearch::SimilaritySearch(rFuncName, rSearchString, score))
return;
if (!bSimilaritySearch && rFuncName.indexOf(rSearchString) < 0
&& rSearchString.indexOf(rFuncName) < 0)
return;
sFuncScores.insert(std::make_pair(score, std::make_pair(rFuncName, pDesc)));
}
/*************************************************************************
#* Member: SetDescription
#*------------------------------------------------------------------------
@ -252,6 +269,7 @@ void ScFunctionWin::UpdateFunctionList(const OUString& rSearchString)
xFuncList->clear();
xFuncList->freeze();
mCategories.clear();
sFuncScores.clear();
bool bCollapse = nCategory == 0;
bool bFilter = !rSearchString.isEmpty();
@ -270,38 +288,31 @@ void ScFunctionWin::UpdateFunctionList(const OUString& rSearchString)
const ScFuncDesc* pDesc = pFuncMgr->First(nCategory);
while (pDesc)
{
OUString aCategory = pDesc->getCategory()->getName();
OUString aFunction = pDesc->getFunctionName();
OUString aFuncDescId = weld::toId(pDesc);
const OUString aCategory(pDesc->getCategory()->getName());
const OUString aFunction(pCharClass->uppercase(pDesc->getFunctionName()));
const OUString aFuncDescId(weld::toId(pDesc));
if (!bFilter || (pCharClass->uppercase(aFunction).startsWith(aSearchStr)))
if (bFilter)
SearchFunction(aFunction, aSearchStr, pDesc, xSimilaritySearch->get_active());
else
{
weld::TreeIter* pCategory = FillCategoriesMap(aCategory, bCollapse);
xFuncList->insert(pCategory, -1, &aFunction, &aFuncDescId, nullptr, nullptr,
false, xScratchIter.get());
false, xScratchIter.get());
}
pDesc = pFuncMgr->Next();
}
// Now add the functions that have the search string in the middle of the function name
// Note that this will only be necessary if the search string is not empty
if (bFilter)
for (const auto& func : sFuncScores)
{
pDesc = pFuncMgr->First( nCategory );
while ( pDesc )
{
OUString aCategory = pDesc->getCategory()->getName();
OUString aFunction = pDesc->getFunctionName();
OUString aFuncDescId = weld::toId(pDesc);
pDesc = func.second.second;
const OUString aCategory(pDesc->getCategory()->getName());
const OUString aFunction(func.second.first);
const OUString aFuncDescId(weld::toId(pDesc));
weld::TreeIter* pCategory = FillCategoriesMap(aCategory, bCollapse);
if (pCharClass->uppercase(aFunction).indexOf(aSearchStr) > 0)
{
weld::TreeIter* pCategory = FillCategoriesMap(aCategory, bCollapse);
xFuncList->insert(pCategory, -1, &aFunction, &aFuncDescId, nullptr, nullptr,
false, xScratchIter.get());
}
pDesc = pFuncMgr->Next();
}
xFuncList->insert(pCategory, -1, &aFunction, &aFuncDescId, nullptr, nullptr, false,
xScratchIter.get());
}
}
else // LRU list
@ -477,6 +488,11 @@ void ScFunctionWin::DoEnter(bool bDoubleOrEnter)
IMPL_LINK_NOARG(ScFunctionWin, ModifyHdl, weld::Entry&, void)
{
if (xCatBox->get_active() == 0)
{
xCatBox->set_active(1);
xHelpButton->set_sensitive(false);
}
OUString searchStr = m_xSearchString->get_text();
UpdateFunctionList(searchStr);
SetDescription();
@ -576,8 +592,9 @@ IMPL_LINK(ScFunctionWin, KeyInputHdl, const KeyEvent&, rEvent, bool)
IMPL_LINK_NOARG(ScFunctionWin, SelComboHdl, weld::ComboBox&, void)
{
if (xCatBox->get_active() == 0)
m_xSearchString->set_text(u""_ustr);
xHelpButton->set_sensitive(xCatBox->get_active() != 1);
m_xSearchString->set_sensitive(xCatBox->get_active() > 0);
OUString searchStr = m_xSearchString->get_text();
UpdateFunctionList(searchStr);
SetDescription();
@ -638,6 +655,13 @@ IMPL_LINK_NOARG( ScFunctionWin, SetHelpClickHdl, weld::Button&, void )
}
}
IMPL_LINK_NOARG(ScFunctionWin, SetSimilarityToggleHdl, weld::Toggleable&, void)
{
OUString searchStr = m_xSearchString->get_text();
UpdateFunctionList(searchStr);
SetDescription();
}
IMPL_LINK_NOARG( ScFunctionWin, SetRowActivatedHdl, weld::TreeView&, bool )
{
DoEnter(true); // saves the input

View file

@ -49,6 +49,7 @@ private:
std::unique_ptr<weld::TreeIter> xScratchIter;
std::unique_ptr<weld::Button> xInsertButton;
std::unique_ptr<weld::Button> xHelpButton;
std::unique_ptr<weld::CheckButton> xSimilaritySearch;
std::unique_ptr<weld::TextView> xFiFuncDesc;
std::unique_ptr<weld::Entry> m_xSearchString;
@ -59,6 +60,8 @@ private:
OUString m_aListHelpId;
OUString m_aSearchHelpId;
::std::set<std::pair<std::pair<sal_Int32, sal_Int32>, std::pair<OUString, const ScFuncDesc*>>>
sFuncScores;
::std::vector< const formula::IFunctionDescription*> aLRUList;
::std::unordered_map<OUString, std::unique_ptr<weld::TreeIter>> mCategories;
@ -70,6 +73,7 @@ private:
DECL_LINK( SetRowActivatedHdl, weld::TreeView&, bool );
DECL_LINK( SetSelectionClickHdl, weld::Button&, void );
DECL_LINK( SetHelpClickHdl, weld::Button&, void );
DECL_LINK( SetSimilarityToggleHdl, weld::Toggleable&, void );
DECL_LINK( SelComboHdl, weld::ComboBox&, void );
DECL_LINK( SelTreeHdl, weld::TreeView&, void );
DECL_LINK( ModifyHdl, weld::Entry&, void );
@ -82,6 +86,7 @@ public:
void InitLRUList();
void UpdateFunctionList(const OUString&);
void SearchFunction(const OUString&, const OUString&, const ScFuncDesc*, const bool);
};
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

View file

@ -145,7 +145,7 @@
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<property name="orientation">horizontal</property>
<property name="spacing">6</property>
<child>
<object class="GtkEntry" id="search">
@ -166,6 +166,26 @@
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="similaritysearch">
<property name="label" translatable="yes" context="functionpanel|similaritysearch">Similar</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
<property name="use-underline">True</property>
<property name="draw-indicator">True</property>
<child internal-child="accessible">
<object class="AtkObject" id="similaritysearch-atkobject">
<property name="AtkObject::accessible-description" translatable="yes" context="functionpanel|extended_tip|similaritysearch">Search and Sort functions by similarity</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="left-attach">0</property>

View file

@ -353,6 +353,103 @@ void TextSearch::ReplaceBackReferences( OUString& rReplaceStr, std::u16string_vi
rReplaceStr = sBuff.makeStringAndClear();
}
bool TextSearch::SimilaritySearch(const OUString& rString, const OUString& rSearchString,
::std::pair<sal_Int32, sal_Int32>& rSimilarityScore)
{
sal_Int32 nScore = 0;
sal_Int32 nFirstScore = GetSubstringSimilarity(rString, rSearchString, nScore, true);
if (nFirstScore == -1)
nFirstScore = GetSubstringSimilarity(rString, rSearchString, nScore, false);
if (nFirstScore == -1)
{
if (rSearchString.getLength() == 1)
{
if (rString.startsWith(rSearchString))
nFirstScore = nScore;
else if (rString.endsWith(rSearchString))
nFirstScore = nScore + 1;
nScore += 2;
}
else if (rString.getLength() == 1 && rSearchString.getLength() < SMALL_STRING_THRESHOLD)
{
if (rSearchString.startsWith(rString))
nFirstScore = nScore;
else if (rSearchString.endsWith(rString))
nFirstScore = nScore + 1;
nScore += 2;
}
}
sal_Int32 nSecondScore = GetWeightedLevenshteinDistance(rString, rSearchString);
if (nFirstScore == -1 && nSecondScore >= WLD_THRESHOLD)
return false;
rSimilarityScore.first = (nFirstScore == -1) ? nScore : nFirstScore;
rSimilarityScore.second = nSecondScore;
return true;
}
sal_Int32 TextSearch::GetSubstringSimilarity(std::u16string_view rString,
std::u16string_view rSearchString,
sal_Int32& nInitialScore, const bool bFromStart)
{
sal_Int32 nScore = -1;
for (sal_Int32 length = rSearchString.length(); length > 1; length--)
{
sal_Int32 nStartPos = bFromStart ? 0 : rSearchString.length() - length;
std::u16string_view rSearchSubString = rSearchString.substr(nStartPos, length);
if (rString.starts_with(rSearchSubString))
{
nScore = nInitialScore;
break;
}
else if (rString.ends_with(rSearchSubString))
{
nScore = nInitialScore + 1;
break;
}
else if (rString.find(rSearchSubString) != std::u16string_view::npos)
{
nScore = nInitialScore + 2;
break;
}
nInitialScore += 3;
}
return nScore;
}
sal_Int32 TextSearch::GetWeightedLevenshteinDistance(const OUString& rString,
const OUString& rSearchString)
{
sal_Int32 n = rString.getLength();
sal_Int32 m = rSearchString.getLength();
std::vector<std::vector<sal_Int32>> ScoreDP(n + 1, std::vector<sal_Int32>(m + 1));
for (sal_Int32 i = 0; i <= n; i++)
{
ScoreDP[i][0] = i;
}
for (sal_Int32 j = 0; j <= m; j++)
{
ScoreDP[0][j] = j;
}
for (sal_Int32 i = 1; i <= n; i++)
{
for (sal_Int32 j = 1; j <= m; j++)
{
sal_Int32& minE = ScoreDP[i][j];
minE = ScoreDP[i - 1][j] + 1;
minE = std::min(minE, ScoreDP[i][j - 1] + 1);
if (rString[i - 1] != rSearchString[j - 1])
minE = std::min(minE, ScoreDP[i - 1][j - 1] + 2);
else
minE = std::min(minE, ScoreDP[i - 1][j - 1]);
}
}
return ScoreDP[n][m];
}
} // namespace utl
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */