Add ranking of search results by number of character in matching words.
Add preselection of the best match on query update.
#include <QHeaderView>
#include <algorithm>
+#include <limits>
SUIT_FindActionDialog::SUIT_FindActionDialog(QWidget* theParent)
connect(this, SIGNAL(itemDoubleClicked(QTreeWidgetItem*, int)), this, SLOT(onItemDoubleClicked(QTreeWidgetItem*, int)));
}
+/*! \brief Compensates lack of std::distance(), which is introduced in C++17.
+\returns -1, if theIt does not belong to the */
+template <class Container>
+size_t indexOf(
+ const Container& theContainer,
+ const typename Container::iterator& theIt
+) {
+ auto it = theContainer.begin();
+ size_t distance = 0;
+ while (it != theContainer.end()) {
+ if (it == theIt)
+ return distance;
+
+ it++;
+ distance++;
+ }
+ return -1;
+}
+
void SUIT_FoundActionTree::updateItems(const std::map<QString, std::map<QString, SUIT_ActionSearcher::AssetsAndSearchData>>& theAssets)
{
- clear();
+ std::set<QString> shownModuleIDs; // To sort module-items by their IDs.
+
+ // Remove shown module items, if updated search results have no matching actions from these modules.
+ for (int moduleIdx = 0; moduleIdx < topLevelItemCount(); ) {
+ SUIT_FoundActionTreeModule* moduleItem = static_cast<SUIT_FoundActionTreeModule*>(topLevelItem(moduleIdx));
+ myModuleItemExpansionStates[moduleItem->myModuleID] = moduleItem->isExpanded();
+
+ const auto itUpdatedAssetsOfShownModule = theAssets.find(moduleItem->myModuleID);
+ if (itUpdatedAssetsOfShownModule == theAssets.end()) {
+ delete takeTopLevelItem(moduleIdx);
+ continue;
+ }
+
+ if (itUpdatedAssetsOfShownModule->second.empty()) {
+ delete takeTopLevelItem(moduleIdx);
+ continue;
+ }
+
+ shownModuleIDs.emplace(moduleItem->myModuleID);
+ moduleIdx++;
+ }
const auto shortcutMgr = SUIT_ShortcutMgr::get();
const QString lang = SUIT_ShortcutMgr::getLang();
+ SUIT_FoundActionTreeAction* preselectedActionItem = nullptr;
+
for (const auto& moduleIDAndAssets : theAssets) {
const QString& moduleID = moduleIDAndAssets.first;
const auto& moduleAssets = moduleIDAndAssets.second;
if (moduleAssets.empty())
continue;
- const auto moduleItem = new SUIT_FoundActionTreeFolder(moduleID);
- moduleItem->setAssets(shortcutMgr->getModuleAssets(moduleID), lang);
- addTopLevelItem(moduleItem);
- moduleItem->setFlags(Qt::ItemIsEnabled);
+ const auto moduleItemAndIdx = findModuleItem(moduleID);
+ SUIT_FoundActionTreeModule* moduleItem = moduleItemAndIdx.first;
+ if (!moduleItem) {
+ moduleItem = new SUIT_FoundActionTreeModule(moduleID);
+ moduleItem->setAssetsAndSearchData(SUIT_ActionSearcher::AssetsAndSearchData(shortcutMgr->getModuleAssets(moduleID)), lang);
+
+ const auto emplaceRes = shownModuleIDs.emplace(moduleID);
+ insertTopLevelItem(indexOf(shownModuleIDs, emplaceRes.first), moduleItem);
+
+ moduleItem->setFlags(Qt::ItemIsEnabled);
+
+ const auto itExpansionState = myModuleItemExpansionStates.find(moduleID);
+ if (itExpansionState == myModuleItemExpansionStates.end())
+ moduleItem->setExpanded(true); // Make module item expanded at first appearance.
+ else
+ moduleItem->setExpanded(itExpansionState->second);
+ }
+ else /* if the tree has the module-item */ {
+ const auto actionItems = moduleItem->takeChildren();
+ for (const auto actionItem : actionItems) {
+ delete actionItem;
+ }
+ }
+
+ // Fill module item with action items.
+ auto sortedActionItems = createActionSetWithComparator();
for (const auto& actionIDAndAssets : moduleAssets) {
const QString& inModuleActionID = actionIDAndAssets.first;
const SUIT_ActionSearcher::AssetsAndSearchData& assetsAndSearchData = actionIDAndAssets.second;
continue;
}
- actionItem->setAssets(assetsAndSearchData.myAssets, lang);
+ actionItem->setAssetsAndSearchData(assetsAndSearchData, lang);
+ sortedActionItems.emplace(actionItem);
+ }
+
+ SUIT_FoundActionTreeAction* preselectedActionItemCand = nullptr;
+ for (const auto actionItem : sortedActionItems) {
+ moduleItem->addChild(actionItem);
+
+ // Consider first ranked available action in the module (if user did not collapsed it) as a candidate for preselected action.
+ if (!preselectedActionItemCand && moduleItem->isExpanded() && actionItem->isEnabledBufferedValue())
+ preselectedActionItemCand = actionItem;
+ }
+
+ if (preselectedActionItem) {
+ if (preselectedActionItemCand) {
+ if (preselectedActionItemCand->matchMetrics() < preselectedActionItem->matchMetrics())
+ preselectedActionItem = preselectedActionItemCand;
+ }
+ }
+ else
+ preselectedActionItem = preselectedActionItemCand;
+ }
+
+ if (preselectedActionItem)
+ setCurrentItem(preselectedActionItem);
+}
+
+void SUIT_FoundActionTree::sort(SUIT_FoundActionTree::SortKey theKey, SUIT_FoundActionTree::SortOrder theOrder)
+{
+ if (theKey == mySortKey && theOrder == mySortOrder)
+ return;
+
+ mySortKey == theKey;
+ mySortOrder = theOrder;
+
+ for (int moduleIdx = 0; moduleIdx < topLevelItemCount(); moduleIdx++) {
+ const auto moduleItem = static_cast<SUIT_FoundActionTreeModule*>(topLevelItem(moduleIdx));
+
+ auto sortedActionItems = createActionSetWithComparator();
+ for (int childIdx = 0; childIdx < moduleItem->childCount(); childIdx++) {
+ SUIT_FoundActionTreeAction* const actionItem = static_cast<SUIT_FoundActionTreeAction*>(moduleItem->child(childIdx));
+ sortedActionItems.emplace(actionItem);
+ }
+
+ moduleItem->takeChildren();
+
+ for (const auto actionItem : sortedActionItems) {
moduleItem->addChild(actionItem);
}
- moduleItem->setExpanded(true); // Make tree expanded on first show.
}
}
-std::pair<SUIT_FoundActionTreeFolder*, int> SUIT_FoundActionTree::findModuleFolderItem(const QString& theModuleID) const
+std::pair<SUIT_FoundActionTreeModule*, int> SUIT_FoundActionTree::findModuleItem(const QString& theModuleID) const
{
for (int moduleIdx = 0; moduleIdx < topLevelItemCount(); moduleIdx++) {
- SUIT_FoundActionTreeFolder* moduleItem = static_cast<SUIT_FoundActionTreeFolder*>(topLevelItem(moduleIdx));
+ SUIT_FoundActionTreeModule* moduleItem = static_cast<SUIT_FoundActionTreeModule*>(topLevelItem(moduleIdx));
if (moduleItem->myModuleID == theModuleID)
- return std::pair<SUIT_FoundActionTreeFolder*, int>(moduleItem, moduleIdx);
+ return std::pair<SUIT_FoundActionTreeModule*, int>(moduleItem, moduleIdx);
+ }
+ return std::pair<SUIT_FoundActionTreeModule*, int>(nullptr, -1);
+}
+
+std::set<SUIT_FoundActionTreeAction*, std::function<bool(SUIT_FoundActionTreeAction*, SUIT_FoundActionTreeAction*)>> SUIT_FoundActionTree::createActionSetWithComparator() const
+{
+ QList<std::pair<SUIT_FoundActionTree::SortKey, SUIT_FoundActionTree::SortOrder>> sortSchema = SUIT_FoundActionTree::DEFAULT_SORT_SCHEMA;
+ {
+ for (auto itSameKey = sortSchema.begin(); itSameKey != sortSchema.end(); itSameKey++) {
+ if (itSameKey->first == mySortKey) {
+ sortSchema.erase(itSameKey);
+ break;
+ }
+ }
+ sortSchema.push_front(std::pair<SUIT_FoundActionTree::SortKey, SUIT_FoundActionTree::SortOrder>(mySortKey, mySortOrder));
}
- return std::pair<SUIT_FoundActionTreeFolder*, int>(nullptr, -1);
+
+ static const QCollator collator;
+ const std::function<bool(SUIT_FoundActionTreeAction*, SUIT_FoundActionTreeAction*)> comparator =
+ [sortSchema, &collator](const SUIT_FoundActionTreeAction* theItemA, const SUIT_FoundActionTreeAction* theItemB) {
+ for (const auto& keyAndOrder : sortSchema) {
+ const QVariant fieldOfA = theItemA->getValue(keyAndOrder.first);
+ const QVariant fieldOfB = theItemB->getValue(keyAndOrder.first);
+
+ bool* const fieldOfAIsDouble = new bool(false);
+ bool* const fieldOfBIsDouble = new bool(false);
+ const double matchMetricsA = fieldOfA.toDouble(fieldOfAIsDouble);
+ const double matchMetricsB = fieldOfB.toDouble(fieldOfBIsDouble);
+ if (fieldOfAIsDouble && fieldOfBIsDouble) {
+ const double res = matchMetricsA - matchMetricsB;
+ if (std::abs(res) > std::numeric_limits<double>::epsilon())
+ return keyAndOrder.second == SUIT_FoundActionTree::SortOrder::Ascending ? res < 0 : res > 0;
+ }
+ else {
+ const int res = collator.compare(fieldOfA.toString(), fieldOfB.toString());
+ if (res != 0)
+ return keyAndOrder.second == SUIT_FoundActionTree::SortOrder::Ascending ? res < 0 : res > 0;
+ }
+ }
+ return false;
+ };
+
+ return std::set<SUIT_FoundActionTreeAction*, std::function<bool(SUIT_FoundActionTreeAction*, SUIT_FoundActionTreeAction*)>>(comparator);
}
void SUIT_FoundActionTree::onItemDoubleClicked(QTreeWidgetItem* theItem, int theColIdx)
{
{
SUIT_FoundActionTreeItem* const item = static_cast<SUIT_FoundActionTreeItem*>(theItem);
- // Do not react if folder-item is clicked.
+ // Do not react if module-item is clicked.
if (item->type() != SUIT_FoundActionTreeItem::Type::Action)
return;
}
static_cast<SUIT_FindActionDialog*>(parentWidget())->accept();
}
+/*static*/ const QList<std::pair<SUIT_FoundActionTree::SortKey, SUIT_FoundActionTree::SortOrder>> SUIT_FoundActionTree::DEFAULT_SORT_SCHEMA =
+{
+ {SUIT_FoundActionTree::SortKey::MatchMetrics, SUIT_FoundActionTree::SortOrder::Ascending},
+ {SUIT_FoundActionTree::SortKey::Name, SUIT_FoundActionTree::SortOrder::Ascending},
+ {SUIT_FoundActionTree::SortKey::ToolTip, SUIT_FoundActionTree::SortOrder::Ascending},
+ {SUIT_FoundActionTree::SortKey::ID, SUIT_FoundActionTree::SortOrder::Ascending}
+};
+
SUIT_FoundActionTreeItem::SUIT_FoundActionTreeItem(const QString& theModuleID)
: QTreeWidgetItem(), myModuleID(theModuleID)
}
-SUIT_FoundActionTreeFolder::SUIT_FoundActionTreeFolder(const QString& theModuleID)
+SUIT_FoundActionTreeModule::SUIT_FoundActionTreeModule(const QString& theModuleID)
: SUIT_FoundActionTreeItem(theModuleID)
{
QFont f = font(SUIT_FoundActionTree::ElementIdx::Name);
setText(SUIT_FoundActionTree::ElementIdx::Name, theModuleID);
}
-void SUIT_FoundActionTreeFolder::setAssets(std::shared_ptr<const SUIT_ActionAssets> theAssets, const QString& theLang)
+void SUIT_FoundActionTreeModule::setAssetsAndSearchData(const SUIT_ActionSearcher::AssetsAndSearchData& theAssetsAndSD, const QString& theLang)
{
- if (!theAssets)
+ if (!theAssetsAndSD.myAssets)
return;
- setIcon(SUIT_FoundActionTree::ElementIdx::Name, theAssets->myIcon);
+ setIcon(SUIT_FoundActionTree::ElementIdx::Name, theAssetsAndSD.myAssets->myIcon);
- const auto& ldaMap = theAssets->myLangDependentAssets;
+ const auto& ldaMap = theAssetsAndSD.myAssets->myLangDependentAssets;
if (ldaMap.empty()) {
setText(SUIT_FoundActionTree::ElementIdx::Name, myModuleID);
return;
setText(SUIT_FoundActionTree::ElementIdx::Name, name);
}
-QString SUIT_FoundActionTreeFolder::getValue(SUIT_FoundActionTree::SortKey theKey) const
+QVariant SUIT_FoundActionTreeModule::getValue(SUIT_FoundActionTree::SortKey theKey) const
{
switch (theKey) {
+ case SUIT_FoundActionTree::SortKey::MatchMetrics:
+ return double(0);
case SUIT_FoundActionTree::SortKey::ID:
return myModuleID;
case SUIT_FoundActionTree::SortKey::Name:
}
}
-bool SUIT_FoundActionTreeFolder::isEnabled() const
+bool SUIT_FoundActionTreeModule::isEnabled() const
{
return true;
}
SUIT_FoundActionTreeAction::SUIT_FoundActionTreeAction(const QString& theModuleID, const QString& theInModuleActionID)
-: SUIT_FoundActionTreeItem(theModuleID), myInModuleActionID(theInModuleActionID)
+: SUIT_FoundActionTreeItem(theModuleID), myInModuleActionID(theInModuleActionID),
+ myMatchMetrics(std::numeric_limits<double>::infinity()), myIsEnabledBufferedValue(false)
{
setText(SUIT_FoundActionTree::ElementIdx::Name, theInModuleActionID);
}
return new SUIT_FoundActionTreeAction(theModuleID, theInModuleActionID);
}
-void SUIT_FoundActionTreeAction::setAssets(std::shared_ptr<const SUIT_ActionAssets> theAssets, const QString& theLang)
+void SUIT_FoundActionTreeAction::setAssetsAndSearchData(const SUIT_ActionSearcher::AssetsAndSearchData& theAssetsAndSD, const QString& theLang)
{
- if (!theAssets)
+ if (!theAssetsAndSD.myAssets)
return;
- setIcon(SUIT_FoundActionTree::ElementIdx::Name, theAssets->myIcon);
+ setIcon(SUIT_FoundActionTree::ElementIdx::Name, theAssetsAndSD.myAssets->myIcon);
- const auto& ldaMap = theAssets->myLangDependentAssets;
+ const auto& ldaMap = theAssetsAndSD.myAssets->myLangDependentAssets;
if (ldaMap.empty()) {
setText(SUIT_FoundActionTree::ElementIdx::Name, myInModuleActionID);
return;
setForeground(SUIT_FoundActionTree::ElementIdx::Name, greyedOutBrush);
setForeground(SUIT_FoundActionTree::ElementIdx::ToolTip, greyedOutBrush);
}
+
+ myMatchMetrics = theAssetsAndSD.matchMetrics();
}
-QString SUIT_FoundActionTreeAction::getValue(SUIT_FoundActionTree::SortKey theKey) const
+QVariant SUIT_FoundActionTreeAction::getValue(SUIT_FoundActionTree::SortKey theKey) const
{
switch (theKey) {
+ case SUIT_FoundActionTree::SortKey::MatchMetrics:
+ return myMatchMetrics;
case SUIT_FoundActionTree::SortKey::ID:
return myInModuleActionID;
case SUIT_FoundActionTree::SortKey::Name:
bool SUIT_FoundActionTreeAction::isEnabled() const
{
const auto& actions = SUIT_ShortcutMgr::get()->getActions(myModuleID, myInModuleActionID);
- return std::find_if(actions.begin(), actions.end(), [](const QAction* const theAction){ return theAction->isEnabled(); }) != actions.end();
+ myIsEnabledBufferedValue = std::find_if(actions.begin(), actions.end(), [](const QAction* const theAction){ return theAction->isEnabled(); }) != actions.end();
+ return myIsEnabledBufferedValue;
}
bool SUIT_FoundActionTreeAction::trigger() const
#define SUIT_FINDACTIONDIALOG_H
#include "SUIT.h"
+#include "SUIT_ShortcutMgr.h"
#include <QDialog>
#include <QFrame>
#include <QTreeWidget>
-#include "SUIT_ShortcutMgr.h"
+#include <QList>
+#include <QVariant>
#include <memory>
#include <map>
#include <set>
#include <functional>
+#include <utility>
class QCheckBox;
class SUIT_FoundActionTreeItem;
-class SUIT_FoundActionTreeFolder;
+class SUIT_FoundActionTreeModule;
class SUIT_FoundActionTreeAction;
};
enum class SortKey {
+ MatchMetrics,
ID,
Name,
ToolTip
};
+ enum class SortOrder {
+ Ascending,
+ Descending
+ };
+
SUIT_FoundActionTree(SUIT_FindActionDialog* theParent);
SUIT_FoundActionTree(const SUIT_FoundActionTree&) = delete;
SUIT_FoundActionTree& operator=(const SUIT_FoundActionTree&) = delete;
void updateItems(const std::map<QString, std::map<QString, SUIT_ActionSearcher::AssetsAndSearchData>>& theAssets);
+ void sort(SUIT_FoundActionTree::SortKey theKey, SUIT_FoundActionTree::SortOrder theOrder);
+
private:
- std::pair<SUIT_FoundActionTreeFolder*, int> findModuleFolderItem(const QString& theModuleID) const;
+ std::pair<SUIT_FoundActionTreeModule*, int> findModuleItem(const QString& theModuleID) const;
+ std::set<SUIT_FoundActionTreeAction*, std::function<bool(SUIT_FoundActionTreeAction*, SUIT_FoundActionTreeAction*)>> createActionSetWithComparator() const;
private slots:
void onItemDoubleClicked(QTreeWidgetItem* theWidgetItem, int theColIdx);
+
+public:
+ static const QList<std::pair<SUIT_FoundActionTree::SortKey, SUIT_FoundActionTree::SortOrder>> DEFAULT_SORT_SCHEMA;
+
+private:
+ SUIT_FoundActionTree::SortKey mySortKey;
+ SUIT_FoundActionTree::SortOrder mySortOrder;
+
+ /** {moduleID, isExpanded}[] */
+ std::map<QString, bool> myModuleItemExpansionStates;
};
{
public:
enum Type {
- Folder = 0,
+ Module = 0,
Action = 1,
};
virtual ~SUIT_FoundActionTreeItem() = default;
virtual SUIT_FoundActionTreeItem::Type type() const = 0;
- virtual void setAssets(std::shared_ptr<const SUIT_ActionAssets> theAssets, const QString& theLang) = 0;
+ virtual void setAssetsAndSearchData(const SUIT_ActionSearcher::AssetsAndSearchData& theAssetsAndSD, const QString& theLang) = 0;
QString name() const;
QString toolTip() const;
- virtual QString getValue(SUIT_FoundActionTree::SortKey theKey) const = 0;
+ virtual QVariant getValue(SUIT_FoundActionTree::SortKey theKey) const = 0;
virtual bool isEnabled() const = 0;
};
-class SUIT_FoundActionTreeFolder : public SUIT_FoundActionTreeItem
+class SUIT_FoundActionTreeModule : public SUIT_FoundActionTreeItem
{
public:
- SUIT_FoundActionTreeFolder(const QString& theModuleID);
- virtual ~SUIT_FoundActionTreeFolder() = default;
- virtual SUIT_FoundActionTreeItem::Type type() const { return SUIT_FoundActionTreeItem::Type::Folder; };
+ SUIT_FoundActionTreeModule(const QString& theModuleID);
+ virtual ~SUIT_FoundActionTreeModule() = default;
+ virtual SUIT_FoundActionTreeItem::Type type() const { return SUIT_FoundActionTreeItem::Type::Module; };
- virtual void setAssets(std::shared_ptr<const SUIT_ActionAssets> theAssets, const QString& theLang);
+ /*! \brief Search data is unused. */
+ virtual void setAssetsAndSearchData(const SUIT_ActionSearcher::AssetsAndSearchData& theAssetsAndSD, const QString& theLang);
- virtual QString getValue(SUIT_FoundActionTree::SortKey theKey) const;
+ virtual QVariant getValue(SUIT_FoundActionTree::SortKey theKey) const;
virtual bool isEnabled() const;
};
virtual ~SUIT_FoundActionTreeAction() = default;
virtual SUIT_FoundActionTreeItem::Type type() const { return SUIT_FoundActionTreeItem::Type::Action; };
- virtual void setAssets(std::shared_ptr<const SUIT_ActionAssets> theAssets, const QString& theLang);
+ virtual void setAssetsAndSearchData(const SUIT_ActionSearcher::AssetsAndSearchData& theAssetsAndSD, const QString& theLang);
- virtual QString getValue(SUIT_FoundActionTree::SortKey theKey) const;
+ virtual QVariant getValue(SUIT_FoundActionTree::SortKey theKey) const;
+ double matchMetrics() const { return myMatchMetrics; };
virtual bool isEnabled() const;
+ bool isEnabledBufferedValue() const { return myIsEnabledBufferedValue; };
bool trigger() const;
const QString myInModuleActionID;
+
+private:
+ double myMatchMetrics;
+ mutable bool myIsEnabledBufferedValue;
};
#endif // SUIT_FINDACTIONDIALOG_H
}
}
-size_t SUIT_SentenceMatcher::match(const QString& theInputString) const
+double SUIT_SentenceMatcher::match(const QString& theInputString) const
{
- size_t n = 0;
+ int n = 0;
if (myUseExactWordOrder) {
n = SUIT_SentenceMatcher::match(theInputString, myWords, myIsCaseSensitive);
- if (n > 0)
- return n;
-
- if (myUseFuzzyWords) {
- n = SUIT_SentenceMatcher::match(theInputString, myFuzzyWords, myIsCaseSensitive);
- if (n > 0)
- return n;
+ if (n != theInputString.length() && myUseFuzzyWords) {
+ const int nFuzzy = SUIT_SentenceMatcher::match(theInputString, myFuzzyWords, myIsCaseSensitive);
+ if (nFuzzy > n)
+ n = nFuzzy;
}
}
else /* if match with permutated query sentences */ {
n = SUIT_SentenceMatcher::match(theInputString, myPermutatedSentences, myIsCaseSensitive);
- if (n > 0)
- return n;
-
- if (myUseFuzzyWords) {
- n = SUIT_SentenceMatcher::match(theInputString, myFuzzyPermutatedSentences, myIsCaseSensitive);
- if (n > 0)
- return n;
+ if (n != theInputString.length() && myUseFuzzyWords) {
+ const int nFuzzy = SUIT_SentenceMatcher::match(theInputString, myFuzzyPermutatedSentences, myIsCaseSensitive);
+ if (nFuzzy > n)
+ n = nFuzzy;
}
}
- return n;
+ if (n <= 0)
+ return std::numeric_limits<double>::infinity();
+
+ const auto strLength = theInputString.length() > myQuery.length() ? theInputString.length() : myQuery.length();
+
+ if (n > strLength)
+ return 0; // Exact match or almost exact.
+
+ return double(strLength - n);
}
QString SUIT_SentenceMatcher::toString() const
}
}
-/*static*/ size_t SUIT_SentenceMatcher::matchWithSentenceIgnoreEndings(const QString& theInputString, const QStringList& theSentence, bool theCaseSensitive)
+/*static*/ int SUIT_SentenceMatcher::matchWithSentenceIgnoreEndings(const QString& theInputString, const QStringList& theSentence, bool theCaseSensitive)
{
- QRegExp regExp("^" + theSentence.join("\\w*\\W+"), theCaseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive);
- if (theInputString.contains(regExp))
- return theSentence.size();
- else
- return 0;
+ const QRegExp regExp("^" + theSentence.join("\\w*\\W+"), theCaseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive);
+ regExp.indexIn(theInputString);
+ const int matchMetrics = regExp.matchedLength();
+ return matchMetrics > 0 ? matchMetrics : 0;
}
-/*static*/ size_t SUIT_SentenceMatcher::matchWithSentencesIgnoreEndings(const QString& theInputString, const QList<QStringList>& theSentences, bool theCaseSensitive)
+/*static*/ int SUIT_SentenceMatcher::matchWithSentencesIgnoreEndings(const QString& theInputString, const QList<QStringList>& theSentences, bool theCaseSensitive)
{
+ int res = 0;
for (const QStringList& sentence : theSentences) {
- if (SUIT_SentenceMatcher::matchWithSentenceIgnoreEndings(theInputString, sentence, theCaseSensitive))
- return sentence.size();
+ const int matchMetrics = SUIT_SentenceMatcher::matchWithSentenceIgnoreEndings(theInputString, sentence, theCaseSensitive);
+ if (matchMetrics > res) {
+ res = matchMetrics;
+ if (res == theInputString.length())
+ return res;
+ }
}
- return 0;
+ return res;
}
-/*static*/ size_t SUIT_SentenceMatcher::matchAtLeastOneWord(const QString& theInputString, const QStringList& theWords, bool theCaseSensitive)
+/*static*/ int SUIT_SentenceMatcher::matchAtLeastOneWord(const QString& theInputString, const QStringList& theWords, bool theCaseSensitive)
{
- size_t n = 0;
+ int res = 0;
for (const QString& word : theWords) {
+ const auto regExp = QRegExp(word, theCaseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive);
+ regExp.indexIn(theInputString);
+ const int matchMetrics = regExp.matchedLength();
// The same input word can be counted multiple times. Nobody cares.
- if (theInputString.contains(QRegExp(word, theCaseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive)))
- n++;
+ if (matchMetrics > 0)
+ res += matchMetrics;
}
- return n;
+ return res;
}
-/*static*/ size_t SUIT_SentenceMatcher::match(
+/*static*/ int SUIT_SentenceMatcher::match(
const QString& theInputString,
const QStringList& theSentence,
bool theCaseSensitive
) {
- size_t n = SUIT_SentenceMatcher::matchWithSentenceIgnoreEndings(theInputString, theSentence, theCaseSensitive);
- if (n > 0)
- return n;
+ int res = SUIT_SentenceMatcher::matchWithSentenceIgnoreEndings(theInputString, theSentence, theCaseSensitive);
+ if (res == theInputString.length())
+ return res;
- return SUIT_SentenceMatcher::matchAtLeastOneWord(theInputString, theSentence, theCaseSensitive);
+ const int matchMetrics = SUIT_SentenceMatcher::matchAtLeastOneWord(theInputString, theSentence, theCaseSensitive);
+ if (matchMetrics > res)
+ res = matchMetrics;
+
+ return res;
}
-/*static*/ size_t SUIT_SentenceMatcher::match(
+/*static*/ int SUIT_SentenceMatcher::match(
const QString& theInputString,
const QList<QStringList>& theSentences,
bool theCaseSensitive
) {
- size_t n = SUIT_SentenceMatcher::matchWithSentencesIgnoreEndings(theInputString, theSentences, theCaseSensitive);
- if (n > 0)
- return n;
+ int res = SUIT_SentenceMatcher::matchWithSentencesIgnoreEndings(theInputString, theSentences, theCaseSensitive);
+ if (res == theInputString.length())
+ return res;
- if (theSentences.size() > 0)
- return SUIT_SentenceMatcher::matchAtLeastOneWord(theInputString, theSentences[0], theCaseSensitive);
- else
- return 0;
+ if (theSentences.size() > 0) {
+ const int matchMetrics = SUIT_SentenceMatcher::matchAtLeastOneWord(theInputString, theSentences[0], theCaseSensitive);
+ if (matchMetrics > res)
+ res = matchMetrics;
+ }
+
+ return res;
}
-SUIT_ActionSearcher::AssetsAndSearchData::AssetsAndSearchData(std::shared_ptr<SUIT_ActionAssets> theAssets, size_t theNumOfMatchingWords)
-: myAssets(theAssets), myNumOfMatchingWords(theNumOfMatchingWords)
-{}
+SUIT_ActionSearcher::AssetsAndSearchData::AssetsAndSearchData(std::shared_ptr<const SUIT_ActionAssets> theAssets, double theMatchMetrics)
+: myAssets(theAssets), myMatchMetrics(theMatchMetrics)
+{
+ if (theMatchMetrics < 0) {
+ myMatchMetrics = std::numeric_limits<double>::infinity();
+ ShCutDbg("WARNING: SUIT_ActionSearcher::AssetsAndSearchData: match metrics < 0. INF is assigned instead.");
+ }
+}
+
+void SUIT_ActionSearcher::AssetsAndSearchData::setMatchMetrics(double theMatchMetrics)
+{
+ if (theMatchMetrics < 0) {
+ myMatchMetrics = std::numeric_limits<double>::infinity();
+ ShCutDbg("WARNING: SUIT_ActionSearcher::AssetsAndSearchData: match metrics < 0. INF is assigned instead.");
+ return;
+ }
+
+ myMatchMetrics = theMatchMetrics;
+}
void SUIT_ActionSearcher::AssetsAndSearchData::toJSON(QJsonObject& oJsonObject) const
{
- oJsonObject["myNumOfMatchingWords"] = int(myNumOfMatchingWords);
+ oJsonObject["myMatchMetrics"] = myMatchMetrics;
if (myAssets) {
QJsonObject assetsJSON;
for (const auto& actionIDAndAssets : actionIDsAndAssets) {
const QString& inModuleActionID = actionIDAndAssets.first;
- const size_t n = matchAction(moduleID, inModuleActionID, actionIDAndAssets.second);
- if (n > 0) {
- mySearchResults[moduleID][inModuleActionID] = SUIT_ActionSearcher::AssetsAndSearchData(actionIDAndAssets.second, n);
+ const double matchMetrics = matchAction(moduleID, inModuleActionID, actionIDAndAssets.second);
+ if (matchMetrics < std::numeric_limits<double>::infinity()) {
+ mySearchResults[moduleID][inModuleActionID] = SUIT_ActionSearcher::AssetsAndSearchData(actionIDAndAssets.second, matchMetrics);
res = true;
}
}
if (itFoundActionIDAndAssets != foundActionIDsAndAssets.end()) {
// Action is already in search results.
SUIT_ActionSearcher::AssetsAndSearchData& aAndD = itFoundActionIDAndAssets->second;
- const size_t n = matchAction(moduleID, inModuleActionID, aAndD.myAssets);
- if (n > 0) {
- if (n != aAndD.myNumOfMatchingWords) {
- aAndD.myNumOfMatchingWords = n;
+ const double matchMetrics = matchAction(moduleID, inModuleActionID, aAndD.myAssets);
+ if (matchMetrics < std::numeric_limits<double>::infinity()) {
+ if (matchMetrics != aAndD.matchMetrics()) {
+ aAndD.setMatchMetrics(matchMetrics);
res.second = true;
}
}
}
}
- const size_t n = matchAction(moduleID, inModuleActionID, actionIDAndAssets.second);
- if (n > 0) {
+ const double matchMetrics = matchAction(moduleID, inModuleActionID, actionIDAndAssets.second);
+ if (matchMetrics < std::numeric_limits<double>::infinity()) {
if (itFoundModuleIDAndAssets == mySearchResults.end())
itFoundModuleIDAndAssets = mySearchResults.emplace(moduleID, std::map<QString, SUIT_ActionSearcher::AssetsAndSearchData>()).first;
- itFoundModuleIDAndAssets->second.emplace(inModuleActionID, SUIT_ActionSearcher::AssetsAndSearchData(actionIDAndAssets.second, n));
+ itFoundModuleIDAndAssets->second.emplace(inModuleActionID, SUIT_ActionSearcher::AssetsAndSearchData(actionIDAndAssets.second, matchMetrics));
res.first = true;
}
}
for (auto itActionIDAndAssets = actionIDsAndAssets.begin(); itActionIDAndAssets != actionIDsAndAssets.end(); ) {
const QString& inModuleActionID = itActionIDAndAssets->first;
SUIT_ActionSearcher::AssetsAndSearchData& assetsAndSearchData = itActionIDAndAssets->second;
- const size_t n = matchAction(moduleID, inModuleActionID, assetsAndSearchData.myAssets);
- if (n == 0) {
+ const double matchMetrics = matchAction(moduleID, inModuleActionID, assetsAndSearchData.myAssets);
+ if (matchMetrics == std::numeric_limits<double>::infinity()) {
itActionIDAndAssets = actionIDsAndAssets.erase(itActionIDAndAssets);
res.first = true;
}
else {
- if (assetsAndSearchData.myNumOfMatchingWords != n) {
- assetsAndSearchData.myNumOfMatchingWords = n;
+ if (assetsAndSearchData.matchMetrics() != matchMetrics) {
+ assetsAndSearchData.setMatchMetrics(matchMetrics);
res.second = true;
}
itActionIDAndAssets++;
}
ShCutDbg() && ShCutDbg("SUIT_ActionSearcher::extendResults(): " + moduleID + "/" + inModuleActionID + "." );
- const size_t n = matchAction(moduleID, inModuleActionID, actionIDAndAssets.second);
- if (n > 0) {
- ShCutDbg("SUIT_ActionSearcher::extendResults(): match");
+ const double matchMetrics = matchAction(moduleID, inModuleActionID, actionIDAndAssets.second);
+ if (matchMetrics < std::numeric_limits<double>::infinity()) {
+ ShCutDbg("SUIT_ActionSearcher::extendResults(): match, metrics = " + QString::fromStdString(std::to_string(matchMetrics)));
if (itFoundModuleIDAndAssets == mySearchResults.end())
itFoundModuleIDAndAssets = mySearchResults.emplace(moduleID, std::map<QString, SUIT_ActionSearcher::AssetsAndSearchData>()).first;
- itFoundModuleIDAndAssets->second.emplace(inModuleActionID, SUIT_ActionSearcher::AssetsAndSearchData(actionIDAndAssets.second, n));
+ itFoundModuleIDAndAssets->second.emplace(inModuleActionID, SUIT_ActionSearcher::AssetsAndSearchData(actionIDAndAssets.second, matchMetrics));
res = true;
}
}
return res;
}
-size_t SUIT_ActionSearcher::matchAction(const QString& theModuleID, const QString& theInModuleActionID, std::shared_ptr<SUIT_ActionAssets> theAssets)
+double SUIT_ActionSearcher::matchAction(const QString& theModuleID, const QString& theInModuleActionID, std::shared_ptr<const SUIT_ActionAssets> theAssets)
{
if (!theAssets) {
ShCutDbg("WARNING: SUIT_ActionSearcher::matchAction: theAssets is nullptr.");
- return 0;
+ return std::numeric_limits<double>::infinity();
}
if (!myIncludeDisabledActions) {
const auto& actions = SUIT_ShortcutMgr::get()->getActions(theModuleID, theInModuleActionID);
const bool actionEnabled = std::find_if(actions.begin(), actions.end(), [](const QAction* const theAction){ return theAction->isEnabled(); } ) != actions.end();
if (!actionEnabled)
- return false;
+ return std::numeric_limits<double>::infinity();
}
+ double res = std::numeric_limits<double>::infinity();
+
for (const auto& langAndLDA : theAssets->myLangDependentAssets) {
if (myFieldsToMatch.find(SUIT_ActionSearcher::MatchField::ToolTip) != myFieldsToMatch.end()) {
- if (myMatcher.match(langAndLDA.second.myToolTip))
- return true;
+ const double matchMetrics = myMatcher.match(langAndLDA.second.myToolTip);
+ if (matchMetrics < res)
+ res = matchMetrics;
}
if (myFieldsToMatch.find(SUIT_ActionSearcher::MatchField::Name) != myFieldsToMatch.end()) {
- if (myMatcher.match(langAndLDA.second.myName))
- return true;
+ const double matchMetrics = myMatcher.match(langAndLDA.second.myName);
+ if (matchMetrics < res)
+ res = matchMetrics;
}
}
if (myFieldsToMatch.find(SUIT_ActionSearcher::MatchField::ID) != myFieldsToMatch.end()) {
- if (myMatcher.match(SUIT_ShortcutMgr::makeActionID(theModuleID, theInModuleActionID)))
- return true;
+ const double matchMetrics = myMatcher.match(SUIT_ShortcutMgr::makeActionID(theModuleID, theInModuleActionID));
+ if (matchMetrics < res)
+ res = matchMetrics;
}
if (myFieldsToMatch.find(SUIT_ActionSearcher::MatchField::KeySequence) != myFieldsToMatch.end()) {
const QString keySequence = SUIT_ShortcutMgr::get()->getKeySequence(theModuleID, theInModuleActionID).toString();
- if (myMatcher.match(keySequence))
- return true;
+ const double matchMetrics = myMatcher.match(keySequence);
+ if (matchMetrics < res)
+ res = matchMetrics;
}
- return false;
+ return res;
}
QString SUIT_ActionSearcher::toString() const
#include <set>
#include <memory>
#include <utility>
+#include <limits>
class QAction;
class QtxAction;
\returns {assetsExist, assets}. */
static std::pair<bool, SUIT_ActionAssets> getActionAssetsFromResources(const QString& theActionID);
- /*! \returns Language being set in resource manager. */
+ /*! \returns Language, which is set in resource manager. */
static QString getLang();
if the module is root (theModuleID is empty) - returns all module IDs, otherwise returns ["", theModuleID]. */
std::set<QString> getIDsOfInterferingModules(const QString& theModuleID) const;
+ /*! \returns assets, which describe module's header, not its content. */
std::shared_ptr<const SUIT_ActionAssets> getModuleAssets(const QString& theModuleID) const;
+ /*! \returns assets, which describe modules' headers, not their content. */
+ std::map<QString, std::shared_ptr<SUIT_ActionAssets>> getModuleAssets() const { return myModuleAssets; }
+
/*! \brief Retrieves module name, if the asset was loaded using \ref setAssetsFromResources(). If theLang is empty, it is effectively current language. */
QString getModuleName(const QString& theModuleID, const QString& theLang = "") const;
std::map<QString, std::map<QString, std::shared_ptr<SUIT_ActionAssets>>> getActionAssets() const { return myActionAssets; }
- std::map<QString, std::shared_ptr<SUIT_ActionAssets>> getModuleAssets() const { return myModuleAssets; }
-
/*! \brief Retrieves action name, if the asset was loaded using \ref setAssetsFromResources(). If theLang is empty, it is effectively current language. */
QString getActionName(const QString& theModuleID, const QString& theInModuleActionID, const QString& theLang = "") const;
inline const QString& getQuery() const { return myQuery; };
- /*! \returns number of matched words. */
- size_t match(const QString& theInputString) const;
+ /*! \returns match metrics. The metrics >= 0. INF means mismatch.
+ The class is unable to differentiate exact match with some approximate matches! */
+ double match(const QString& theInputString) const;
+ /** \brief For debug. */
QString toString() const;
private:
static bool makePermutatedSentences(const QStringList& theWords, QList<QStringList>& theSentences);
static void makeFuzzyWords(const QStringList& theWords, QStringList& theFuzzyWords);
- static size_t matchWithSentenceIgnoreEndings(const QString& theInputString, const QStringList& theSentence, bool theCaseSensitive);
- static size_t matchWithSentencesIgnoreEndings(const QString& theInputString, const QList<QStringList>& theSentences, bool theCaseSensitive);
+ /*! \returns number of characters in matched words. The number >= 0. */
+ static int matchWithSentenceIgnoreEndings(const QString& theInputString, const QStringList& theSentence, bool theCaseSensitive);
+ /*! \returns number of characters in matched words. The number >= 0. */
+ static int matchWithSentencesIgnoreEndings(const QString& theInputString, const QList<QStringList>& theSentences, bool theCaseSensitive);
- static size_t matchAtLeastOneWord(const QString& theInputString, const QStringList& theWords, bool theCaseSensitive);
+ /*! \returns number of characters in matched words. The number >= 0. */
+ static int matchAtLeastOneWord(const QString& theInputString, const QStringList& theWords, bool theCaseSensitive);
- static size_t match(
+ /*! \returns number of characters in matched words. The number >= 0. */
+ static int match(
const QString& theInputString,
const QStringList& theSentence,
bool theCaseSensitive
);
- static size_t match(
+ /*! \returns number of characters in matched words. The number >= 0. */
+ static int match(
const QString& theInputString,
const QList<QStringList>& theSentences,
bool theCaseSensitive
KeySequence
};
- struct AssetsAndSearchData
+ class AssetsAndSearchData
{
- AssetsAndSearchData() = default;
- AssetsAndSearchData(std::shared_ptr<SUIT_ActionAssets> theAssets, size_t theNumOfMatchingWords);
+ public:
+ AssetsAndSearchData(std::shared_ptr<const SUIT_ActionAssets> theAssets = nullptr, double theMatchMetrics = std::numeric_limits<double>::infinity());
+
+ void setMatchMetrics(double theMatchMetrics);
+ double matchMetrics() const { return myMatchMetrics; };
- std::shared_ptr<SUIT_ActionAssets> myAssets;
- size_t myNumOfMatchingWords;
+ std::shared_ptr<const SUIT_ActionAssets> myAssets;
void toJSON(QJsonObject& oJsonObject) const;
QString toString() const;
+
+ private:
+ /*! \brief Ideally it should be number of weighted character permutations. Now it is just a number of characters in unmatched words. */
+ double myMatchMetrics;
};
/*! Default config:
private:
/*! \brief Applies filter to all actions, provided in asset files for SUIT_ShortcutMgr.
- \returns { true, _ } if set of results is changed; { _ , true } if number of matching words is changed for at least one result. */
+ \returns { true, _ } if set of results is changed; { _ , true } if matching metrics is changed for at least one result. */
std::pair<bool, bool> filter();
/*! \brief Applies filter to search results only.
- \returns { true, _ } if set of results is shrunk; { _ , true } if number of matching words is changed for at least one result. */
+ \returns { true, _ } if set of results is shrunk; { _ , true } if matching metrics is changed for at least one result. */
std::pair<bool, bool> filterResults();
/*! \brief Applies filter only to actions, which are not in search results.
\returns True, if set of results is extended. */
bool extendResults();
- size_t matchAction(const QString& theModuleID, const QString& theInModuleActionID, std::shared_ptr<SUIT_ActionAssets> theAssets);
+ double matchAction(const QString& theModuleID, const QString& theInModuleActionID, std::shared_ptr<const SUIT_ActionAssets> theAssets);
QString toString() const;
static const QCollator collator;
const std::function<bool(SUIT_ShortcutTreeItem*, SUIT_ShortcutTreeItem*)> comparator =
[this, sortSchema, &collator](const SUIT_ShortcutTreeItem* theItemA, const SUIT_ShortcutTreeItem* theItemB) {
- int res = 0;
for (const auto& keyAndOrder : sortSchema) {
- int res = 0;
- res = collator.compare(theItemA->getValue(keyAndOrder.first), theItemB->getValue(keyAndOrder.first));
+ const int res = collator.compare(theItemA->getValue(keyAndOrder.first), theItemB->getValue(keyAndOrder.first));
if (res != 0)
return keyAndOrder.second == SUIT_ShortcutTree::SortOrder::Ascending ? res < 0 : res > 0;
}
#include <SUIT_ViewManager.h>
#include <SUIT_ViewModel.h>
#include <SUIT_OverrideCursor.h>
+#include <SUIT_FindActionDialog.h>
#include <QtxTreeView.h>
-#include <SUIT_FindActionDialog.h>
#include <SALOME_EventFilter.h>