From e972dbe09821a618cce6e5926b50c6b8fc0221a2 Mon Sep 17 00:00:00 2001 From: Eberhard Graether Date: Sun, 2 Nov 2014 17:21:51 +0100 Subject: [PATCH] ui: augmented autocompletion list This change adds search match information to the autocompletion list: - matching letters - color for token or command - node type as string --- .gitignore | 4 + src/app/CMakeLists.txt | 4 +- src/app/qt/element/QtAutocompletionList.cpp | 188 ++++++++++++++++++++ src/app/qt/element/QtAutocompletionList.h | 70 ++++++++ src/app/qt/element/QtSmartSearchBox.cpp | 61 ++++--- src/app/qt/element/QtSmartSearchBox.h | 7 +- src/lib/ApplicationSettings.cpp | 2 +- src/lib/data/Storage.cpp | 12 ++ src/lib/data/graph/Edge.h | 2 +- src/lib/data/graph/Node.h | 2 +- src/lib/data/graph/Token.h | 3 + src/lib/data/search/SearchMatch.h | 1 + src/test/ConfigManagerTestSuite.h | 4 +- src/test/GraphTestSuite.h | 5 + 14 files changed, 330 insertions(+), 35 deletions(-) create mode 100644 src/app/qt/element/QtAutocompletionList.cpp create mode 100644 src/app/qt/element/QtAutocompletionList.h diff --git a/.gitignore b/.gitignore index c91565c4..d85718ab 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,15 @@ /build/ + /bin/app/Debug/ /bin/app/Release/ /bin/app/data/log/ + /bin/lib/ + /bin/test/Debug/ /bin/test/Release/ /bin/test/data/log/ +/bin/test/data/temp.xml /bin/app/data/window_settings.ini /bin/app/data/ApplicationSettings.xml diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt index 6a7ed0a2..b851da14 100644 --- a/src/app/CMakeLists.txt +++ b/src/app/CMakeLists.txt @@ -5,6 +5,8 @@ add_files( platform_includes/includesMac.h platform_includes/includesWindows.h + qt/element/QtAutocompletionList.cpp + qt/element/QtAutocompletionList.h qt/element/QtCodeFile.cpp qt/element/QtCodeFile.h qt/element/QtCodeFileList.cpp @@ -30,7 +32,7 @@ add_files( qt/view/graphElements/nodeComponents/QtGraphNodeComponentMoveable.h qt/view/graphElements/nodeComponents/QtGraphNodeComponentToggleButton.cpp qt/view/graphElements/nodeComponents/QtGraphNodeComponentToggleButton.h - + qt/view/graphElements/QtGraphEdge.cpp qt/view/graphElements/QtGraphEdge.h qt/view/graphElements/QtGraphNode.cpp diff --git a/src/app/qt/element/QtAutocompletionList.cpp b/src/app/qt/element/QtAutocompletionList.cpp new file mode 100644 index 00000000..d6000dfd --- /dev/null +++ b/src/app/qt/element/QtAutocompletionList.cpp @@ -0,0 +1,188 @@ +#include "qt/element/QtAutocompletionList.h" + +#include + +QtAutocompletionModel::QtAutocompletionModel(const std::vector& matchList, QObject* parent) + : QAbstractTableModel(parent) + , m_matchList(matchList) +{ +} + +QtAutocompletionModel::~QtAutocompletionModel() +{ +} + +int QtAutocompletionModel::rowCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent); + return m_matchList.size(); +} + +int QtAutocompletionModel::columnCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent); + return 3; +} + +QVariant QtAutocompletionModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid() || index.row() < 0 || size_t(index.row()) >= m_matchList.size() || role != Qt::DisplayRole) + { + return QVariant(); + } + + const SearchMatch& match = m_matchList[index.row()]; + + switch (index.column()) + { + case 0: + return QString::fromStdString(match.fullName); + case 1: + return QString::fromStdString(match.typeName); + case 2: + { + QList indices; + for (const size_t idx : match.indices) + { + indices.push_back(quint64(idx)); + } + return indices; + } + default: + return QVariant(); + } +} + +const SearchMatch* QtAutocompletionModel::getSearchMatchAt(int idx) const +{ + if (idx >= 0 && size_t(idx) < m_matchList.size()) + { + return &m_matchList[idx]; + } + return nullptr; +} + + +QtAutocompletionDelegate::QtAutocompletionDelegate(QObject* parent) + : QItemDelegate(parent) +{ +} + +QtAutocompletionDelegate::~QtAutocompletionDelegate() +{ +} + +void QtAutocompletionDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const +{ + QPen pen = painter->pen(); + QFont font = painter->font(); + + if (option.state & QStyle::State_Selected) + { + QPen highlightPen = pen; + highlightPen.setColor(option.palette.color(QPalette::HighlightedText)); + painter->setPen(highlightPen); + + painter->fillRect(option.rect, option.palette.color(QPalette::Highlight)); + } + else + { + painter->fillRect(option.rect, option.palette.color(QPalette::Base)); + } + + QFont higlightFont = font; + higlightFont.setWeight(QFont::Bold); + + QString name = index.data().toString(); + QList indices = index.sibling(index.row(), index.column() + 2).data().toList(); + int idx = 0; + + int x = 0; + int m = option.fontMetrics.width(QLatin1Char('9')); + for (int i = 0; i < name.size(); i++) + { + if (idx < indices.size() && i == indices[idx]) + { + painter->setFont(higlightFont); + idx++; + } + else + { + painter->setFont(font); + } + + painter->drawText(option.rect.adjusted(8 + x, 0, 0, 0), Qt::AlignLeft, name.at(i)); + x += m; + } + + if (font.pixelSize() > 0) + { + QFont typeFont = font; + typeFont.setPixelSize(0.8f * font.pixelSize()); + painter->setFont(typeFont); + } + + QString type = index.sibling(index.row(), index.column() + 1).data().toString(); + QRect rect = option.rect.adjusted(1, 1, 0, -1); + rect.setWidth(5); + + if (type.size()) + { + painter->fillRect(rect, QColor(153, 22, 165)); + painter->drawText(option.rect.adjusted(0, 0, -3, 0), Qt::AlignRight, type); + } + else + { + painter->fillRect(rect, QColor(172, 150, 0)); + } + + painter->setFont(font); + painter->setPen(pen); +} + + +QtAutocompletionList::QtAutocompletionList(const std::vector& autocompletionList, QWidget* parent) + : QCompleter(parent) +{ + m_model = std::make_shared(autocompletionList, this); + setModel(m_model.get()); + + m_delegate = std::make_shared(this); + + QListView* list = new QListView(parent); + list->setItemDelegateForColumn(0, m_delegate.get()); + list->setObjectName("search_box_popup"); + setPopup(list); + + setCaseSensitivity(Qt::CaseInsensitive); + + 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); +} + +QtAutocompletionList::~QtAutocompletionList() +{ +} + +const SearchMatch* QtAutocompletionList::getSearchMatchAt(int idx) const +{ + return m_model->getSearchMatchAt(idx); +} + +void QtAutocompletionList::onHighlighted(const QModelIndex& index) +{ + const SearchMatch* match = getSearchMatchAt(index.row()); + if (match) + { + emit matchHighlighted(*match); + } +} + +void QtAutocompletionList::onActivated(const QModelIndex& index) +{ + const SearchMatch* match = getSearchMatchAt(index.row()); + if (match) + { + emit matchActivated(*match); + } +} diff --git a/src/app/qt/element/QtAutocompletionList.h b/src/app/qt/element/QtAutocompletionList.h new file mode 100644 index 00000000..f30e1a18 --- /dev/null +++ b/src/app/qt/element/QtAutocompletionList.h @@ -0,0 +1,70 @@ +#ifndef QT_AUTOCOMPLETION_LIST +#define QT_AUTOCOMPLETION_LIST + +#include +#include + +#include +#include +#include +#include + +#include "data/search/SearchMatch.h" + +class QtAutocompletionModel + : public QAbstractTableModel +{ + Q_OBJECT + +public: + QtAutocompletionModel(const std::vector& matchList, QObject* parent = 0); + virtual ~QtAutocompletionModel(); + + virtual int rowCount(const QModelIndex& parent) const; + virtual int columnCount(const QModelIndex& parent) const; + + virtual QVariant data(const QModelIndex& index, int role) const; + + const SearchMatch* getSearchMatchAt(int idx) const; + +private: + const std::vector& m_matchList; +}; + + +class QtAutocompletionDelegate + : public QItemDelegate +{ +public: + explicit QtAutocompletionDelegate(QObject* parent = 0); + virtual ~QtAutocompletionDelegate(); + + virtual void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const; +}; + + +class QtAutocompletionList + : public QCompleter +{ + Q_OBJECT + +signals: + void matchHighlighted(const SearchMatch&); + void matchActivated(const SearchMatch&); + +public: + QtAutocompletionList(const std::vector& autocompletionList, QWidget* parent = 0); + virtual ~QtAutocompletionList(); + + const SearchMatch* getSearchMatchAt(int idx) const; + +private slots: + void onHighlighted(const QModelIndex& index); + void onActivated(const QModelIndex& index); + +private: + std::shared_ptr m_model; + std::shared_ptr m_delegate; +}; + +#endif // QT_AUTOCOMPLETION_LIST diff --git a/src/app/qt/element/QtSmartSearchBox.cpp b/src/app/qt/element/QtSmartSearchBox.cpp index 6bc4d469..718f1c23 100644 --- a/src/app/qt/element/QtSmartSearchBox.cpp +++ b/src/app/qt/element/QtSmartSearchBox.cpp @@ -2,18 +2,18 @@ #include -#include #include #include -#include #include -#include "data/query/QueryTree.h" #include "utility/messaging/type/MessageSearch.h" #include "utility/messaging/type/MessageSearchAutocomplete.h" #include "utility/text/TextAccess.h" #include "utility/utilityString.h" +#include "data/query/QueryTree.h" +#include "qt/element/QtAutocompletionList.h" + QtQueryElement::QtQueryElement(const QString& text, QWidget* parent) : QPushButton(text, parent) { @@ -54,29 +54,18 @@ QtSmartSearchBox::~QtSmartSearchBox() void QtSmartSearchBox::setAutocompletionList(const std::vector& autocompletionList) { - m_matches = autocompletionList; - - if (!m_matches.size()) + if (!autocompletionList.size()) { setCompleter(0); return; } - QStringList wordList; - for (const SearchMatch& match: autocompletionList) - { - wordList << match.fullName.c_str(); - } - - QCompleter *completer = new QCompleter(wordList, this); - completer->popup()->setObjectName("search_box_popup"); - completer->setCaseSensitivity(Qt::CaseInsensitive); - + QCompleter* completer = new QtAutocompletionList(autocompletionList, this); setCompleter(completer); - completer->complete(); + completer->complete(QRect(textMargins().left() + 3, height(), 300, 1)); - connect(completer, SIGNAL(highlighted(const QModelIndex&)), this, SLOT(onSearchCompletionHighlighted(const QModelIndex&)), Qt::DirectConnection); - connect(completer, SIGNAL(activated(const QModelIndex&)), this, SLOT(onSearchCompletionActivated(const QModelIndex&)), Qt::DirectConnection); + connect(completer, SIGNAL(matchHighlighted(const SearchMatch&)), this, SLOT(onAutocompletionHighlighted(const SearchMatch&)), Qt::DirectConnection); + connect(completer, SIGNAL(matchActivated(const SearchMatch&)), this, SLOT(onAutocompletionActivated(const SearchMatch&)), Qt::DirectConnection); completer->popup()->setCurrentIndex(completer->completionModel()->index(0, 0)); } @@ -96,6 +85,21 @@ void QtSmartSearchBox::setFocus() selectAllElementsWith(true); } +bool QtSmartSearchBox::event(QEvent *event) +{ + if (event->type() == QEvent::KeyPress) + { + QKeyEvent *keyEvent = static_cast(event); + if (keyEvent->key() == Qt::Key_Tab && completer() && completer()->popup()->isVisible()) + { + onAutocompletionActivated(m_highlightedMatch); + return true; + } + } + + return QWidget::event(event); +} + void QtSmartSearchBox::resizeEvent(QResizeEvent* event) { QLineEdit::resizeEvent(event); @@ -371,19 +375,20 @@ void QtSmartSearchBox::onTextChanged(const QString& text) updatePlaceholder(); } -void QtSmartSearchBox::onSearchCompletionHighlighted(const QModelIndex& index) +void QtSmartSearchBox::onAutocompletionHighlighted(const SearchMatch& match) { + m_highlightedMatch = match; } -void QtSmartSearchBox::onSearchCompletionActivated(const QModelIndex& index) +void QtSmartSearchBox::onAutocompletionActivated(const SearchMatch& match) { - if (index.row() >= 0 && index.row() < int(m_matches.size())) + if (match.fullName.size()) { m_oldText.clear(); clearLineEdit(); - std::string match = m_matches[index.row()].encodeForQuery(); - textToToken(match); + std::string name = match.encodeForQuery(); + textToToken(name); updateElements(); } @@ -457,9 +462,13 @@ void QtSmartSearchBox::textToToken(std::string text) return; } - if (m_matches.size() && utility::equalsCaseInsensitive(text, m_matches.front().fullName)) + if (completer()) { - text = m_matches.front().encodeForQuery(); + const SearchMatch* match = dynamic_cast(completer())->getSearchMatchAt(0); + if (match && utility::equalsCaseInsensitive(text, match->fullName)) + { + text = match->encodeForQuery(); + } } m_tokens.insert(m_tokens.begin() + m_cursorIndex, text); diff --git a/src/app/qt/element/QtSmartSearchBox.h b/src/app/qt/element/QtSmartSearchBox.h index 9560244f..9ab4a1d0 100644 --- a/src/app/qt/element/QtSmartSearchBox.h +++ b/src/app/qt/element/QtSmartSearchBox.h @@ -43,6 +43,7 @@ public: void setFocus(); protected: + virtual bool event(QEvent *event); virtual void resizeEvent(QResizeEvent* event); virtual void keyPressEvent(QKeyEvent* event); virtual void keyReleaseEvent(QKeyEvent* event); @@ -55,8 +56,8 @@ private slots: void onTextEdited(const QString& text); void onTextChanged(const QString& text); - void onSearchCompletionHighlighted(const QModelIndex& index); - void onSearchCompletionActivated(const QModelIndex& index); + void onAutocompletionHighlighted(const SearchMatch& match); + void onAutocompletionActivated(const SearchMatch& match); void onElementSelected(QtQueryElement* element); @@ -91,7 +92,7 @@ private: size_t m_cursorIndex; - std::vector m_matches; + SearchMatch m_highlightedMatch; bool m_shiftKeyDown; bool m_mousePressed; diff --git a/src/lib/ApplicationSettings.cpp b/src/lib/ApplicationSettings.cpp index 90277683..d7f16ea6 100644 --- a/src/lib/ApplicationSettings.cpp +++ b/src/lib/ApplicationSettings.cpp @@ -20,7 +20,7 @@ std::vector ApplicationSettings::getHeaderSearchPaths() const { //TODO: defaultValues? std::vector defaultValues; - return getValues("source/HeaderSearchPaths", defaultValues); + return getValues("source/HeaderSearchPaths/HeaderSearchPath", defaultValues); } int ApplicationSettings::getCodeTabWidth() const diff --git a/src/lib/data/Storage.cpp b/src/lib/data/Storage.cpp index ca332b93..95e994fe 100644 --- a/src/lib/data/Storage.cpp +++ b/src/lib/data/Storage.cpp @@ -376,6 +376,18 @@ std::vector Storage::getAutocompletionMatches( std::vector matches = SearchIndex::getMatches(tokenResults, word); SearchMatch::log(matches, word); + + for (SearchMatch& match : matches) + { + if (!match.tokenIds.size()) + { + continue; + } + + Token* token = m_graph.getTokenById(*match.tokenIds.cbegin()); + match.typeName = token->getTypeString(); + } + return matches; } diff --git a/src/lib/data/graph/Edge.h b/src/lib/data/graph/Edge.h index 6cd2e99c..46df1009 100644 --- a/src/lib/data/graph/Edge.h +++ b/src/lib/data/graph/Edge.h @@ -49,7 +49,7 @@ public: // Logging. std::string getTypeString(EdgeType type) const; - std::string getTypeString() const; + virtual std::string getTypeString() const; std::string getAsString() const; private: diff --git a/src/lib/data/graph/Node.h b/src/lib/data/graph/Node.h index 605dff6e..a3d7e382 100644 --- a/src/lib/data/graph/Node.h +++ b/src/lib/data/graph/Node.h @@ -79,7 +79,7 @@ public: // Logging. std::string getTypeString(NodeType type) const; - std::string getTypeString() const; + virtual std::string getTypeString() const; std::string getAsString() const; private: diff --git a/src/lib/data/graph/Token.h b/src/lib/data/graph/Token.h index e29f43ae..309333bd 100644 --- a/src/lib/data/graph/Token.h +++ b/src/lib/data/graph/Token.h @@ -31,6 +31,9 @@ public: template std::shared_ptr removeComponent(); + // Logging. + virtual std::string getTypeString() const = 0; + protected: Token(const Token& other); diff --git a/src/lib/data/search/SearchMatch.h b/src/lib/data/search/SearchMatch.h index 0fc8fde4..8141ed7d 100644 --- a/src/lib/data/search/SearchMatch.h +++ b/src/lib/data/search/SearchMatch.h @@ -16,6 +16,7 @@ struct SearchMatch std::string encodeForQuery() const; std::string fullName; + std::string typeName; std::set tokenIds; std::vector indices; size_t weight; diff --git a/src/test/ConfigManagerTestSuite.h b/src/test/ConfigManagerTestSuite.h index 15f623db..0cc7939b 100644 --- a/src/test/ConfigManagerTestSuite.h +++ b/src/test/ConfigManagerTestSuite.h @@ -115,8 +115,8 @@ public: void test_config_manager_save_and_load_configuration_and_compare() { std::shared_ptr config = ConfigManager::createAndLoad(getConfigTextAccess()); - config->save("temp.xml"); - std::shared_ptr config2 = ConfigManager::createAndLoad(TextAccess::createFromFile("temp.xml")); + config->save("data/temp.xml"); + std::shared_ptr config2 = ConfigManager::createAndLoad(TextAccess::createFromFile("data/temp.xml")); TS_ASSERT_EQUALS(config->toString(), config2->toString()); } diff --git a/src/test/GraphTestSuite.h b/src/test/GraphTestSuite.h index a88cc053..a8a8ff5e 100644 --- a/src/test/GraphTestSuite.h +++ b/src/test/GraphTestSuite.h @@ -326,6 +326,11 @@ private: { return Token::removeComponent(); } + + virtual std::string getTypeString() const + { + return ""; + } }; class TestComponent: public TokenComponent