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)); + } + };