diff --git a/bin/app/data/color_schemes/bad_rainbow.xml b/bin/app/data/color_schemes/bad_rainbow.xml index 275d5223..800c3911 100644 --- a/bin/app/data/color_schemes/bad_rainbow.xml +++ b/bin/app/data/color_schemes/bad_rainbow.xml @@ -20,9 +20,10 @@ #3D3D3D - #A0A0A0 + #808080 #CCCCCC #FFFFFF + #B1B1B1 diff --git a/bin/app/data/color_schemes/bright.xml b/bin/app/data/color_schemes/bright.xml index 058c87f7..cc77ca47 100644 --- a/bin/app/data/color_schemes/bright.xml +++ b/bin/app/data/color_schemes/bright.xml @@ -20,9 +20,10 @@ black - #A0A0A0 + #626262 white - #B2B2B2 + #D0D0D0 + #E0E0E0 diff --git a/bin/app/data/color_schemes/dark.xml b/bin/app/data/color_schemes/dark.xml index 3d59fbcc..61aa83cd 100644 --- a/bin/app/data/color_schemes/dark.xml +++ b/bin/app/data/color_schemes/dark.xml @@ -22,7 +22,8 @@ #F7F7F7 #A0A0A0 #272728 - #555555 + #333333 + #444444 diff --git a/bin/app/data/gui/search_view/images/arrow.png b/bin/app/data/gui/search_view/images/arrow.png new file mode 100644 index 00000000..f12ba7df Binary files /dev/null and b/bin/app/data/gui/search_view/images/arrow.png differ diff --git a/src/lib/component/controller/FeatureController.cpp b/src/lib/component/controller/FeatureController.cpp index 36b9b1da..f5f1bb01 100644 --- a/src/lib/component/controller/FeatureController.cpp +++ b/src/lib/component/controller/FeatureController.cpp @@ -159,7 +159,7 @@ void FeatureController::handleMessage(MessageSearch* message) MessageActivateTokens m(message, tokenIds); for (const SearchMatch& match : matches) { - m.unknownNames.push_back(match.text); + m.unknownNames.push_back(match.name); } if (!message->isReplayed()) { diff --git a/src/lib/data/HierarchyCache.cpp b/src/lib/data/HierarchyCache.cpp index 8511cb2e..356969cc 100644 --- a/src/lib/data/HierarchyCache.cpp +++ b/src/lib/data/HierarchyCache.cpp @@ -121,6 +121,32 @@ Id HierarchyCache::getLastVisibleParentNodeId(Id nodeId) const return nodeId; } +size_t HierarchyCache::getIndexOfLastVisibleParentNode(Id nodeId) const +{ + HierarchyNode* node = nullptr; + HierarchyNode* parent = getNode(nodeId); + + size_t idx = 0; + bool visible = false; + + while (parent) + { + node = parent; + parent = node->getParent(); + + if (node->isVisible()) + { + visible = true; + } + else if (visible) + { + idx++; + } + } + + return idx; +} + void HierarchyCache::addAllChildIdsForNodeId(Id nodeId, std::vector* nodeIds, std::vector* edgeIds) const { HierarchyNode* node = getNode(nodeId); diff --git a/src/lib/data/HierarchyCache.h b/src/lib/data/HierarchyCache.h index ee45ba18..37278aab 100644 --- a/src/lib/data/HierarchyCache.h +++ b/src/lib/data/HierarchyCache.h @@ -15,6 +15,7 @@ public: void createConnection(Id edgeId, Id fromId, Id toId, bool fromVisible); Id getLastVisibleParentNodeId(Id nodeId) const; + size_t getIndexOfLastVisibleParentNode(Id nodeId) const; void addAllChildIdsForNodeId(Id nodeId, std::vector* nodeIds, std::vector* edgeIds) const; void addFirstChildIdsForNodeId(Id nodeId, std::vector* nodeIds) const; diff --git a/src/lib/data/PersistentStorage.cpp b/src/lib/data/PersistentStorage.cpp index 15565eec..4331a5dc 100644 --- a/src/lib/data/PersistentStorage.cpp +++ b/src/lib/data/PersistentStorage.cpp @@ -30,9 +30,9 @@ PersistentStorage::PersistentStorage(const FilePath& dbPath) : m_sqliteStorage(dbPath) { - m_commandIndex.addNode(0, NameHierarchy(SearchMatch::getCommandName(SearchMatch::COMMAND_ALL))); - m_commandIndex.addNode(0, NameHierarchy(SearchMatch::getCommandName(SearchMatch::COMMAND_ERROR))); - // m_commandIndex.addNode(0, NameHierarchy(SearchMatch::getCommandName(SearchMatch::COMMAND_COLOR_SCHEME_TEST))); + m_commandIndex.addNode(0, SearchMatch::getCommandName(SearchMatch::COMMAND_ALL)); + m_commandIndex.addNode(0, SearchMatch::getCommandName(SearchMatch::COMMAND_ERROR)); + // m_commandIndex.addNode(0, SearchMatch::getCommandName(SearchMatch::COMMAND_COLOR_SCHEME_TEST)); m_commandIndex.finishSetup(); } @@ -275,6 +275,7 @@ void PersistentStorage::clear() void PersistentStorage::clearCaches() { m_elementIndex.clear(); + m_fileIndex.clear(); m_fileNodeIds.clear(); m_fileNodePaths.clear(); m_hierarchyCache.clear(); @@ -344,8 +345,8 @@ void PersistentStorage::buildCaches() clearCaches(); - buildSearchIndex(); buildFilePathMaps(); + buildSearchIndex(); buildHierarchyCache(); } @@ -478,55 +479,14 @@ std::vector PersistentStorage::getAutocompletionMatches(const std:: { TRACE(); - std::vector commandResults = m_commandIndex.search(query, 0); - - const size_t maxResultCount = 100; - std::vector elementResults = m_elementIndex.search(query, maxResultCount); - + // search in indices + size_t maxResultsCount = 100; std::vector results; - utility::append(results, commandResults); - utility::append(results, elementResults); - - std::sort(results.begin(), results.end(), - [](const SearchResult& a, const SearchResult& b) - { - // should a be ranked higher than b? - if (a.score > b.score) - { - return true; - } - else if (a.score == b.score) - { - if (a.text.size() < b.text.size()) - { - return true; - } - else if (a.text.size() == b.text.size()) - { - for (size_t i = 0; i < a.text.size(); i++) - { - if (tolower(a.text[i]) != tolower(b.text[i])) - { - return tolower(a.text[i]) < tolower(b.text[i]); - } - else - { - if (a.text[i] < b.text[i]) - { - return true; - } - else if (a.text[i] > b.text[i]) - { - return false; - } - } - } - } - } - return false; - } - ); + utility::append(results, m_commandIndex.search(query, 0)); + utility::append(results, m_elementIndex.search(query, maxResultsCount, 100)); + utility::append(results, m_fileIndex.search(query, 20)); + // fetch StorageNodes for node ids std::map storageNodesMap; { std::vector elementIds; @@ -547,7 +507,8 @@ std::vector PersistentStorage::getAutocompletionMatches(const std:: } } - std::vector matches; + // create SearchMatches + std::set matches; for (const SearchResult& result : results) { SearchMatch match; @@ -572,14 +533,41 @@ std::vector PersistentStorage::getAutocompletionMatches(const std:: } } - match.text = result.text; + match.name = result.text; match.indices = result.indices; + match.score = result.score; if (firstNode) { match.nodeType = Node::intToType(firstNode->type); match.typeName = Node::getTypeString(match.nodeType); + size_t idx = 0; + if (match.nodeType == Node::NODE_FILE) + { + idx = 1; + + FilePath path(match.name); + match.text = path.fileName(); + match.subtext = path.str(); + } + else + { + idx = m_hierarchyCache.getIndexOfLastVisibleParentNode(firstNode->id); + const NameHierarchy& name = match.nameHierarchies[0]; + + match.text = name.getRange(idx, name.size()).getQualifiedName(); + match.subtext = name.getRange(0, idx).getQualifiedName(); + } + + // rescore match + if (idx && match.indices.size()) + { + SearchResult newResult = SearchIndex::rescoreText(match.name, match.text, match.indices, match.score); + match.score = newResult.score; + match.indices = newResult.indices; + } + if (intToDefinitionType(firstNode->definitionType) == DEFINITION_NONE && match.nodeType != Node::NODE_UNDEFINED) { @@ -593,10 +581,16 @@ std::vector PersistentStorage::getAutocompletionMatches(const std:: match.typeName = "command"; } - matches.push_back(match); + matches.insert(match); } - return matches; + std::vector matchesVector = utility::toVector(matches); + if (matchesVector.size() > maxResultsCount) + { + matchesVector.resize(maxResultsCount); + } + + return matchesVector; } std::vector PersistentStorage::getSearchMatchesForTokenIds(const std::vector& elementIds) const @@ -624,7 +618,7 @@ std::vector PersistentStorage::getSearchMatchesForTokenIds(const st } NameHierarchy nameHierarchy = NameHierarchy::deserialize(m_sqliteStorage.getNodeById(elementId).serializedName); - match.text = nameHierarchy.getQualifiedName(); + match.name = nameHierarchy.getQualifiedName(); match.nameHierarchies.push_back(nameHierarchy.getQualifiedName()); match.searchType = SearchMatch::SEARCH_TOKEN; @@ -1430,14 +1424,33 @@ void PersistentStorage::buildSearchIndex() { TRACE(); + FilePath dbPath = getDbFilePath(); + for (StorageNode node : m_sqliteStorage.getAllNodes()) { if (intToDefinitionType(node.definitionType) != DEFINITION_IMPLICIT) { - m_elementIndex.addNode(node.id, NameHierarchy::deserialize(node.serializedName)); + if (Node::intToType(node.type) == Node::NODE_FILE) + { + FilePath filePath = m_fileNodePaths[node.id]; + + if (filePath.exists()) + { + filePath = filePath.relativeTo(dbPath); + } + + m_fileIndex.addNode(node.id, filePath.str()); + } + else + { + // we don't use the signature here, so elements with the same signature share the same node. + m_elementIndex.addNode(node.id, NameHierarchy::deserialize(node.serializedName).getQualifiedName()); + } } } + m_elementIndex.finishSetup(); + m_fileIndex.finishSetup(); } void PersistentStorage::buildFilePathMaps() diff --git a/src/lib/data/PersistentStorage.h b/src/lib/data/PersistentStorage.h index 6d15f009..74aa0e8e 100644 --- a/src/lib/data/PersistentStorage.h +++ b/src/lib/data/PersistentStorage.h @@ -151,6 +151,7 @@ private: SearchIndex m_commandIndex; SearchIndex m_elementIndex; + SearchIndex m_fileIndex; mutable FullTextSearchIndex m_fullTextSearchIndex; diff --git a/src/lib/data/name/NameHierarchy.cpp b/src/lib/data/name/NameHierarchy.cpp index 65a73464..af31b3f6 100644 --- a/src/lib/data/name/NameHierarchy.cpp +++ b/src/lib/data/name/NameHierarchy.cpp @@ -92,6 +92,18 @@ std::shared_ptr NameHierarchy::operator[](size_t pos) const return m_elements[pos]; } +NameHierarchy NameHierarchy::getRange(size_t first, size_t last) const +{ + NameHierarchy hierarchy; + + for (size_t i = first; i < last; i++) + { + hierarchy.push(m_elements[i]); + } + + return hierarchy; +} + size_t NameHierarchy::size() const { return m_elements.size(); diff --git a/src/lib/data/name/NameHierarchy.h b/src/lib/data/name/NameHierarchy.h index 2ec5d5a2..38067825 100644 --- a/src/lib/data/name/NameHierarchy.h +++ b/src/lib/data/name/NameHierarchy.h @@ -23,8 +23,12 @@ public: void push(std::shared_ptr element); void pop(); + std::shared_ptr back() const; std::shared_ptr operator[](size_t pos) const; + + NameHierarchy getRange(size_t first, size_t last) const; + size_t size() const; std::string getQualifiedName() const; diff --git a/src/lib/data/search/SearchIndex.cpp b/src/lib/data/search/SearchIndex.cpp index a238c775..79afebc5 100644 --- a/src/lib/data/search/SearchIndex.cpp +++ b/src/lib/data/search/SearchIndex.cpp @@ -16,13 +16,11 @@ SearchIndex::~SearchIndex() { } -void SearchIndex::addNode(Id id, const NameHierarchy& nameHierarchy) +void SearchIndex::addNode(Id id, const std::string& name) { Node* currentNode = m_root; - // we don't use the signature here, so elements with the same signature share the same node in the search index. - std::string remaining = nameHierarchy.getQualifiedName(); - + std::string remaining = name; while (remaining.size() > 0) { bool matchingEdgeFound = false; @@ -106,7 +104,8 @@ void SearchIndex::clear() m_root = n.get(); } -std::vector SearchIndex::search(const std::string& query, size_t maxResultCount) const +std::vector SearchIndex::search( + const std::string& query, size_t maxResultCount, size_t maxBestScoredLength) const { // find paths containing query Path startPath; @@ -123,7 +122,7 @@ std::vector SearchIndex::search(const std::string& query, size_t m std::multiset bestResults; for (const SearchResult& result : searchResults) { - bestResults.insert(bestScoredResult(result, &scoresCache)); + bestResults.insert(bestScoredResult(result, &scoresCache, maxBestScoredLength)); } // narrow down to max result count @@ -212,7 +211,7 @@ std::multiset SearchIndex::createScoredResults(const std::vector

