Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

improved search results #2116

Merged
merged 7 commits into from
Feb 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
20 changes: 18 additions & 2 deletions source/MRViewer/MRRibbonMenuSearch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@ void RibbonMenuSearch::drawWindow_( const Parameters& params )
{
if ( ImGui::IsKeyPressed( ImGuiKey_Escape ) )
deactivateSearch_();
#ifndef NDEBUG
if ( ImGui::IsKeyPressed( ImGuiKey_F11 ) )
showResultWeight_ = !showResultWeight_;
#endif

const float minSearchSize = cSearchSize * params.scaling;
if ( isSmallUI() )
Expand All @@ -75,7 +79,7 @@ void RibbonMenuSearch::drawWindow_( const Parameters& params )
ImGui::SetNextItemWidth( minSearchSize );
if ( ImGui::InputText( "##SearchLine", searchLine_ ) )
{
searchResult_ = RibbonSchemaHolder::search( searchLine_ );
searchResult_ = RibbonSchemaHolder::search( searchLine_, &searchResultWeight_ );
hightlightedSearchItem_ = -1;
}
if ( !ImGui::IsWindowAppearing() &&
Expand Down Expand Up @@ -125,6 +129,18 @@ void RibbonMenuSearch::drawWindow_( const Parameters& params )
params.btnDrawer.drawButtonItem( *foundItem.item, dbParams );
if ( foundItem.item->item->isActive() != pluginActive )
deactivateSearch_();
#ifndef NDEBUG
if ( showResultWeight_ && !searchLine_.empty() )
{
const auto& weights = searchResultWeight_[i];
ImGui::SameLine();
ImGui::Text( "%s", "(?)" );
if ( ImGui::IsItemHovered() )
ImGui::SetTooltip( "caption = %.3f\ncaption order = %.3f\ntooltip = %.3f\ntooltip order = %.3f",
weights.captionWeight, weights.captionOrderWeight,
weights.tooltipWeight, weights.tooltipOrderWeight );
}
#endif
}
ImGui::PopStyleVar( 1 );
ImGui::PopStyleColor( 3 );
Expand Down Expand Up @@ -164,7 +180,7 @@ void RibbonMenuSearch::drawMenuUI( const Parameters& params )
}
if ( searchInputText_( "##SearchLine", searchLine_, params ) )
{
searchResult_ = RibbonSchemaHolder::search( searchLine_ );
searchResult_ = RibbonSchemaHolder::search( searchLine_, &searchResultWeight_ );
hightlightedSearchItem_ = -1;
}
if ( mainInputFocused_ && !ImGui::IsItemFocused() )
Expand Down
4 changes: 4 additions & 0 deletions source/MRViewer/MRRibbonMenuSearch.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ class MRVIEWER_CLASS RibbonMenuSearch

std::string searchLine_;
std::vector<RibbonSchemaHolder::SearchResult> searchResult_;
std::vector<RibbonSchemaHolder::SearchResultWeight> searchResultWeight_;
std::vector<RibbonSchemaHolder::SearchResult> recentItems_;
int hightlightedSearchItem_{ -1 };

Expand All @@ -58,6 +59,9 @@ class MRVIEWER_CLASS RibbonMenuSearch
bool mainInputFocused_ = false;
bool blockSearchBtn_ = false;
bool setMainInputFocus_ = false;
#ifndef NDEBUG
bool showResultWeight_ = false;
#endif
};

}
168 changes: 128 additions & 40 deletions source/MRViewer/MRRibbonSchema.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,51 +32,102 @@ bool RibbonSchemaHolder::addItem( std::shared_ptr<RibbonMenuItem> item )
return true;
}

