From a9c93a9437d3ca66491a1db98d736876a9f6f66d Mon Sep 17 00:00:00 2001 From: Eberhard Graether Date: Wed, 22 Oct 2014 17:28:46 +0200 Subject: [PATCH] ui: added smart search box This change switches the search box in the search view from QLineEdit to the derived QtSmartSearchBox implementation, which is capeable of displaying tokens of the query as selectable button elements in different colors. It allows for all the usual mouse and keyboard interactions that the QLineEdit offers. fortune cookie message = Good news from someone dear is coming soon. --- .../data/gui/search_view/search_element.css | 41 ++ bin/app/data/gui/search_view/search_view.css | 2 +- bin/app/data/src/header.h | 10 +- src/app/CMakeLists.txt | 6 +- src/app/qt/element/QtSearchBar.cpp | 64 ++ .../element/{QtSearchBox.h => QtSearchBar.h} | 23 +- src/app/qt/element/QtSearchBox.cpp | 147 ---- src/app/qt/element/QtSmartSearchBox.cpp | 640 ++++++++++++++++++ src/app/qt/element/QtSmartSearchBox.h | 97 +++ src/app/qt/view/QtSearchView.cpp | 2 +- src/app/qt/view/QtSearchView.h | 4 +- .../component/controller/SearchController.cpp | 7 +- .../component/controller/SearchController.h | 2 + src/lib/data/SearchIndex.cpp | 3 +- src/lib/data/query/QueryCommand.cpp | 15 +- src/lib/data/query/QueryCommand.h | 9 +- src/lib/data/query/QueryNode.h | 3 + src/lib/data/query/QueryOperator.cpp | 22 +- src/lib/data/query/QueryOperator.h | 9 +- src/lib/data/query/QueryToken.cpp | 8 +- src/lib/data/query/QueryToken.h | 5 +- src/lib/data/query/QueryTree.cpp | 87 ++- src/lib/data/query/QueryTree.h | 7 +- src/lib/utility/utilityString.cpp | 7 + src/lib/utility/utilityString.h | 2 + src/test/GraphFilterConductorTestSuite.h | 14 +- src/test/QueryTreeTestSuite.h | 109 +-- src/test/UtilityStringTestSuite.h | 25 + 28 files changed, 1102 insertions(+), 268 deletions(-) create mode 100644 bin/app/data/gui/search_view/search_element.css create mode 100644 src/app/qt/element/QtSearchBar.cpp rename src/app/qt/element/{QtSearchBox.h => QtSearchBar.h} (50%) delete mode 100644 src/app/qt/element/QtSearchBox.cpp create mode 100644 src/app/qt/element/QtSmartSearchBox.cpp create mode 100644 src/app/qt/element/QtSmartSearchBox.h diff --git a/bin/app/data/gui/search_view/search_element.css b/bin/app/data/gui/search_view/search_element.css new file mode 100644 index 00000000..c8481979 --- /dev/null +++ b/bin/app/data/gui/search_view/search_element.css @@ -0,0 +1,41 @@ +QPushButton { + border: none; + font-family: "Source Code Pro"; + font-size: 16px; + height: 20px; + padding-left: 4px; + padding-right: 4px; + border-radius: 2px; +} + +#search_element_token { + background-color: rgb(153,22,165); +} + +#search_element_token:checked { + background-color: rgb(160,102,165); +} + +#search_element_command { + background-color: rgb(172,150,0); +} + +#search_element_command:checked { + background-color: rgb(172,163,103); +} + +#search_element_operator { + background-color: rgb(27,136,86); +} + +#search_element_operator:checked { + background-color: rgb(103,136,121); +} + +#search_element_none { + background-color: rgb(208,93,24); +} + +#search_element_none:checked { + background-color: rgb(208,138,96); +} diff --git a/bin/app/data/gui/search_view/search_view.css b/bin/app/data/gui/search_view/search_view.css index 48ece17b..7fe32fcf 100644 --- a/bin/app/data/gui/search_view/search_view.css +++ b/bin/app/data/gui/search_view/search_view.css @@ -1,4 +1,4 @@ -#search_view { +#search_bar { background: rgb(255, 255, 255); } diff --git a/bin/app/data/src/header.h b/bin/app/data/src/header.h index 52ebb0ce..97bb97ab 100644 --- a/bin/app/data/src/header.h +++ b/bin/app/data/src/header.h @@ -1,11 +1,3 @@ -/* -#include -#include -#include -#include -#include -/* */ - const bool *abd(int abc, int bca); bool const *abc(int a, int b); @@ -137,7 +129,7 @@ public: SubE(){} ~SubE(){} }; - + private: int m_importantInt; }; diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt index e908ccfb..6a7ed0a2 100644 --- a/src/app/CMakeLists.txt +++ b/src/app/CMakeLists.txt @@ -13,8 +13,10 @@ add_files( qt/element/QtCodeSnippet.h qt/element/QtMainWindow.cpp qt/element/QtMainWindow.h - qt/element/QtSearchBox.cpp - qt/element/QtSearchBox.h + qt/element/QtSearchBar.cpp + qt/element/QtSearchBar.h + qt/element/QtSmartSearchBox.cpp + qt/element/QtSmartSearchBox.h qt/utility/QtHighlighter.cpp qt/utility/QtHighlighter.h diff --git a/src/app/qt/element/QtSearchBar.cpp b/src/app/qt/element/QtSearchBar.cpp new file mode 100644 index 00000000..02997a3c --- /dev/null +++ b/src/app/qt/element/QtSearchBar.cpp @@ -0,0 +1,64 @@ +#include "qt/element/QtSearchBar.h" + +#include +#include +#include + +#include "qt/element/QtSmartSearchBox.h" + +QtSearchBar::QtSearchBar() +{ + setObjectName("search_bar"); + + QBoxLayout* layout = new QHBoxLayout(); + layout->setSpacing(0); + layout->setAlignment(Qt::AlignTop); + setLayout(layout); + + m_searchButton = new QPushButton(this); + m_searchButton->setObjectName("search_button"); + m_searchButton->setAttribute(Qt::WA_LayoutUsesWidgetRect); // fixes layouting on Mac + layout->addWidget(m_searchButton); + + m_searchBox = new QtSmartSearchBox(this); + m_searchBox->setObjectName("search_box"); + m_searchBox->setAttribute(Qt::WA_LayoutUsesWidgetRect); // fixes layouting on Mac + layout->addWidget(m_searchBox); + + m_caseSensitiveButton = new QPushButton(this); + m_caseSensitiveButton->setObjectName("case_sensitive_button"); + m_caseSensitiveButton->setCheckable(true); + m_caseSensitiveButton->setToolTip("case sensitive"); + m_caseSensitiveButton->setAttribute(Qt::WA_LayoutUsesWidgetRect); // fixes layouting on Mac + layout->addWidget(m_caseSensitiveButton); + + connect(m_searchButton, SIGNAL(clicked()), m_searchBox, SLOT(search())); +} + +QtSearchBar::~QtSearchBar() +{ +} + +void QtSearchBar::setText(const std::string& text) +{ + m_searchBox->setQuery(text); +} + +void QtSearchBar::setFocus() +{ + m_searchBox->setFocus(); +} + +void QtSearchBar::setAutocompletionList(const std::vector& autocompletionList) +{ + m_searchBox->setAutocompletionList(autocompletionList); +} + +QAbstractItemView* QtSearchBar::getCompleterPopup() +{ + if (m_searchBox->completer()) + { + return m_searchBox->completer()->popup(); + } + return nullptr; +} diff --git a/src/app/qt/element/QtSearchBox.h b/src/app/qt/element/QtSearchBar.h similarity index 50% rename from src/app/qt/element/QtSearchBox.h rename to src/app/qt/element/QtSearchBar.h index eefe7d5d..50f775c1 100644 --- a/src/app/qt/element/QtSearchBox.h +++ b/src/app/qt/element/QtSearchBar.h @@ -8,17 +8,17 @@ #include "data/SearchIndex.h" -class QLineEdit; class QPushButton; +class QtSmartSearchBox; -class QtSearchBox +class QtSearchBar : public QFrame { Q_OBJECT public: - QtSearchBox(); - virtual ~QtSearchBox(); + QtSearchBar(); + virtual ~QtSearchBar(); void setText(const std::string& text); void setFocus(); @@ -26,23 +26,10 @@ public: QAbstractItemView* getCompleterPopup(); -private slots: - void onSearchButtonClick(); - void onSearchQueryEdited(const QString& text); - void onSearchQueryChanged(const QString& text); - void onSearchCompletionHighlighted(const QModelIndex& index); - void onSearchCompletionActivated(const QString& text); - private: - QLineEdit* m_searchBox; + QtSmartSearchBox* m_searchBox; QPushButton* m_searchButton; QPushButton* m_caseSensitiveButton; - - std::string m_query; - std::string m_oldQuery; - bool m_preventQueryChange; - - std::vector m_matches; }; #endif // QT_SEARCH_BOX_H diff --git a/src/app/qt/element/QtSearchBox.cpp b/src/app/qt/element/QtSearchBox.cpp deleted file mode 100644 index 03a21ff7..00000000 --- a/src/app/qt/element/QtSearchBox.cpp +++ /dev/null @@ -1,147 +0,0 @@ -#include "qt/element/QtSearchBox.h" - -#include -#include -#include -#include - -#include "data/query/QueryTree.h" -#include "utility/messaging/type/MessageSearch.h" -#include "utility/messaging/type/MessageSearchAutocomplete.h" -#include "utility/utilityString.h" - -QtSearchBox::QtSearchBox() - : m_preventQueryChange(false) -{ - setObjectName("search_view"); - - QBoxLayout* layout = new QHBoxLayout(); - layout->setSpacing(0); - layout->setAlignment(Qt::AlignTop); - setLayout(layout); - - m_searchButton = new QPushButton(this); - m_searchButton->setObjectName("search_button"); - m_searchButton->setAttribute(Qt::WA_LayoutUsesWidgetRect); // fixes layouting on Mac - connect(m_searchButton, SIGNAL(clicked()), this, SLOT(onSearchButtonClick())); - layout->addWidget(m_searchButton); - - m_searchBox = new QLineEdit(this); - m_searchBox->setObjectName("search_box"); - m_searchBox->setPlaceholderText("Please enter your search string."); - m_searchBox->setAttribute(Qt::WA_LayoutUsesWidgetRect); // fixes layouting on Mac - connect(m_searchBox, SIGNAL(returnPressed()), this, SLOT(onSearchButtonClick())); - connect(m_searchBox, SIGNAL(textEdited(const QString&)), this, SLOT(onSearchQueryEdited(const QString&))); - connect(m_searchBox, SIGNAL(textChanged(const QString&)), this, SLOT(onSearchQueryChanged(const QString&))); - layout->addWidget(m_searchBox); - - m_caseSensitiveButton = new QPushButton(this); - m_caseSensitiveButton->setObjectName("case_sensitive_button"); - m_caseSensitiveButton->setCheckable(true); - m_caseSensitiveButton->setToolTip("case sensitive"); - m_caseSensitiveButton->setAttribute(Qt::WA_LayoutUsesWidgetRect); // fixes layouting on Mac - layout->addWidget(m_caseSensitiveButton); -} - -QtSearchBox::~QtSearchBox() -{ -} - -void QtSearchBox::setText(const std::string& text) -{ - if (m_searchBox->text() != text.c_str()) - { - m_searchBox->setText(text.c_str()); - } -} - -void QtSearchBox::setFocus() -{ - m_searchBox->setFocus(Qt::ShortcutFocusReason); -} - -void QtSearchBox::setAutocompletionList(const std::vector& autocompletionList) -{ - m_matches = autocompletionList; - - QStringList wordList; - for (const SearchIndex::SearchMatch& match: autocompletionList) - { - wordList << match.fullName.c_str(); - } - - QCompleter *completer = new QCompleter(wordList, m_searchBox); - completer->popup()->setObjectName("search_box_popup"); - completer->setCaseSensitivity(Qt::CaseInsensitive); - m_searchBox->setCompleter(completer); - completer->complete(); - - connect(completer, SIGNAL(highlighted(const QModelIndex&)), this, SLOT(onSearchCompletionHighlighted(const QModelIndex&))); - connect(completer, SIGNAL(activated(const QString&)), this, SLOT(onSearchCompletionActivated(const QString&))); -} - -QAbstractItemView* QtSearchBox::getCompleterPopup() -{ - if (m_searchBox->completer()) - { - return m_searchBox->completer()->popup(); - } - return nullptr; -} - -void QtSearchBox::onSearchButtonClick() -{ - m_query = m_searchBox->text().toStdString(); - MessageSearch(m_query).dispatch(); -} - -void QtSearchBox::onSearchQueryEdited(const QString& text) -{ - m_query = text.toStdString(); - m_oldQuery = m_query; - - std::deque tokens = QueryTree::tokenizeQuery(text.toStdString()); - if (tokens.size()) - { - MessageSearchAutocomplete(tokens.back()).dispatch(); - } -} - -void QtSearchBox::onSearchQueryChanged(const QString& text) -{ - if (m_preventQueryChange) - { - m_preventQueryChange = false; - setText(m_query); - } -} - -void QtSearchBox::onSearchCompletionHighlighted(const QModelIndex& index) -{ - if (index.row() < 0 || index.row() >= int(m_matches.size())) - { - m_query = m_oldQuery; - } - else - { - std::deque tokens = QueryTree::tokenizeQuery(m_query); - if (tokens.size()) - { - tokens.pop_back(); - } - - std::string match = m_matches[index.row()].encodeForQuery(); - tokens.push_back(match); - - m_query = utility::join(tokens, ""); - } - - setText(m_query); - m_preventQueryChange = true; -} - -void QtSearchBox::onSearchCompletionActivated(const QString& text) -{ - setText(m_query); - m_preventQueryChange = true; -} diff --git a/src/app/qt/element/QtSmartSearchBox.cpp b/src/app/qt/element/QtSmartSearchBox.cpp new file mode 100644 index 00000000..1bf4515b --- /dev/null +++ b/src/app/qt/element/QtSmartSearchBox.cpp @@ -0,0 +1,640 @@ +#include "qt/element/QtSmartSearchBox.h" + +#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" + +QtQueryElement::QtQueryElement(const QString& text, QWidget* parent) + : QPushButton(text, parent) +{ + show(); + setCheckable(true); + connect(this, SIGNAL(clicked(bool)), this, SLOT(onChecked(bool))); +} + +void QtQueryElement::onChecked(bool) +{ + emit wasChecked(this); +} + + +void QtSmartSearchBox::search() +{ + editTextToElement(); + MessageSearch(utility::join(m_tokens, "") + text().toStdString()).dispatch(); +} + +QtSmartSearchBox::QtSmartSearchBox(QWidget* parent) + : QLineEdit(parent) + , m_cursorIndex(0) + , m_shiftKeyDown(false) + , m_mousePressed(false) +{ + connect(this, SIGNAL(returnPressed()), this, SLOT(search()), Qt::QueuedConnection); + connect(this, SIGNAL(textEdited(const QString&)), this, SLOT(onTextEdited(const QString&))); + connect(this, SIGNAL(textChanged(const QString&)), this, SLOT(onTextChanged(const QString&))); + + updatePlaceholder(); +} + +QtSmartSearchBox::~QtSmartSearchBox() +{ +} + +void QtSmartSearchBox::setAutocompletionList(const std::vector& autocompletionList) +{ + m_matches = autocompletionList; + + QStringList wordList; + for (const SearchIndex::SearchMatch& match: autocompletionList) + { + wordList << match.fullName.c_str(); + } + + QCompleter *completer = new QCompleter(wordList, this); + completer->popup()->setObjectName("search_box_popup"); + completer->setCaseSensitivity(Qt::CaseInsensitive); + + setCompleter(completer); + completer->complete(); + + 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); +} + +void QtSmartSearchBox::setQuery(const std::string& text) +{ + clearLineEdit(); + m_tokens = QueryTree::tokenizeQuery(text); + m_cursorIndex = m_tokens.size(); + + updateElements(); +} + +void QtSmartSearchBox::setFocus() +{ + QLineEdit::setFocus(Qt::ShortcutFocusReason); + selectAllElementsWith(true); +} + +void QtSmartSearchBox::resizeEvent(QResizeEvent* event) +{ + QLineEdit::resizeEvent(event); + layoutElements(); +} + +void QtSmartSearchBox::keyPressEvent(QKeyEvent* event) +{ + m_shiftKeyDown = event->modifiers() & Qt::ShiftModifier; + + if (event->key() == Qt::Key_Backspace) + { + if (hasSelectedElements()) + { + deleteSelectedElements(); + return; + } + else if (!hasSelectedText() && cursorPosition() == 0 && m_cursorIndex > 0) + { + m_elements[m_cursorIndex - 1]->setChecked(true); + deleteSelectedElements(); + return; + } + } + else if (event->matches(QKeySequence::MoveToPreviousChar)) + { + if (hasSelectedElements()) + { + for (size_t i = 0; i < m_elements.size(); i++) + { + if (m_elements[i]->isChecked()) + { + m_cursorIndex = i; + break; + } + } + + selectAllElementsWith(false); + layoutElements(); + } + else if (cursorPosition() == 0 && m_cursorIndex > 0) + { + editTextToElement(); + moveCursor(-1); + return; + } + } + else if (event->matches(QKeySequence::MoveToNextChar)) + { + if (hasSelectedElements()) + { + for (size_t i = m_elements.size(); i > 0; i--) + { + if (m_elements[i - 1]->isChecked()) + { + m_cursorIndex = i; + break; + } + } + + selectAllElementsWith(false); + layoutElements(); + } + else if (cursorPosition() == text().size() && (text().size() || m_cursorIndex < static_cast(m_elements.size()))) + { + if (!editTextToElement()) + { + moveCursor(1); + } + return; + } + } + else if (event->matches(QKeySequence::SelectPreviousChar)) + { + if (cursorPosition() == 0 && m_cursorIndex > 0) + { + editTextToElement(); + m_elements[m_cursorIndex - 1]->setChecked(!m_elements[m_cursorIndex - 1]->isChecked()); + moveCursor(-1); + } + } + else if (event->matches(QKeySequence::SelectNextChar)) + { + if (cursorPosition() == text().size() && m_cursorIndex < static_cast(m_elements.size())) + { + editTextToElement(); + m_elements[m_cursorIndex]->setChecked(!m_elements[m_cursorIndex]->isChecked()); + moveCursor(1); + } + } + else if (event->matches(QKeySequence::MoveToStartOfLine)) + { + editTextToElement(); + moveCursorTo(0); + return; + } + else if (event->matches(QKeySequence::MoveToEndOfLine)) + { + if (m_cursorIndex < static_cast(m_elements.size())) + { + editTextToElement(); + moveCursorTo(m_elements.size()); + return; + } + } + else if (event->matches(QKeySequence::SelectAll)) + { + if (m_elements.size()) + { + editTextToElement(); + selectAllElementsWith(true); + layoutElements(); + return; + } + } + else if (event->matches(QKeySequence::Cut)) + { + if (hasSelectedElements()) + { + std::string str = getSelectedString(); + deleteSelectedElements(); + QApplication::clipboard()->setText(QString::fromStdString(str)); + return; + } + } + else if (event->matches(QKeySequence::Copy)) + { + if (hasSelectedElements()) + { + std::string str = getSelectedString(); + QApplication::clipboard()->setText(QString::fromStdString(str)); + return; + } + } + else if (event->matches(QKeySequence::Paste)) + { + setText(text() + QApplication::clipboard()->text()); + onTextEdited(text()); + return; + } + + QLineEdit::keyPressEvent(event); +} + +void QtSmartSearchBox::keyReleaseEvent(QKeyEvent* event) +{ + m_shiftKeyDown = event->modifiers() & Qt::ShiftModifier; +} + +void QtSmartSearchBox::mouseMoveEvent(QMouseEvent* event) +{ + QLineEdit::mouseMoveEvent(event); + + if (!m_mousePressed) + { + return; + } + + int lo = event->x() < m_mouseX ? event->x() : m_mouseX; + int hi = event->x() > m_mouseX ? event->x() : m_mouseX; + + for (size_t i = 0; i < m_elements.size(); i++) + { + int widgetX = m_elements[i]->x() + m_elements[i]->width() / 2; + m_elements[i]->setChecked(lo < widgetX && widgetX < hi); + } + + layoutElements(); +} + +void QtSmartSearchBox::mousePressEvent(QMouseEvent* event) +{ + QLineEdit::mousePressEvent(event); + + m_mousePressed = true; + m_mouseX = event->x(); +} + +void QtSmartSearchBox::mouseReleaseEvent(QMouseEvent* event) +{ + QLineEdit::mouseReleaseEvent(event); + + m_mousePressed = false; + + if (abs(event->x() - m_mouseX) > 5) + { + return; + } + + int minDist = event->x(); + int pos = 0; + for (size_t i = 0; i < m_elements.size(); i++) + { + int dist = m_elements[i]->x() + m_elements[i]->width() - event->x(); + if (abs(dist) < abs(minDist)) + { + pos = i + 1; + minDist = dist; + } + } + + bool hasSelected = hasSelectedElements(); + selectAllElementsWith(false); + + if (pos - m_cursorIndex != 0) + { + editTextToElement(); + moveCursor(pos - m_cursorIndex); + } + else if (hasSelected) + { + layoutElements(); + } +} + +void QtSmartSearchBox::onTextEdited(const QString& text) +{ + deleteSelectedElements(); + + std::string token; + std::deque tokens = QueryTree::tokenizeQuery(text.toStdString()); + while (tokens.size()) + { + token = tokens.front(); + tokens.pop_front(); + + if (tokens.size() || QueryTree::getTokenTypeName(token) != "none") + { + textToToken(token); + token.clear(); + } + } + + if (text.toStdString() != token) + { + setText(QString::fromStdString(token)); + updateElements(); + } + else + { + layoutElements(); + } + + if (token.size()) + { + MessageSearchAutocomplete(token).dispatch(); + } +} + +void QtSmartSearchBox::onTextChanged(const QString& text) +{ + if (m_oldText.size()) + { + setText(m_oldText); + } + + m_oldText.clear(); + updatePlaceholder(); +} + +void QtSmartSearchBox::onSearchCompletionHighlighted(const QModelIndex& index) +{ + m_oldText = text(); +} + +void QtSmartSearchBox::onSearchCompletionActivated(const QModelIndex& index) +{ + if (index.row() >= 0 && index.row() < int(m_matches.size())) + { + m_oldText.clear(); + clearLineEdit(); + + std::string match = m_matches[index.row()].encodeForQuery(); + textToToken(match); + updateElements(); + } + + completer()->blockSignals(true); +} + +void QtSmartSearchBox::onElementSelected(QtQueryElement* element) +{ + if (!hasSelectedElements() && !m_shiftKeyDown) + { + editElement(element); + return; + } + + int idx = 0; + bool checked = element->isChecked(); + for (size_t i = 0; i < m_elements.size(); i++) + { + if (m_elements[i].get() == element) + { + idx = i; + break; + } + } + + if (text().size()) + { + if (m_cursorIndex <= idx) + { + idx++; + } + + editTextToElement(); + + element = m_elements[idx].get(); + element->setChecked(checked); + } + + if (m_shiftKeyDown) + { + selectElementsTo(idx, checked); + } + else + { + selectAllElementsWith(false); + element->setChecked(true); + m_cursorIndex = idx + 1; + } + + layoutElements(); +} + +void QtSmartSearchBox::moveCursor(int offset) +{ + moveCursorTo(m_cursorIndex + offset); +} + +void QtSmartSearchBox::moveCursorTo(int target) +{ + if (target >= 0 && target <= static_cast(m_elements.size())) + { + m_cursorIndex = target; + layoutElements(); + } +} + +void QtSmartSearchBox::textToToken(std::string text) +{ + if (!text.size()) + { + return; + } + + if (m_matches.size() && utility::equalsCaseInsensitive(text, m_matches.front().fullName)) + { + text = m_matches.front().encodeForQuery(); + } + + m_tokens.insert(m_tokens.begin() + m_cursorIndex, text); + m_cursorIndex++; +} + +bool QtSmartSearchBox::editTextToElement() +{ + if (text().size()) + { + textToToken(text().toStdString()); + clearLineEdit(); + updateElements(); + + return true; + } + + return false; +} + +void QtSmartSearchBox::editElement(QtQueryElement* element) +{ + for (int i = m_elements.size() - 1; i >= 0; i--) + { + if (m_elements[i].get() == element) + { + m_cursorIndex = i; + break; + } + } + + std::string token = QueryTree::getTokenName(m_tokens[m_cursorIndex]); + m_tokens.erase(m_tokens.begin() + m_cursorIndex); + + setText(QString::fromStdString(token)); + updateElements(); + + MessageSearchAutocomplete(token).dispatch(); +} + +void QtSmartSearchBox::updateElements() +{ + m_elements.clear(); + + for (const std::string& token : m_tokens) + { + std::string name = QueryTree::getTokenName(token); + + std::shared_ptr element = std::make_shared(QString::fromStdString(name), this); + m_elements.push_back(element); + + element->setObjectName(QString::fromStdString("search_element_" + QueryTree::getTokenTypeName(token))); + + connect(element.get(), SIGNAL(wasChecked(QtQueryElement*)), this, SLOT(onElementSelected(QtQueryElement*))); + } + + setStyleSheet(TextAccess::createFromFile("data/gui/search_view/search_element.css")->getText().c_str()); + + updatePlaceholder(); + + layoutElements(); +} + +void QtSmartSearchBox::layoutElements() +{ + ensurePolished(); + + int x = 7; + bool hasSelected = hasSelectedElements(); + + for (size_t i = 0; i <= m_elements.size(); i++) + { + if (!hasSelected && static_cast(i) == m_cursorIndex) + { + int left, top, right, bottom; + getTextMargins(&left, &top, &right, &bottom); + setTextMargins(x - 9, top, right, bottom); + x += fontMetrics().width(text()); + } + + if (i < m_elements.size()) + { + QtQueryElement* button = m_elements[i].get(); + QSize size = button->minimumSizeHint(); + int y = (rect().height() - size.height()) / 2.0; + button->setGeometry(x, y, size.width(), size.height()); + x += size.width() + 5; + } + } + + if (hasSelected) + { + setTextMargins(width() + 10, 0, 0, 0); + } +} + + +bool QtSmartSearchBox::hasSelectedElements() const +{ + for (const std::shared_ptr element : m_elements) + { + if (element->isChecked()) + { + return true; + } + } + + return false; +} + +std::string QtSmartSearchBox::getSelectedString() const +{ + std::string str; + for (size_t i = 0; i < m_elements.size(); i++) + { + if (m_elements[i]->isChecked()) + { + str += m_tokens[i]; + } + } + return str; +} + +void QtSmartSearchBox::selectAllElementsWith(bool selected) +{ + for (const std::shared_ptr element : m_elements) + { + element->setChecked(selected); + } +} + +void QtSmartSearchBox::selectElementsTo(int idx, bool selected) +{ + int low = idx < m_cursorIndex ? idx : m_cursorIndex; + int hi = idx > m_cursorIndex ? idx + 1 : m_cursorIndex; + + while (low < hi) + { + m_elements[low]->setChecked(selected); + low++; + } + + if (!selected) + { + m_elements[idx]->setChecked(true); + } + + if (idx < m_cursorIndex) + { + m_cursorIndex = idx; + } + else + { + m_cursorIndex = idx + 1; + } +} + +void QtSmartSearchBox::deleteSelectedElements() +{ + if (!hasSelectedElements()) + { + return; + } + + for (int i = m_elements.size() - 1; i >= 0; i--) + { + if (m_elements[i]->isChecked()) + { + m_tokens.erase(m_tokens.begin() + i); + + if (i < m_cursorIndex) + { + m_cursorIndex--; + } + } + } + + updateElements(); +} + +void QtSmartSearchBox::updatePlaceholder() +{ + if (!text().size() && !m_elements.size()) + { + setPlaceholderText("Please enter your search string."); + } + else + { + setPlaceholderText(""); + } +} + +void QtSmartSearchBox::clearLineEdit() +{ + setText(""); + + if (completer()) + { + completer()->popup()->hide(); + } +} diff --git a/src/app/qt/element/QtSmartSearchBox.h b/src/app/qt/element/QtSmartSearchBox.h new file mode 100644 index 00000000..f5f3485c --- /dev/null +++ b/src/app/qt/element/QtSmartSearchBox.h @@ -0,0 +1,97 @@ +#ifndef QT_SMART_SEARCH_BOX_H +#define QT_SMART_SEARCH_BOX_H + +#include +#include + +#include +#include + +#include "data/SearchIndex.h" + +class QtQueryElement + : public QPushButton +{ + Q_OBJECT + +signals: + void wasChecked(QtQueryElement*); + +public: + QtQueryElement(const QString& text, QWidget* parent); + +private slots: + void onChecked(bool); +}; + + +class QtSmartSearchBox + : public QLineEdit +{ + Q_OBJECT + +public slots: + void search(); + +public: + QtSmartSearchBox(QWidget* parent); + virtual ~QtSmartSearchBox(); + + void setAutocompletionList(const std::vector& autocompletionList); + void setQuery(const std::string& text); + void setFocus(); + +protected: + virtual void resizeEvent(QResizeEvent* event); + virtual void keyPressEvent(QKeyEvent* event); + virtual void keyReleaseEvent(QKeyEvent* event); + + virtual void mouseMoveEvent(QMouseEvent* event); + virtual void mousePressEvent(QMouseEvent* event); + virtual void mouseReleaseEvent(QMouseEvent* event); + +private slots: + void onTextEdited(const QString& text); + void onTextChanged(const QString& text); + + void onSearchCompletionHighlighted(const QModelIndex& index); + void onSearchCompletionActivated(const QModelIndex& index); + + void onElementSelected(QtQueryElement* element); + +private: + void moveCursor(int offset); + void moveCursorTo(int goal); + + void textToToken(std::string text); + bool editTextToElement(); + void editElement(QtQueryElement* element); + + void updateElements(); + void layoutElements(); + + bool hasSelectedElements() const; + std::string getSelectedString() const; + + void selectAllElementsWith(bool selected); + void selectElementsTo(int idx, bool selected); + void deleteSelectedElements(); + + void updatePlaceholder(); + void clearLineEdit(); + + QString m_oldText; + + std::deque m_tokens; + std::vector> m_elements; + + int m_cursorIndex; + + std::vector m_matches; + + bool m_shiftKeyDown; + bool m_mousePressed; + int m_mouseX; +}; + +#endif // QT_SMART_SEARCH_BOX_H diff --git a/src/app/qt/view/QtSearchView.cpp b/src/app/qt/view/QtSearchView.cpp index 3eb69ae5..19d93eaa 100644 --- a/src/app/qt/view/QtSearchView.cpp +++ b/src/app/qt/view/QtSearchView.cpp @@ -11,7 +11,7 @@ QtSearchView::QtSearchView(ViewLayout* viewLayout) , m_setFocusFunctor(std::bind(&QtSearchView::doSetFocus, this)) , m_setAutocompletionListFunctor(std::bind(&QtSearchView::doSetAutocompletionList, this, std::placeholders::_1)) { - m_widget = std::make_shared(); + m_widget = std::make_shared(); setStyleSheet(); } diff --git a/src/app/qt/view/QtSearchView.h b/src/app/qt/view/QtSearchView.h index 031ce0f3..140efe4a 100644 --- a/src/app/qt/view/QtSearchView.h +++ b/src/app/qt/view/QtSearchView.h @@ -4,7 +4,7 @@ #include #include "component/view/SearchView.h" -#include "qt/element/QtSearchBox.h" +#include "qt/element/QtSearchBar.h" #include "qt/utility/QtThreadedFunctor.h" class QtSearchView: public SearchView @@ -36,7 +36,7 @@ private: QtThreadedFunctor<> m_setFocusFunctor; QtThreadedFunctor&> m_setAutocompletionListFunctor; - std::shared_ptr m_widget; + std::shared_ptr m_widget; }; # endif // QT_SEARCH_VIEW_H diff --git a/src/lib/component/controller/SearchController.cpp b/src/lib/component/controller/SearchController.cpp index d02a66ad..0d738935 100644 --- a/src/lib/component/controller/SearchController.cpp +++ b/src/lib/component/controller/SearchController.cpp @@ -6,6 +6,7 @@ SearchController::SearchController(GraphAccess* graphAccess) : m_graphAccess(graphAccess) + , m_ignoreNextMessageActivateToken(false) { } @@ -15,10 +16,12 @@ SearchController::~SearchController() void SearchController::handleMessage(MessageActivateToken* message) { - if (message->tokenId) + if (!m_ignoreNextMessageActivateToken && message->tokenId) { getView()->setText(m_graphAccess->getNameForNodeWithId(message->tokenId)); } + + m_ignoreNextMessageActivateToken = false; } void SearchController::handleMessage(MessageFind* message) @@ -37,6 +40,8 @@ void SearchController::handleMessage(MessageSearch* message) LOG_INFO("search string: \"" + query + "\""); + m_ignoreNextMessageActivateToken = true; + std::vector ids = m_graphAccess->getTokenIdsForQuery(query); if (ids.size()) { diff --git a/src/lib/component/controller/SearchController.h b/src/lib/component/controller/SearchController.h index cff6a382..3fc22b3c 100644 --- a/src/lib/component/controller/SearchController.h +++ b/src/lib/component/controller/SearchController.h @@ -36,6 +36,8 @@ private: SearchView* getView(); GraphAccess* m_graphAccess; + + bool m_ignoreNextMessageActivateToken; }; #endif // SEARCH_CONTROLLER_H diff --git a/src/lib/data/SearchIndex.cpp b/src/lib/data/SearchIndex.cpp index bf190609..af8dcc7a 100644 --- a/src/lib/data/SearchIndex.cpp +++ b/src/lib/data/SearchIndex.cpp @@ -3,6 +3,7 @@ #include #include +#include "data/query/QueryCommand.h" #include "data/query/QueryToken.h" #include "utility/logging/logging.h" #include "utility/utilityString.h" @@ -41,7 +42,7 @@ std::string SearchIndex::SearchMatch::encodeForQuery() const { if (!tokenIds.size()) { - return fullName; + return QueryCommand::BOUNDARY + fullName + QueryCommand::BOUNDARY; } std::stringstream ss; diff --git a/src/lib/data/query/QueryCommand.cpp b/src/lib/data/query/QueryCommand.cpp index 1e95e6b9..f6019397 100644 --- a/src/lib/data/query/QueryCommand.cpp +++ b/src/lib/data/query/QueryCommand.cpp @@ -48,10 +48,12 @@ std::map QueryCommand::getCommandTypeMap return commandMap; } -QueryCommand::QueryCommand(const std::string& name) +QueryCommand::QueryCommand(std::string name) : m_type(COMMAND_INVALID) - , m_name(name) { + name.erase(std::remove(name.begin(), name.end(), BOUNDARY), name.end()); + m_name = name; + std::map commandMap = getCommandTypeMap(); std::map::iterator it = commandMap.find(name); @@ -85,12 +87,19 @@ bool QueryCommand::derivedIsComplete() const return m_type != COMMAND_INVALID; } +std::string QueryCommand::getName() const +{ + return m_name; +} + void QueryCommand::print(std::ostream& ostream) const { - ostream << m_name; + ostream << BOUNDARY << m_name << BOUNDARY; } QueryCommand::CommandType QueryCommand::getType() const { return m_type; } + +const char QueryCommand::BOUNDARY = '\''; diff --git a/src/lib/data/query/QueryCommand.h b/src/lib/data/query/QueryCommand.h index ca71ebb3..ca9917b6 100644 --- a/src/lib/data/query/QueryCommand.h +++ b/src/lib/data/query/QueryCommand.h @@ -2,7 +2,6 @@ #define QUERY_COMMAND_H #include -#include #include "data/query/QueryNode.h" @@ -46,7 +45,7 @@ public: static std::map getCommandTypeMap(); - QueryCommand(const std::string& name); + QueryCommand(std::string name); ~QueryCommand(); virtual bool isCommand() const; @@ -55,13 +54,17 @@ public: virtual bool derivedIsComplete() const; + virtual std::string getName() const; + virtual void print(std::ostream& ostream) const; CommandType getType() const; + static const char BOUNDARY; + private: CommandType m_type; - const std::string m_name; + std::string m_name; }; #endif // QUERY_COMMAND_H diff --git a/src/lib/data/query/QueryNode.h b/src/lib/data/query/QueryNode.h index bf836b97..47b47cf7 100644 --- a/src/lib/data/query/QueryNode.h +++ b/src/lib/data/query/QueryNode.h @@ -2,6 +2,7 @@ #define QUERY_NODE_H #include +#include class QueryNode { @@ -15,6 +16,8 @@ public: virtual bool derivedIsComplete() const = 0; + virtual std::string getName() const = 0; + virtual void print(std::ostream& ostream) const = 0; virtual void print(std::ostream& ostream, int n) const; diff --git a/src/lib/data/query/QueryOperator.cpp b/src/lib/data/query/QueryOperator.cpp index 80ad3785..7a4730e6 100644 --- a/src/lib/data/query/QueryOperator.cpp +++ b/src/lib/data/query/QueryOperator.cpp @@ -1,5 +1,6 @@ #include "data/query/QueryOperator.h" +#include "data/query/QueryCommand.h" #include "data/query/QueryToken.h" const std::map& QueryOperator::getOperatorTypeMap() @@ -15,11 +16,12 @@ const std::map& QueryOperator::getOperatorTyp operatorMap.emplace('!', OPERATOR_NOT); operatorMap.emplace('.', OPERATOR_SUB); - operatorMap.emplace(':', OPERATOR_HAS); + operatorMap.emplace('>', OPERATOR_HAS); operatorMap.emplace('&', OPERATOR_AND); operatorMap.emplace('|', OPERATOR_OR); operatorMap.emplace(QueryToken::BOUNDARY, OPERATOR_TOKEN); + operatorMap.emplace(QueryCommand::BOUNDARY, OPERATOR_COMMAND); operatorMap.emplace('(', OPERATOR_GROUP_OPEN); operatorMap.emplace(')', OPERATOR_GROUP_CLOSE); @@ -53,8 +55,9 @@ char QueryOperator::getOperator(OperatorType t) return '\0'; } -QueryOperator::QueryOperator(OperatorType type) +QueryOperator::QueryOperator(OperatorType type, bool isImplicit) : m_type(type) + , m_isImplicit(isImplicit) { } @@ -87,9 +90,19 @@ bool QueryOperator::derivedIsComplete() const return getLeft() && getRight(); } +std::string QueryOperator::getName() const +{ + return std::string(1, getOperator(m_type)); +} + void QueryOperator::print(std::ostream& ostream) const { ostream << getOperator(m_type); + + if (isImplicit()) + { + ostream << " IMPLICIT"; + } } void QueryOperator::print(std::ostream& ostream, int n) const @@ -136,3 +149,8 @@ bool QueryOperator::lowerPrecedence(const QueryOperator& other) { return m_type < other.m_type; } + +bool QueryOperator::isImplicit() const +{ + return m_isImplicit; +} diff --git a/src/lib/data/query/QueryOperator.h b/src/lib/data/query/QueryOperator.h index 6100d489..a510cb1f 100644 --- a/src/lib/data/query/QueryOperator.h +++ b/src/lib/data/query/QueryOperator.h @@ -21,6 +21,8 @@ public: OPERATOR_OR, OPERATOR_TOKEN, + OPERATOR_COMMAND, + OPERATOR_GROUP_OPEN, OPERATOR_GROUP_CLOSE }; @@ -29,7 +31,7 @@ public: static OperatorType getOperatorType(char c); static char getOperator(OperatorType t); - QueryOperator(OperatorType type); + QueryOperator(OperatorType type, bool isImplicit); ~QueryOperator(); virtual bool isCommand() const; @@ -38,6 +40,8 @@ public: virtual bool derivedIsComplete() const; + virtual std::string getName() const; + virtual void print(std::ostream& ostream) const; virtual void print(std::ostream& ostream, int n) const; @@ -51,11 +55,14 @@ public: bool lowerPrecedence(const QueryOperator& other); + bool isImplicit() const; + private: std::shared_ptr m_left; std::shared_ptr m_right; const OperatorType m_type; + bool m_isImplicit; }; #endif // QUERY_OPERATOR_H diff --git a/src/lib/data/query/QueryToken.cpp b/src/lib/data/query/QueryToken.cpp index 44b6b10b..f240ee76 100644 --- a/src/lib/data/query/QueryToken.cpp +++ b/src/lib/data/query/QueryToken.cpp @@ -4,8 +4,9 @@ #include "utility/utilityString.h" -QueryToken::QueryToken(const std::string& name) +QueryToken::QueryToken(std::string name) { + name.erase(std::remove(name.begin(), name.end(), BOUNDARY), name.end()); std::deque names = utility::split(name, DELIMITER); m_tokenName = names.front(); @@ -50,6 +51,11 @@ bool QueryToken::derivedIsComplete() const return true; } +std::string QueryToken::getName() const +{ + return m_tokenName; +} + void QueryToken::print(std::ostream& ostream) const { ostream << BOUNDARY << m_tokenName; diff --git a/src/lib/data/query/QueryToken.h b/src/lib/data/query/QueryToken.h index 75b71365..911a6d36 100644 --- a/src/lib/data/query/QueryToken.h +++ b/src/lib/data/query/QueryToken.h @@ -2,7 +2,6 @@ #define QUERY_TOKEN_H #include -#include #include "data/query/QueryNode.h" #include "utility/types.h" @@ -11,7 +10,7 @@ class QueryToken : public QueryNode { public: - QueryToken(const std::string& name); + QueryToken(std::string name); ~QueryToken(); virtual bool isCommand() const; @@ -20,6 +19,8 @@ public: virtual bool derivedIsComplete() const; + virtual std::string getName() const; + virtual void print(std::ostream& ostream) const; const std::string& getTokenName() const; diff --git a/src/lib/data/query/QueryTree.cpp b/src/lib/data/query/QueryTree.cpp index 06345c34..64db8d2e 100644 --- a/src/lib/data/query/QueryTree.cpp +++ b/src/lib/data/query/QueryTree.cpp @@ -16,7 +16,10 @@ std::deque QueryTree::tokenizeQuery(const std::string& query) } char operatorToken = QueryOperator::getOperator(QueryOperator::OPERATOR_TOKEN); + char operatorCommand = QueryOperator::getOperator(QueryOperator::OPERATOR_COMMAND); + bool isToken = false; + bool isCommand = false; std::string token; std::deque tokens; @@ -28,12 +31,19 @@ std::deque QueryTree::tokenizeQuery(const std::string& query) token += tokenTmp; - if (tokenTmp.size() == 1 && tokenTmp[0] == operatorToken) + if (tokenTmp.size() == 1) { - isToken = !isToken; + if (tokenTmp[0] == operatorToken && !isCommand) + { + isToken = !isToken; + } + else if (tokenTmp[0] == operatorCommand && !isToken) + { + isCommand = !isCommand; + } } - if (!isToken || (!tokensTmp.size() && token.size())) + if ((!isToken && !isCommand) || (!tokensTmp.size() && token.size())) { tokens.push_back(token); token.clear(); @@ -43,14 +53,47 @@ std::deque QueryTree::tokenizeQuery(const std::string& query) return tokens; } +std::string QueryTree::getTokenName(const std::string& token) +{ + if (token.size() > 2 && token.front() == QueryToken::BOUNDARY && token.back() == QueryToken::BOUNDARY) + { + return QueryToken(token).getName(); + } + else if (token.size() > 2 && token.front() == QueryCommand::BOUNDARY && token.back() == QueryCommand::BOUNDARY) + { + return QueryCommand(token).getName(); + } + + return token; +} + +std::string QueryTree::getTokenTypeName(const std::string& token) +{ + if (token.size() > 2 && token.front() == QueryToken::BOUNDARY && token.back() == QueryToken::BOUNDARY) + { + return "token"; + } + else if (token.size() > 2 && token.front() == QueryCommand::BOUNDARY && token.back() == QueryCommand::BOUNDARY) + { + return "command"; + } + else if (token.size() == 1 && QueryOperator::getOperatorType(token.front()) != QueryOperator::OPERATOR_NONE) + { + return "operator"; + } + + return "none"; +} + +QueryTree::QueryTree() + : m_valid(true) +{ +} + QueryTree::QueryTree(const std::string& query) : m_valid(true) { - std::deque tokens = tokenizeQuery(query); - - m_query = utility::join(tokens, ' '); - - m_root = buildTree(tokens, nullptr); + build(query); } QueryTree::~QueryTree() @@ -67,6 +110,16 @@ bool QueryTree::isValid() const return m_valid; } +void QueryTree::build(const std::string& query) +{ + std::deque tokens = tokenizeQuery(query); + + m_query = utility::join(tokens, ' '); + + m_valid = true; + m_root = buildTree(tokens, nullptr); +} + void QueryTree::print(std::ostream& ostream) const { ostream << m_query; @@ -99,7 +152,7 @@ std::shared_ptr QueryTree::buildTree(std::deque& tokens, { if (node->isComplete()) { - std::shared_ptr subNode = std::make_shared(QueryOperator::OPERATOR_SUB); + std::shared_ptr subNode = std::make_shared(QueryOperator::OPERATOR_SUB, true); subNode->setLeft(frontNode); subNode->setRight(node); node = subNode; @@ -207,11 +260,14 @@ std::shared_ptr QueryTree::getNextNode(std::deque& token case QueryOperator::OPERATOR_HAS: case QueryOperator::OPERATOR_AND: case QueryOperator::OPERATOR_OR: - return std::make_shared(type); + return std::make_shared(type, false); case QueryOperator::OPERATOR_TOKEN: return createToken(token); + case QueryOperator::OPERATOR_COMMAND: + return createCommand(token); + case QueryOperator::OPERATOR_GROUP_OPEN: return buildGroup(tokens, QueryOperator::OPERATOR_GROUP_CLOSE); case QueryOperator::OPERATOR_GROUP_CLOSE: @@ -219,7 +275,7 @@ std::shared_ptr QueryTree::getNextNode(std::deque& token break; case QueryOperator::OPERATOR_NONE: - return createCommand(token); + return createToken(token); } } @@ -228,12 +284,16 @@ std::shared_ptr QueryTree::getNextNode(std::deque& token std::shared_ptr QueryTree::createCommand(const std::string& name) { + if (name.size() < 3 || name.front() != QueryCommand::BOUNDARY || name.back() != QueryCommand::BOUNDARY) + { + m_valid = false; + } + std::shared_ptr node = std::make_shared(name); if (node->getType() == QueryCommand::COMMAND_INVALID) { m_valid = false; - return nullptr; } return node; @@ -244,10 +304,9 @@ std::shared_ptr QueryTree::createToken(const std::string& name) if (name.size() < 3 || name.front() != QueryToken::BOUNDARY || name.back() != QueryToken::BOUNDARY) { m_valid = false; - return nullptr; } - return std::make_shared(name.substr(1, name.size() - 2)); + return std::make_shared(name); } std::ostream& operator<<(std::ostream& ostream, const QueryTree& tree) diff --git a/src/lib/data/query/QueryTree.h b/src/lib/data/query/QueryTree.h index 380be4b4..74fb0235 100644 --- a/src/lib/data/query/QueryTree.h +++ b/src/lib/data/query/QueryTree.h @@ -8,13 +8,14 @@ #include "data/query/QueryOperator.h" -class QueryNode; - class QueryTree { public: static std::deque tokenizeQuery(const std::string& query); + static std::string getTokenName(const std::string& token); + static std::string getTokenTypeName(const std::string& token); + QueryTree(); QueryTree(const std::string& query); ~QueryTree(); @@ -22,6 +23,8 @@ public: bool isValid() const; + void build(const std::string& query); + void print(std::ostream& ostream) const; private: diff --git a/src/lib/utility/utilityString.cpp b/src/lib/utility/utilityString.cpp index 2854880d..541e311e 100644 --- a/src/lib/utility/utilityString.cpp +++ b/src/lib/utility/utilityString.cpp @@ -1,5 +1,7 @@ #include "utility/utilityString.h" +#include + namespace utility { std::deque split(const std::string& str, char delimiter) @@ -116,4 +118,9 @@ namespace utility return res.first == prefix.end(); } + + bool equalsCaseInsensitive(const std::string& a, const std::string& b) + { + return strcasecmp(a.c_str(), b.c_str()) == 0; + } } diff --git a/src/lib/utility/utilityString.h b/src/lib/utility/utilityString.h index 730b3b70..7a988391 100644 --- a/src/lib/utility/utilityString.h +++ b/src/lib/utility/utilityString.h @@ -21,6 +21,8 @@ namespace utility std::string substrAfter(const std::string& str, char delimiter); bool isPrefix(const std::string& prefix, const std::string& text); + + bool equalsCaseInsensitive(const std::string& a, const std::string& b); } #endif // UTILITY_STRING_H diff --git a/src/test/GraphFilterConductorTestSuite.h b/src/test/GraphFilterConductorTestSuite.h index 307b27ff..41965897 100644 --- a/src/test/GraphFilterConductorTestSuite.h +++ b/src/test/GraphFilterConductorTestSuite.h @@ -77,14 +77,14 @@ public: void test_command_query() { TS_ASSERT_EQUALS( - printedFilteredTestGraph("method"), + printedFilteredTestGraph("'method'"), "5 nodes: method:A::A method:A::A method:A::getCount method:A::process method:B::process\n" "0 edges:\n" ); TS_ASSERT_EQUALS( - printedFilteredTestGraph("class"), + printedFilteredTestGraph("'class'"), "2 nodes: class:A class:B\n" "0 edges:\n" @@ -94,7 +94,7 @@ public: void test_operator_not() { TS_ASSERT_EQUALS( - printedFilteredTestGraph("!method"), + printedFilteredTestGraph("!'method'"), "7 nodes: " "class:A field:A::count undefined_type:int undefined_type:void class:B function:main " @@ -108,7 +108,7 @@ public: void test_operator_sub() { TS_ASSERT_EQUALS( - printedFilteredTestGraph("class.base"), + printedFilteredTestGraph("'class'.'base'"), "1 nodes: class:A\n" "0 edges:\n" @@ -118,7 +118,7 @@ public: void test_operator_has() { TS_ASSERT_EQUALS( - printedFilteredTestGraph("\"A\":field"), + printedFilteredTestGraph("\"A\">'field'"), "1 nodes: field:A::count\n" "0 edges:\n" @@ -128,7 +128,7 @@ public: void test_operator_or() { TS_ASSERT_EQUALS( - printedFilteredTestGraph("(static|const)"), + printedFilteredTestGraph("('static'|'const')"), "4 nodes: field:A::count method:A::getCount method:A::process method:B::process\n" "0 edges:\n" @@ -138,7 +138,7 @@ public: void test_operator_group() { TS_ASSERT_EQUALS( - printedFilteredTestGraph("(static|const).public"), + printedFilteredTestGraph("('static'|'const').'public'"), "1 nodes: method:A::getCount\n" "0 edges:\n" diff --git a/src/test/QueryTreeTestSuite.h b/src/test/QueryTreeTestSuite.h index 22812752..09345ec6 100644 --- a/src/test/QueryTreeTestSuite.h +++ b/src/test/QueryTreeTestSuite.h @@ -20,16 +20,17 @@ public: printedQueryTree(" -"), "- INVALID\n" + "\"-\"\n" ); } void test_command_query() { TS_ASSERT_EQUALS( - printedQueryTree("class"), + printedQueryTree("'class'"), - "class\n" - "class\n" + "'class'\n" + "'class'\n" ); } @@ -39,6 +40,7 @@ public: printedQueryTree("banana"), "banana INVALID\n" + "\"banana\"\n" ); } @@ -58,18 +60,23 @@ public: printedQueryTree("\"A"), "\"A INVALID\n" + "\"A\"\n" ); TS_ASSERT_EQUALS( printedQueryTree("\"\""), "\"\" INVALID\n" + "\"\"\n" ); TS_ASSERT_EQUALS( printedQueryTree("A\""), "A \" INVALID\n" + " \"A\"\n" + ". IMPLICIT\n" + " \"\"\n" ); } @@ -96,20 +103,20 @@ public: void test_operator_not_query() { TS_ASSERT_EQUALS( - printedQueryTree("!field"), + printedQueryTree("!'field'"), - "! field\n" + "! 'field'\n" "!\n" - " field\n" + " 'field'\n" ); TS_ASSERT_EQUALS( - printedQueryTree("!!field"), + printedQueryTree("!!'field'"), - "! ! field\n" + "! ! 'field'\n" "!\n" " !\n" - " field\n" + " 'field'\n" ); } @@ -123,10 +130,10 @@ public: ); TS_ASSERT_EQUALS( - printedQueryTree("field!"), + printedQueryTree("'field'!"), - "field ! INVALID\n" - " field\n" + "'field' ! INVALID\n" + " 'field'\n" "! INVALID\n" ); } @@ -182,11 +189,11 @@ public: void test_operator_has_query() { TS_ASSERT_EQUALS( - printedQueryTree("\"A\":\"B\""), + printedQueryTree("\"A\">\"B\""), - "\"A\" : \"B\"\n" + "\"A\" > \"B\"\n" " \"A\"\n" - ":\n" + ">\n" " \"B\"\n" ); } @@ -269,7 +276,7 @@ public: "\"A\" ( \"B\" )\n" " \"A\"\n" - ".\n" + ". IMPLICIT\n" " (\"B\")\n" ); } @@ -277,85 +284,85 @@ public: void test_operator_precedence_not_before_sub() { TS_ASSERT_EQUALS( - printedQueryTree("!method.!const"), + printedQueryTree("!'method'.!'const'"), - "! method . ! const\n" + "! 'method' . ! 'const'\n" " !\n" - " method\n" + " 'method'\n" ".\n" " !\n" - " const\n" + " 'const'\n" ); } void test_operator_precedence_sub_before_has() { TS_ASSERT_EQUALS( - printedQueryTree("namespace.class:method"), + printedQueryTree("'namespace'.'class'>'method'"), - "namespace . class : method\n" - " namespace\n" + "'namespace' . 'class' > 'method'\n" + " 'namespace'\n" " .\n" - " class\n" - ":\n" - " method\n" + " 'class'\n" + ">\n" + " 'method'\n" ); } void test_operator_precedence_has_before_or() { TS_ASSERT_EQUALS( - printedQueryTree("class:method|field"), + printedQueryTree("'class'>'method'|'field'"), - "class : method | field\n" - " class\n" - " :\n" - " method\n" + "'class' > 'method' | 'field'\n" + " 'class'\n" + " >\n" + " 'method'\n" "|\n" - " field\n" + " 'field'\n" ); } void test_operator_precedence_respects_groups() { TS_ASSERT_EQUALS( - printedQueryTree("namespace.(class:method)"), + printedQueryTree("'namespace'.('class'>'method')"), - "namespace . ( class : method )\n" - " namespace\n" + "'namespace' . ( 'class' > 'method' )\n" + " 'namespace'\n" ".\n" - " class\n" - " (:)\n" - " method\n" + " 'class'\n" + " (>)\n" + " 'method'\n" ); TS_ASSERT_EQUALS( - printedQueryTree("class:(method|field)"), + printedQueryTree("'class'>('method'|'field')"), - "class : ( method | field )\n" - " class\n" - ":\n" - " method\n" + "'class' > ( 'method' | 'field' )\n" + " 'class'\n" + ">\n" + " 'method'\n" " (|)\n" - " field\n" + " 'field'\n" ); } void test_spaces_get_stripped_out_of_query() { TS_ASSERT_EQUALS( - printedQueryTree(" \"Field \":(method | field) .const | public "), + printedQueryTree(" \"Field \">('method' | 'field') .'const' | 'public' "), - "\"Field\" : ( method | field ) . const | public\n" + "\"Field\" > ( 'method' | 'field' ) . 'const' | 'public'\n" " \"Field\"\n" - " :\n" - " method\n" + " >\n" + " 'method'\n" " (|)\n" - " field\n" + " 'field'\n" " .\n" - " const\n" + " 'const'\n" "|\n" - " public\n" + " 'public'\n" ); } diff --git a/src/test/UtilityStringTestSuite.h b/src/test/UtilityStringTestSuite.h index 4227538e..87531c15 100644 --- a/src/test/UtilityStringTestSuite.h +++ b/src/test/UtilityStringTestSuite.h @@ -211,4 +211,29 @@ public: TS_ASSERT(!utility::isPrefix(foo, bar)); TS_ASSERT(!utility::isPrefix(bar, foo)); } + + void test_equals_case_insensitive_with_different_cases() + { + const std::string foo = "FooBar"; + const std::string foo2 = "foobar"; + + TS_ASSERT(utility::equalsCaseInsensitive(foo, foo2)); + } + + void test_equals_case_insensitive_with_same_cases() + { + const std::string foo = "foobar"; + const std::string foo2 = "foobar"; + + TS_ASSERT(utility::equalsCaseInsensitive(foo, foo2)); + } + + void test_equals_case_insensitive_with_different_strings() + { + const std::string foo = "foo"; + const std::string foo2 = "foobar"; + + TS_ASSERT(!utility::equalsCaseInsensitive(foo, foo2)); + } + };