SearchIndex::createScoredResults(const std::vector

elementIds; result.indices = path.indices; - result.score = score(path.text, path.indices); + result.score = scoreText(path.text, path.indices); searchResults.insert(result); if (maxResultCount && searchResults.size() >= maxResultCount) @@ -260,9 +259,10 @@ std::multiset SearchIndex::createScoredResults(const std::vector

* scoresCache) const +SearchResult SearchIndex::bestScoredResult( + SearchResult result, std::map* scoresCache, size_t maxBestScoredLength) { - if (result.text.size() > 100) + if (maxBestScoredLength && result.text.size() > maxBestScoredLength) { return result; } @@ -296,7 +296,7 @@ SearchResult SearchIndex::bestScoredResult(SearchResult result, std::map& indices, const size_t indicesPos, - std::map* scoresCache, SearchResult* result) const + std::map* scoresCache, SearchResult* result) { // left for debugging // std::cout << lowerText << std::endl; @@ -326,7 +326,7 @@ void SearchIndex::bestScoredResultRecursive( std::vector newIndices = indices; newIndices[indicesPos] = i; - int newScore = score(result->text, newIndices); + int newScore = scoreText(result->text, newIndices); if (newScore > result->score) { result->score = newScore; @@ -362,12 +362,13 @@ void SearchIndex::bestScoredResultRecursive( } } -int SearchIndex::score(const std::string& text, const std::vector& indices) const +int SearchIndex::scoreText(const std::string& text, const std::vector& indices) { const int unmatchedLetterBonus = -1; const int consecutiveLetterBonus = 5; const int camelCaseBonus = 4; const int noLetterBonus = 3; + const int firstLetterBonus = 4; const int delayedStartBonus = -1; const int minDelayedStartBonus = -20; @@ -375,6 +376,7 @@ int SearchIndex::score(const std::string& text, const std::vector& indic int consecutiveLetterScore = 0; int camelCaseScore = 0; int noLetterScore = 0; + int firstLetterScore = 0; static std::set noLetters; if (!noLetters.size()) @@ -386,6 +388,8 @@ int SearchIndex::score(const std::string& text, const std::vector& indic noLetters.insert(':'); noLetters.insert('<'); noLetters.insert('>'); + noLetters.insert('/'); + noLetters.insert('\\'); } for (size_t i = 0; i < indices.size(); i++) @@ -399,9 +403,13 @@ int SearchIndex::score(const std::string& text, const std::vector& indic size_t index = indices[i]; + // first letter + if (index == 0) + { + firstLetterScore += firstLetterBonus; + } // after no letter - bool prevIsNoLetter = (index == 0 || noLetters.find(text[index - 1]) != noLetters.end()); - if (prevIsNoLetter) + else if ((index != 0 && noLetters.find(text[index - 1]) != noLetters.end())) { noLetterScore += noLetterBonus; } @@ -416,6 +424,8 @@ int SearchIndex::score(const std::string& text, const std::vector& indic camelCaseScore += camelCaseBonus; } } + + } int leadingStartScore = std::max(int(indices[0]) * delayedStartBonus, minDelayedStartBonus); @@ -425,7 +435,66 @@ int SearchIndex::score(const std::string& text, const std::vector& indic consecutiveLetterScore + camelCaseScore + noLetterScore + + firstLetterScore + leadingStartScore; return score; } + +SearchResult SearchIndex::rescoreText( + const std::string& fulltext, + const std::string& text, + const std::vector& indices, + int score, + size_t maxBestScoredLength) +{ + SearchResult result; + result.text = text; + result.score = score; + result.indices = indices; + + std::vector textIndices; + + // match is already within text + int newIdx = indices[0] - (fulltext.size() - text.size()); + if (newIdx >= 0) + { + for (size_t idx : indices) + { + textIndices.push_back(idx - (fulltext.size() - text.size())); + } + } + // try if match is within text + else + { + size_t idx = 0; + for (size_t i = 0; i < text.size() && idx < indices.size(); i++) + { + if (tolower(text[i]) == tolower(fulltext[indices[idx]])) + { + textIndices.push_back(i); + idx++; + } + } + + // match was not found + if (idx != indices.size()) + { + result.score -= 1; + return result; + } + } + + result.score = scoreText(text, textIndices); + result.indices = textIndices; + + std::map scoresCache; + result = bestScoredResult(result, &scoresCache, maxBestScoredLength); + + for (size_t i = 0; i < result.indices.size(); i++) + { + result.indices[i] += fulltext.size() - text.size(); + } + + return result; +} diff --git a/src/lib/data/search/SearchIndex.h b/src/lib/data/search/SearchIndex.h index 46bb6f46..902b1efb 100644 --- a/src/lib/data/search/SearchIndex.h +++ b/src/lib/data/search/SearchIndex.h @@ -8,20 +8,19 @@ #include #include -#include "data/name/NameHierarchy.h" #include "utility/types.h" struct SearchResult { - std::string text; - std::set elementIds; - std::vector indices; - int score; - bool operator<(const SearchResult& other) const { return score > other.score; } + + std::string text; + std::set elementIds; + std::vector indices; + int score; }; class SearchIndex @@ -30,12 +29,12 @@ public: SearchIndex(); virtual ~SearchIndex(); - void addNode(Id id, const NameHierarchy& nameHierarchy); + void addNode(Id id, const std::string& name); void finishSetup(); void clear(); // maxResultCount == 0 means "no restriction". - std::vector search(const std::string& query, size_t maxResultCount) const; + std::vector search(const std::string& query, size_t maxResultCount, size_t maxBestScoredLength = 0) const; private: struct Node; @@ -65,12 +64,23 @@ private: void searchRecursive(const Path& path, const std::string& remainingQuery, std::vector* results) const; std::multiset createScoredResults(const std::vector& paths, size_t maxResultCount) const; - SearchResult bestScoredResult(SearchResult result, std::map* scoresCache) const; - void bestScoredResultRecursive( - const std::string& lowerText, const std::vector& indices, const size_t indicesPos, - std::map* scoresCache, SearchResult* result) const; - int score(const std::string& text, const std::vector& indices) const; + static SearchResult bestScoredResult( + SearchResult result, std::map* scoresCache, size_t maxBestScoredLength); + static void bestScoredResultRecursive( + const std::string& lowerText, const std::vector& indices, const size_t indicesPos, + std::map* scoresCache, SearchResult* result); + static int scoreText(const std::string& text, const std::vector& indices); + +public: + static SearchResult rescoreText( + const std::string& fulltext, + const std::string& text, + const std::vector& indices, + int score, + size_t maxBestScoredLength = 0); + +private: std::vector> m_nodes; std::vector> m_edges; Node* m_root; diff --git a/src/lib/data/search/SearchMatch.cpp b/src/lib/data/search/SearchMatch.cpp index 61f1df30..87a2c829 100644 --- a/src/lib/data/search/SearchMatch.cpp +++ b/src/lib/data/search/SearchMatch.cpp @@ -49,7 +49,8 @@ std::string SearchMatch::searchMatchesToString(const std::vector& m SearchMatch SearchMatch::createCommand(CommandType type) { SearchMatch match; - match.text = getCommandName(type); + match.name = getCommandName(type); + match.text = match.name; match.typeName = "command"; match.searchType = SEARCH_COMMAND; return match; @@ -96,13 +97,60 @@ SearchMatch::SearchMatch() } SearchMatch::SearchMatch(const std::string& query) - : text(query) + : name(query) , typeName("") , searchType(SEARCH_NONE) , hasChildren(false) { } + +bool SearchMatch::operator<(const SearchMatch& other) const +{ + // score + if (score > other.score) + { + return true; + } + else if (score < other.score) + { + return false; + } + + // text size + if (text.size() < other.text.size()) + { + return true; + } + else if (text.size() > other.text.size()) + { + return false; + } + + // lower case + for (size_t i = 0; i < text.size(); i++) + { + if (tolower(text[i]) != tolower(other.text[i])) + { + return tolower(text[i]) < tolower(other.text[i]); + } + else + { + // alphabetical + if (text[i] < other.text[i]) + { + return true; + } + else if (text[i] > other.text[i]) + { + return false; + } + } + } + + return false; +} + bool SearchMatch::isValid() const { return searchType != SEARCH_NONE; @@ -110,7 +158,7 @@ bool SearchMatch::isValid() const void SearchMatch::print(std::ostream& ostream) const { - ostream << text << std::endl << '\t'; + ostream << name << std::endl << '\t'; size_t i = 0; for (size_t index : indices) { @@ -127,7 +175,12 @@ void SearchMatch::print(std::ostream& ostream) const std::string SearchMatch::getFullName() const { - return text; + if (searchType == SEARCH_TOKEN && nodeType == Node::NODE_FILE) + { + return text; + } + + return name; } std::string SearchMatch::getNodeTypeAsString() const diff --git a/src/lib/data/search/SearchMatch.h b/src/lib/data/search/SearchMatch.h index b8768a99..b8684bb8 100644 --- a/src/lib/data/search/SearchMatch.h +++ b/src/lib/data/search/SearchMatch.h @@ -41,6 +41,8 @@ struct SearchMatch SearchMatch(); SearchMatch(const std::string& query); + bool operator<(const SearchMatch& other) const; + bool isValid() const; void print(std::ostream& ostream) const; @@ -49,12 +51,19 @@ struct SearchMatch std::string getNodeTypeAsString() const; std::string getSearchTypeName() const; + std::string name; + std::string text; + std::string subtext; + std::string typeName; + Node::NodeType nodeType; SearchType searchType; std::vector indices; + int score; + std::vector nameHierarchies; bool hasChildren; diff --git a/src/lib_gui/qt/element/QtAutocompletionList.cpp b/src/lib_gui/qt/element/QtAutocompletionList.cpp index 6d3be70b..070aa5e6 100644 --- a/src/lib_gui/qt/element/QtAutocompletionList.cpp +++ b/src/lib_gui/qt/element/QtAutocompletionList.cpp @@ -4,8 +4,10 @@ #include #include "component/view/GraphViewStyle.h" +#include "qt/utility/QtDeviceScaledPixmap.h" #include "settings/ApplicationSettings.h" #include "settings/ColorScheme.h" +#include "utility/ResourcePaths.h" QtAutocompletionModel::QtAutocompletionModel(QObject* parent) : QAbstractTableModel(parent) @@ -30,7 +32,7 @@ int QtAutocompletionModel::rowCount(const QModelIndex &parent) const int QtAutocompletionModel::columnCount(const QModelIndex &parent) const { Q_UNUSED(parent); - return 4; + return 6; } QVariant QtAutocompletionModel::data(const QModelIndex &index, int role) const @@ -45,10 +47,14 @@ QVariant QtAutocompletionModel::data(const QModelIndex &index, int role) const switch (index.column()) { case 0: - return QString::fromStdString(match.getFullName()); + return QString::fromStdString(match.name); case 1: - return QString::fromStdString(match.typeName); + return QString::fromStdString(match.text); case 2: + return QString::fromStdString(match.subtext); + case 3: + return QString::fromStdString(match.typeName); + case 4: { QList indices; for (const size_t idx : match.indices) @@ -57,7 +63,7 @@ QVariant QtAutocompletionModel::data(const QModelIndex &index, int role) const } return indices; } - case 3: + case 5: return match.nodeType; default: return QVariant(); @@ -73,10 +79,52 @@ const SearchMatch* QtAutocompletionModel::getSearchMatchAt(int idx) const return nullptr; } - -QtAutocompletionDelegate::QtAutocompletionDelegate(QObject* parent) - : QStyledItemDelegate(parent) +QString QtAutocompletionModel::longestText() const { + std::string str; + for (const SearchMatch& match : m_matchList) + { + if (match.text.size() > str.size()) + { + str = match.text; + } + } + return QString::fromStdString(str); +} + +QString QtAutocompletionModel::longestSubText() const +{ + std::string str; + for (const SearchMatch& match : m_matchList) + { + if (match.subtext.size() > str.size()) + { + str = match.subtext; + } + } + return QString::fromStdString(str); +} + +QString QtAutocompletionModel::longestType() const +{ + std::string str; + for (const SearchMatch& match : m_matchList) + { + if (match.typeName.size() > str.size()) + { + str = match.typeName; + } + } + return QString::fromStdString(str); +} + + +QtAutocompletionDelegate::QtAutocompletionDelegate(QtAutocompletionModel* model, QObject* parent) + : QStyledItemDelegate(parent) + , m_model(model) + , m_arrow() +{ + resetCharSizes(); } QtAutocompletionDelegate::~QtAutocompletionDelegate() @@ -87,108 +135,219 @@ void QtAutocompletionDelegate::paint(QPainter* painter, const QStyleOptionViewIt { painter->save(); - ColorScheme* scheme = ColorScheme::getInstance().get(); - - if (option.state & QStyle::State_Selected) - { - painter->fillRect(option.rect, option.palette.color(QPalette::Highlight)); - } - else - { - painter->fillRect(option.rect, option.palette.color(QPalette::Base)); - } - + // get data QString name = index.data().toString(); + QString text = index.sibling(index.row(), index.column() + 1).data().toString(); + QString subtext = index.sibling(index.row(), index.column() + 2).data().toString(); + QString type = index.sibling(index.row(), index.column() + 3).data().toString(); + QList indices = index.sibling(index.row(), index.column() + 4).data().toList(); + Node::NodeType nodeType = static_cast(index.sibling(index.row(), index.column() + 5).data().toInt()); - - QString type = index.sibling(index.row(), index.column() + 1).data().toString(); - QColor color("#FFFFFF"); + // define highlight colors + ColorScheme* scheme = ColorScheme::getInstance().get(); + QColor fillColor("#FFFFFF"); QColor textColor("#000000"); - Node::NodeType nodeType = static_cast(index.sibling(index.row(), index.column() + 3).data().toInt()); if (type.size() && type != "command") { const GraphViewStyle::NodeColor& nodeColor = GraphViewStyle::getNodeColor(Node::getTypeString(nodeType), false); - color = QColor(nodeColor.fill.c_str()); + fillColor = QColor(nodeColor.fill.c_str()); textColor = QColor(nodeColor.text.c_str()); } else { - color = QColor(scheme->getSearchTypeColor(SearchMatch::getSearchTypeName(SearchMatch::SEARCH_COMMAND), "fill").c_str()); + fillColor = QColor(scheme->getSearchTypeColor(SearchMatch::getSearchTypeName(SearchMatch::SEARCH_COMMAND), "fill").c_str()); textColor = QColor(scheme->getSearchTypeColor(SearchMatch::getSearchTypeName(SearchMatch::SEARCH_COMMAND), "text").c_str()); } - float charWidth = option.fontMetrics.width( - "----------------------------------------------------------------------------------------------------" - "----------------------------------------------------------------------------------------------------" - "----------------------------------------------------------------------------------------------------" - "----------------------------------------------------------------------------------------------------" - "----------------------------------------------------------------------------------------------------" - ) / 500.0f; + int top1 = 6; + int top2 = m_charHeight1 + 3; - QString highlightName(name.size(), ' '); + // draw background + QColor backgroundColor = option.palette.color((option.state & QStyle::State_Selected ? QPalette::Highlight : QPalette::Base)); + painter->fillRect(option.rect, backgroundColor); - QList indices = index.sibling(index.row(), index.column() + 2).data().toList(); + // draw highlights at indices + QString highlightText(text.size(), ' '); if (indices.size()) { for (int i = 0; i < indices.size(); i++) { - int idx = indices[i].toInt(); + int idx = indices[i].toInt() - (name.size() - text.size()); + if (idx < 0) + { + continue; + } - QRect rect = option.rect.adjusted(charWidth * (idx + 1) + 2, 2, 0, -1); - rect.setWidth(charWidth + 1); - painter->fillRect(rect, color); + QRect rect( + option.rect.left() + m_charWidth1 * (idx + 1) + 2, + option.rect.top() + top1 - 1, + m_charWidth1 + 1, + m_charHeight1 - 1 + ); + painter->fillRect(rect, fillColor); - highlightName[idx] = name.at(idx); - name[idx] = ' '; + highlightText[idx] = text.at(idx); + text[idx] = ' '; } } else { - QRect rect = option.rect.adjusted(0, 2, 0, -1); - rect.setWidth(charWidth - 1); - painter->fillRect(rect, color); + QRect rect(option.rect.left(), option.rect.top() + top1, m_charWidth1 - 1, m_charHeight1 - 2); + painter->fillRect(rect, fillColor); } - painter->drawText(option.rect.adjusted(charWidth + 2, -1, 0, 0), Qt::AlignLeft, name); + // draw text normal + painter->drawText(option.rect.adjusted(m_charWidth1 + 2, top1 - 3, 0, 0), Qt::AlignLeft, text); + // draw text highlighted painter->save(); QPen highlightPen = painter->pen(); highlightPen.setColor(textColor); painter->setPen(highlightPen); - painter->drawText(option.rect.adjusted(charWidth + 2, -1, 0, 0), Qt::AlignLeft, highlightName); + painter->drawText(option.rect.adjusted(m_charWidth1 + 2, top1 - 3, 0, 0), Qt::AlignLeft, highlightText); painter->restore(); - if (type.size()) + // draw subtext + if (subtext.size()) { - QFont font = painter->font(); - font.setPixelSize(ApplicationSettings::getInstance()->getFontSize() - 4); - painter->setFont(font); + // draw arrow icon + painter->drawPixmap( + option.rect.left() + m_charWidth2 * 2, + option.rect.top() + top2 + 1 + (m_charHeight2 - m_arrow.height()) / 2, + m_arrow.pixmap() + ); + + painter->setFont(m_font2); + + QString highlightSubtext(subtext.size(), ' '); + if (indices.size()) + { + for (int i = 0; i < indices.size(); i++) + { + int idx = indices[i].toInt(); + if (idx >= subtext.size()) + { + continue; + } + + QRect rect( + option.rect.left() + m_charWidth2 * (idx + 3) + 2, + option.rect.top() + top2 + 1, + m_charWidth2 + 1, + m_charHeight2 + ); + painter->fillRect(rect, fillColor); + + highlightSubtext[idx] = subtext.at(idx); + subtext[idx] = ' '; + } + } QPen typePen = painter->pen(); typePen.setColor(scheme->getColor("search/popup/by_text").c_str()); painter->setPen(typePen); - painter->drawText(option.rect.adjusted(0, 3, -charWidth, 0), Qt::AlignRight, type); + // draw subtext normal + painter->drawText(option.rect.adjusted((3 * m_charWidth2) + 2, top2, 0, 0), Qt::AlignLeft, subtext); + + // draw subtext highlighted + painter->save(); + QPen highlightPen = painter->pen(); + highlightPen.setColor(textColor); + painter->setPen(highlightPen); + painter->drawText(option.rect.adjusted((3 * m_charWidth2) + 2, top2, 0, 0), Qt::AlignLeft, highlightSubtext); + painter->restore(); } + // draw type + if (type.size()) + { + painter->setFont(m_font2); + + QPen typePen = painter->pen(); + typePen.setColor(scheme->getColor("search/popup/by_text").c_str()); + painter->setPen(typePen); + + int width = m_charWidth2 * type.size(); + int x = painter->viewport().right() - width - m_charWidth2; + int y = option.rect.top() + top2; + + painter->fillRect(QRect(x - m_charWidth2, y, width + m_charWidth2 * 3, m_charHeight2 + 2), backgroundColor); + painter->drawText(QRect(x, y, width, m_charHeight2), Qt::AlignRight, type); + } + + // draw bottom line + QRect rect(0, option.rect.bottom(), option.rect.width(), 1); + painter->fillRect(rect, scheme->getColor("search/popup/line").c_str()); + painter->restore(); } QSize QtAutocompletionDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const { - QString name = index.data().toString(); - QString type = index.sibling(index.row(), index.column() + 1).data().toString(); - return QSize(option.fontMetrics.width(name + type) + 5, option.fontMetrics.height()); + const_cast(this)->calculateCharSizes(option.font); + + QString text = m_model->longestText(); + QString subtext = m_model->longestSubText(); + QString type = m_model->longestType(); + + return QSize( + std::max((text.size() + 2) * m_charWidth1, (subtext.size() + type.size() + 6) * m_charWidth2), + m_charHeight1 * 2 + 3 + ); } +void QtAutocompletionDelegate::calculateCharSizes(QFont font) +{ + if (m_charWidth1 > 0) + { + return; + } + + m_font1 = font; + + QFontMetrics metrics1(font); + m_charWidth1 = metrics1.width( + "----------------------------------------------------------------------------------------------------" + "----------------------------------------------------------------------------------------------------" + "----------------------------------------------------------------------------------------------------" + "----------------------------------------------------------------------------------------------------" + "----------------------------------------------------------------------------------------------------" + ) / 500.0f; + m_charHeight1 = metrics1.height(); + + font.setPixelSize(ApplicationSettings::getInstance()->getFontSize() - 3); + m_font2 = font; + + QFontMetrics metrics2(font); + m_charWidth2 = metrics2.width( + "----------------------------------------------------------------------------------------------------" + "----------------------------------------------------------------------------------------------------" + "----------------------------------------------------------------------------------------------------" + "----------------------------------------------------------------------------------------------------" + "----------------------------------------------------------------------------------------------------" + ) / 500.0f; + m_charHeight2 = metrics2.height(); + + m_arrow = QtDeviceScaledPixmap(QString::fromStdString(ResourcePaths::getGuiPath() + "search_view/images/arrow.png")); + m_arrow.scaleToWidth(m_charWidth2); + m_arrow.colorize(ColorScheme::getInstance()->getColor("search/popup/by_text").c_str()); +} + +void QtAutocompletionDelegate::resetCharSizes() +{ + m_charWidth1 = m_charHeight1 = m_charWidth2 = m_charHeight2 = 0.0f; +} + + QtAutocompletionList::QtAutocompletionList(QWidget* parent) : QCompleter(parent) { m_model = std::make_shared(this); setModel(m_model.get()); - m_delegate = std::make_shared(this); + m_delegate = std::make_shared(m_model.get(), this); QListView* list = new QListView(parent); list->setItemDelegateForColumn(0, m_delegate.get()); @@ -201,7 +360,7 @@ QtAutocompletionList::QtAutocompletionList(QWidget* parent) setCompletionMode(QCompleter::UnfilteredPopupCompletion); setModelSorting(QCompleter::UnsortedModel); setCompletionPrefix(""); - setMaxVisibleItems(20); + setMaxVisibleItems(8); m_scrollSpeedChangeListenerHorizontal.setScrollBar(list->horizontalScrollBar()); m_scrollSpeedChangeListenerVertical.setScrollBar(list->verticalScrollBar()); @@ -222,6 +381,8 @@ void QtAutocompletionList::completeAt(const QPoint& pos, const std::vectorresetCharSizes(); + disconnect(); // must be done because of a bug where signals are no longer received by QtSmartSearchBox connect(this, SIGNAL(highlighted(const QModelIndex&)), this, SLOT(onHighlighted(const QModelIndex&)), Qt::DirectConnection); connect(this, SIGNAL(activated(const QModelIndex&)), this, SLOT(onActivated(const QModelIndex&)), Qt::DirectConnection); diff --git a/src/lib_gui/qt/element/QtAutocompletionList.h b/src/lib_gui/qt/element/QtAutocompletionList.h index bdbd690e..ad811c46 100644 --- a/src/lib_gui/qt/element/QtAutocompletionList.h +++ b/src/lib_gui/qt/element/QtAutocompletionList.h @@ -10,6 +10,7 @@ #include #include "data/search/SearchMatch.h" +#include "qt/utility/QtDeviceScaledPixmap.h" #include "qt/utility/QtScrollSpeedChangeListener.h" class QtAutocompletionModel @@ -30,6 +31,10 @@ public: const SearchMatch* getSearchMatchAt(int idx) const; + QString longestText() const; + QString longestSubText() const; + QString longestType() const; + private: std::vector m_matchList; }; @@ -39,11 +44,27 @@ class QtAutocompletionDelegate : public QStyledItemDelegate { public: - explicit QtAutocompletionDelegate(QObject* parent = 0); + explicit QtAutocompletionDelegate(QtAutocompletionModel* model, QObject* parent = 0); virtual ~QtAutocompletionDelegate(); virtual void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const; virtual QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const; + + void calculateCharSizes(QFont font); + void resetCharSizes(); + +private: + QtAutocompletionModel* m_model; + + QFont m_font1; + QFont m_font2; + + float m_charWidth1; + float m_charHeight1; + float m_charWidth2; + float m_charHeight2; + + QtDeviceScaledPixmap m_arrow; }; diff --git a/src/lib_gui/qt/element/QtSmartSearchBox.cpp b/src/lib_gui/qt/element/QtSmartSearchBox.cpp index 6e48ad1f..22caa824 100644 --- a/src/lib_gui/qt/element/QtSmartSearchBox.cpp +++ b/src/lib_gui/qt/element/QtSmartSearchBox.cpp @@ -146,11 +146,11 @@ bool QtSmartSearchBox::event(QEvent *event) { if (m_highlightedMatch.hasChildren) { - setEditText((m_highlightedMatch.text + NameHierarchy::getDelimiter()).c_str()); + setEditText((m_highlightedMatch.getFullName() + NameHierarchy::getDelimiter()).c_str()); } else { - setEditText(m_highlightedMatch.text.c_str()); + setEditText(m_highlightedMatch.getFullName().c_str()); } requestAutoCompletions(); @@ -456,7 +456,7 @@ void QtSmartSearchBox::onTextEdited(const QString& text) } } - if (match.text.size() && !m_allowMultipleElements) + if (match.name.size() && !m_allowMultipleElements) { if (m_matches.size()) { @@ -475,7 +475,7 @@ void QtSmartSearchBox::onTextEdited(const QString& text) layoutElements(); } - if (match.text.size() || m_elements.size()) + if (match.name.size() || m_elements.size()) { requestAutoCompletions(); } @@ -510,7 +510,7 @@ void QtSmartSearchBox::onAutocompletionActivated(const SearchMatch& match) { addMatchAndUpdate(match); - if (match.text.size()) + if (match.name.size()) { search(); } @@ -580,7 +580,7 @@ void QtSmartSearchBox::moveCursorTo(int target) void QtSmartSearchBox::addMatch(const SearchMatch& match) { - if (!match.text.size()) + if (!match.name.size()) { return; } @@ -607,7 +607,7 @@ void QtSmartSearchBox::addMatch(const SearchMatch& match) void QtSmartSearchBox::addMatchAndUpdate(const SearchMatch& match) { - if (match.text.size()) + if (match.name.size()) { m_oldText.clear(); clearLineEdit(); diff --git a/src/lib_gui/qt/utility/QtDeviceScaledPixmap.cpp b/src/lib_gui/qt/utility/QtDeviceScaledPixmap.cpp index 8beaa0e6..bb83d3ad 100644 --- a/src/lib_gui/qt/utility/QtDeviceScaledPixmap.cpp +++ b/src/lib_gui/qt/utility/QtDeviceScaledPixmap.cpp @@ -2,12 +2,18 @@ #include +#include "qt/utility/utilityQt.h" + qreal QtDeviceScaledPixmap::devicePixelRatio() { QApplication* app = dynamic_cast(QCoreApplication::instance()); return app->devicePixelRatio(); } +QtDeviceScaledPixmap::QtDeviceScaledPixmap() +{ +} + QtDeviceScaledPixmap::QtDeviceScaledPixmap(QString filePath) : m_pixmap(filePath) { @@ -48,3 +54,8 @@ void QtDeviceScaledPixmap::mirror(bool horizontal, bool vertical) m_pixmap = QPixmap::fromImage(m_pixmap.toImage().mirrored(horizontal, vertical)); m_pixmap.setDevicePixelRatio(devicePixelRatio()); } + +void QtDeviceScaledPixmap::colorize(QColor color) +{ + m_pixmap = utility::colorizePixmap(m_pixmap, color); +} diff --git a/src/lib_gui/qt/utility/QtDeviceScaledPixmap.h b/src/lib_gui/qt/utility/QtDeviceScaledPixmap.h index 706ae607..1b7371e9 100644 --- a/src/lib_gui/qt/utility/QtDeviceScaledPixmap.h +++ b/src/lib_gui/qt/utility/QtDeviceScaledPixmap.h @@ -8,7 +8,8 @@ class QtDeviceScaledPixmap public: static qreal devicePixelRatio(); - explicit QtDeviceScaledPixmap(QString filePath); + QtDeviceScaledPixmap(); + QtDeviceScaledPixmap(QString filePath); virtual ~QtDeviceScaledPixmap(); const QPixmap& pixmap() const; @@ -20,6 +21,7 @@ public: void scaleToHeight(int height); void mirror(bool horizontal = false, bool vertical = true); + void colorize(QColor color); private: QPixmap m_pixmap; diff --git a/src/test/SearchIndexTestSuite.h b/src/test/SearchIndexTestSuite.h index 31ff1d74..211c7ef3 100644 --- a/src/test/SearchIndexTestSuite.h +++ b/src/test/SearchIndexTestSuite.h @@ -11,7 +11,7 @@ public: void test_search_index_finds_id_of_element_added() { SearchIndex index; - index.addNode(1, NameHierarchy::deserialize("foo\tsvoid\tp() const")); + index.addNode(1, NameHierarchy::deserialize("foo\tsvoid\tp() const").getQualifiedName()); index.finishSetup(); std::vector results = index.search("oo", 0); @@ -23,7 +23,7 @@ public: void test_search_index_finds_correct_indices_for_query() { SearchIndex index; - index.addNode(1, NameHierarchy::deserialize("foo\tsvoid\tp() const")); + index.addNode(1, NameHierarchy::deserialize("foo\tsvoid\tp() const").getQualifiedName()); index.finishSetup(); std::vector results = index.search("oo", 0); @@ -36,8 +36,8 @@ public: void test_search_index_finds_ids_for_ambiguous_query() { SearchIndex index; - index.addNode(1, NameHierarchy::deserialize("for\tsvoid\tp() const")); - index.addNode(2, NameHierarchy::deserialize("fos\tsvoid\tp() const")); + index.addNode(1, NameHierarchy::deserialize("for\tsvoid\tp() const").getQualifiedName()); + index.addNode(2, NameHierarchy::deserialize("fos\tsvoid\tp() const").getQualifiedName()); index.finishSetup(); std::vector results = index.search("fo", 0); @@ -51,7 +51,7 @@ public: void test_search_index_does_not_find_anything_after_clear() { SearchIndex index; - index.addNode(1, NameHierarchy::deserialize("foo\tsvoid\tp() const")); + index.addNode(1, NameHierarchy::deserialize("foo\tsvoid\tp() const").getQualifiedName()); index.finishSetup(); index.clear(); std::vector results = index.search("oo", 0); @@ -62,8 +62,8 @@ public: void test_search_index_does_not_find_all_results_when_max_amount_is_limited() { SearchIndex index; - index.addNode(1, NameHierarchy::deserialize("foo1\tsvoid\tp() const")); - index.addNode(2, NameHierarchy::deserialize("foo2\tsvoid\tp() const")); + index.addNode(1, NameHierarchy::deserialize("foo1\tsvoid\tp() const").getQualifiedName()); + index.addNode(2, NameHierarchy::deserialize("foo2\tsvoid\tp() const").getQualifiedName()); index.finishSetup(); std::vector results = index.search("oo", 1); @@ -73,8 +73,8 @@ public: void test_search_index_query_is_case_insensitive() { SearchIndex index; - index.addNode(1, NameHierarchy::deserialize("foo1\tsvoid\tp() const")); - index.addNode(2, NameHierarchy::deserialize("FOO2\tsvoid\tp() const")); + index.addNode(1, NameHierarchy::deserialize("foo1\tsvoid\tp() const").getQualifiedName()); + index.addNode(2, NameHierarchy::deserialize("FOO2\tsvoid\tp() const").getQualifiedName()); index.finishSetup(); std::vector results = index.search("oo", 0); @@ -85,8 +85,8 @@ public: { SearchIndex index; - index.addNode(1, NameHierarchy::deserialize("oaabbcc\tsvoid\tp() const")); - index.addNode(2, NameHierarchy::deserialize("ocbcabc\tsvoid\tp() const")); + index.addNode(1, NameHierarchy::deserialize("oaabbcc\tsvoid\tp() const").getQualifiedName()); + index.addNode(2, NameHierarchy::deserialize("ocbcabc\tsvoid\tp() const").getQualifiedName()); index.finishSetup(); std::vector results = index.search("abc", 0);