std::vector<RibbonSchemaHolder::SearchResult> RibbonSchemaHolder::search( const std::string& searchStr )
std::vector<RibbonSchemaHolder::SearchResult> RibbonSchemaHolder::search( const std::string& searchStr,
std::vector<SearchResultWeight>* weights /*= nullptr*/ )
{
std::vector<SearchResult> res;
std::vector<std::pair<SearchResult, SearchResultWeight>> rawResult;

if ( searchStr.empty() )
return res;
return {};
auto words = split( searchStr, " " );
std::erase_if( words, [] ( const auto& str ) { return str.empty(); } );
std::vector<std::pair<float, SearchResult>> resultListForSort;
auto enweight = [] ( const std::vector<std::string>& searchWords, const std::string& testLine )->float
auto calcWeight = [&words] ( const std::string& sourceStr )->Vector2f
{
if ( testLine.empty() )
return 1.0f;
float sumWeight = 0.0f;
for ( const auto& word : searchWords )
if ( sourceStr.empty() )
return { 1.0f, 1.f };

auto sourceWords = split( sourceStr, " " );
std::erase_if( sourceWords, [] ( const auto& str ) { return str.empty(); } );
if ( sourceWords.empty() )
return { 1.0f, 1.f };

const int sourceWordsSize = int( sourceWords.size() );
//std::vector<int> errorArr( words.size() * sourceWordsSize, -1 );
std::vector<bool> busyWord( sourceWordsSize, false );
int sumError = 0;
int searchCharCount = 0;
int posWeight = sourceWordsSize;
for ( int i = 0; i < words.size(); ++i )
{
int minDistance = std::abs( int( word.size() ) - int( testLine.size() ) );
int maxDistance = std::max( int( word.size() ), int( testLine.size() ) );
int distance = calcDamerauLevenshteinDistance( word, testLine, false );
sumWeight += float( distance - minDistance ) / ( maxDistance - minDistance );
searchCharCount += int( words[i].size() );
int minError = int( words[i].size() );
int minErrorIndex = -1;
for ( int j = 0; j < sourceWordsSize; ++j )
{
if ( busyWord[j] )
continue;
int error = calcDamerauLevenshteinDistance( words[i], sourceWords[j], false );
if ( i == words.size() - 1 )
error -= std::max( int( sourceWords[j].size() ) - int( words[i].size() ), 0 );
if ( error < minError )
{
minError = error;
minErrorIndex = j;
}
}
if ( minErrorIndex != -1 )
{
busyWord[minErrorIndex] = true;
posWeight += minErrorIndex;
}
sumError += minError;
}
return std::clamp( sumWeight / float( searchWords.size() ), 0.0f, 1.0f );
return { std::clamp( float( sumError ) / searchCharCount, 0.0f, 1.0f ), float( posWeight ) / sourceWordsSize / words.size()};
};

const float maxWeight = 0.25f;
bool exactMatch = false;
// check item (calc difference from search item) and add item to raw results if difference less than threshold
auto checkItem = [&] ( const MenuItemInfo& item, int t )
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please add comment for this function

{
const auto& caption = item.caption.empty() ? item.item->name() : item.caption;
const auto& tooltip = item.tooltip;
auto captionRes = enweight( words, caption );
auto tooltipRes = enweight( words, tooltip );
if ( captionRes > 0.1f && tooltipRes > 0.1f )
std::pair<SearchResult, SearchResultWeight> itemRes;
itemRes.first.tabIndex = t;
itemRes.first.item = &item;
const auto posCE = findSubstringCaseInsensitive( caption, searchStr );
if ( posCE != std::string::npos )
{
if ( !exactMatch )
{
rawResult.clear();
exactMatch = true;
}
itemRes.second.captionWeight = 0.f;
itemRes.second.captionOrderWeight = float( posCE ) / caption.size();
rawResult.push_back( itemRes );
return;
for ( const auto& word : words )
}
else if ( exactMatch )
{
auto posC = findSubstringCaseInsensitive( caption, word );
auto posT = findSubstringCaseInsensitive( tooltip, word );
if ( posC == std::string::npos )
captionRes += 10.0f;
else
captionRes += 0.5f * ( float( posC ) / caption.size() );
if ( posT == std::string::npos )
tooltipRes += 10.0f;
else
tooltipRes += 0.5f * ( float( posT ) / tooltip.size() );
const auto posTE = findSubstringCaseInsensitive( tooltip, searchStr );
if ( posTE == std::string::npos )
return;
itemRes.second.tooltipWeight = 0.f;
itemRes.second.tooltipOrderWeight = float( posTE ) / tooltip.size();
rawResult.push_back( itemRes );
return;
}
resultListForSort.push_back( { captionRes + 0.5f * tooltipRes, SearchResult{t,&item} } );

Vector2f weightEP = calcWeight( caption );
itemRes.second.captionWeight = weightEP.x;
itemRes.second.captionOrderWeight = weightEP.y;
weightEP = calcWeight( tooltip );
itemRes.second.tooltipWeight = weightEP.x;
itemRes.second.tooltipOrderWeight = weightEP.y;
if ( itemRes.second.captionWeight > maxWeight && itemRes.second.tooltipWeight > maxWeight )
return;
rawResult.push_back( itemRes );
};
const auto& schema = RibbonSchemaHolder::schema();
auto lookUpMenuItemList = [&] ( const MenuItemsList& list, int t )
Expand Down Expand Up @@ -121,25 +172,62 @@ std::vector<RibbonSchemaHolder::SearchResult> RibbonSchemaHolder::search( const
lookUpMenuItemList( schema.headerQuickAccessList, -1 );
lookUpMenuItemList( schema.sceneButtonsList, -1 );

std::sort( resultListForSort.begin(), resultListForSort.end(), [] ( const auto& a, const auto& b )
// clear duplicated results
std::sort( rawResult.begin(), rawResult.end(), [] ( const auto& a, const auto& b )
{
return intptr_t( a.second.item ) < intptr_t( b.second.item );
return intptr_t( a.first.item ) < intptr_t( b.first.item );
} );
resultListForSort.erase(
std::unique( resultListForSort.begin(), resultListForSort.end(),
rawResult.erase(
std::unique( rawResult.begin(), rawResult.end(),
[] ( const auto& a, const auto& b )
{
return a.second.item == b.second.item;
return a.first.item == b.first.item;
} ),
resultListForSort.end() );
rawResult.end() );

std::sort( resultListForSort.begin(), resultListForSort.end(), [] ( const auto& a, const auto& b )
std::sort( rawResult.begin(), rawResult.end(), [] ( const auto& a, const auto& b )
{
return a.first < b.first;
if ( a.second.captionWeight < b.second.captionWeight )
return true;
else if ( a.second.captionWeight > b.second.captionWeight )
return false;
else if ( a.second.captionOrderWeight < b.second.captionOrderWeight )
return true;
else if ( a.second.captionOrderWeight > b.second.captionOrderWeight )
return false;
else if ( a.second.tooltipWeight < b.second.tooltipWeight )
return true;
else if ( a.second.tooltipWeight > b.second.tooltipWeight )
return false;
else if ( a.second.tooltipOrderWeight < b.second.tooltipOrderWeight )
return true;
else
return false;
} );
res.reserve( resultListForSort.size() );
for ( const auto& sortedRes : resultListForSort )
res.push_back( sortedRes.second );

// filter results with error threshold as 3x minimum caption error
if ( !rawResult.empty() && rawResult[0].second.captionWeight < maxWeight / 3.f )
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please add comment for this block

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

{
const float maxWeightNew = rawResult[0].second.captionWeight * 3.f;
if ( rawResult.back().second.captionWeight > maxWeightNew )
{
auto tailIt = std::find_if( rawResult.begin(), rawResult.end(), [&] ( const auto& a )
{
return a.second.captionWeight > maxWeightNew && a.second.tooltipWeight > maxWeightNew;
} );
rawResult.erase( tailIt, rawResult.end() );
}
}

std::vector<SearchResult> res( rawResult.size() );
if ( weights )
*weights = std::vector<SearchResultWeight>( rawResult.size() );
for ( int i = 0; i < rawResult.size(); ++i )
{
res[i] = rawResult[i].first;
if ( weights )
( *weights )[i] = rawResult[i].second;
}

return res;
}
Expand Down
10 changes: 9 additions & 1 deletion source/MRViewer/MRRibbonSchema.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,15 @@ class MRVIEWER_CLASS RibbonSchemaHolder
int tabIndex{ -1 }; // -1 is default value if item has no tab
const MenuItemInfo* item{ nullptr }; // item info to show correct caption
};
MRVIEWER_API static std::vector<SearchResult> search( const std::string& searchStr );
// ancillary struct to hold information for search result order
struct SearchResultWeight
{
float captionWeight{ 1.f };
float captionOrderWeight{ 1.f };
float tooltipWeight{ 1.f };
float tooltipOrderWeight{ 1.f };
};
MRVIEWER_API static std::vector<SearchResult> search( const std::string& searchStr, std::vector<SearchResultWeight>* weights = nullptr );
private:
RibbonSchemaHolder() = default;
};
Expand Down